@ccpocket/bridge 1.76.0 → 1.77.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,6 +4543,7 @@ export class BridgeWebSocketServer {
4448
4543
  });
4449
4544
  this.send(ws, {
4450
4545
  type: "file_list",
4546
+ ...projectRequestMetadata(msg),
4451
4547
  files: result.files,
4452
4548
  totalFiles: result.totalFiles,
4453
4549
  truncated: result.truncated,
@@ -4456,8 +4552,10 @@ export class BridgeWebSocketServer {
4456
4552
  catch (err) {
4457
4553
  const message = err instanceof Error ? err.message : String(err);
4458
4554
  this.send(ws, {
4459
- type: "error",
4460
- message: `Failed to list files: ${message}`,
4555
+ type: "file_list",
4556
+ ...projectRequestMetadata(msg),
4557
+ files: [],
4558
+ error: `Failed to list files: ${message}`,
4461
4559
  });
4462
4560
  }
4463
4561
  })();
@@ -4547,7 +4645,15 @@ export class BridgeWebSocketServer {
4547
4645
  }
4548
4646
  case "get_diff": {
4549
4647
  if (!this.isPathAllowed(msg.projectPath)) {
4550
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4648
+ this.send(ws, {
4649
+ type: "diff_result",
4650
+ ...projectRequestMetadata(msg),
4651
+ staged: msg.staged === true,
4652
+ diff: "",
4653
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId)
4654
+ .message,
4655
+ errorCode: "path_not_allowed",
4656
+ });
4551
4657
  break;
4552
4658
  }
4553
4659
  this.collectGitDiff(msg.projectPath, ({ diff, error }) => {
@@ -4555,6 +4661,8 @@ export class BridgeWebSocketServer {
4555
4661
  if (/not a git repository/i.test(error)) {
4556
4662
  this.send(ws, {
4557
4663
  type: "diff_result",
4664
+ ...projectRequestMetadata(msg),
4665
+ staged: msg.staged === true,
4558
4666
  diff: "",
4559
4667
  error: "This project is not a git repository",
4560
4668
  errorCode: "git_not_available",
@@ -4563,6 +4671,8 @@ export class BridgeWebSocketServer {
4563
4671
  else {
4564
4672
  this.send(ws, {
4565
4673
  type: "diff_result",
4674
+ ...projectRequestMetadata(msg),
4675
+ staged: msg.staged === true,
4566
4676
  diff: "",
4567
4677
  error: `Failed to get diff: ${error}`,
4568
4678
  });
@@ -4571,10 +4681,21 @@ export class BridgeWebSocketServer {
4571
4681
  }
4572
4682
  void this.collectImageChanges(msg.projectPath, diff).then((imageChanges) => {
4573
4683
  if (imageChanges.length > 0) {
4574
- this.send(ws, { type: "diff_result", diff, imageChanges });
4684
+ this.send(ws, {
4685
+ type: "diff_result",
4686
+ ...projectRequestMetadata(msg),
4687
+ staged: msg.staged === true,
4688
+ diff,
4689
+ imageChanges,
4690
+ });
4575
4691
  }
4576
4692
  else {
4577
- this.send(ws, { type: "diff_result", diff });
4693
+ this.send(ws, {
4694
+ type: "diff_result",
4695
+ ...projectRequestMetadata(msg),
4696
+ staged: msg.staged === true,
4697
+ diff,
4698
+ });
4578
4699
  }
4579
4700
  });
4580
4701
  }, msg.staged === true
@@ -4587,7 +4708,13 @@ export class BridgeWebSocketServer {
4587
4708
  case "get_diff_image": {
4588
4709
  if (!this.isPathAllowed(msg.projectPath) ||
4589
4710
  !this.isPathAllowed(resolve(msg.projectPath, msg.filePath))) {
4590
- this.send(ws, { type: "error", message: `Path not allowed` });
4711
+ this.send(ws, {
4712
+ type: "diff_image_result",
4713
+ ...projectRequestMetadata(msg),
4714
+ filePath: msg.filePath,
4715
+ version: msg.version,
4716
+ error: "Path not allowed",
4717
+ });
4591
4718
  break;
4592
4719
  }
4593
4720
  if (msg.version === "both") {
@@ -4600,6 +4727,7 @@ export class BridgeWebSocketServer {
4600
4727
  const errors = [oldResult.error, newResult.error].filter(Boolean);
4601
4728
  this.send(ws, {
4602
4729
  type: "diff_image_result",
4730
+ ...projectRequestMetadata(msg),
4603
4731
  filePath: msg.filePath,
4604
4732
  version: "both",
4605
4733
  oldBase64: oldResult.base64,
@@ -4620,6 +4748,7 @@ export class BridgeWebSocketServer {
4620
4748
  const result = await this.loadDiffImageAsync(msg.projectPath, msg.filePath, version);
4621
4749
  this.send(ws, {
4622
4750
  type: "diff_image_result",
4751
+ ...projectRequestMetadata(msg),
4623
4752
  filePath: msg.filePath,
4624
4753
  version,
4625
4754
  ...result,
@@ -4634,25 +4763,42 @@ export class BridgeWebSocketServer {
4634
4763
  }
4635
4764
  case "list_worktrees": {
4636
4765
  if (!this.isPathAllowed(msg.projectPath)) {
4637
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4766
+ this.send(ws, {
4767
+ type: "worktree_list",
4768
+ ...projectRequestMetadata(msg),
4769
+ worktrees: [],
4770
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4771
+ });
4638
4772
  break;
4639
4773
  }
4640
4774
  try {
4641
4775
  const worktrees = listWorktrees(msg.projectPath);
4642
4776
  const mainBranch = getMainBranch(msg.projectPath);
4643
- this.send(ws, { type: "worktree_list", worktrees, mainBranch });
4777
+ this.send(ws, {
4778
+ type: "worktree_list",
4779
+ ...projectRequestMetadata(msg),
4780
+ worktrees,
4781
+ mainBranch,
4782
+ });
4644
4783
  }
4645
4784
  catch (err) {
4646
4785
  this.send(ws, {
4647
- type: "error",
4648
- message: `Failed to list worktrees: ${err}`,
4786
+ type: "worktree_list",
4787
+ ...projectRequestMetadata(msg),
4788
+ worktrees: [],
4789
+ error: `Failed to list worktrees: ${err}`,
4649
4790
  });
4650
4791
  }
4651
4792
  break;
4652
4793
  }
4653
4794
  case "remove_worktree": {
4654
4795
  if (!this.isPathAllowed(msg.projectPath)) {
4655
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4796
+ this.send(ws, {
4797
+ type: "worktree_removed",
4798
+ ...projectRequestMetadata(msg),
4799
+ worktreePath: msg.worktreePath,
4800
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4801
+ });
4656
4802
  break;
4657
4803
  }
4658
4804
  try {
@@ -4660,13 +4806,16 @@ export class BridgeWebSocketServer {
4660
4806
  this.worktreeStore.deleteByWorktreePath(msg.worktreePath);
4661
4807
  this.send(ws, {
4662
4808
  type: "worktree_removed",
4809
+ ...projectRequestMetadata(msg),
4663
4810
  worktreePath: msg.worktreePath,
4664
4811
  });
4665
4812
  }
4666
4813
  catch (err) {
4667
4814
  this.send(ws, {
4668
- type: "error",
4669
- message: `Failed to remove worktree: ${err}`,
4815
+ type: "worktree_removed",
4816
+ ...projectRequestMetadata(msg),
4817
+ worktreePath: msg.worktreePath,
4818
+ error: `Failed to remove worktree: ${err}`,
4670
4819
  });
4671
4820
  }
4672
4821
  break;
@@ -4674,7 +4823,12 @@ export class BridgeWebSocketServer {
4674
4823
  // ---- Git Operations (Phase 1-3) ----
4675
4824
  case "git_stage": {
4676
4825
  if (!this.isPathAllowed(msg.projectPath)) {
4677
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4826
+ this.send(ws, {
4827
+ type: "git_stage_result",
4828
+ ...projectRequestMetadata(msg),
4829
+ success: false,
4830
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4831
+ });
4678
4832
  break;
4679
4833
  }
4680
4834
  try {
@@ -4682,11 +4836,16 @@ export class BridgeWebSocketServer {
4682
4836
  stageFiles(msg.projectPath, msg.files);
4683
4837
  if (msg.hunks?.length)
4684
4838
  stageHunks(msg.projectPath, msg.hunks);
4685
- this.send(ws, { type: "git_stage_result", success: true });
4839
+ this.send(ws, {
4840
+ type: "git_stage_result",
4841
+ ...projectRequestMetadata(msg),
4842
+ success: true,
4843
+ });
4686
4844
  }
4687
4845
  catch (err) {
4688
4846
  this.send(ws, {
4689
4847
  type: "git_stage_result",
4848
+ ...projectRequestMetadata(msg),
4690
4849
  success: false,
4691
4850
  error: String(err),
4692
4851
  });
@@ -4695,16 +4854,26 @@ export class BridgeWebSocketServer {
4695
4854
  }
4696
4855
  case "git_unstage": {
4697
4856
  if (!this.isPathAllowed(msg.projectPath)) {
4698
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4857
+ this.send(ws, {
4858
+ type: "git_unstage_result",
4859
+ ...projectRequestMetadata(msg),
4860
+ success: false,
4861
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4862
+ });
4699
4863
  break;
4700
4864
  }
4701
4865
  try {
4702
4866
  unstageFiles(msg.projectPath, msg.files ?? []);
4703
- this.send(ws, { type: "git_unstage_result", success: true });
4867
+ this.send(ws, {
4868
+ type: "git_unstage_result",
4869
+ ...projectRequestMetadata(msg),
4870
+ success: true,
4871
+ });
4704
4872
  }
4705
4873
  catch (err) {
4706
4874
  this.send(ws, {
4707
4875
  type: "git_unstage_result",
4876
+ ...projectRequestMetadata(msg),
4708
4877
  success: false,
4709
4878
  error: String(err),
4710
4879
  });
@@ -4713,16 +4882,26 @@ export class BridgeWebSocketServer {
4713
4882
  }
4714
4883
  case "git_unstage_hunks": {
4715
4884
  if (!this.isPathAllowed(msg.projectPath)) {
4716
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4885
+ this.send(ws, {
4886
+ type: "git_unstage_hunks_result",
4887
+ ...projectRequestMetadata(msg),
4888
+ success: false,
4889
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4890
+ });
4717
4891
  break;
4718
4892
  }
4719
4893
  try {
4720
4894
  unstageHunks(msg.projectPath, msg.hunks);
4721
- this.send(ws, { type: "git_unstage_hunks_result", success: true });
4895
+ this.send(ws, {
4896
+ type: "git_unstage_hunks_result",
4897
+ ...projectRequestMetadata(msg),
4898
+ success: true,
4899
+ });
4722
4900
  }
4723
4901
  catch (err) {
4724
4902
  this.send(ws, {
4725
4903
  type: "git_unstage_hunks_result",
4904
+ ...projectRequestMetadata(msg),
4726
4905
  success: false,
4727
4906
  error: String(err),
4728
4907
  });
@@ -4731,7 +4910,12 @@ export class BridgeWebSocketServer {
4731
4910
  }
4732
4911
  case "git_commit": {
4733
4912
  if (!this.isPathAllowed(msg.projectPath)) {
4734
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4913
+ this.send(ws, {
4914
+ type: "git_commit_result",
4915
+ ...projectRequestMetadata(msg),
4916
+ success: false,
4917
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4918
+ });
4735
4919
  break;
4736
4920
  }
4737
4921
  const session = msg.sessionId
@@ -4765,6 +4949,7 @@ export class BridgeWebSocketServer {
4765
4949
  const result = gitCommit(msg.projectPath, message);
4766
4950
  this.send(ws, {
4767
4951
  type: "git_commit_result",
4952
+ ...projectRequestMetadata(msg),
4768
4953
  success: true,
4769
4954
  commitHash: result.hash,
4770
4955
  message: result.message,
@@ -4773,6 +4958,7 @@ export class BridgeWebSocketServer {
4773
4958
  catch (err) {
4774
4959
  this.send(ws, {
4775
4960
  type: "git_commit_result",
4961
+ ...projectRequestMetadata(msg),
4776
4962
  success: false,
4777
4963
  error: err instanceof Error ? err.message : String(err),
4778
4964
  });
@@ -4781,19 +4967,26 @@ export class BridgeWebSocketServer {
4781
4967
  }
4782
4968
  case "git_push": {
4783
4969
  if (!this.isPathAllowed(msg.projectPath)) {
4784
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4970
+ this.send(ws, {
4971
+ type: "git_push_result",
4972
+ ...projectRequestMetadata(msg),
4973
+ success: false,
4974
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
4975
+ });
4785
4976
  break;
4786
4977
  }
4787
4978
  try {
4788
4979
  gitPush(msg.projectPath);
4789
4980
  this.send(ws, {
4790
4981
  type: "git_push_result",
4982
+ ...projectRequestMetadata(msg),
4791
4983
  success: true,
4792
4984
  });
4793
4985
  }
4794
4986
  catch (err) {
4795
4987
  this.send(ws, {
4796
4988
  type: "git_push_result",
4989
+ ...projectRequestMetadata(msg),
4797
4990
  success: false,
4798
4991
  error: String(err),
4799
4992
  });
@@ -4802,13 +4995,22 @@ export class BridgeWebSocketServer {
4802
4995
  }
4803
4996
  case "git_branches": {
4804
4997
  if (!this.isPathAllowed(msg.projectPath)) {
4805
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
4998
+ this.send(ws, {
4999
+ type: "git_branches_result",
5000
+ ...projectRequestMetadata(msg),
5001
+ current: "",
5002
+ branches: [],
5003
+ checkedOutBranches: [],
5004
+ remoteStatusByBranch: {},
5005
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5006
+ });
4806
5007
  break;
4807
5008
  }
4808
5009
  try {
4809
5010
  const result = listBranches(msg.projectPath);
4810
5011
  this.send(ws, {
4811
5012
  type: "git_branches_result",
5013
+ ...projectRequestMetadata(msg),
4812
5014
  current: result.current,
4813
5015
  branches: result.branches,
4814
5016
  checkedOutBranches: result.checkedOutBranches,
@@ -4818,6 +5020,7 @@ export class BridgeWebSocketServer {
4818
5020
  catch (err) {
4819
5021
  this.send(ws, {
4820
5022
  type: "git_branches_result",
5023
+ ...projectRequestMetadata(msg),
4821
5024
  current: "",
4822
5025
  branches: [],
4823
5026
  remoteStatusByBranch: {},
@@ -4828,16 +5031,26 @@ export class BridgeWebSocketServer {
4828
5031
  }
4829
5032
  case "git_create_branch": {
4830
5033
  if (!this.isPathAllowed(msg.projectPath)) {
4831
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5034
+ this.send(ws, {
5035
+ type: "git_create_branch_result",
5036
+ ...projectRequestMetadata(msg),
5037
+ success: false,
5038
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5039
+ });
4832
5040
  break;
4833
5041
  }
4834
5042
  try {
4835
5043
  createBranch(msg.projectPath, msg.name, msg.checkout);
4836
- this.send(ws, { type: "git_create_branch_result", success: true });
5044
+ this.send(ws, {
5045
+ type: "git_create_branch_result",
5046
+ ...projectRequestMetadata(msg),
5047
+ success: true,
5048
+ });
4837
5049
  }
4838
5050
  catch (err) {
4839
5051
  this.send(ws, {
4840
5052
  type: "git_create_branch_result",
5053
+ ...projectRequestMetadata(msg),
4841
5054
  success: false,
4842
5055
  error: String(err),
4843
5056
  });
@@ -4846,16 +5059,26 @@ export class BridgeWebSocketServer {
4846
5059
  }
4847
5060
  case "git_checkout_branch": {
4848
5061
  if (!this.isPathAllowed(msg.projectPath)) {
4849
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5062
+ this.send(ws, {
5063
+ type: "git_checkout_branch_result",
5064
+ ...projectRequestMetadata(msg),
5065
+ success: false,
5066
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5067
+ });
4850
5068
  break;
4851
5069
  }
4852
5070
  try {
4853
5071
  checkoutBranch(msg.projectPath, msg.branch);
4854
- this.send(ws, { type: "git_checkout_branch_result", success: true });
5072
+ this.send(ws, {
5073
+ type: "git_checkout_branch_result",
5074
+ ...projectRequestMetadata(msg),
5075
+ success: true,
5076
+ });
4855
5077
  }
4856
5078
  catch (err) {
4857
5079
  this.send(ws, {
4858
5080
  type: "git_checkout_branch_result",
5081
+ ...projectRequestMetadata(msg),
4859
5082
  success: false,
4860
5083
  error: String(err),
4861
5084
  });
@@ -4864,16 +5087,26 @@ export class BridgeWebSocketServer {
4864
5087
  }
4865
5088
  case "git_revert_file": {
4866
5089
  if (!this.isPathAllowed(msg.projectPath)) {
4867
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5090
+ this.send(ws, {
5091
+ type: "git_revert_file_result",
5092
+ ...projectRequestMetadata(msg),
5093
+ success: false,
5094
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5095
+ });
4868
5096
  break;
4869
5097
  }
4870
5098
  try {
4871
5099
  revertFiles(msg.projectPath, msg.files);
4872
- this.send(ws, { type: "git_revert_file_result", success: true });
5100
+ this.send(ws, {
5101
+ type: "git_revert_file_result",
5102
+ ...projectRequestMetadata(msg),
5103
+ success: true,
5104
+ });
4873
5105
  }
4874
5106
  catch (err) {
4875
5107
  this.send(ws, {
4876
5108
  type: "git_revert_file_result",
5109
+ ...projectRequestMetadata(msg),
4877
5110
  success: false,
4878
5111
  error: String(err),
4879
5112
  });
@@ -4882,16 +5115,26 @@ export class BridgeWebSocketServer {
4882
5115
  }
4883
5116
  case "git_revert_hunks": {
4884
5117
  if (!this.isPathAllowed(msg.projectPath)) {
4885
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5118
+ this.send(ws, {
5119
+ type: "git_revert_hunks_result",
5120
+ ...projectRequestMetadata(msg),
5121
+ success: false,
5122
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5123
+ });
4886
5124
  break;
4887
5125
  }
4888
5126
  try {
4889
5127
  revertHunks(msg.projectPath, msg.hunks);
4890
- this.send(ws, { type: "git_revert_hunks_result", success: true });
5128
+ this.send(ws, {
5129
+ type: "git_revert_hunks_result",
5130
+ ...projectRequestMetadata(msg),
5131
+ success: true,
5132
+ });
4891
5133
  }
4892
5134
  catch (err) {
4893
5135
  this.send(ws, {
4894
5136
  type: "git_revert_hunks_result",
5137
+ ...projectRequestMetadata(msg),
4895
5138
  success: false,
4896
5139
  error: String(err),
4897
5140
  });
@@ -4900,16 +5143,26 @@ export class BridgeWebSocketServer {
4900
5143
  }
4901
5144
  case "git_fetch": {
4902
5145
  if (!this.isPathAllowed(msg.projectPath)) {
4903
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5146
+ this.send(ws, {
5147
+ type: "git_fetch_result",
5148
+ ...projectRequestMetadata(msg),
5149
+ success: false,
5150
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5151
+ });
4904
5152
  break;
4905
5153
  }
4906
5154
  try {
4907
5155
  gitFetch(msg.projectPath);
4908
- this.send(ws, { type: "git_fetch_result", success: true });
5156
+ this.send(ws, {
5157
+ type: "git_fetch_result",
5158
+ ...projectRequestMetadata(msg),
5159
+ success: true,
5160
+ });
4909
5161
  }
4910
5162
  catch (err) {
4911
5163
  this.send(ws, {
4912
5164
  type: "git_fetch_result",
5165
+ ...projectRequestMetadata(msg),
4913
5166
  success: false,
4914
5167
  error: String(err),
4915
5168
  });
@@ -4918,7 +5171,12 @@ export class BridgeWebSocketServer {
4918
5171
  }
4919
5172
  case "git_pull": {
4920
5173
  if (!this.isPathAllowed(msg.projectPath)) {
4921
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5174
+ this.send(ws, {
5175
+ type: "git_pull_result",
5176
+ ...projectRequestMetadata(msg),
5177
+ success: false,
5178
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5179
+ });
4922
5180
  break;
4923
5181
  }
4924
5182
  try {
@@ -4926,6 +5184,7 @@ export class BridgeWebSocketServer {
4926
5184
  if (result.success) {
4927
5185
  this.send(ws, {
4928
5186
  type: "git_pull_result",
5187
+ ...projectRequestMetadata(msg),
4929
5188
  success: true,
4930
5189
  message: result.message,
4931
5190
  });
@@ -4933,6 +5192,7 @@ export class BridgeWebSocketServer {
4933
5192
  else {
4934
5193
  this.send(ws, {
4935
5194
  type: "git_pull_result",
5195
+ ...projectRequestMetadata(msg),
4936
5196
  success: false,
4937
5197
  error: result.message,
4938
5198
  });
@@ -4941,6 +5201,7 @@ export class BridgeWebSocketServer {
4941
5201
  catch (err) {
4942
5202
  this.send(ws, {
4943
5203
  type: "git_pull_result",
5204
+ ...projectRequestMetadata(msg),
4944
5205
  success: false,
4945
5206
  error: String(err),
4946
5207
  });
@@ -4951,6 +5212,7 @@ export class BridgeWebSocketServer {
4951
5212
  if (!this.isPathAllowed(msg.projectPath)) {
4952
5213
  this.send(ws, {
4953
5214
  type: "git_status_result",
5215
+ requestId: msg.requestId,
4954
5216
  sessionId: msg.sessionId,
4955
5217
  projectPath: msg.projectPath,
4956
5218
  hasUncommittedChanges: false,
@@ -4972,6 +5234,7 @@ export class BridgeWebSocketServer {
4972
5234
  });
4973
5235
  this.send(ws, {
4974
5236
  type: "git_status_result",
5237
+ requestId: msg.requestId,
4975
5238
  sessionId: msg.sessionId,
4976
5239
  projectPath: msg.projectPath,
4977
5240
  hasUncommittedChanges: result.hasUncommittedChanges,
@@ -4990,6 +5253,7 @@ export class BridgeWebSocketServer {
4990
5253
  catch (err) {
4991
5254
  this.send(ws, {
4992
5255
  type: "git_status_result",
5256
+ requestId: msg.requestId,
4993
5257
  sessionId: msg.sessionId,
4994
5258
  projectPath: msg.projectPath,
4995
5259
  hasUncommittedChanges: false,
@@ -5008,13 +5272,22 @@ export class BridgeWebSocketServer {
5008
5272
  }
5009
5273
  case "git_remote_status": {
5010
5274
  if (!this.isPathAllowed(msg.projectPath)) {
5011
- this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
5275
+ this.send(ws, {
5276
+ type: "git_remote_status_result",
5277
+ ...projectRequestMetadata(msg),
5278
+ ahead: 0,
5279
+ behind: 0,
5280
+ branch: "",
5281
+ hasUpstream: false,
5282
+ error: this.buildPathNotAllowedError(msg.projectPath, msg.requestId).message,
5283
+ });
5012
5284
  break;
5013
5285
  }
5014
5286
  try {
5015
5287
  const result = gitRemoteStatus(msg.projectPath);
5016
5288
  this.send(ws, {
5017
5289
  type: "git_remote_status_result",
5290
+ ...projectRequestMetadata(msg),
5018
5291
  ahead: result.ahead,
5019
5292
  behind: result.behind,
5020
5293
  branch: result.branch,
@@ -5024,6 +5297,7 @@ export class BridgeWebSocketServer {
5024
5297
  catch (err) {
5025
5298
  this.send(ws, {
5026
5299
  type: "git_remote_status_result",
5300
+ ...projectRequestMetadata(msg),
5027
5301
  ahead: 0,
5028
5302
  behind: 0,
5029
5303
  branch: "",
@@ -5037,6 +5311,7 @@ export class BridgeWebSocketServer {
5037
5311
  if (!session) {
5038
5312
  this.send(ws, {
5039
5313
  type: "rewind_preview",
5314
+ sessionId: msg.sessionId,
5040
5315
  canRewind: false,
5041
5316
  error: `Session ${msg.sessionId} not found`,
5042
5317
  });
@@ -5045,6 +5320,7 @@ export class BridgeWebSocketServer {
5045
5320
  if (session.provider === "codex") {
5046
5321
  this.send(ws, {
5047
5322
  type: "rewind_preview",
5323
+ sessionId: msg.sessionId,
5048
5324
  canRewind: false,
5049
5325
  error: "Codex rewind does not restore files",
5050
5326
  });
@@ -5055,6 +5331,7 @@ export class BridgeWebSocketServer {
5055
5331
  .then((result) => {
5056
5332
  this.send(ws, {
5057
5333
  type: "rewind_preview",
5334
+ sessionId: msg.sessionId,
5058
5335
  canRewind: result.canRewind,
5059
5336
  filesChanged: result.filesChanged,
5060
5337
  insertions: result.insertions,
@@ -5065,6 +5342,7 @@ export class BridgeWebSocketServer {
5065
5342
  .catch((err) => {
5066
5343
  this.send(ws, {
5067
5344
  type: "rewind_preview",
5345
+ sessionId: msg.sessionId,
5068
5346
  canRewind: false,
5069
5347
  error: `Dry run failed: ${err}`,
5070
5348
  });
@@ -5076,6 +5354,7 @@ export class BridgeWebSocketServer {
5076
5354
  if (!session) {
5077
5355
  this.send(ws, {
5078
5356
  type: "rewind_result",
5357
+ sessionId: msg.sessionId,
5079
5358
  success: false,
5080
5359
  mode: msg.mode,
5081
5360
  error: `Session ${msg.sessionId} not found`,
@@ -5086,6 +5365,7 @@ export class BridgeWebSocketServer {
5086
5365
  const errMsg = err instanceof Error ? err.message : String(err);
5087
5366
  this.send(ws, {
5088
5367
  type: "rewind_result",
5368
+ sessionId: msg.sessionId,
5089
5369
  success: false,
5090
5370
  mode: msg.mode,
5091
5371
  error: errMsg,
@@ -5104,6 +5384,7 @@ export class BridgeWebSocketServer {
5104
5384
  if (result.canRewind) {
5105
5385
  this.send(ws, {
5106
5386
  type: "rewind_result",
5387
+ sessionId: msg.sessionId,
5107
5388
  success: true,
5108
5389
  mode: "code",
5109
5390
  });
@@ -5111,6 +5392,7 @@ export class BridgeWebSocketServer {
5111
5392
  else {
5112
5393
  this.send(ws, {
5113
5394
  type: "rewind_result",
5395
+ sessionId: msg.sessionId,
5114
5396
  success: false,
5115
5397
  mode: "code",
5116
5398
  error: result.error ?? "Cannot rewind files",
@@ -5126,6 +5408,7 @@ export class BridgeWebSocketServer {
5126
5408
  this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
5127
5409
  this.send(ws, {
5128
5410
  type: "rewind_result",
5411
+ sessionId: msg.sessionId,
5129
5412
  success: true,
5130
5413
  mode: "conversation",
5131
5414
  });
@@ -5157,6 +5440,7 @@ export class BridgeWebSocketServer {
5157
5440
  if (!result.canRewind) {
5158
5441
  this.send(ws, {
5159
5442
  type: "rewind_result",
5443
+ sessionId: msg.sessionId,
5160
5444
  success: false,
5161
5445
  mode: "both",
5162
5446
  error: result.error ?? "Cannot rewind files",
@@ -5168,6 +5452,7 @@ export class BridgeWebSocketServer {
5168
5452
  this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
5169
5453
  this.send(ws, {
5170
5454
  type: "rewind_result",
5455
+ sessionId: msg.sessionId,
5171
5456
  success: true,
5172
5457
  mode: "both",
5173
5458
  });
@@ -5199,6 +5484,7 @@ export class BridgeWebSocketServer {
5199
5484
  const errMsg = err instanceof Error ? err.message : String(err);
5200
5485
  this.send(ws, {
5201
5486
  type: "error",
5487
+ sessionId: msg.sessionId,
5202
5488
  message: errMsg,
5203
5489
  errorCode: "fork_failed",
5204
5490
  });
@@ -5244,6 +5530,8 @@ export class BridgeWebSocketServer {
5244
5530
  const info = this.galleryStore.metaToInfo(meta);
5245
5531
  this.send(ws, {
5246
5532
  type: "screenshot_result",
5533
+ ...projectRequestMetadata(msg),
5534
+ sessionId: msg.sessionId,
5247
5535
  success: true,
5248
5536
  image: info,
5249
5537
  });
@@ -5253,6 +5541,8 @@ export class BridgeWebSocketServer {
5253
5541
  }
5254
5542
  this.send(ws, {
5255
5543
  type: "screenshot_result",
5544
+ ...projectRequestMetadata(msg),
5545
+ sessionId: msg.sessionId,
5256
5546
  success: false,
5257
5547
  error: "Failed to save screenshot to gallery",
5258
5548
  });
@@ -5265,6 +5555,8 @@ export class BridgeWebSocketServer {
5265
5555
  .catch((err) => {
5266
5556
  this.send(ws, {
5267
5557
  type: "screenshot_result",
5558
+ ...projectRequestMetadata(msg),
5559
+ sessionId: msg.sessionId,
5268
5560
  success: false,
5269
5561
  error: err instanceof Error ? err.message : String(err),
5270
5562
  });
@@ -5817,6 +6109,7 @@ export class BridgeWebSocketServer {
5817
6109
  defaultCodexProfile: this.defaultCodexProfile,
5818
6110
  codexAutoReviewDisabled: this.codexAutoReviewDisabled,
5819
6111
  bridgeVersion: getPackageVersion(),
6112
+ protocolCapabilities: ["project_request_correlation_v1"],
5820
6113
  });
5821
6114
  }
5822
6115
  sendPromptHistoryStatus(ws) {
@@ -5849,6 +6142,7 @@ export class BridgeWebSocketServer {
5849
6142
  defaultCodexProfile: this.defaultCodexProfile,
5850
6143
  codexAutoReviewDisabled: this.codexAutoReviewDisabled,
5851
6144
  bridgeVersion: getPackageVersion(),
6145
+ protocolCapabilities: ["project_request_correlation_v1"],
5852
6146
  });
5853
6147
  }
5854
6148
  broadcastPromptHistoryStatus() {
@@ -6740,7 +7034,7 @@ export class BridgeWebSocketServer {
6740
7034
  if (!compatibleMsg)
6741
7035
  return;
6742
7036
  const sessionId = this.extractSessionIdFromServerMessage(compatibleMsg);
6743
- if (sessionId) {
7037
+ if (sessionId && this.debugEvents.has(sessionId)) {
6744
7038
  this.recordDebugEvent(sessionId, {
6745
7039
  direction: "outgoing",
6746
7040
  channel: "ws",