@ccpocket/bridge 1.76.0 → 1.78.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
@@ -27,6 +27,12 @@ import { getPackageVersion } from "./version.js";
27
27
  import { isPathWithinAllowedDirectory, resolvePlatformPath, resolvePlatformPathFrom, } from "./path-utils.js";
28
28
  import { DirectoryListingError, listAllowedDirectories, } from "./directory-listing.js";
29
29
  import { deriveCodexPermissionsMode, normalizeCodexPermissionsMode, withDerivedCodexPermissionsMode, } from "./codex-permissions.js";
30
+ function projectRequestMetadata(request) {
31
+ return {
32
+ projectPath: request.projectPath,
33
+ ...(request.requestId ? { requestId: request.requestId } : {}),
34
+ };
35
+ }
30
36
  const RESUME_OPERATION_TIMEOUT_MS = 5 * 60 * 1000;
31
37
  const RESUME_COMPLETED_TTL_MS = 30 * 1000;
32
38
  // ---- Available model lists (delivered to clients via session_list) ----
@@ -792,11 +798,13 @@ export class BridgeWebSocketServer {
792
798
  }
793
799
  }
794
800
  /** Build a user-friendly error for disallowed project paths. */
795
- buildPathNotAllowedError(projectPath) {
801
+ buildPathNotAllowedError(projectPath, requestId) {
796
802
  return {
797
803
  type: "error",
798
804
  message: `⚠ Project path not allowed\n\n"${projectPath}" is not in the allowed directories.\n\nFix: Update BRIDGE_ALLOWED_DIRS on the Bridge server to include this path.`,
799
805
  errorCode: "path_not_allowed",
806
+ path: projectPath,
807
+ requestId,
800
808
  };
801
809
  }
802
810
  sendToolActionError(ws, message, error) {
@@ -827,7 +835,7 @@ export class BridgeWebSocketServer {
827
835
  return { roots: [...normalized.values()] };
828
836
  }
829
837
  buildSessionCreatedMessage(params) {
830
- const { sessionId, provider, projectPath, session, permissionMode, executionMode, planMode, approvalsReviewer, codexPermissionsMode, sandboxMode, slashCommands, skills, skillMetadata, apps, appMetadata, plugins, pluginMetadata, sourceSessionId, resumeRequestId, } = params;
838
+ const { sessionId, provider, projectPath, session, permissionMode, executionMode, planMode, approvalsReviewer, codexPermissionsMode, sandboxMode, slashCommands, skills, skillMetadata, apps, appMetadata, plugins, pluginMetadata, sourceSessionId, resumeRequestId, requestId, } = params;
831
839
  const derivedCodexSettings = provider === "codex"
832
840
  ? withDerivedCodexPermissionsMode(session?.codexSettings)
833
841
  : session?.codexSettings;
@@ -923,6 +931,7 @@ export class BridgeWebSocketServer {
923
931
  : {}),
924
932
  ...(sourceSessionId ? { sourceSessionId } : {}),
925
933
  ...(resumeRequestId ? { resumeRequestId } : {}),
934
+ ...(requestId ? { requestId } : {}),
926
935
  };
927
936
  if (provider === "codex" && derivedCodexSettings) {
928
937
  if (derivedCodexSettings.model !== undefined) {
@@ -957,6 +966,7 @@ export class BridgeWebSocketServer {
957
966
  if (mode !== "conversation") {
958
967
  this.send(ws, {
959
968
  type: "rewind_result",
969
+ sessionId,
960
970
  success: false,
961
971
  mode,
962
972
  error: "Codex only supports conversation rewind",
@@ -967,6 +977,7 @@ export class BridgeWebSocketServer {
967
977
  if (!session) {
968
978
  this.send(ws, {
969
979
  type: "rewind_result",
980
+ sessionId,
970
981
  success: false,
971
982
  mode,
972
983
  error: `Session ${sessionId} not found`,
@@ -978,6 +989,7 @@ export class BridgeWebSocketServer {
978
989
  typeof codexProcess.rollbackThread !== "function") {
979
990
  this.send(ws, {
980
991
  type: "rewind_result",
992
+ sessionId,
981
993
  success: false,
982
994
  mode,
983
995
  error: "Session is not a Codex session",
@@ -987,6 +999,7 @@ export class BridgeWebSocketServer {
987
999
  if (session.status !== "idle" || (codexProcess.status ?? session.status) !== "idle") {
988
1000
  this.send(ws, {
989
1001
  type: "rewind_result",
1002
+ sessionId,
990
1003
  success: false,
991
1004
  mode,
992
1005
  error: "Cannot rewind while Codex is running",
@@ -996,6 +1009,7 @@ export class BridgeWebSocketServer {
996
1009
  if (session.codexQueuedInput) {
997
1010
  this.send(ws, {
998
1011
  type: "rewind_result",
1012
+ sessionId,
999
1013
  success: false,
1000
1014
  mode,
1001
1015
  error: "Cannot rewind while Codex has queued input",
@@ -1007,6 +1021,7 @@ export class BridgeWebSocketServer {
1007
1021
  if (targetOrdinal === null || targetOrdinal > totalUserTurns) {
1008
1022
  this.send(ws, {
1009
1023
  type: "rewind_result",
1024
+ sessionId,
1010
1025
  success: false,
1011
1026
  mode,
1012
1027
  error: "Invalid Codex rewind target",
@@ -1017,6 +1032,7 @@ export class BridgeWebSocketServer {
1017
1032
  if (numTurns <= 0) {
1018
1033
  this.send(ws, {
1019
1034
  type: "rewind_result",
1035
+ sessionId,
1020
1036
  success: false,
1021
1037
  mode,
1022
1038
  error: "Invalid Codex rewind target",
@@ -1027,6 +1043,7 @@ export class BridgeWebSocketServer {
1027
1043
  if (!threadId) {
1028
1044
  this.send(ws, {
1029
1045
  type: "rewind_result",
1046
+ sessionId,
1030
1047
  success: false,
1031
1048
  mode,
1032
1049
  error: "No Codex thread ID available for rewind",
@@ -1055,6 +1072,7 @@ export class BridgeWebSocketServer {
1055
1072
  const newSession = this.sessionManager.get(newSessionId);
1056
1073
  this.send(ws, {
1057
1074
  type: "rewind_result",
1075
+ sessionId,
1058
1076
  success: true,
1059
1077
  mode,
1060
1078
  });
@@ -1888,7 +1906,11 @@ export class BridgeWebSocketServer {
1888
1906
  case "start": {
1889
1907
  const projectPath = resolvePlatformPath(msg.projectPath, this.platform);
1890
1908
  if (!this.isPathAllowed(projectPath)) {
1891
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
1909
+ this.send(ws, {
1910
+ ...this.buildPathNotAllowedError(msg.projectPath),
1911
+ requestId: msg.requestId,
1912
+ path: msg.projectPath,
1913
+ });
1892
1914
  break;
1893
1915
  }
1894
1916
  try {
@@ -1933,6 +1955,8 @@ export class BridgeWebSocketServer {
1933
1955
  !(await this.validateCodexProfile(msg.profile, projectPath))) {
1934
1956
  this.send(ws, {
1935
1957
  type: "error",
1958
+ requestId: msg.requestId,
1959
+ path: projectPath,
1936
1960
  message: `Codex profile not found: ${msg.profile}`,
1937
1961
  });
1938
1962
  break;
@@ -1942,7 +1966,11 @@ export class BridgeWebSocketServer {
1942
1966
  ? this.normalizeAdditionalWritableRoots(msg.additionalWritableRoots, projectPath)
1943
1967
  : {};
1944
1968
  if (additionalWritableRoots.deniedRoot) {
1945
- this.send(ws, this.buildPathNotAllowedError(additionalWritableRoots.deniedRoot));
1969
+ this.send(ws, {
1970
+ ...this.buildPathNotAllowedError(additionalWritableRoots.deniedRoot),
1971
+ requestId: msg.requestId,
1972
+ path: projectPath,
1973
+ });
1946
1974
  break;
1947
1975
  }
1948
1976
  const { sessionId, permissionMode: effectivePermissionMode, executionMode: effectiveExecutionMode, planMode: effectivePlanMode, usedFallback: autoFallbackUsed, } = provider === "claude"
@@ -2028,6 +2056,7 @@ export class BridgeWebSocketServer {
2028
2056
  : msg.sandboxMode,
2029
2057
  codexPermissionsMode: createdSession?.codexSettings?.codexPermissionsMode,
2030
2058
  approvalsReviewer: createdSession?.codexSettings?.approvalsReviewer,
2059
+ requestId: msg.requestId,
2031
2060
  ...(cached
2032
2061
  ? {
2033
2062
  slashCommands: cached.slashCommands,
@@ -2079,6 +2108,8 @@ export class BridgeWebSocketServer {
2079
2108
  console.error(`[ws] Failed to start session:`, err);
2080
2109
  this.send(ws, {
2081
2110
  type: "error",
2111
+ requestId: msg.requestId,
2112
+ path: msg.projectPath,
2082
2113
  message: `Failed to start session: ${err.message}`,
2083
2114
  });
2084
2115
  }
@@ -2096,6 +2127,7 @@ export class BridgeWebSocketServer {
2096
2127
  }
2097
2128
  this.send(ws, {
2098
2129
  type: "error",
2130
+ sessionId: msg.sessionId,
2099
2131
  message: "No active session. Send 'start' first.",
2100
2132
  });
2101
2133
  return;
@@ -2409,12 +2441,17 @@ export class BridgeWebSocketServer {
2409
2441
  case "update_queued_input": {
2410
2442
  const session = this.resolveSession(msg.sessionId);
2411
2443
  if (!session || session.provider !== "codex") {
2412
- this.send(ws, { type: "error", message: "No active Codex session." });
2444
+ this.send(ws, {
2445
+ type: "error",
2446
+ sessionId: msg.sessionId,
2447
+ message: "No active Codex session.",
2448
+ });
2413
2449
  return;
2414
2450
  }
2415
2451
  if (!msg.text.trim()) {
2416
2452
  this.send(ws, {
2417
2453
  type: "error",
2454
+ sessionId: msg.sessionId,
2418
2455
  message: "Queued message cannot be empty.",
2419
2456
  });
2420
2457
  return;
@@ -2423,6 +2460,7 @@ export class BridgeWebSocketServer {
2423
2460
  if (!success) {
2424
2461
  this.send(ws, {
2425
2462
  type: "error",
2463
+ sessionId: msg.sessionId,
2426
2464
  message: "Queued message not found.",
2427
2465
  errorCode: "queued_input_not_found",
2428
2466
  });
@@ -2434,13 +2472,18 @@ export class BridgeWebSocketServer {
2434
2472
  case "cancel_queued_input": {
2435
2473
  const session = this.resolveSession(msg.sessionId);
2436
2474
  if (!session || session.provider !== "codex") {
2437
- this.send(ws, { type: "error", message: "No active Codex session." });
2475
+ this.send(ws, {
2476
+ type: "error",
2477
+ sessionId: msg.sessionId,
2478
+ message: "No active Codex session.",
2479
+ });
2438
2480
  return;
2439
2481
  }
2440
2482
  const success = this.sessionManager.cancelCodexQueuedInput(session.id, msg.itemId);
2441
2483
  if (!success) {
2442
2484
  this.send(ws, {
2443
2485
  type: "error",
2486
+ sessionId: msg.sessionId,
2444
2487
  message: "Queued message not found.",
2445
2488
  errorCode: "queued_input_not_found",
2446
2489
  });
@@ -2452,13 +2495,18 @@ export class BridgeWebSocketServer {
2452
2495
  case "steer_queued_input": {
2453
2496
  const session = this.resolveSession(msg.sessionId);
2454
2497
  if (!session || session.provider !== "codex") {
2455
- this.send(ws, { type: "error", message: "No active Codex session." });
2498
+ this.send(ws, {
2499
+ type: "error",
2500
+ sessionId: msg.sessionId,
2501
+ message: "No active Codex session.",
2502
+ });
2456
2503
  return;
2457
2504
  }
2458
2505
  const result = await this.sessionManager.steerCodexQueuedInput(session.id, msg.itemId);
2459
2506
  if (!result.ok) {
2460
2507
  this.send(ws, {
2461
2508
  type: "error",
2509
+ sessionId: msg.sessionId,
2462
2510
  message: result.error,
2463
2511
  errorCode: result.error === "Queued message not found."
2464
2512
  ? "queued_input_not_found"
@@ -2559,6 +2607,7 @@ export class BridgeWebSocketServer {
2559
2607
  if (this.failSetPermissionMode) {
2560
2608
  this.send(ws, {
2561
2609
  type: "error",
2610
+ sessionId: msg.sessionId,
2562
2611
  message: "Failed to set permission mode: forced test failure",
2563
2612
  errorCode: "set_permission_mode_rejected",
2564
2613
  });
@@ -2566,7 +2615,11 @@ export class BridgeWebSocketServer {
2566
2615
  }
2567
2616
  const session = this.resolveSession(msg.sessionId);
2568
2617
  if (!session) {
2569
- this.send(ws, { type: "error", message: "No active session." });
2618
+ this.send(ws, {
2619
+ type: "error",
2620
+ sessionId: msg.sessionId,
2621
+ message: "No active session.",
2622
+ });
2570
2623
  return;
2571
2624
  }
2572
2625
  if (session.provider === "codex") {
@@ -2816,6 +2869,7 @@ export class BridgeWebSocketServer {
2816
2869
  .catch((err) => {
2817
2870
  this.send(ws, {
2818
2871
  type: "error",
2872
+ sessionId: msg.sessionId,
2819
2873
  message: `Failed to restart session for permission mode change: ${err}`,
2820
2874
  });
2821
2875
  });
@@ -2828,6 +2882,7 @@ export class BridgeWebSocketServer {
2828
2882
  isClaudeAutoModeUnavailableError(err)) {
2829
2883
  this.send(ws, {
2830
2884
  type: "error",
2885
+ sessionId: msg.sessionId,
2831
2886
  message: "Auto mode is unavailable in this environment. Keeping the current permission mode.",
2832
2887
  errorCode: "auto_mode_unavailable",
2833
2888
  });
@@ -2835,6 +2890,7 @@ export class BridgeWebSocketServer {
2835
2890
  }
2836
2891
  this.send(ws, {
2837
2892
  type: "error",
2893
+ sessionId: msg.sessionId,
2838
2894
  message: `Failed to set permission mode: ${errorMessageOf(err)}`,
2839
2895
  });
2840
2896
  });
@@ -2843,12 +2899,17 @@ export class BridgeWebSocketServer {
2843
2899
  case "set_codex_model": {
2844
2900
  const session = this.resolveSession(msg.sessionId);
2845
2901
  if (!session) {
2846
- this.send(ws, { type: "error", message: "No active session." });
2902
+ this.send(ws, {
2903
+ type: "error",
2904
+ sessionId: msg.sessionId,
2905
+ message: "No active session.",
2906
+ });
2847
2907
  return;
2848
2908
  }
2849
2909
  if (session.provider !== "codex") {
2850
2910
  this.send(ws, {
2851
2911
  type: "error",
2912
+ sessionId: msg.sessionId,
2852
2913
  message: "Model switching is only supported for Codex sessions.",
2853
2914
  errorCode: "set_codex_model_unsupported",
2854
2915
  });
@@ -2858,6 +2919,7 @@ export class BridgeWebSocketServer {
2858
2919
  if (!model) {
2859
2920
  this.send(ws, {
2860
2921
  type: "error",
2922
+ sessionId: msg.sessionId,
2861
2923
  message: `Invalid Codex model: ${msg.model}`,
2862
2924
  errorCode: "set_codex_model_rejected",
2863
2925
  });
@@ -2904,6 +2966,7 @@ export class BridgeWebSocketServer {
2904
2966
  if (!session || session.provider !== "codex") {
2905
2967
  this.send(ws, {
2906
2968
  type: "error",
2969
+ sessionId: msg.sessionId,
2907
2970
  message: "No active Codex session.",
2908
2971
  errorCode: "set_codex_speed_unsupported",
2909
2972
  });
@@ -2935,6 +2998,7 @@ export class BridgeWebSocketServer {
2935
2998
  if (!session || session.provider !== "codex") {
2936
2999
  this.send(ws, {
2937
3000
  type: "error",
3001
+ sessionId: msg.sessionId,
2938
3002
  message: "Goal lookup is only supported for active Codex sessions.",
2939
3003
  errorCode: "goal_get_unsupported",
2940
3004
  });
@@ -2948,6 +3012,7 @@ export class BridgeWebSocketServer {
2948
3012
  catch (err) {
2949
3013
  this.send(ws, {
2950
3014
  type: "error",
3015
+ sessionId: msg.sessionId,
2951
3016
  message: `Failed to get goal: ${errorMessageOf(err)}`,
2952
3017
  errorCode: "goal_get_failed",
2953
3018
  });
@@ -2959,6 +3024,7 @@ export class BridgeWebSocketServer {
2959
3024
  if (!session || session.provider !== "codex") {
2960
3025
  this.send(ws, {
2961
3026
  type: "error",
3027
+ sessionId: msg.sessionId,
2962
3028
  message: "Goal updates are only supported for active Codex sessions.",
2963
3029
  errorCode: "goal_set_unsupported",
2964
3030
  });
@@ -2980,6 +3046,7 @@ export class BridgeWebSocketServer {
2980
3046
  catch (err) {
2981
3047
  this.send(ws, {
2982
3048
  type: "error",
3049
+ sessionId: msg.sessionId,
2983
3050
  message: `Failed to set goal: ${errorMessageOf(err)}`,
2984
3051
  errorCode: "goal_set_failed",
2985
3052
  });
@@ -2991,6 +3058,7 @@ export class BridgeWebSocketServer {
2991
3058
  if (!session || session.provider !== "codex") {
2992
3059
  this.send(ws, {
2993
3060
  type: "error",
3061
+ sessionId: msg.sessionId,
2994
3062
  message: "Goal clearing is only supported for active Codex sessions.",
2995
3063
  errorCode: "goal_clear_unsupported",
2996
3064
  });
@@ -3007,6 +3075,7 @@ export class BridgeWebSocketServer {
3007
3075
  catch (err) {
3008
3076
  this.send(ws, {
3009
3077
  type: "error",
3078
+ sessionId: msg.sessionId,
3010
3079
  message: `Failed to clear goal: ${errorMessageOf(err)}`,
3011
3080
  errorCode: "goal_clear_failed",
3012
3081
  });
@@ -3017,6 +3086,7 @@ export class BridgeWebSocketServer {
3017
3086
  if (this.failSetSandboxMode) {
3018
3087
  this.send(ws, {
3019
3088
  type: "error",
3089
+ sessionId: msg.sessionId,
3020
3090
  message: "Failed to set sandbox mode: forced test failure",
3021
3091
  errorCode: "set_sandbox_mode_rejected",
3022
3092
  });
@@ -3024,12 +3094,17 @@ export class BridgeWebSocketServer {
3024
3094
  }
3025
3095
  const session = this.resolveSession(msg.sessionId);
3026
3096
  if (!session) {
3027
- this.send(ws, { type: "error", message: "No active session." });
3097
+ this.send(ws, {
3098
+ type: "error",
3099
+ sessionId: msg.sessionId,
3100
+ message: "No active session.",
3101
+ });
3028
3102
  return;
3029
3103
  }
3030
3104
  if (msg.sandboxMode !== "on" && msg.sandboxMode !== "off") {
3031
3105
  this.send(ws, {
3032
3106
  type: "error",
3107
+ sessionId: msg.sessionId,
3033
3108
  message: `Invalid sandbox mode: ${msg.sandboxMode}`,
3034
3109
  });
3035
3110
  return;
@@ -3217,6 +3292,7 @@ export class BridgeWebSocketServer {
3217
3292
  .catch((err) => {
3218
3293
  this.send(ws, {
3219
3294
  type: "error",
3295
+ sessionId: msg.sessionId,
3220
3296
  message: `Failed to restart session for sandbox mode change: ${err}`,
3221
3297
  });
3222
3298
  });
@@ -3388,6 +3464,7 @@ export class BridgeWebSocketServer {
3388
3464
  else {
3389
3465
  this.send(ws, {
3390
3466
  type: "error",
3467
+ sessionId: msg.sessionId,
3391
3468
  message: `Session ${msg.sessionId} not found`,
3392
3469
  });
3393
3470
  }
@@ -3497,6 +3574,7 @@ export class BridgeWebSocketServer {
3497
3574
  if (!result) {
3498
3575
  this.send(ws, {
3499
3576
  type: "error",
3577
+ sessionId: msg.sessionId,
3500
3578
  message: `Session ${msg.sessionId} not found`,
3501
3579
  });
3502
3580
  break;
@@ -3524,6 +3602,7 @@ export class BridgeWebSocketServer {
3524
3602
  if (!result) {
3525
3603
  this.send(ws, {
3526
3604
  type: "error",
3605
+ sessionId: msg.sessionId,
3527
3606
  message: `Session ${msg.sessionId} not found`,
3528
3607
  });
3529
3608
  break;
@@ -3554,6 +3633,7 @@ export class BridgeWebSocketServer {
3554
3633
  else {
3555
3634
  this.send(ws, {
3556
3635
  type: "error",
3636
+ sessionId: msg.sessionId,
3557
3637
  message: `Session ${msg.sessionId} not found`,
3558
3638
  });
3559
3639
  }
@@ -3584,6 +3664,7 @@ export class BridgeWebSocketServer {
3584
3664
  else {
3585
3665
  this.send(ws, {
3586
3666
  type: "error",
3667
+ sessionId: msg.sessionId,
3587
3668
  message: `Session ${msg.sessionId} not found`,
3588
3669
  });
3589
3670
  }
@@ -3594,6 +3675,7 @@ export class BridgeWebSocketServer {
3594
3675
  if (!session) {
3595
3676
  this.send(ws, {
3596
3677
  type: "error",
3678
+ sessionId: msg.sessionId,
3597
3679
  message: `Session ${msg.sessionId} not found`,
3598
3680
  });
3599
3681
  return;
@@ -4168,7 +4250,11 @@ export class BridgeWebSocketServer {
4168
4250
  case "interrupt": {
4169
4251
  const session = this.resolveSession(msg.sessionId);
4170
4252
  if (!session) {
4171
- this.send(ws, { type: "error", message: "No active session." });
4253
+ this.send(ws, {
4254
+ type: "error",
4255
+ sessionId: msg.sessionId,
4256
+ message: "No active session.",
4257
+ });
4172
4258
  return;
4173
4259
  }
4174
4260
  session.process.interrupt();
@@ -4228,11 +4314,15 @@ export class BridgeWebSocketServer {
4228
4314
  }
4229
4315
  case "read_file":
4230
4316
  case "read_media_file": {
4317
+ const responseMetadata = {
4318
+ ...projectRequestMetadata(msg),
4319
+ filePath: msg.filePath,
4320
+ };
4231
4321
  const absPath = resolve(msg.projectPath, msg.filePath);
4232
4322
  if (!this.isPathAllowed(absPath)) {
4233
4323
  this.send(ws, {
4234
4324
  type: "file_content",
4235
- filePath: msg.filePath,
4325
+ ...responseMetadata,
4236
4326
  content: "",
4237
4327
  error: "Path not allowed",
4238
4328
  });
@@ -4243,7 +4333,7 @@ export class BridgeWebSocketServer {
4243
4333
  if (!existsSync(absPath)) {
4244
4334
  this.send(ws, {
4245
4335
  type: "file_content",
4246
- filePath: msg.filePath,
4336
+ ...responseMetadata,
4247
4337
  content: "",
4248
4338
  error: "File not found",
4249
4339
  });
@@ -4265,7 +4355,7 @@ export class BridgeWebSocketServer {
4265
4355
  catch {
4266
4356
  this.send(ws, {
4267
4357
  type: "file_content",
4268
- filePath: msg.filePath,
4358
+ ...responseMetadata,
4269
4359
  content: "",
4270
4360
  error: targetPath.length > 0
4271
4361
  ? `This symbolic link points to a missing target: ${targetPath}`
@@ -4276,7 +4366,7 @@ export class BridgeWebSocketServer {
4276
4366
  if (resolvedTargetStat.isDirectory()) {
4277
4367
  this.send(ws, {
4278
4368
  type: "file_content",
4279
- filePath: msg.filePath,
4369
+ ...responseMetadata,
4280
4370
  content: "",
4281
4371
  error: targetPath.length > 0
4282
4372
  ? `This symbolic link points to a directory (${targetPath}). Open the target directory instead.`
@@ -4288,7 +4378,7 @@ export class BridgeWebSocketServer {
4288
4378
  else if (fileStat.isDirectory()) {
4289
4379
  this.send(ws, {
4290
4380
  type: "file_content",
4291
- filePath: msg.filePath,
4381
+ ...responseMetadata,
4292
4382
  content: "",
4293
4383
  error: "This path is a directory. Open a file instead.",
4294
4384
  });
@@ -4301,7 +4391,7 @@ export class BridgeWebSocketServer {
4301
4391
  if (!(await this.isCanonicalPathAllowed(canonicalPath))) {
4302
4392
  this.send(ws, {
4303
4393
  type: "file_content",
4304
- filePath: msg.filePath,
4394
+ ...responseMetadata,
4305
4395
  content: "",
4306
4396
  error: "Path not allowed",
4307
4397
  });
@@ -4313,7 +4403,7 @@ export class BridgeWebSocketServer {
4313
4403
  if (!this.mediaStore) {
4314
4404
  this.send(ws, {
4315
4405
  type: "file_content",
4316
- filePath: msg.filePath,
4406
+ ...responseMetadata,
4317
4407
  kind: mediaType.kind,
4318
4408
  content: "",
4319
4409
  mimeType: mediaType.mimeType,
@@ -4325,7 +4415,7 @@ export class BridgeWebSocketServer {
4325
4415
  const ref = await this.mediaStore.register(canonicalPath, mediaType.mimeType, resolvedFileStat.size);
4326
4416
  this.send(ws, {
4327
4417
  type: "file_content",
4328
- filePath: msg.filePath,
4418
+ ...responseMetadata,
4329
4419
  kind: mediaType.kind,
4330
4420
  content: "",
4331
4421
  mimeType: ref.mimeType,
@@ -4337,7 +4427,7 @@ export class BridgeWebSocketServer {
4337
4427
  if (msg.type === "read_media_file") {
4338
4428
  this.send(ws, {
4339
4429
  type: "file_content",
4340
- filePath: msg.filePath,
4430
+ ...responseMetadata,
4341
4431
  content: "",
4342
4432
  error: "Unsupported media file type.",
4343
4433
  });
@@ -4348,7 +4438,7 @@ export class BridgeWebSocketServer {
4348
4438
  if (resolvedFileStat.size > BridgeWebSocketServer.MAX_IMAGE_SIZE) {
4349
4439
  this.send(ws, {
4350
4440
  type: "file_content",
4351
- filePath: msg.filePath,
4441
+ ...responseMetadata,
4352
4442
  kind: "image",
4353
4443
  content: "",
4354
4444
  mimeType,
@@ -4360,7 +4450,7 @@ export class BridgeWebSocketServer {
4360
4450
  const buf = await readFile(absPath);
4361
4451
  this.send(ws, {
4362
4452
  type: "file_content",
4363
- filePath: msg.filePath,
4453
+ ...responseMetadata,
4364
4454
  kind: "image",
4365
4455
  content: "",
4366
4456
  base64: buf.toString("base64"),
@@ -4416,7 +4506,7 @@ export class BridgeWebSocketServer {
4416
4506
  : raw;
4417
4507
  this.send(ws, {
4418
4508
  type: "file_content",
4419
- filePath: msg.filePath,
4509
+ ...responseMetadata,
4420
4510
  kind: "text",
4421
4511
  content,
4422
4512
  language,
@@ -4427,7 +4517,7 @@ export class BridgeWebSocketServer {
4427
4517
  catch (err) {
4428
4518
  this.send(ws, {
4429
4519
  type: "file_content",
4430
- filePath: msg.filePath,
4520
+ ...responseMetadata,
4431
4521
  content: "",
4432
4522
  error: `Failed to read file: ${err}`,
4433
4523
  });
@@ -4437,7 +4527,12 @@ export class BridgeWebSocketServer {
4437
4527
  }
4438
4528
  case "list_files": {
4439
4529
  if (!this.isPathAllowed(msg.projectPath)) {
4440
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4530
+ this.send(ws, {
4531
+ type: "file_list",
4532
+ ...projectRequestMetadata(msg),
4533
+ files: [],
4534
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4535
+ });
4441
4536
  break;
4442
4537
  }
4443
4538
  void (async () => {
@@ -4448,7 +4543,10 @@ export class BridgeWebSocketServer {
4448
4543
  });
4449
4544
  this.send(ws, {
4450
4545
  type: "file_list",
4546
+ ...projectRequestMetadata(msg),
4451
4547
  files: result.files,
4548
+ ignored: result.ignored,
4549
+ modifiedAt: result.modifiedAt,
4452
4550
  totalFiles: result.totalFiles,
4453
4551
  truncated: result.truncated,
4454
4552
  });
@@ -4456,8 +4554,10 @@ export class BridgeWebSocketServer {
4456
4554
  catch (err) {
4457
4555
  const message = err instanceof Error ? err.message : String(err);
4458
4556
  this.send(ws, {
4459
- type: "error",
4460
- message: `Failed to list files: ${message}`,
4557
+ type: "file_list",
4558
+ ...projectRequestMetadata(msg),
4559
+ files: [],
4560
+ error: `Failed to list files: ${message}`,
4461
4561
  });
4462
4562
  }
4463
4563
  })();
@@ -4547,7 +4647,15 @@ export class BridgeWebSocketServer {
4547
4647
  }
4548
4648
  case "get_diff": {
4549
4649
  if (!this.isPathAllowed(msg.projectPath)) {
4550
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4650
+ this.send(ws, {
4651
+ type: "diff_result",
4652
+ ...projectRequestMetadata(msg),
4653
+ staged: msg.staged === true,
4654
+ diff: "",
4655
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId)
4656
+ .message,
4657
+ errorCode: "path_not_allowed",
4658
+ });
4551
4659
  break;
4552
4660
  }
4553
4661
  this.collectGitDiff(msg.projectPath, ({ diff, error }) => {
@@ -4555,6 +4663,8 @@ export class BridgeWebSocketServer {
4555
4663
  if (/not a git repository/i.test(error)) {
4556
4664
  this.send(ws, {
4557
4665
  type: "diff_result",
4666
+ ...projectRequestMetadata(msg),
4667
+ staged: msg.staged === true,
4558
4668
  diff: "",
4559
4669
  error: "This project is not a git repository",
4560
4670
  errorCode: "git_not_available",
@@ -4563,6 +4673,8 @@ export class BridgeWebSocketServer {
4563
4673
  else {
4564
4674
  this.send(ws, {
4565
4675
  type: "diff_result",
4676
+ ...projectRequestMetadata(msg),
4677
+ staged: msg.staged === true,
4566
4678
  diff: "",
4567
4679
  error: `Failed to get diff: ${error}`,
4568
4680
  });
@@ -4571,10 +4683,21 @@ export class BridgeWebSocketServer {
4571
4683
  }
4572
4684
  void this.collectImageChanges(msg.projectPath, diff).then((imageChanges) => {
4573
4685
  if (imageChanges.length > 0) {
4574
- this.send(ws, { type: "diff_result", diff, imageChanges });
4686
+ this.send(ws, {
4687
+ type: "diff_result",
4688
+ ...projectRequestMetadata(msg),
4689
+ staged: msg.staged === true,
4690
+ diff,
4691
+ imageChanges,
4692
+ });
4575
4693
  }
4576
4694
  else {
4577
- this.send(ws, { type: "diff_result", diff });
4695
+ this.send(ws, {
4696
+ type: "diff_result",
4697
+ ...projectRequestMetadata(msg),
4698
+ staged: msg.staged === true,
4699
+ diff,
4700
+ });
4578
4701
  }
4579
4702
  });
4580
4703
  }, msg.staged === true
@@ -4587,7 +4710,13 @@ export class BridgeWebSocketServer {
4587
4710
  case "get_diff_image": {
4588
4711
  if (!this.isPathAllowed(msg.projectPath) ||
4589
4712
  !this.isPathAllowed(resolve(msg.projectPath, msg.filePath))) {
4590
- this.send(ws, { type: "error", message: `Path not allowed` });
4713
+ this.send(ws, {
4714
+ type: "diff_image_result",
4715
+ ...projectRequestMetadata(msg),
4716
+ filePath: msg.filePath,
4717
+ version: msg.version,
4718
+ error: "Path not allowed",
4719
+ });
4591
4720
  break;
4592
4721
  }
4593
4722
  if (msg.version === "both") {
@@ -4600,6 +4729,7 @@ export class BridgeWebSocketServer {
4600
4729
  const errors = [oldResult.error, newResult.error].filter(Boolean);
4601
4730
  this.send(ws, {
4602
4731
  type: "diff_image_result",
4732
+ ...projectRequestMetadata(msg),
4603
4733
  filePath: msg.filePath,
4604
4734
  version: "both",
4605
4735
  oldBase64: oldResult.base64,
@@ -4620,6 +4750,7 @@ export class BridgeWebSocketServer {
4620
4750
  const result = await this.loadDiffImageAsync(msg.projectPath, msg.filePath, version);
4621
4751
  this.send(ws, {
4622
4752
  type: "diff_image_result",
4753
+ ...projectRequestMetadata(msg),
4623
4754
  filePath: msg.filePath,
4624
4755
  version,
4625
4756
  ...result,
@@ -4634,25 +4765,42 @@ export class BridgeWebSocketServer {
4634
4765
  }
4635
4766
  case "list_worktrees": {
4636
4767
  if (!this.isPathAllowed(msg.projectPath)) {
4637
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4768
+ this.send(ws, {
4769
+ type: "worktree_list",
4770
+ ...projectRequestMetadata(msg),
4771
+ worktrees: [],
4772
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4773
+ });
4638
4774
  break;
4639
4775
  }
4640
4776
  try {
4641
4777
  const worktrees = listWorktrees(msg.projectPath);
4642
4778
  const mainBranch = getMainBranch(msg.projectPath);
4643
- this.send(ws, { type: "worktree_list", worktrees, mainBranch });
4779
+ this.send(ws, {
4780
+ type: "worktree_list",
4781
+ ...projectRequestMetadata(msg),
4782
+ worktrees,
4783
+ mainBranch,
4784
+ });
4644
4785
  }
4645
4786
  catch (err) {
4646
4787
  this.send(ws, {
4647
- type: "error",
4648
- message: `Failed to list worktrees: ${err}`,
4788
+ type: "worktree_list",
4789
+ ...projectRequestMetadata(msg),
4790
+ worktrees: [],
4791
+ error: `Failed to list worktrees: ${err}`,
4649
4792
  });
4650
4793
  }
4651
4794
  break;
4652
4795
  }
4653
4796
  case "remove_worktree": {
4654
4797
  if (!this.isPathAllowed(msg.projectPath)) {
4655
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4798
+ this.send(ws, {
4799
+ type: "worktree_removed",
4800
+ ...projectRequestMetadata(msg),
4801
+ worktreePath: msg.worktreePath,
4802
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4803
+ });
4656
4804
  break;
4657
4805
  }
4658
4806
  try {
@@ -4660,13 +4808,16 @@ export class BridgeWebSocketServer {
4660
4808
  this.worktreeStore.deleteByWorktreePath(msg.worktreePath);
4661
4809
  this.send(ws, {
4662
4810
  type: "worktree_removed",
4811
+ ...projectRequestMetadata(msg),
4663
4812
  worktreePath: msg.worktreePath,
4664
4813
  });
4665
4814
  }
4666
4815
  catch (err) {
4667
4816
  this.send(ws, {
4668
- type: "error",
4669
- message: `Failed to remove worktree: ${err}`,
4817
+ type: "worktree_removed",
4818
+ ...projectRequestMetadata(msg),
4819
+ worktreePath: msg.worktreePath,
4820
+ error: `Failed to remove worktree: ${err}`,
4670
4821
  });
4671
4822
  }
4672
4823
  break;
@@ -4674,7 +4825,12 @@ export class BridgeWebSocketServer {
4674
4825
  // ---- Git Operations (Phase 1-3) ----
4675
4826
  case "git_stage": {
4676
4827
  if (!this.isPathAllowed(msg.projectPath)) {
4677
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4828
+ this.send(ws, {
4829
+ type: "git_stage_result",
4830
+ ...projectRequestMetadata(msg),
4831
+ success: false,
4832
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4833
+ });
4678
4834
  break;
4679
4835
  }
4680
4836
  try {
@@ -4682,11 +4838,16 @@ export class BridgeWebSocketServer {
4682
4838
  stageFiles(msg.projectPath, msg.files);
4683
4839
  if (msg.hunks?.length)
4684
4840
  stageHunks(msg.projectPath, msg.hunks);
4685
- this.send(ws, { type: "git_stage_result", success: true });
4841
+ this.send(ws, {
4842
+ type: "git_stage_result",
4843
+ ...projectRequestMetadata(msg),
4844
+ success: true,
4845
+ });
4686
4846
  }
4687
4847
  catch (err) {
4688
4848
  this.send(ws, {
4689
4849
  type: "git_stage_result",
4850
+ ...projectRequestMetadata(msg),
4690
4851
  success: false,
4691
4852
  error: String(err),
4692
4853
  });
@@ -4695,16 +4856,26 @@ export class BridgeWebSocketServer {
4695
4856
  }
4696
4857
  case "git_unstage": {
4697
4858
  if (!this.isPathAllowed(msg.projectPath)) {
4698
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4859
+ this.send(ws, {
4860
+ type: "git_unstage_result",
4861
+ ...projectRequestMetadata(msg),
4862
+ success: false,
4863
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4864
+ });
4699
4865
  break;
4700
4866
  }
4701
4867
  try {
4702
4868
  unstageFiles(msg.projectPath, msg.files ?? []);
4703
- this.send(ws, { type: "git_unstage_result", success: true });
4869
+ this.send(ws, {
4870
+ type: "git_unstage_result",
4871
+ ...projectRequestMetadata(msg),
4872
+ success: true,
4873
+ });
4704
4874
  }
4705
4875
  catch (err) {
4706
4876
  this.send(ws, {
4707
4877
  type: "git_unstage_result",
4878
+ ...projectRequestMetadata(msg),
4708
4879
  success: false,
4709
4880
  error: String(err),
4710
4881
  });
@@ -4713,16 +4884,26 @@ export class BridgeWebSocketServer {
4713
4884
  }
4714
4885
  case "git_unstage_hunks": {
4715
4886
  if (!this.isPathAllowed(msg.projectPath)) {
4716
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4887
+ this.send(ws, {
4888
+ type: "git_unstage_hunks_result",
4889
+ ...projectRequestMetadata(msg),
4890
+ success: false,
4891
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4892
+ });
4717
4893
  break;
4718
4894
  }
4719
4895
  try {
4720
4896
  unstageHunks(msg.projectPath, msg.hunks);
4721
- this.send(ws, { type: "git_unstage_hunks_result", success: true });
4897
+ this.send(ws, {
4898
+ type: "git_unstage_hunks_result",
4899
+ ...projectRequestMetadata(msg),
4900
+ success: true,
4901
+ });
4722
4902
  }
4723
4903
  catch (err) {
4724
4904
  this.send(ws, {
4725
4905
  type: "git_unstage_hunks_result",
4906
+ ...projectRequestMetadata(msg),
4726
4907
  success: false,
4727
4908
  error: String(err),
4728
4909
  });
@@ -4731,7 +4912,12 @@ export class BridgeWebSocketServer {
4731
4912
  }
4732
4913
  case "git_commit": {
4733
4914
  if (!this.isPathAllowed(msg.projectPath)) {
4734
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4915
+ this.send(ws, {
4916
+ type: "git_commit_result",
4917
+ ...projectRequestMetadata(msg),
4918
+ success: false,
4919
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4920
+ });
4735
4921
  break;
4736
4922
  }
4737
4923
  const session = msg.sessionId
@@ -4765,6 +4951,7 @@ export class BridgeWebSocketServer {
4765
4951
  const result = gitCommit(msg.projectPath, message);
4766
4952
  this.send(ws, {
4767
4953
  type: "git_commit_result",
4954
+ ...projectRequestMetadata(msg),
4768
4955
  success: true,
4769
4956
  commitHash: result.hash,
4770
4957
  message: result.message,
@@ -4773,6 +4960,7 @@ export class BridgeWebSocketServer {
4773
4960
  catch (err) {
4774
4961
  this.send(ws, {
4775
4962
  type: "git_commit_result",
4963
+ ...projectRequestMetadata(msg),
4776
4964
  success: false,
4777
4965
  error: err instanceof Error ? err.message : String(err),
4778
4966
  });
@@ -4781,19 +4969,26 @@ export class BridgeWebSocketServer {
4781
4969
  }
4782
4970
  case "git_push": {
4783
4971
  if (!this.isPathAllowed(msg.projectPath)) {
4784
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4972
+ this.send(ws, {
4973
+ type: "git_push_result",
4974
+ ...projectRequestMetadata(msg),
4975
+ success: false,
4976
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4977
+ });
4785
4978
  break;
4786
4979
  }
4787
4980
  try {
4788
4981
  gitPush(msg.projectPath);
4789
4982
  this.send(ws, {
4790
4983
  type: "git_push_result",
4984
+ ...projectRequestMetadata(msg),
4791
4985
  success: true,
4792
4986
  });
4793
4987
  }
4794
4988
  catch (err) {
4795
4989
  this.send(ws, {
4796
4990
  type: "git_push_result",
4991
+ ...projectRequestMetadata(msg),
4797
4992
  success: false,
4798
4993
  error: String(err),
4799
4994
  });
@@ -4802,13 +4997,22 @@ export class BridgeWebSocketServer {
4802
4997
  }
4803
4998
  case "git_branches": {
4804
4999
  if (!this.isPathAllowed(msg.projectPath)) {
4805
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5000
+ this.send(ws, {
5001
+ type: "git_branches_result",
5002
+ ...projectRequestMetadata(msg),
5003
+ current: "",
5004
+ branches: [],
5005
+ checkedOutBranches: [],
5006
+ remoteStatusByBranch: {},
5007
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5008
+ });
4806
5009
  break;
4807
5010
  }
4808
5011
  try {
4809
5012
  const result = listBranches(msg.projectPath);
4810
5013
  this.send(ws, {
4811
5014
  type: "git_branches_result",
5015
+ ...projectRequestMetadata(msg),
4812
5016
  current: result.current,
4813
5017
  branches: result.branches,
4814
5018
  checkedOutBranches: result.checkedOutBranches,
@@ -4818,6 +5022,7 @@ export class BridgeWebSocketServer {
4818
5022
  catch (err) {
4819
5023
  this.send(ws, {
4820
5024
  type: "git_branches_result",
5025
+ ...projectRequestMetadata(msg),
4821
5026
  current: "",
4822
5027
  branches: [],
4823
5028
  remoteStatusByBranch: {},
@@ -4828,16 +5033,26 @@ export class BridgeWebSocketServer {
4828
5033
  }
4829
5034
  case "git_create_branch": {
4830
5035
  if (!this.isPathAllowed(msg.projectPath)) {
4831
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5036
+ this.send(ws, {
5037
+ type: "git_create_branch_result",
5038
+ ...projectRequestMetadata(msg),
5039
+ success: false,
5040
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5041
+ });
4832
5042
  break;
4833
5043
  }
4834
5044
  try {
4835
5045
  createBranch(msg.projectPath, msg.name, msg.checkout);
4836
- this.send(ws, { type: "git_create_branch_result", success: true });
5046
+ this.send(ws, {
5047
+ type: "git_create_branch_result",
5048
+ ...projectRequestMetadata(msg),
5049
+ success: true,
5050
+ });
4837
5051
  }
4838
5052
  catch (err) {
4839
5053
  this.send(ws, {
4840
5054
  type: "git_create_branch_result",
5055
+ ...projectRequestMetadata(msg),
4841
5056
  success: false,
4842
5057
  error: String(err),
4843
5058
  });
@@ -4846,16 +5061,26 @@ export class BridgeWebSocketServer {
4846
5061
  }
4847
5062
  case "git_checkout_branch": {
4848
5063
  if (!this.isPathAllowed(msg.projectPath)) {
4849
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5064
+ this.send(ws, {
5065
+ type: "git_checkout_branch_result",
5066
+ ...projectRequestMetadata(msg),
5067
+ success: false,
5068
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5069
+ });
4850
5070
  break;
4851
5071
  }
4852
5072
  try {
4853
5073
  checkoutBranch(msg.projectPath, msg.branch);
4854
- this.send(ws, { type: "git_checkout_branch_result", success: true });
5074
+ this.send(ws, {
5075
+ type: "git_checkout_branch_result",
5076
+ ...projectRequestMetadata(msg),
5077
+ success: true,
5078
+ });
4855
5079
  }
4856
5080
  catch (err) {
4857
5081
  this.send(ws, {
4858
5082
  type: "git_checkout_branch_result",
5083
+ ...projectRequestMetadata(msg),
4859
5084
  success: false,
4860
5085
  error: String(err),
4861
5086
  });
@@ -4864,16 +5089,26 @@ export class BridgeWebSocketServer {
4864
5089
  }
4865
5090
  case "git_revert_file": {
4866
5091
  if (!this.isPathAllowed(msg.projectPath)) {
4867
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5092
+ this.send(ws, {
5093
+ type: "git_revert_file_result",
5094
+ ...projectRequestMetadata(msg),
5095
+ success: false,
5096
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5097
+ });
4868
5098
  break;
4869
5099
  }
4870
5100
  try {
4871
5101
  revertFiles(msg.projectPath, msg.files);
4872
- this.send(ws, { type: "git_revert_file_result", success: true });
5102
+ this.send(ws, {
5103
+ type: "git_revert_file_result",
5104
+ ...projectRequestMetadata(msg),
5105
+ success: true,
5106
+ });
4873
5107
  }
4874
5108
  catch (err) {
4875
5109
  this.send(ws, {
4876
5110
  type: "git_revert_file_result",
5111
+ ...projectRequestMetadata(msg),
4877
5112
  success: false,
4878
5113
  error: String(err),
4879
5114
  });
@@ -4882,16 +5117,26 @@ export class BridgeWebSocketServer {
4882
5117
  }
4883
5118
  case "git_revert_hunks": {
4884
5119
  if (!this.isPathAllowed(msg.projectPath)) {
4885
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5120
+ this.send(ws, {
5121
+ type: "git_revert_hunks_result",
5122
+ ...projectRequestMetadata(msg),
5123
+ success: false,
5124
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5125
+ });
4886
5126
  break;
4887
5127
  }
4888
5128
  try {
4889
5129
  revertHunks(msg.projectPath, msg.hunks);
4890
- this.send(ws, { type: "git_revert_hunks_result", success: true });
5130
+ this.send(ws, {
5131
+ type: "git_revert_hunks_result",
5132
+ ...projectRequestMetadata(msg),
5133
+ success: true,
5134
+ });
4891
5135
  }
4892
5136
  catch (err) {
4893
5137
  this.send(ws, {
4894
5138
  type: "git_revert_hunks_result",
5139
+ ...projectRequestMetadata(msg),
4895
5140
  success: false,
4896
5141
  error: String(err),
4897
5142
  });
@@ -4900,16 +5145,26 @@ export class BridgeWebSocketServer {
4900
5145
  }
4901
5146
  case "git_fetch": {
4902
5147
  if (!this.isPathAllowed(msg.projectPath)) {
4903
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5148
+ this.send(ws, {
5149
+ type: "git_fetch_result",
5150
+ ...projectRequestMetadata(msg),
5151
+ success: false,
5152
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5153
+ });
4904
5154
  break;
4905
5155
  }
4906
5156
  try {
4907
5157
  gitFetch(msg.projectPath);
4908
- this.send(ws, { type: "git_fetch_result", success: true });
5158
+ this.send(ws, {
5159
+ type: "git_fetch_result",
5160
+ ...projectRequestMetadata(msg),
5161
+ success: true,
5162
+ });
4909
5163
  }
4910
5164
  catch (err) {
4911
5165
  this.send(ws, {
4912
5166
  type: "git_fetch_result",
5167
+ ...projectRequestMetadata(msg),
4913
5168
  success: false,
4914
5169
  error: String(err),
4915
5170
  });
@@ -4918,7 +5173,12 @@ export class BridgeWebSocketServer {
4918
5173
  }
4919
5174
  case "git_pull": {
4920
5175
  if (!this.isPathAllowed(msg.projectPath)) {
4921
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5176
+ this.send(ws, {
5177
+ type: "git_pull_result",
5178
+ ...projectRequestMetadata(msg),
5179
+ success: false,
5180
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5181
+ });
4922
5182
  break;
4923
5183
  }
4924
5184
  try {
@@ -4926,6 +5186,7 @@ export class BridgeWebSocketServer {
4926
5186
  if (result.success) {
4927
5187
  this.send(ws, {
4928
5188
  type: "git_pull_result",
5189
+ ...projectRequestMetadata(msg),
4929
5190
  success: true,
4930
5191
  message: result.message,
4931
5192
  });
@@ -4933,6 +5194,7 @@ export class BridgeWebSocketServer {
4933
5194
  else {
4934
5195
  this.send(ws, {
4935
5196
  type: "git_pull_result",
5197
+ ...projectRequestMetadata(msg),
4936
5198
  success: false,
4937
5199
  error: result.message,
4938
5200
  });
@@ -4941,6 +5203,7 @@ export class BridgeWebSocketServer {
4941
5203
  catch (err) {
4942
5204
  this.send(ws, {
4943
5205
  type: "git_pull_result",
5206
+ ...projectRequestMetadata(msg),
4944
5207
  success: false,
4945
5208
  error: String(err),
4946
5209
  });
@@ -4951,6 +5214,7 @@ export class BridgeWebSocketServer {
4951
5214
  if (!this.isPathAllowed(msg.projectPath)) {
4952
5215
  this.send(ws, {
4953
5216
  type: "git_status_result",
5217
+ requestId: msg.requestId,
4954
5218
  sessionId: msg.sessionId,
4955
5219
  projectPath: msg.projectPath,
4956
5220
  hasUncommittedChanges: false,
@@ -4972,6 +5236,7 @@ export class BridgeWebSocketServer {
4972
5236
  });
4973
5237
  this.send(ws, {
4974
5238
  type: "git_status_result",
5239
+ requestId: msg.requestId,
4975
5240
  sessionId: msg.sessionId,
4976
5241
  projectPath: msg.projectPath,
4977
5242
  hasUncommittedChanges: result.hasUncommittedChanges,
@@ -4990,6 +5255,7 @@ export class BridgeWebSocketServer {
4990
5255
  catch (err) {
4991
5256
  this.send(ws, {
4992
5257
  type: "git_status_result",
5258
+ requestId: msg.requestId,
4993
5259
  sessionId: msg.sessionId,
4994
5260
  projectPath: msg.projectPath,
4995
5261
  hasUncommittedChanges: false,
@@ -5008,13 +5274,22 @@ export class BridgeWebSocketServer {
5008
5274
  }
5009
5275
  case "git_remote_status": {
5010
5276
  if (!this.isPathAllowed(msg.projectPath)) {
5011
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5277
+ this.send(ws, {
5278
+ type: "git_remote_status_result",
5279
+ ...projectRequestMetadata(msg),
5280
+ ahead: 0,
5281
+ behind: 0,
5282
+ branch: "",
5283
+ hasUpstream: false,
5284
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5285
+ });
5012
5286
  break;
5013
5287
  }
5014
5288
  try {
5015
5289
  const result = gitRemoteStatus(msg.projectPath);
5016
5290
  this.send(ws, {
5017
5291
  type: "git_remote_status_result",
5292
+ ...projectRequestMetadata(msg),
5018
5293
  ahead: result.ahead,
5019
5294
  behind: result.behind,
5020
5295
  branch: result.branch,
@@ -5024,6 +5299,7 @@ export class BridgeWebSocketServer {
5024
5299
  catch (err) {
5025
5300
  this.send(ws, {
5026
5301
  type: "git_remote_status_result",
5302
+ ...projectRequestMetadata(msg),
5027
5303
  ahead: 0,
5028
5304
  behind: 0,
5029
5305
  branch: "",
@@ -5037,6 +5313,7 @@ export class BridgeWebSocketServer {
5037
5313
  if (!session) {
5038
5314
  this.send(ws, {
5039
5315
  type: "rewind_preview",
5316
+ sessionId: msg.sessionId,
5040
5317
  canRewind: false,
5041
5318
  error: `Session ${msg.sessionId} not found`,
5042
5319
  });
@@ -5045,6 +5322,7 @@ export class BridgeWebSocketServer {
5045
5322
  if (session.provider === "codex") {
5046
5323
  this.send(ws, {
5047
5324
  type: "rewind_preview",
5325
+ sessionId: msg.sessionId,
5048
5326
  canRewind: false,
5049
5327
  error: "Codex rewind does not restore files",
5050
5328
  });
@@ -5055,6 +5333,7 @@ export class BridgeWebSocketServer {
5055
5333
  .then((result) => {
5056
5334
  this.send(ws, {
5057
5335
  type: "rewind_preview",
5336
+ sessionId: msg.sessionId,
5058
5337
  canRewind: result.canRewind,
5059
5338
  filesChanged: result.filesChanged,
5060
5339
  insertions: result.insertions,
@@ -5065,6 +5344,7 @@ export class BridgeWebSocketServer {
5065
5344
  .catch((err) => {
5066
5345
  this.send(ws, {
5067
5346
  type: "rewind_preview",
5347
+ sessionId: msg.sessionId,
5068
5348
  canRewind: false,
5069
5349
  error: `Dry run failed: ${err}`,
5070
5350
  });
@@ -5076,6 +5356,7 @@ export class BridgeWebSocketServer {
5076
5356
  if (!session) {
5077
5357
  this.send(ws, {
5078
5358
  type: "rewind_result",
5359
+ sessionId: msg.sessionId,
5079
5360
  success: false,
5080
5361
  mode: msg.mode,
5081
5362
  error: `Session ${msg.sessionId} not found`,
@@ -5086,6 +5367,7 @@ export class BridgeWebSocketServer {
5086
5367
  const errMsg = err instanceof Error ? err.message : String(err);
5087
5368
  this.send(ws, {
5088
5369
  type: "rewind_result",
5370
+ sessionId: msg.sessionId,
5089
5371
  success: false,
5090
5372
  mode: msg.mode,
5091
5373
  error: errMsg,
@@ -5104,6 +5386,7 @@ export class BridgeWebSocketServer {
5104
5386
  if (result.canRewind) {
5105
5387
  this.send(ws, {
5106
5388
  type: "rewind_result",
5389
+ sessionId: msg.sessionId,
5107
5390
  success: true,
5108
5391
  mode: "code",
5109
5392
  });
@@ -5111,6 +5394,7 @@ export class BridgeWebSocketServer {
5111
5394
  else {
5112
5395
  this.send(ws, {
5113
5396
  type: "rewind_result",
5397
+ sessionId: msg.sessionId,
5114
5398
  success: false,
5115
5399
  mode: "code",
5116
5400
  error: result.error ?? "Cannot rewind files",
@@ -5126,6 +5410,7 @@ export class BridgeWebSocketServer {
5126
5410
  this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
5127
5411
  this.send(ws, {
5128
5412
  type: "rewind_result",
5413
+ sessionId: msg.sessionId,
5129
5414
  success: true,
5130
5415
  mode: "conversation",
5131
5416
  });
@@ -5157,6 +5442,7 @@ export class BridgeWebSocketServer {
5157
5442
  if (!result.canRewind) {
5158
5443
  this.send(ws, {
5159
5444
  type: "rewind_result",
5445
+ sessionId: msg.sessionId,
5160
5446
  success: false,
5161
5447
  mode: "both",
5162
5448
  error: result.error ?? "Cannot rewind files",
@@ -5168,6 +5454,7 @@ export class BridgeWebSocketServer {
5168
5454
  this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
5169
5455
  this.send(ws, {
5170
5456
  type: "rewind_result",
5457
+ sessionId: msg.sessionId,
5171
5458
  success: true,
5172
5459
  mode: "both",
5173
5460
  });
@@ -5199,6 +5486,7 @@ export class BridgeWebSocketServer {
5199
5486
  const errMsg = err instanceof Error ? err.message : String(err);
5200
5487
  this.send(ws, {
5201
5488
  type: "error",
5489
+ sessionId: msg.sessionId,
5202
5490
  message: errMsg,
5203
5491
  errorCode: "fork_failed",
5204
5492
  });
@@ -5244,6 +5532,8 @@ export class BridgeWebSocketServer {
5244
5532
  const info = this.galleryStore.metaToInfo(meta);
5245
5533
  this.send(ws, {
5246
5534
  type: "screenshot_result",
5535
+ ...projectRequestMetadata(msg),
5536
+ sessionId: msg.sessionId,
5247
5537
  success: true,
5248
5538
  image: info,
5249
5539
  });
@@ -5253,6 +5543,8 @@ export class BridgeWebSocketServer {
5253
5543
  }
5254
5544
  this.send(ws, {
5255
5545
  type: "screenshot_result",
5546
+ ...projectRequestMetadata(msg),
5547
+ sessionId: msg.sessionId,
5256
5548
  success: false,
5257
5549
  error: "Failed to save screenshot to gallery",
5258
5550
  });
@@ -5265,6 +5557,8 @@ export class BridgeWebSocketServer {
5265
5557
  .catch((err) => {
5266
5558
  this.send(ws, {
5267
5559
  type: "screenshot_result",
5560
+ ...projectRequestMetadata(msg),
5561
+ sessionId: msg.sessionId,
5268
5562
  success: false,
5269
5563
  error: err instanceof Error ? err.message : String(err),
5270
5564
  });
@@ -5817,6 +6111,7 @@ export class BridgeWebSocketServer {
5817
6111
  defaultCodexProfile: this.defaultCodexProfile,
5818
6112
  codexAutoReviewDisabled: this.codexAutoReviewDisabled,
5819
6113
  bridgeVersion: getPackageVersion(),
6114
+ protocolCapabilities: ["project_request_correlation_v1"],
5820
6115
  });
5821
6116
  }
5822
6117
  sendPromptHistoryStatus(ws) {
@@ -5849,6 +6144,7 @@ export class BridgeWebSocketServer {
5849
6144
  defaultCodexProfile: this.defaultCodexProfile,
5850
6145
  codexAutoReviewDisabled: this.codexAutoReviewDisabled,
5851
6146
  bridgeVersion: getPackageVersion(),
6147
+ protocolCapabilities: ["project_request_correlation_v1"],
5852
6148
  });
5853
6149
  }
5854
6150
  broadcastPromptHistoryStatus() {
@@ -6740,7 +7036,7 @@ export class BridgeWebSocketServer {
6740
7036
  if (!compatibleMsg)
6741
7037
  return;
6742
7038
  const sessionId = this.extractSessionIdFromServerMessage(compatibleMsg);
6743
- if (sessionId) {
7039
+ if (sessionId && this.debugEvents.has(sessionId)) {
6744
7040
  this.recordDebugEvent(sessionId, {
6745
7041
  direction: "outgoing",
6746
7042
  channel: "ws",