@reconcrap/boss-recommend-mcp 2.0.54 → 2.0.56

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.
@@ -45,7 +45,9 @@ import {
45
45
  llmResultToScreening,
46
46
  screenCandidate
47
47
  } from "../../core/screening/index.js";
48
- import {
48
+ import {
49
+ closeRecommendBlockingPanels,
50
+ closeRecommendAvatarPreview,
49
51
  closeRecommendDetail,
50
52
  createRecommendDetailNetworkRecorder,
51
53
  extractRecommendDetailCandidate,
@@ -465,12 +467,17 @@ function countPassedResults(results = []) {
465
467
 
466
468
  function compactCloseResult(closeResult) {
467
469
  if (!closeResult) return null;
468
- return {
470
+ const result = {
469
471
  closed: Boolean(closeResult.closed),
470
472
  reason: closeResult.reason || null,
473
+ probe: closeResult.probe || null,
471
474
  attempts: closeResult.attempts || [],
472
475
  verification: closeResult.verification || null
473
476
  };
477
+ if (closeResult.already_closed !== undefined) {
478
+ result.already_closed = Boolean(closeResult.already_closed);
479
+ }
480
+ return result;
474
481
  }
475
482
 
476
483
  function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
@@ -482,6 +489,9 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
482
489
  if (error.close_result) {
483
490
  result.close_result = compactCloseResult(error.close_result);
484
491
  }
492
+ if (error.phase) {
493
+ result.phase = error.phase;
494
+ }
485
495
  if (error.refresh_attempt) {
486
496
  result.refresh_attempt = error.refresh_attempt;
487
497
  }
@@ -497,6 +507,16 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
497
507
  if (Array.isArray(error.recommend_detail_open_attempts)) {
498
508
  result.recommend_detail_open_attempts = error.recommend_detail_open_attempts;
499
509
  }
510
+ if (Array.isArray(error.click_attempts)) {
511
+ result.click_attempts = error.click_attempts;
512
+ }
513
+ if (error.avatar_preview) {
514
+ result.avatar_preview = {
515
+ open: Boolean(error.avatar_preview.open),
516
+ selector: error.avatar_preview.preview?.selector || null,
517
+ rect: error.avatar_preview.preview?.rect || null
518
+ };
519
+ }
500
520
  return result;
501
521
  }
502
522
 
@@ -507,6 +527,14 @@ function createRecommendCloseFailureError(closeResult) {
507
527
  return error;
508
528
  }
509
529
 
530
+ function createRecommendBlockingPanelCloseFailureError(closeResult, phase = "") {
531
+ const error = new Error(closeResult?.reason || "Boss account-rights panel did not close before recovery");
532
+ error.code = "ACCOUNT_RIGHTS_PANEL_CLOSE_FAILED";
533
+ error.close_result = closeResult || null;
534
+ error.phase = phase || null;
535
+ return error;
536
+ }
537
+
510
538
  function createRecommendRefreshFailureError(refreshAttempt, {
511
539
  listEndReason = "",
512
540
  targetCount = 0,
@@ -699,10 +727,11 @@ export async function runRecommendWorkflow({
699
727
  let refreshRounds = 0;
700
728
  let contextRecoveryAttempts = 0;
701
729
  let greetCount = 0;
702
- const candidateRecoveryCounts = new Map();
703
- let jobSelection = null;
730
+ const candidateRecoveryCounts = new Map();
731
+ let jobSelection = null;
704
732
  let pageScopeSelection = null;
705
733
  let filterResult = null;
734
+ let rootState = null;
706
735
  let cardNodeIds = [];
707
736
  let listEndReason = "";
708
737
  let lastHumanEvent = null;
@@ -769,9 +798,9 @@ export async function runRecommendWorkflow({
769
798
  });
770
799
  }
771
800
 
772
- function checkpointInProgressCandidate({
773
- index = results.length,
774
- candidateKey = "",
801
+ function checkpointInProgressCandidate({
802
+ index = results.length,
803
+ candidateKey = "",
775
804
  cardNodeId = null,
776
805
  detailStep = "",
777
806
  error = null
@@ -785,11 +814,22 @@ export async function runRecommendWorkflow({
785
814
  counters: countRecommendResultStatuses(results, { greetCount }),
786
815
  error: compactError(error, "RECOMMEND_IN_PROGRESS_ERROR")
787
816
  },
788
- candidate_list: compactInfiniteListState(listState)
789
- });
790
- }
791
-
792
- async function recoverAndReapplyRecommendContext(reason = "context_recovery", error = null, {
817
+ candidate_list: compactInfiniteListState(listState)
818
+ });
819
+ }
820
+
821
+ async function closeRecommendBlockingPanelsForRun(phase = "cleanup") {
822
+ const result = await closeRecommendBlockingPanels(client, {
823
+ attemptsLimit: 2,
824
+ rootState
825
+ });
826
+ if (!result?.closed) {
827
+ throw createRecommendBlockingPanelCloseFailureError(result, phase);
828
+ }
829
+ return result;
830
+ }
831
+
832
+ async function recoverAndReapplyRecommendContext(reason = "context_recovery", error = null, {
793
833
  forceRecentNotView = true
794
834
  } = {}) {
795
835
  await runControl.waitIfPaused();
@@ -797,9 +837,9 @@ export async function runRecommendWorkflow({
797
837
  const started = Date.now();
798
838
  runControl.setPhase("recommend:recover-context");
799
839
  contextRecoveryAttempts += 1;
800
- const refreshResult = await refreshRecommendListAtEnd(client, {
801
- rootState,
802
- jobLabel,
840
+ const refreshResult = await refreshRecommendListAtEnd(client, {
841
+ rootState,
842
+ jobLabel,
803
843
  pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
804
844
  fallbackPageScope: normalizedFallbackPageScope,
805
845
  filter: normalizedFilter,
@@ -808,16 +848,24 @@ export async function runRecommendWorkflow({
808
848
  targetUrl: targetUrl || RECOMMEND_TARGET_URL,
809
849
  forceRecentNotView,
810
850
  cardTimeoutMs,
811
- buttonSettleMs: refreshButtonSettleMs,
812
- reloadSettleMs: refreshReloadSettleMs
813
- });
814
- const compactRefresh = {
815
- ...compactRefreshAttempt(refreshResult),
816
- context_recovery: true,
817
- recovery_reason: reason,
818
- trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
819
- elapsed_ms: Date.now() - started
820
- };
851
+ buttonSettleMs: refreshButtonSettleMs,
852
+ reloadSettleMs: refreshReloadSettleMs
853
+ });
854
+ let blockingPanelClose = null;
855
+ if (refreshResult.ok) {
856
+ blockingPanelClose = await closeRecommendBlockingPanels(client, {
857
+ attemptsLimit: 2,
858
+ rootState: refreshResult.root_state || rootState
859
+ });
860
+ }
861
+ const compactRefresh = {
862
+ ...compactRefreshAttempt(refreshResult),
863
+ context_recovery: true,
864
+ recovery_reason: reason,
865
+ trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
866
+ account_rights_panel_close: compactCloseResult(blockingPanelClose),
867
+ elapsed_ms: Date.now() - started
868
+ };
821
869
  refreshAttempts.push(compactRefresh);
822
870
  runControl.checkpoint({
823
871
  context_recovery: {
@@ -834,10 +882,15 @@ export async function runRecommendWorkflow({
834
882
  refresh_method: refreshResult.method || null,
835
883
  refresh_forced_recent_not_view: forceRecentNotView,
836
884
  recovery_reason: reason
837
- });
838
- throw new Error(`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`);
839
- }
840
- rootState = refreshResult.root_state || await getRecommendRoots(client);
885
+ });
886
+ throw new Error(`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`);
887
+ }
888
+ if (!blockingPanelClose?.closed) {
889
+ const panelError = createRecommendBlockingPanelCloseFailureError(blockingPanelClose, `recover:${reason}`);
890
+ panelError.refresh_attempt = compactRefresh;
891
+ throw panelError;
892
+ }
893
+ rootState = refreshResult.root_state || await getRecommendRoots(client);
841
894
  rootState = await ensureRecommendViewport(rootState, "recover_after");
842
895
  cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
843
896
  timeoutMs: cardTimeoutMs,
@@ -863,13 +916,15 @@ export async function runRecommendWorkflow({
863
916
  return refreshResult;
864
917
  }
865
918
 
866
- runControl.setPhase("recommend:cleanup");
867
- await closeRecommendDetail(client, { attemptsLimit: 2 });
868
-
869
- await runControl.waitIfPaused();
870
- runControl.throwIfCanceled();
871
- runControl.setPhase("recommend:roots");
872
- let rootState = await getRecommendRoots(client);
919
+ runControl.setPhase("recommend:cleanup");
920
+ await closeRecommendDetail(client, { attemptsLimit: 2 });
921
+ await closeRecommendAvatarPreview(client, { attemptsLimit: 2 });
922
+ await closeRecommendBlockingPanelsForRun("cleanup");
923
+
924
+ await runControl.waitIfPaused();
925
+ runControl.throwIfCanceled();
926
+ runControl.setPhase("recommend:roots");
927
+ rootState = await getRecommendRoots(client);
873
928
  rootState = await ensureRecommendViewport(rootState, "roots");
874
929
  runControl.checkpoint({
875
930
  iframe_selector: rootState.iframe.selector,
@@ -1067,9 +1122,28 @@ export async function runRecommendWorkflow({
1067
1122
  try {
1068
1123
  await runControl.waitIfPaused();
1069
1124
  runControl.throwIfCanceled();
1070
- runControl.setPhase("recommend:detail");
1071
- detailStep = "ensure_viewport";
1072
- rootState = await ensureRecommendViewport(rootState, "detail");
1125
+ runControl.setPhase("recommend:detail");
1126
+ detailStep = "ensure_viewport";
1127
+ rootState = await ensureRecommendViewport(rootState, "detail");
1128
+ const blockingPanelClose = await closeRecommendBlockingPanels(client, {
1129
+ attemptsLimit: 2,
1130
+ rootState
1131
+ });
1132
+ if (!blockingPanelClose?.closed) {
1133
+ const panelError = createRecommendBlockingPanelCloseFailureError(
1134
+ blockingPanelClose,
1135
+ "before_detail_open"
1136
+ );
1137
+ timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
1138
+ checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error: panelError });
1139
+ await recoverAndReapplyRecommendContext("account_rights_panel_before_detail", panelError, {
1140
+ forceRecentNotView: true
1141
+ });
1142
+ continue;
1143
+ }
1144
+ if (blockingPanelClose.already_closed === false) {
1145
+ timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
1146
+ }
1073
1147
  checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
1074
1148
  detailStep = "open_detail";
1075
1149
  networkRecorder.clear();
@@ -1182,12 +1256,14 @@ export async function runRecommendWorkflow({
1182
1256
  const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
1183
1257
  if (recoveryCount < 1) {
1184
1258
  candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
1185
- timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
1186
- checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
1187
- await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
1188
- await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
1189
- forceRecentNotView: true
1190
- });
1259
+ timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
1260
+ checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
1261
+ await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
1262
+ await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
1263
+ await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
1264
+ await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
1265
+ forceRecentNotView: true
1266
+ });
1191
1267
  continue;
1192
1268
  }
1193
1269
  imageEvidence = createRecoverableImageCaptureEvidence(error, {
@@ -1232,20 +1308,24 @@ export async function runRecommendWorkflow({
1232
1308
  if (!isRecoverableRecommendDetailError(error)) throw error;
1233
1309
  const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
1234
1310
  if (recoveryCount < 1) {
1235
- candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
1236
- timings.detail_recovery_trigger = compactRecoverableDetailError(error);
1237
- checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
1238
- await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
1239
- await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
1240
- forceRecentNotView: true
1241
- });
1311
+ candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
1312
+ timings.detail_recovery_trigger = compactRecoverableDetailError(error);
1313
+ checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
1314
+ await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
1315
+ await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
1316
+ await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
1317
+ await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
1318
+ forceRecentNotView: true
1319
+ });
1242
1320
  continue;
1243
1321
  }
1244
- recoverableDetailError = error;
1245
- detailResult = null;
1246
- timings.detail_recovered_error = compactRecoverableDetailError(error);
1247
- await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
1248
- }
1322
+ recoverableDetailError = error;
1323
+ detailResult = null;
1324
+ timings.detail_recovered_error = compactRecoverableDetailError(error);
1325
+ await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
1326
+ await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
1327
+ await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
1328
+ }
1249
1329
  }
1250
1330
 
1251
1331
  await runControl.waitIfPaused();
@@ -13,6 +13,10 @@ import {
13
13
  buildScreeningCandidateFromDetail,
14
14
  htmlToText
15
15
  } from "../../core/screening/index.js";
16
+ import {
17
+ closeBossAccountRightsBlockingPanel,
18
+ findBossAccountRightsBlockingPanel
19
+ } from "../common/account-rights-panel.js";
16
20
  import {
17
21
  RECRUIT_DETAIL_CLOSE_SELECTORS,
18
22
  RECRUIT_DETAIL_NETWORK_PATTERNS,
@@ -64,6 +68,17 @@ export function createRecruitDetailNetworkRecorder(client) {
64
68
  };
65
69
  }
66
70
 
71
+ export async function findRecruitBlockingPanel(client, options = {}) {
72
+ return findBossAccountRightsBlockingPanel(client, options);
73
+ }
74
+
75
+ export async function closeRecruitBlockingPanels(client, options = {}) {
76
+ return closeBossAccountRightsBlockingPanel(client, {
77
+ resolveRoots: getRecruitRoots,
78
+ ...options
79
+ });
80
+ }
81
+
67
82
  export async function waitForRecruitDetailNetworkEvents(recorder, {
68
83
  minCount = 1,
69
84
  requireLoaded = true,
@@ -44,6 +44,7 @@ import {
44
44
  screenCandidate
45
45
  } from "../../core/screening/index.js";
46
46
  import {
47
+ closeRecruitBlockingPanels,
47
48
  closeRecruitDetail,
48
49
  createRecruitDetailNetworkRecorder,
49
50
  extractRecruitDetailCandidate,
@@ -159,6 +160,12 @@ function compactError(error, fallbackCode = "RECRUIT_RUN_ERROR") {
159
160
  code: error.code || fallbackCode,
160
161
  message: error.message || String(error)
161
162
  };
163
+ if (error.close_result) {
164
+ result.close_result = compactCloseResult(error.close_result);
165
+ }
166
+ if (error.phase) {
167
+ result.phase = error.phase;
168
+ }
162
169
  if (error.refresh_attempt) {
163
170
  result.refresh_attempt = error.refresh_attempt;
164
171
  }
@@ -174,6 +181,21 @@ function compactError(error, fallbackCode = "RECRUIT_RUN_ERROR") {
174
181
  return result;
175
182
  }
176
183
 
184
+ function compactCloseResult(closeResult) {
185
+ if (!closeResult) return null;
186
+ const result = {
187
+ closed: Boolean(closeResult.closed),
188
+ reason: closeResult.reason || null,
189
+ probe: closeResult.probe || null,
190
+ attempts: closeResult.attempts || [],
191
+ verification: closeResult.verification || null
192
+ };
193
+ if (closeResult.already_closed !== undefined) {
194
+ result.already_closed = Boolean(closeResult.already_closed);
195
+ }
196
+ return result;
197
+ }
198
+
177
199
  function createRecruitCloseFailureError(closeResult) {
178
200
  const error = new Error(closeResult?.reason || "Recruit detail did not close before recovery");
179
201
  error.code = "DETAIL_CLOSE_FAILED";
@@ -181,6 +203,14 @@ function createRecruitCloseFailureError(closeResult) {
181
203
  return error;
182
204
  }
183
205
 
206
+ function createRecruitBlockingPanelCloseFailureError(closeResult, phase = "") {
207
+ const error = new Error(closeResult?.reason || "Boss account-rights panel did not close before recovery");
208
+ error.code = "ACCOUNT_RIGHTS_PANEL_CLOSE_FAILED";
209
+ error.close_result = closeResult || null;
210
+ error.phase = phase || null;
211
+ return error;
212
+ }
213
+
184
214
  function createRecruitRefreshFailureError(refreshAttempt, {
185
215
  listEndReason = "",
186
216
  targetCount = 0,
@@ -397,6 +427,7 @@ export async function runRecruitWorkflow({
397
427
  let refreshRounds = 0;
398
428
  let contextRecoveryAttempts = 0;
399
429
  const candidateRecoveryCounts = new Map();
430
+ let rootState = null;
400
431
  let cardNodeIds = [];
401
432
  let listEndReason = "";
402
433
  let lastHumanEvent = null;
@@ -482,6 +513,17 @@ export async function runRecruitWorkflow({
482
513
  });
483
514
  }
484
515
 
516
+ async function closeRecruitBlockingPanelsForRun(phase = "cleanup") {
517
+ const result = await closeRecruitBlockingPanels(client, {
518
+ attemptsLimit: 2,
519
+ rootState
520
+ });
521
+ if (!result?.closed) {
522
+ throw createRecruitBlockingPanelCloseFailureError(result, phase);
523
+ }
524
+ return result;
525
+ }
526
+
485
527
  async function recoverAndReapplyRecruitContext(reason = "context_recovery", error = null, {
486
528
  forceRecentViewed = true
487
529
  } = {}) {
@@ -499,11 +541,18 @@ export async function runRecruitWorkflow({
499
541
  cityOptionTimeoutMs,
500
542
  forceRecentViewed
501
543
  });
544
+ let blockingPanelClose = null;
545
+ if (refreshResult.ok) {
546
+ blockingPanelClose = await closeRecruitBlockingPanels(client, {
547
+ attemptsLimit: 2
548
+ });
549
+ }
502
550
  const compactRefresh = {
503
551
  ...compactRefreshAttempt(refreshResult),
504
552
  context_recovery: true,
505
553
  recovery_reason: reason,
506
554
  trigger_error: compactError(error, "RECRUIT_CONTEXT_RECOVERY_TRIGGER"),
555
+ account_rights_panel_close: compactCloseResult(blockingPanelClose),
507
556
  elapsed_ms: Date.now() - started
508
557
  };
509
558
  refreshAttempts.push(compactRefresh);
@@ -525,6 +574,11 @@ export async function runRecruitWorkflow({
525
574
  });
526
575
  throw new Error(`Recruit context recovery failed after ${reason}: ${refreshResult.application?.reason || "refresh returned no cards"}`);
527
576
  }
577
+ if (!blockingPanelClose?.closed) {
578
+ const panelError = createRecruitBlockingPanelCloseFailureError(blockingPanelClose, `recover:${reason}`);
579
+ panelError.refresh_attempt = compactRefresh;
580
+ throw panelError;
581
+ }
528
582
  rootState = await getRecruitRoots(client);
529
583
  rootState = await ensureRecruitViewport(rootState, "recover_after");
530
584
  cardNodeIds = await waitForRecruitCardNodeIds(client, rootState.iframe.documentNodeId, {
@@ -553,11 +607,12 @@ export async function runRecruitWorkflow({
553
607
 
554
608
  runControl.setPhase("recruit:cleanup");
555
609
  await closeRecruitDetail(client, { attemptsLimit: 2 });
610
+ await closeRecruitBlockingPanelsForRun("cleanup");
556
611
 
557
612
  await runControl.waitIfPaused();
558
613
  runControl.throwIfCanceled();
559
614
  runControl.setPhase("recruit:roots");
560
- let rootState = await getRecruitRoots(client);
615
+ rootState = await getRecruitRoots(client);
561
616
  rootState = await ensureRecruitViewport(rootState, "roots");
562
617
  runControl.checkpoint({
563
618
  iframe_selector: rootState.iframe.selector,
@@ -732,6 +787,25 @@ export async function runRecruitWorkflow({
732
787
  runControl.setPhase("recruit:detail");
733
788
  detailStep = "ensure_viewport";
734
789
  rootState = await ensureRecruitViewport(rootState, "detail");
790
+ const blockingPanelClose = await closeRecruitBlockingPanels(client, {
791
+ attemptsLimit: 2,
792
+ rootState
793
+ });
794
+ if (!blockingPanelClose?.closed) {
795
+ const panelError = createRecruitBlockingPanelCloseFailureError(
796
+ blockingPanelClose,
797
+ "before_detail_open"
798
+ );
799
+ timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
800
+ checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error: panelError });
801
+ await recoverAndReapplyRecruitContext("account_rights_panel_before_detail", panelError, {
802
+ forceRecentViewed: true
803
+ });
804
+ continue;
805
+ }
806
+ if (blockingPanelClose.already_closed === false) {
807
+ timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
808
+ }
735
809
  checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
736
810
  detailStep = "open_detail";
737
811
  networkRecorder.clear();
@@ -835,6 +909,7 @@ export async function runRecruitWorkflow({
835
909
  timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
836
910
  checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
837
911
  await closeRecruitDetail(client, { attemptsLimit: 2 }).catch(() => null);
912
+ await closeRecruitBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
838
913
  await recoverAndReapplyRecruitContext(`image_capture:${detailStep}`, error, {
839
914
  forceRecentViewed: true
840
915
  });
@@ -907,6 +982,7 @@ export async function runRecruitWorkflow({
907
982
  timings.detail_recovery_trigger = compactRecoverableDetailError(error);
908
983
  checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
909
984
  await closeRecruitDetail(client, { attemptsLimit: 2 }).catch(() => null);
985
+ await closeRecruitBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
910
986
  await recoverAndReapplyRecruitContext(`detail:${detailStep}`, error, {
911
987
  forceRecentViewed: true
912
988
  });
@@ -916,6 +992,7 @@ export async function runRecruitWorkflow({
916
992
  detailResult = null;
917
993
  timings.detail_recovered_error = compactRecoverableDetailError(error);
918
994
  await closeRecruitDetail(client, { attemptsLimit: 2 }).catch(() => null);
995
+ await closeRecruitBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
919
996
  }
920
997
  }
921
998