@ccpocket-base-auth/bridge 1.26.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/websocket.js CHANGED
@@ -38,6 +38,39 @@ const CODEX_MODELS = [
38
38
  function permissionModeToApprovalPolicy(mode) {
39
39
  return mode === "bypassPermissions" ? "never" : "on-request";
40
40
  }
41
+ function deriveExecutionMode(params) {
42
+ if (params.executionMode === "default" ||
43
+ params.executionMode === "acceptEdits" ||
44
+ params.executionMode === "fullAccess") {
45
+ return params.executionMode;
46
+ }
47
+ if (params.permissionMode === "bypassPermissions" ||
48
+ params.approvalPolicy === "never") {
49
+ return "fullAccess";
50
+ }
51
+ if (params.permissionMode === "acceptEdits") {
52
+ return params.provider === "codex" ? "default" : "acceptEdits";
53
+ }
54
+ return "default";
55
+ }
56
+ function derivePlanMode(params) {
57
+ return params.planMode ??
58
+ ((params.permissionMode === "plan") ||
59
+ (params.collaborationMode === "plan"));
60
+ }
61
+ function modesToLegacyPermissionMode(provider, executionMode, planMode) {
62
+ if (planMode)
63
+ return "plan";
64
+ switch (executionMode) {
65
+ case "fullAccess":
66
+ return "bypassPermissions";
67
+ case "acceptEdits":
68
+ return "acceptEdits";
69
+ case "default":
70
+ default:
71
+ return provider === "codex" ? "acceptEdits" : "default";
72
+ }
73
+ }
41
74
  /** Map simplified SandboxMode (on/off) to Codex internal sandbox mode. */
42
75
  function sandboxModeToInternal(mode) {
43
76
  switch (mode) {
@@ -58,6 +91,10 @@ function sandboxModeToExternal(mode) {
58
91
  function threadTimestampToIso(value) {
59
92
  return value > 0 ? new Date(value * 1000).toISOString() : "";
60
93
  }
94
+ function envFlagEnabled(name) {
95
+ const value = process.env[name]?.trim().toLowerCase();
96
+ return value === "1" || value === "true" || value === "yes" || value === "on";
97
+ }
61
98
  function codexThreadToRecentSession(thread, indexed) {
62
99
  return {
63
100
  sessionId: thread.id,
@@ -98,6 +135,8 @@ export class BridgeWebSocketServer {
98
135
  /** FCM token → push notification locale */
99
136
  tokenLocales = new Map();
100
137
  tokenPrivacyMode = new Map();
138
+ failSetPermissionMode = envFlagEnabled("BRIDGE_FAIL_SET_PERMISSION_MODE");
139
+ failSetSandboxMode = envFlagEnabled("BRIDGE_FAIL_SET_SANDBOX_MODE");
101
140
  constructor(options) {
102
141
  const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup } = options;
103
142
  this.apiKey = apiKey ?? null;
@@ -176,6 +215,91 @@ export class BridgeWebSocketServer {
176
215
  errorCode: "path_not_allowed",
177
216
  };
178
217
  }
218
+ buildSessionCreatedMessage(params) {
219
+ const { sessionId, provider, projectPath, session, permissionMode, executionMode, planMode, sandboxMode, slashCommands, skills, skillMetadata, sourceSessionId, } = params;
220
+ const msg = {
221
+ type: "system",
222
+ subtype: "session_created",
223
+ sessionId,
224
+ provider,
225
+ projectPath,
226
+ ...(permissionMode ? { permissionMode: permissionMode } : {}),
227
+ ...((executionMode ?? (session?.process instanceof SdkProcess
228
+ ? session.process.permissionMode === "bypassPermissions"
229
+ ? "fullAccess"
230
+ : session.process.permissionMode === "acceptEdits"
231
+ ? "acceptEdits"
232
+ : "default"
233
+ : session?.process instanceof CodexProcess
234
+ ? session.process.approvalPolicy === "never"
235
+ ? "fullAccess"
236
+ : "default"
237
+ : undefined))
238
+ ? {
239
+ executionMode: (executionMode ?? (session?.process instanceof SdkProcess
240
+ ? session.process.permissionMode === "bypassPermissions"
241
+ ? "fullAccess"
242
+ : session.process.permissionMode === "acceptEdits"
243
+ ? "acceptEdits"
244
+ : "default"
245
+ : session?.process instanceof CodexProcess
246
+ ? session.process.approvalPolicy === "never"
247
+ ? "fullAccess"
248
+ : "default"
249
+ : undefined)),
250
+ }
251
+ : {}),
252
+ ...((planMode ??
253
+ (session?.process instanceof SdkProcess
254
+ ? session.process.permissionMode === "plan"
255
+ : session?.process instanceof CodexProcess
256
+ ? session.process.collaborationMode === "plan"
257
+ : undefined)) !=
258
+ null
259
+ ? {
260
+ planMode: planMode ??
261
+ (session?.process instanceof SdkProcess
262
+ ? session.process.permissionMode === "plan"
263
+ : session?.process instanceof CodexProcess
264
+ ? session.process.collaborationMode === "plan"
265
+ : false),
266
+ }
267
+ : {}),
268
+ ...(sandboxMode ? { sandboxMode } : {}),
269
+ ...(slashCommands ? { slashCommands } : {}),
270
+ ...(skills ? { skills } : {}),
271
+ ...(skillMetadata
272
+ ? {
273
+ skillMetadata: skillMetadata,
274
+ }
275
+ : {}),
276
+ ...(session?.worktreePath
277
+ ? {
278
+ worktreePath: session.worktreePath,
279
+ worktreeBranch: session.worktreeBranch,
280
+ }
281
+ : {}),
282
+ ...(sourceSessionId ? { sourceSessionId } : {}),
283
+ };
284
+ if (provider === "codex" && session?.codexSettings) {
285
+ if (session.codexSettings.model !== undefined) {
286
+ msg.model = session.codexSettings.model;
287
+ }
288
+ if (session.codexSettings.approvalPolicy !== undefined) {
289
+ msg.approvalPolicy = session.codexSettings.approvalPolicy;
290
+ }
291
+ if (session.codexSettings.modelReasoningEffort !== undefined) {
292
+ msg.modelReasoningEffort = session.codexSettings.modelReasoningEffort;
293
+ }
294
+ if (session.codexSettings.networkAccessEnabled !== undefined) {
295
+ msg.networkAccessEnabled = session.codexSettings.networkAccessEnabled;
296
+ }
297
+ if (session.codexSettings.webSearchMode !== undefined) {
298
+ msg.webSearchMode = session.codexSettings.webSearchMode;
299
+ }
300
+ }
301
+ return msg;
302
+ }
179
303
  close() {
180
304
  console.log("[ws] Shutting down...");
181
305
  this.sessionManager.destroyAll();
@@ -244,14 +368,24 @@ export class BridgeWebSocketServer {
244
368
  }
245
369
  try {
246
370
  const provider = msg.provider ?? "claude";
371
+ const executionMode = deriveExecutionMode({
372
+ provider,
373
+ permissionMode: msg.permissionMode,
374
+ executionMode: msg.executionMode,
375
+ });
376
+ const planMode = derivePlanMode({
377
+ permissionMode: msg.permissionMode,
378
+ planMode: msg.planMode,
379
+ });
380
+ const legacyPermissionMode = modesToLegacyPermissionMode(provider, executionMode, planMode);
247
381
  if (provider === "codex") {
248
- console.log(`[ws] start(codex): permissionMode=${msg.permissionMode} → collaboration=${msg.permissionMode === "plan" ? "plan" : "default"}`);
382
+ console.log(`[ws] start(codex): execution=${executionMode} plan=${planMode}`);
249
383
  }
250
384
  const cached = provider === "claude" ? this.sessionManager.getCachedCommands(msg.projectPath) : undefined;
251
385
  const sessionId = this.sessionManager.create(msg.projectPath, {
252
386
  sessionId: msg.sessionId,
253
387
  continueMode: msg.continue,
254
- permissionMode: msg.permissionMode,
388
+ permissionMode: legacyPermissionMode,
255
389
  model: msg.model,
256
390
  effort: msg.effort,
257
391
  maxTurns: msg.maxTurns,
@@ -269,33 +403,38 @@ export class BridgeWebSocketServer {
269
403
  existingWorktreePath: msg.existingWorktreePath,
270
404
  }, provider, provider === "codex"
271
405
  ? {
272
- approvalPolicy: permissionModeToApprovalPolicy(msg.permissionMode),
406
+ approvalPolicy: executionMode === "fullAccess" ? "never" : "on-request",
273
407
  sandboxMode: sandboxModeToInternal(msg.sandboxMode),
274
408
  model: msg.model,
275
409
  modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
276
410
  networkAccessEnabled: msg.networkAccessEnabled,
277
411
  webSearchMode: msg.webSearchMode ?? undefined,
278
412
  threadId: msg.sessionId,
279
- collaborationMode: msg.permissionMode === "plan" ? "plan" : "default",
413
+ collaborationMode: planMode ? "plan" : "default",
280
414
  }
281
415
  : undefined);
282
416
  const createdSession = this.sessionManager.get(sessionId);
283
417
  // Load saved session name from CLI storage (for resumed sessions)
284
418
  void this.loadAndSetSessionName(createdSession, provider, msg.projectPath, msg.sessionId).then(() => {
285
- this.send(ws, {
286
- type: "system",
287
- subtype: "session_created",
419
+ this.send(ws, this.buildSessionCreatedMessage({
288
420
  sessionId,
289
421
  provider,
290
422
  projectPath: msg.projectPath,
291
- ...(msg.permissionMode ? { permissionMode: msg.permissionMode } : {}),
292
- ...(msg.sandboxMode ? { sandboxMode: msg.sandboxMode } : {}),
293
- ...(cached ? { slashCommands: cached.slashCommands, skills: cached.skills, ...(cached.skillMetadata ? { skillMetadata: cached.skillMetadata } : {}) } : {}),
294
- ...(createdSession?.worktreePath ? {
295
- worktreePath: createdSession.worktreePath,
296
- worktreeBranch: createdSession.worktreeBranch,
297
- } : {}),
298
- });
423
+ session: createdSession,
424
+ permissionMode: legacyPermissionMode,
425
+ executionMode,
426
+ planMode,
427
+ sandboxMode: msg.sandboxMode,
428
+ ...(cached
429
+ ? {
430
+ slashCommands: cached.slashCommands,
431
+ skills: cached.skills,
432
+ ...(cached.skillMetadata
433
+ ? { skillMetadata: cached.skillMetadata }
434
+ : {}),
435
+ }
436
+ : {}),
437
+ }));
299
438
  this.broadcastSessionList();
300
439
  // Send a gentle tip when the project is not a git repository
301
440
  if (createdSession && !createdSession.gitBranch) {
@@ -510,6 +649,14 @@ export class BridgeWebSocketServer {
510
649
  break;
511
650
  }
512
651
  case "set_permission_mode": {
652
+ if (this.failSetPermissionMode) {
653
+ this.send(ws, {
654
+ type: "error",
655
+ message: "Failed to set permission mode: forced test failure",
656
+ errorCode: "set_permission_mode_rejected",
657
+ });
658
+ break;
659
+ }
513
660
  const session = this.resolveSession(msg.sessionId);
514
661
  if (!session) {
515
662
  this.send(ws, { type: "error", message: "No active session." });
@@ -519,14 +666,52 @@ export class BridgeWebSocketServer {
519
666
  // Permission mode for Codex requires a session restart (like sandbox mode).
520
667
  // approvalPolicy and collaborationMode are thread-level settings that
521
668
  // only take effect reliably at thread/start or thread/resume time.
522
- const newApproval = permissionModeToApprovalPolicy(msg.mode);
523
- const newCollaboration = msg.mode === "plan" ? "plan" : "default";
669
+ const executionMode = deriveExecutionMode({
670
+ provider: "codex",
671
+ permissionMode: msg.mode,
672
+ executionMode: msg.executionMode,
673
+ });
674
+ const planMode = derivePlanMode({
675
+ permissionMode: msg.mode,
676
+ planMode: msg.planMode,
677
+ });
678
+ const legacyPermissionMode = modesToLegacyPermissionMode("codex", executionMode, planMode);
679
+ const newApproval = executionMode === "fullAccess" ? "never" : "on-request";
680
+ const newCollaboration = planMode ? "plan" : "default";
524
681
  const currentApproval = session.process.approvalPolicy;
525
682
  const currentCollaboration = session.process.collaborationMode;
526
683
  if (newApproval === currentApproval && newCollaboration === currentCollaboration) {
527
684
  break; // No change needed
528
685
  }
529
- console.log(`[ws] set_permission_mode(codex): mode=${msg.mode} approval=${newApproval}, collaboration=${newCollaboration} (restart)`);
686
+ const canApplyModeInPlace = session.status === "idle";
687
+ if (canApplyModeInPlace) {
688
+ const process = session.process;
689
+ if (newApproval !== currentApproval) {
690
+ process.setApprovalPolicy(newApproval);
691
+ }
692
+ if (newCollaboration !== currentCollaboration) {
693
+ process.setCollaborationMode(newCollaboration);
694
+ }
695
+ session.lastActivityAt = new Date();
696
+ this.broadcast({
697
+ type: "system",
698
+ subtype: "set_permission_mode",
699
+ sessionId: session.id,
700
+ permissionMode: legacyPermissionMode,
701
+ executionMode,
702
+ planMode,
703
+ });
704
+ this.broadcastSessionList();
705
+ this.recordDebugEvent(session.id, {
706
+ direction: "internal",
707
+ channel: "bridge",
708
+ type: "permission_mode_changed",
709
+ detail: `mode=${msg.mode} approval=${newApproval} collaboration=${newCollaboration} applied=in-place`,
710
+ });
711
+ console.log(`[ws] set_permission_mode(codex): execution=${executionMode} plan=${planMode} → approval=${newApproval}, collaboration=${newCollaboration} (in-place)`);
712
+ break;
713
+ }
714
+ console.log(`[ws] set_permission_mode(codex): execution=${executionMode} plan=${planMode} → approval=${newApproval}, collaboration=${newCollaboration} (restart)`);
530
715
  const oldSessionId = session.id;
531
716
  const threadId = session.claudeSessionId;
532
717
  const projectPath = session.projectPath;
@@ -550,17 +735,19 @@ export class BridgeWebSocketServer {
550
735
  const newSession = this.sessionManager.get(newId);
551
736
  if (newSession && sessionName)
552
737
  newSession.name = sessionName;
553
- this.broadcast({
554
- type: "system",
555
- subtype: "session_created",
738
+ this.broadcast(this.buildSessionCreatedMessage({
556
739
  sessionId: newId,
557
740
  provider: "codex",
558
741
  projectPath,
559
- permissionMode: msg.mode,
560
- ...(oldSettings.sandboxMode ? { sandboxMode: sandboxModeToExternal(oldSettings.sandboxMode) } : {}),
742
+ session: newSession,
743
+ permissionMode: legacyPermissionMode,
744
+ executionMode,
745
+ planMode,
746
+ sandboxMode: oldSettings.sandboxMode
747
+ ? sandboxModeToExternal(oldSettings.sandboxMode)
748
+ : undefined,
561
749
  sourceSessionId: oldSessionId,
562
- ...(newSession?.worktreePath ? { worktreePath: newSession.worktreePath, worktreeBranch: newSession.worktreeBranch } : {}),
563
- });
750
+ }));
564
751
  this.broadcastSessionList();
565
752
  console.log(`[ws] Permission mode change (no thread): created new session ${newId} (mode=${msg.mode})`);
566
753
  break;
@@ -596,20 +783,19 @@ export class BridgeWebSocketServer {
596
783
  newSession.name = sessionName;
597
784
  }
598
785
  void this.loadAndSetSessionName(newSession, "codex", effectiveProjectPath, threadId).then(() => {
599
- this.broadcast({
600
- type: "system",
601
- subtype: "session_created",
786
+ this.broadcast(this.buildSessionCreatedMessage({
602
787
  sessionId: newId,
603
788
  provider: "codex",
604
789
  projectPath: effectiveProjectPath,
605
- permissionMode: msg.mode,
606
- ...(oldSettings.sandboxMode ? { sandboxMode: sandboxModeToExternal(oldSettings.sandboxMode) } : {}),
790
+ session: newSession,
791
+ permissionMode: legacyPermissionMode,
792
+ executionMode,
793
+ planMode,
794
+ sandboxMode: oldSettings.sandboxMode
795
+ ? sandboxModeToExternal(oldSettings.sandboxMode)
796
+ : undefined,
607
797
  sourceSessionId: oldSessionId,
608
- ...(newSession?.worktreePath ? {
609
- worktreePath: newSession.worktreePath,
610
- worktreeBranch: newSession.worktreeBranch,
611
- } : {}),
612
- });
798
+ }));
613
799
  this.broadcastSessionList();
614
800
  });
615
801
  this.debugEvents.set(newId, []);
@@ -634,6 +820,14 @@ export class BridgeWebSocketServer {
634
820
  break;
635
821
  }
636
822
  case "set_sandbox_mode": {
823
+ if (this.failSetSandboxMode) {
824
+ this.send(ws, {
825
+ type: "error",
826
+ message: "Failed to set sandbox mode: forced test failure",
827
+ errorCode: "set_sandbox_mode_rejected",
828
+ });
829
+ break;
830
+ }
637
831
  const session = this.resolveSession(msg.sessionId);
638
832
  if (!session) {
639
833
  this.send(ws, { type: "error", message: "No active session." });
@@ -670,19 +864,14 @@ export class BridgeWebSocketServer {
670
864
  if (newSession && sessionName)
671
865
  newSession.name = sessionName;
672
866
  void this.loadAndSetSessionName(newSession, "claude", projectPath, claudeSessionId).then(() => {
673
- this.broadcast({
674
- type: "system",
675
- subtype: "session_created",
867
+ this.broadcast(this.buildSessionCreatedMessage({
676
868
  sessionId: newId,
677
869
  provider: "claude",
678
870
  projectPath,
871
+ session: newSession,
679
872
  sandboxMode: msg.sandboxMode,
680
873
  sourceSessionId: oldSessionId,
681
- ...(newSession?.worktreePath ? {
682
- worktreePath: newSession.worktreePath,
683
- worktreeBranch: newSession.worktreeBranch,
684
- } : {}),
685
- });
874
+ }));
686
875
  this.broadcastSessionList();
687
876
  });
688
877
  this.debugEvents.set(newId, []);
@@ -713,6 +902,9 @@ export class BridgeWebSocketServer {
713
902
  const worktreeBranch = session.worktreeBranch;
714
903
  const sessionName = session.name;
715
904
  const collaborationMode = session.process.collaborationMode;
905
+ const executionMode = oldSettings.approvalPolicy === "never" ? "fullAccess" : "default";
906
+ const planMode = collaborationMode === "plan";
907
+ const legacyPermissionMode = modesToLegacyPermissionMode("codex", executionMode, planMode);
716
908
  this.sessionManager.destroy(oldSessionId);
717
909
  console.log(`[ws] Sandbox mode change: destroyed session ${oldSessionId}`);
718
910
  // Check if the user actually exchanged messages in this session.
@@ -737,16 +929,17 @@ export class BridgeWebSocketServer {
737
929
  const newSession = this.sessionManager.get(newId);
738
930
  if (newSession && sessionName)
739
931
  newSession.name = sessionName;
740
- this.broadcast({
741
- type: "system",
742
- subtype: "session_created",
932
+ this.broadcast(this.buildSessionCreatedMessage({
743
933
  sessionId: newId,
744
934
  provider: "codex",
745
935
  projectPath,
936
+ session: newSession,
937
+ permissionMode: legacyPermissionMode,
938
+ executionMode,
939
+ planMode,
746
940
  sandboxMode: sandboxModeToExternal(newSandboxMode),
747
941
  sourceSessionId: oldSessionId,
748
- ...(newSession?.worktreePath ? { worktreePath: newSession.worktreePath, worktreeBranch: newSession.worktreeBranch } : {}),
749
- });
942
+ }));
750
943
  this.broadcastSessionList();
751
944
  console.log(`[ws] Sandbox mode change (no thread): created new session ${newId} (sandbox=${newSandboxMode})`);
752
945
  break;
@@ -783,19 +976,17 @@ export class BridgeWebSocketServer {
783
976
  newSession.name = sessionName;
784
977
  }
785
978
  void this.loadAndSetSessionName(newSession, "codex", effectiveProjectPath, threadId).then(() => {
786
- this.broadcast({
787
- type: "system",
788
- subtype: "session_created",
979
+ this.broadcast(this.buildSessionCreatedMessage({
789
980
  sessionId: newId,
790
981
  provider: "codex",
791
982
  projectPath: effectiveProjectPath,
983
+ session: newSession,
984
+ permissionMode: legacyPermissionMode,
985
+ executionMode,
986
+ planMode,
792
987
  sandboxMode: sandboxModeToExternal(newSandboxMode),
793
988
  sourceSessionId: oldSessionId,
794
- ...(newSession?.worktreePath ? {
795
- worktreePath: newSession.worktreePath,
796
- worktreeBranch: newSession.worktreeBranch,
797
- } : {}),
798
- });
989
+ }));
799
990
  this.broadcastSessionList();
800
991
  });
801
992
  this.debugEvents.set(newId, []);
@@ -856,16 +1047,15 @@ export class BridgeWebSocketServer {
856
1047
  console.log(`[ws] Clear context: created new session ${newId} (CLI session: ${claudeSessionId ?? "new"})`);
857
1048
  // Notify all clients. Broadcast is used so reconnecting clients also receive it.
858
1049
  const newSession = this.sessionManager.get(newId);
859
- this.broadcast({
860
- type: "system",
861
- subtype: "session_created",
1050
+ const createdMsg = this.buildSessionCreatedMessage({
862
1051
  sessionId: newId,
863
1052
  provider: newSession?.provider ?? "claude",
864
1053
  projectPath,
865
- ...(permissionMode ? { permissionMode } : {}),
866
- clearContext: true,
1054
+ session: newSession,
1055
+ permissionMode,
867
1056
  sourceSessionId: sessionId,
868
1057
  });
1058
+ this.broadcast({ ...createdMsg, clearContext: true });
869
1059
  this.broadcastSessionList();
870
1060
  }
871
1061
  else {
@@ -1109,6 +1299,16 @@ export class BridgeWebSocketServer {
1109
1299
  break;
1110
1300
  }
1111
1301
  const provider = msg.provider ?? "claude";
1302
+ const executionMode = deriveExecutionMode({
1303
+ provider,
1304
+ permissionMode: msg.permissionMode,
1305
+ executionMode: msg.executionMode,
1306
+ });
1307
+ const planMode = derivePlanMode({
1308
+ permissionMode: msg.permissionMode,
1309
+ planMode: msg.planMode,
1310
+ });
1311
+ const legacyPermissionMode = modesToLegacyPermissionMode(provider, executionMode, planMode);
1112
1312
  const sessionRefId = msg.sessionId;
1113
1313
  // Resume flow: keep past history in SessionInfo and deliver it only
1114
1314
  // via get_history(sessionId) to avoid duplicate/missed replay races.
@@ -1133,29 +1333,28 @@ export class BridgeWebSocketServer {
1133
1333
  getCodexSessionHistory(sessionRefId).then((pastMessages) => {
1134
1334
  const sessionId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", {
1135
1335
  threadId: sessionRefId,
1136
- approvalPolicy: permissionModeToApprovalPolicy(msg.permissionMode),
1336
+ approvalPolicy: executionMode === "fullAccess" ? "never" : "on-request",
1137
1337
  sandboxMode: sandboxModeToInternal(msg.sandboxMode),
1138
1338
  model: msg.model,
1139
1339
  modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
1140
1340
  networkAccessEnabled: msg.networkAccessEnabled,
1141
1341
  webSearchMode: msg.webSearchMode ?? undefined,
1142
- collaborationMode: msg.permissionMode === "plan" ? "plan" : "default",
1342
+ collaborationMode: planMode ? "plan" : "default",
1143
1343
  });
1144
1344
  const createdSession = this.sessionManager.get(sessionId);
1145
1345
  void this.loadAndSetSessionName(createdSession, "codex", effectiveProjectPath, sessionRefId).then(() => {
1146
- this.send(ws, {
1147
- type: "system",
1148
- subtype: "session_created",
1346
+ this.send(ws, this.buildSessionCreatedMessage({
1149
1347
  sessionId,
1150
1348
  provider: "codex",
1151
1349
  projectPath: effectiveProjectPath,
1152
- ...(createdSession?.codexSettings?.sandboxMode ? { sandboxMode: sandboxModeToExternal(createdSession.codexSettings.sandboxMode) } : {}),
1153
- ...(msg.permissionMode ? { permissionMode: msg.permissionMode } : {}),
1154
- ...(createdSession?.worktreePath ? {
1155
- worktreePath: createdSession.worktreePath,
1156
- worktreeBranch: createdSession.worktreeBranch,
1157
- } : {}),
1158
- });
1350
+ session: createdSession,
1351
+ sandboxMode: createdSession?.codexSettings?.sandboxMode
1352
+ ? sandboxModeToExternal(createdSession.codexSettings.sandboxMode)
1353
+ : undefined,
1354
+ permissionMode: legacyPermissionMode,
1355
+ executionMode,
1356
+ planMode,
1357
+ }));
1159
1358
  this.broadcastSessionList();
1160
1359
  });
1161
1360
  this.debugEvents.set(sessionId, []);
@@ -1192,7 +1391,7 @@ export class BridgeWebSocketServer {
1192
1391
  getSessionHistory(claudeSessionId).then((pastMessages) => {
1193
1392
  const sessionId = this.sessionManager.create(msg.projectPath, {
1194
1393
  sessionId: claudeSessionId,
1195
- permissionMode: msg.permissionMode,
1394
+ permissionMode: legacyPermissionMode,
1196
1395
  model: msg.model,
1197
1396
  effort: msg.effort,
1198
1397
  maxTurns: msg.maxTurns,
@@ -1205,19 +1404,26 @@ export class BridgeWebSocketServer {
1205
1404
  const createdSession = this.sessionManager.get(sessionId);
1206
1405
  void this.loadAndSetSessionName(createdSession, "claude", msg.projectPath, claudeSessionId).then(() => {
1207
1406
  this.send(ws, {
1208
- type: "system",
1209
- subtype: "session_created",
1210
- sessionId,
1407
+ ...this.buildSessionCreatedMessage({
1408
+ sessionId,
1409
+ provider: "claude",
1410
+ projectPath: msg.projectPath,
1411
+ session: createdSession,
1412
+ permissionMode: legacyPermissionMode,
1413
+ executionMode,
1414
+ planMode,
1415
+ sandboxMode: msg.sandboxMode,
1416
+ ...(cached
1417
+ ? {
1418
+ slashCommands: cached.slashCommands,
1419
+ skills: cached.skills,
1420
+ ...(cached.skillMetadata
1421
+ ? { skillMetadata: cached.skillMetadata }
1422
+ : {}),
1423
+ }
1424
+ : {}),
1425
+ }),
1211
1426
  claudeSessionId,
1212
- provider: "claude",
1213
- projectPath: msg.projectPath,
1214
- ...(msg.permissionMode ? { permissionMode: msg.permissionMode } : {}),
1215
- ...(msg.sandboxMode ? { sandboxMode: msg.sandboxMode } : {}),
1216
- ...(cached ? { slashCommands: cached.slashCommands, skills: cached.skills, ...(cached.skillMetadata ? { skillMetadata: cached.skillMetadata } : {}) } : {}),
1217
- ...(createdSession?.worktreePath ? {
1218
- worktreePath: createdSession.worktreePath,
1219
- worktreeBranch: createdSession.worktreeBranch,
1220
- } : {}),
1221
1427
  });
1222
1428
  this.broadcastSessionList();
1223
1429
  });
@@ -1527,15 +1733,14 @@ export class BridgeWebSocketServer {
1527
1733
  // Notify the new session ID
1528
1734
  const newSession = this.sessionManager.get(newSessionId);
1529
1735
  const rewindPermMode = newSession?.process instanceof SdkProcess ? newSession.process.permissionMode : undefined;
1530
- this.send(ws, {
1531
- type: "system",
1532
- subtype: "session_created",
1736
+ this.send(ws, this.buildSessionCreatedMessage({
1533
1737
  sessionId: newSessionId,
1534
1738
  provider: newSession?.provider ?? "claude",
1535
1739
  projectPath: newSession?.projectPath ?? "",
1536
- ...(rewindPermMode ? { permissionMode: rewindPermMode } : {}),
1740
+ session: newSession,
1741
+ permissionMode: rewindPermMode,
1537
1742
  sourceSessionId: msg.sessionId,
1538
- });
1743
+ }));
1539
1744
  this.sendSessionList(ws);
1540
1745
  });
1541
1746
  }
@@ -1555,15 +1760,14 @@ export class BridgeWebSocketServer {
1555
1760
  this.send(ws, { type: "rewind_result", success: true, mode: "both" });
1556
1761
  const newSession = this.sessionManager.get(newSessionId);
1557
1762
  const rewindPermMode2 = newSession?.process instanceof SdkProcess ? newSession.process.permissionMode : undefined;
1558
- this.send(ws, {
1559
- type: "system",
1560
- subtype: "session_created",
1763
+ this.send(ws, this.buildSessionCreatedMessage({
1561
1764
  sessionId: newSessionId,
1562
1765
  provider: newSession?.provider ?? "claude",
1563
1766
  projectPath: newSession?.projectPath ?? "",
1564
- ...(rewindPermMode2 ? { permissionMode: rewindPermMode2 } : {}),
1767
+ session: newSession,
1768
+ permissionMode: rewindPermMode2,
1565
1769
  sourceSessionId: msg.sessionId,
1566
- });
1770
+ }));
1567
1771
  this.sendSessionList(ws);
1568
1772
  });
1569
1773
  }