@reconcrap/boss-recommend-mcp 1.3.31 → 1.3.33

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.
@@ -921,6 +921,21 @@ function browserConversationReadyState() {
921
921
  const askResume = Array.from(document.querySelectorAll('span.operate-btn, button, a, span')).find(
922
922
  (el) => isVisible(el) && isAskResumeText(el.textContent || ''),
923
923
  );
924
+ const editor = document.querySelector(
925
+ '#boss-chat-editor-input, .conversation-editor #boss-chat-editor-input, .conversation-editor .boss-chat-editor-input',
926
+ );
927
+ const activeSubmit = Array.from(
928
+ document.querySelectorAll(
929
+ '.conversation-editor .submit.active, .conversation-editor .submit-content .submit.active, .submit.active',
930
+ ),
931
+ ).find((node) => isVisible(node));
932
+ const anySubmit = Array.from(
933
+ document.querySelectorAll(
934
+ '.conversation-editor .submit-content .submit, .conversation-editor .submit, .submit-content .submit, .submit',
935
+ ),
936
+ ).find((node) => isVisible(node) && normalize(node.textContent || '').includes('发送'));
937
+ const resumeState = browserIsResumeModalOpen();
938
+ const detailState = browserCollectCandidateDetailSnapshot();
924
939
  const attachmentResumeEnabled = Boolean(attachmentResume) && !isDisabledDeep(attachmentResume);
925
940
  return {
926
941
  hasOnlineResume: Boolean(onlineResume),
@@ -929,6 +944,345 @@ function browserConversationReadyState() {
929
944
  hasAttachmentResume: Boolean(attachmentResume),
930
945
  attachmentResumeEnabled,
931
946
  attachmentResumeClass: String(attachmentResume?.className || ''),
947
+ resumeModalOpen:
948
+ Boolean(resumeState?.open) ||
949
+ Number(resumeState?.iframeCount || 0) > 0 ||
950
+ Number(resumeState?.scopeCount || 0) > 0,
951
+ candidateDetailOpen:
952
+ Boolean(detailState?.open) ||
953
+ Number(detailState?.panelCount || 0) > 0 ||
954
+ Number(detailState?.closeCount || 0) > 0,
955
+ panelsClosed:
956
+ !(
957
+ Boolean(resumeState?.open) ||
958
+ Number(resumeState?.iframeCount || 0) > 0 ||
959
+ Number(resumeState?.scopeCount || 0) > 0 ||
960
+ Boolean(detailState?.open) ||
961
+ Number(detailState?.panelCount || 0) > 0 ||
962
+ Number(detailState?.closeCount || 0) > 0
963
+ ),
964
+ editorVisible: editor instanceof HTMLElement && isVisible(editor),
965
+ activeSubmit: Boolean(activeSubmit),
966
+ hasAnySubmit: Boolean(anySubmit),
967
+ messageInputReady:
968
+ editor instanceof HTMLElement &&
969
+ isVisible(editor) &&
970
+ (Boolean(activeSubmit) || Boolean(anySubmit)),
971
+ };
972
+ }
973
+
974
+ function browserNormalizeVisibleText(value) {
975
+ return String(value || '').replace(/\s+/g, ' ').trim();
976
+ }
977
+
978
+ function browserIsVisibleElement(el, minimum = 2) {
979
+ if (!(el instanceof HTMLElement)) return false;
980
+ const style = getComputedStyle(el);
981
+ if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.01) {
982
+ return false;
983
+ }
984
+ const rect = el.getBoundingClientRect();
985
+ return rect.width > minimum && rect.height > minimum;
986
+ }
987
+
988
+ function browserRectToJson(rect) {
989
+ return {
990
+ left: rect.left,
991
+ top: rect.top,
992
+ width: rect.width,
993
+ height: rect.height,
994
+ right: rect.right,
995
+ bottom: rect.bottom,
996
+ };
997
+ }
998
+
999
+ function browserCollectCandidateDetailSnapshot() {
1000
+ const normalize = browserNormalizeVisibleText;
1001
+ const isVisible = browserIsVisibleElement;
1002
+ const rectToJson = browserRectToJson;
1003
+ const viewportWidth = Math.max(document.documentElement?.clientWidth || 0, window.innerWidth || 0);
1004
+ const viewportHeight = Math.max(document.documentElement?.clientHeight || 0, window.innerHeight || 0);
1005
+ const panelSelectors = [
1006
+ '.base-info-single-top-detail',
1007
+ '.resume-detail-wrap',
1008
+ '.geek-card-detail',
1009
+ '.candidate-detail-wrap',
1010
+ '.chat-detail-wrap',
1011
+ '.new-resume-online-main-ui',
1012
+ '.resume-recommend.resume-common-wrap',
1013
+ '.resume-recommend',
1014
+ '.boss-dialog__body',
1015
+ ];
1016
+ const overlaySelectors = [
1017
+ '.dialog-wrap.active',
1018
+ '.dialog-wrap',
1019
+ '.boss-dialog.active',
1020
+ '.boss-dialog',
1021
+ '.boss-popup__wrapper',
1022
+ '.v-modal',
1023
+ '.modal-mask',
1024
+ '.geek-detail-modal',
1025
+ '.modal',
1026
+ ];
1027
+ const closeButtons = Array.from(document.querySelectorAll('.close-btn')).filter((el) => isVisible(el));
1028
+ const contentEntries = [];
1029
+ const overlayEntries = [];
1030
+ const contentSeen = new Set();
1031
+ const overlaySeen = new Set();
1032
+
1033
+ const pushEntry = (entries, seen, node, source, { minWidth = 240, minHeight = 160 } = {}) => {
1034
+ if (!(node instanceof HTMLElement) || !isVisible(node)) return;
1035
+ const rect = node.getBoundingClientRect();
1036
+ if (rect.width < minWidth || rect.height < minHeight) return;
1037
+ const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}:${normalize(node.className || '')}`;
1038
+ if (seen.has(key)) return;
1039
+ seen.add(key);
1040
+ entries.push({ node, rect, source });
1041
+ };
1042
+
1043
+ const hasContentHint = (node, rect, source = '') => {
1044
+ const classText = normalize(node.className || '').toLowerCase();
1045
+ const text = normalize(node.textContent || '').slice(0, 240).toLowerCase();
1046
+ const containsClose = closeButtons.some((button) => node.contains(button));
1047
+ const anchoredRight =
1048
+ rect.left >= viewportWidth * 0.35 ||
1049
+ rect.right >= viewportWidth * 0.68;
1050
+ const hasKnownContentClass =
1051
+ classText.includes('base-info-single-top-detail') ||
1052
+ classText.includes('resume-detail-wrap') ||
1053
+ classText.includes('candidate-detail') ||
1054
+ classText.includes('chat-detail') ||
1055
+ classText.includes('geek-card-detail') ||
1056
+ classText.includes('new-resume-online-main-ui') ||
1057
+ classText.includes('resume-common-wrap') ||
1058
+ classText.includes('boss-dialog__body');
1059
+ const hasDetailHint =
1060
+ text.includes('在线简历') ||
1061
+ text.includes('附件简历') ||
1062
+ text.includes('牛人分析器') ||
1063
+ text.includes('活跃');
1064
+ const notFullScreen =
1065
+ rect.width < viewportWidth * 0.96 ||
1066
+ rect.height < viewportHeight * 0.96;
1067
+ return (
1068
+ containsClose ||
1069
+ hasKnownContentClass ||
1070
+ hasDetailHint ||
1071
+ (source === 'close-ancestor' && (anchoredRight || notFullScreen))
1072
+ );
1073
+ };
1074
+
1075
+ const hasOverlayHint = (node, rect) => {
1076
+ const classText = normalize(node.className || '').toLowerCase();
1077
+ const hasOverlayClass =
1078
+ classText.includes('dialog-wrap') ||
1079
+ classText.includes('boss-dialog') ||
1080
+ classText.includes('popup') ||
1081
+ classText.includes('modal') ||
1082
+ classText.includes('mask') ||
1083
+ classText.includes('overlay');
1084
+ const coversViewport =
1085
+ rect.left <= 8 &&
1086
+ rect.top <= 8 &&
1087
+ rect.width >= viewportWidth * 0.85 &&
1088
+ rect.height >= viewportHeight * 0.85;
1089
+ return hasOverlayClass || coversViewport;
1090
+ };
1091
+
1092
+ for (const selector of panelSelectors) {
1093
+ for (const node of Array.from(document.querySelectorAll(selector))) {
1094
+ pushEntry(contentEntries, contentSeen, node, `selector:${selector}`);
1095
+ }
1096
+ }
1097
+
1098
+ for (const selector of overlaySelectors) {
1099
+ for (const node of Array.from(document.querySelectorAll(selector))) {
1100
+ pushEntry(overlayEntries, overlaySeen, node, `selector:${selector}`, {
1101
+ minWidth: 180,
1102
+ minHeight: 120,
1103
+ });
1104
+ }
1105
+ }
1106
+
1107
+ for (const closeButton of closeButtons) {
1108
+ let current = closeButton.parentElement;
1109
+ let depth = 0;
1110
+ while (current instanceof HTMLElement && depth < 12) {
1111
+ const rect = current.getBoundingClientRect();
1112
+ if (hasContentHint(current, rect, 'close-ancestor')) {
1113
+ pushEntry(contentEntries, contentSeen, current, 'close-ancestor');
1114
+ }
1115
+ if (hasOverlayHint(current, rect)) {
1116
+ pushEntry(overlayEntries, overlaySeen, current, 'close-ancestor', {
1117
+ minWidth: 180,
1118
+ minHeight: 120,
1119
+ });
1120
+ }
1121
+ current = current.parentElement;
1122
+ depth += 1;
1123
+ }
1124
+ }
1125
+
1126
+ const scoredContent = contentEntries
1127
+ .map((entry) => {
1128
+ const classText = normalize(entry.node.className || '').toLowerCase();
1129
+ const text = normalize(entry.node.textContent || '').slice(0, 240).toLowerCase();
1130
+ const containsClose = closeButtons.some((button) => entry.node.contains(button));
1131
+ const anchoredRight =
1132
+ entry.rect.left >= viewportWidth * 0.35 ||
1133
+ entry.rect.right >= viewportWidth * 0.68;
1134
+ const hasKnownContentClass =
1135
+ classText.includes('base-info-single-top-detail') ||
1136
+ classText.includes('resume-detail-wrap') ||
1137
+ classText.includes('candidate-detail') ||
1138
+ classText.includes('chat-detail') ||
1139
+ classText.includes('geek-card-detail') ||
1140
+ classText.includes('new-resume-online-main-ui') ||
1141
+ classText.includes('resume-common-wrap') ||
1142
+ classText.includes('boss-dialog__body');
1143
+ const hasDetailHint =
1144
+ text.includes('在线简历') ||
1145
+ text.includes('附件简历') ||
1146
+ text.includes('牛人分析器') ||
1147
+ text.includes('活跃');
1148
+ const resemblesFullscreen =
1149
+ entry.rect.left <= 8 &&
1150
+ entry.rect.top <= 8 &&
1151
+ entry.rect.width >= viewportWidth * 0.92 &&
1152
+ entry.rect.height >= viewportHeight * 0.92;
1153
+
1154
+ let score = 0;
1155
+ if (containsClose) score += 220;
1156
+ if (anchoredRight) score += 150;
1157
+ if (hasKnownContentClass) score += 200;
1158
+ if (hasDetailHint) score += 80;
1159
+ if (entry.source === 'close-ancestor') score += 40;
1160
+ if (entry.rect.width <= viewportWidth * 0.82) score += 70;
1161
+ if (entry.rect.height <= viewportHeight * 0.98) score += 20;
1162
+ if (resemblesFullscreen) score -= 220;
1163
+ score += Math.min(140, Math.floor((entry.rect.width * entry.rect.height) / 18000));
1164
+
1165
+ return {
1166
+ ...entry,
1167
+ score,
1168
+ };
1169
+ })
1170
+ .filter((entry) => entry.score > 0)
1171
+ .sort((a, b) => b.score - a.score);
1172
+
1173
+ const scoredOverlay = overlayEntries
1174
+ .map((entry) => {
1175
+ const classText = normalize(entry.node.className || '').toLowerCase();
1176
+ const coversViewport =
1177
+ entry.rect.left <= 8 &&
1178
+ entry.rect.top <= 8 &&
1179
+ entry.rect.width >= viewportWidth * 0.85 &&
1180
+ entry.rect.height >= viewportHeight * 0.85;
1181
+ let score = 0;
1182
+ if (classText.includes('dialog-wrap')) score += 160;
1183
+ if (classText.includes('boss-dialog')) score += 120;
1184
+ if (classText.includes('modal') || classText.includes('overlay') || classText.includes('mask')) {
1185
+ score += 80;
1186
+ }
1187
+ if (coversViewport) score += 180;
1188
+ if (entry.source === 'close-ancestor') score += 20;
1189
+ return {
1190
+ ...entry,
1191
+ score,
1192
+ };
1193
+ })
1194
+ .filter((entry) => entry.score > 0)
1195
+ .sort((a, b) => b.score - a.score);
1196
+
1197
+ const topContent = scoredContent[0] || null;
1198
+ const topOverlay = scoredOverlay[0] || null;
1199
+ const topContentNode = topContent?.node || null;
1200
+ const topOverlayNode = topOverlay?.node || null;
1201
+ const closeButton =
1202
+ closeButtons.find((button) => topContentNode instanceof HTMLElement && topContentNode.contains(button)) ||
1203
+ closeButtons[0] ||
1204
+ null;
1205
+ const topPanel = topContent || topOverlay;
1206
+ const topPanelNode = topPanel?.node || null;
1207
+
1208
+ return {
1209
+ open: Boolean(topContent || topOverlay || closeButton),
1210
+ panelCount: scoredContent.length,
1211
+ closeCount: closeButtons.length,
1212
+ topPanelClass: normalize(topPanelNode?.className || ''),
1213
+ topPanelScore: Number(topPanel?.score || 0),
1214
+ panelRect: topContent ? rectToJson(topContent.rect) : topOverlay ? rectToJson(topOverlay.rect) : null,
1215
+ closeRect: closeButton ? rectToJson(closeButton.getBoundingClientRect()) : null,
1216
+ overlayClass: normalize(topOverlayNode?.className || ''),
1217
+ overlayRect: topOverlay ? rectToJson(topOverlay.rect) : null,
1218
+ contentClass: normalize(topContentNode?.className || ''),
1219
+ contentRect: topContent ? rectToJson(topContent.rect) : null,
1220
+ closeButton,
1221
+ };
1222
+ }
1223
+
1224
+ function browserFindCandidateDetailOutsideClickPoint() {
1225
+ const state = browserCollectCandidateDetailSnapshot();
1226
+ const viewportWidth = Math.max(document.documentElement?.clientWidth || 0, window.innerWidth || 0);
1227
+ const viewportHeight = Math.max(document.documentElement?.clientHeight || 0, window.innerHeight || 0);
1228
+ const contentRect = state?.contentRect;
1229
+ if (!state?.open || !contentRect) {
1230
+ return {
1231
+ ok: false,
1232
+ error: 'CANDIDATE_DETAIL_OUTSIDE_POINT_NOT_FOUND',
1233
+ state,
1234
+ };
1235
+ }
1236
+
1237
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
1238
+ const candidates = [];
1239
+ const safeMidY = clamp(contentRect.top + contentRect.height / 2, 18, viewportHeight - 18);
1240
+ const safeMidX = clamp(contentRect.left + contentRect.width / 2, 18, viewportWidth - 18);
1241
+
1242
+ if (contentRect.left >= 32) {
1243
+ candidates.push({
1244
+ strategy: 'left-gap',
1245
+ x: clamp(Math.min(contentRect.left - 18, contentRect.left / 2), 18, viewportWidth - 18),
1246
+ y: safeMidY,
1247
+ });
1248
+ }
1249
+ if (viewportWidth - contentRect.right >= 32) {
1250
+ candidates.push({
1251
+ strategy: 'right-gap',
1252
+ x: clamp(Math.max(contentRect.right + 18, contentRect.right + (viewportWidth - contentRect.right) / 2), 18, viewportWidth - 18),
1253
+ y: safeMidY,
1254
+ });
1255
+ }
1256
+ if (contentRect.top >= 32) {
1257
+ candidates.push({
1258
+ strategy: 'top-gap',
1259
+ x: safeMidX,
1260
+ y: clamp(Math.min(contentRect.top - 18, contentRect.top / 2), 18, viewportHeight - 18),
1261
+ });
1262
+ }
1263
+
1264
+ const point = candidates.find((candidate) => {
1265
+ const outsideHorizontally = candidate.x < contentRect.left - 6 || candidate.x > contentRect.right + 6;
1266
+ const outsideVertically = candidate.y < contentRect.top - 6 || candidate.y > contentRect.bottom + 6;
1267
+ return outsideHorizontally || outsideVertically;
1268
+ });
1269
+
1270
+ if (!point) {
1271
+ return {
1272
+ ok: false,
1273
+ error: 'CANDIDATE_DETAIL_OUTSIDE_POINT_NOT_FOUND',
1274
+ state,
1275
+ };
1276
+ }
1277
+
1278
+ return {
1279
+ ok: true,
1280
+ strategy: point.strategy,
1281
+ point: {
1282
+ x: Math.round(point.x),
1283
+ y: Math.round(point.y),
1284
+ },
1285
+ state,
932
1286
  };
933
1287
  }
934
1288
 
@@ -1012,226 +1366,10 @@ function browserOpenOnlineResume(options = {}) {
1012
1366
  }
1013
1367
 
1014
1368
  function browserIsCandidateDetailOpen() {
1015
- const collectSnapshot = () => {
1016
- const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
1017
- const isVisible = (el) => {
1018
- if (!(el instanceof HTMLElement)) return false;
1019
- const style = getComputedStyle(el);
1020
- if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.01) {
1021
- return false;
1022
- }
1023
- const rect = el.getBoundingClientRect();
1024
- return rect.width > 2 && rect.height > 2;
1025
- };
1026
- const rectToJson = (rect) => ({
1027
- left: rect.left,
1028
- top: rect.top,
1029
- width: rect.width,
1030
- height: rect.height,
1031
- right: rect.right,
1032
- bottom: rect.bottom,
1033
- });
1034
- const panelSelectors = [
1035
- '.base-info-single-top-detail',
1036
- '.resume-detail-wrap',
1037
- '.geek-card-detail',
1038
- '.candidate-detail-wrap',
1039
- '.chat-detail-wrap',
1040
- ];
1041
- const closeButtons = Array.from(document.querySelectorAll('.close-btn')).filter(isVisible);
1042
- const panelEntries = [];
1043
- const seen = new Set();
1044
- const pushPanel = (node, source) => {
1045
- if (!(node instanceof HTMLElement) || !isVisible(node)) return;
1046
- const rect = node.getBoundingClientRect();
1047
- if (rect.width < 240 || rect.height < 160) return;
1048
- const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}:${normalize(node.className || '')}`;
1049
- if (seen.has(key)) return;
1050
- seen.add(key);
1051
- panelEntries.push({ node, rect, source });
1052
- };
1053
-
1054
- for (const selector of panelSelectors) {
1055
- for (const node of Array.from(document.querySelectorAll(selector))) {
1056
- pushPanel(node, `selector:${selector}`);
1057
- }
1058
- }
1059
-
1060
- for (const closeButton of closeButtons) {
1061
- let current = closeButton.parentElement;
1062
- let depth = 0;
1063
- while (current instanceof HTMLElement && depth < 10) {
1064
- pushPanel(current, 'close-ancestor');
1065
- current = current.parentElement;
1066
- depth += 1;
1067
- }
1068
- }
1069
-
1070
- const scoredPanels = panelEntries
1071
- .map((entry) => {
1072
- const classText = normalize(entry.node.className || '').toLowerCase();
1073
- const text = normalize(entry.node.textContent || '').slice(0, 240).toLowerCase();
1074
- const containsClose = closeButtons.some((button) => entry.node.contains(button));
1075
- const anchoredRight =
1076
- entry.rect.left >= window.innerWidth * 0.4 ||
1077
- entry.rect.right >= window.innerWidth * 0.72;
1078
- const hasKnownDetailClass =
1079
- classText.includes('base-info-single-top-detail') ||
1080
- classText.includes('resume-detail-wrap') ||
1081
- classText.includes('candidate-detail') ||
1082
- classText.includes('chat-detail') ||
1083
- classText.includes('geek-card-detail');
1084
- const hasDetailHint =
1085
- text.includes('在线简历') ||
1086
- text.includes('附件简历') ||
1087
- text.includes('牛人分析器') ||
1088
- text.includes('活跃');
1089
-
1090
- let score = 0;
1091
- if (containsClose) score += 220;
1092
- if (anchoredRight) score += 140;
1093
- if (hasKnownDetailClass) score += 160;
1094
- if (hasDetailHint) score += 80;
1095
- if (entry.source === 'close-ancestor') score += 40;
1096
- score += Math.min(180, Math.floor((entry.rect.width * entry.rect.height) / 12000));
1097
-
1098
- return {
1099
- ...entry,
1100
- score,
1101
- };
1102
- })
1103
- .sort((a, b) => b.score - a.score);
1104
-
1105
- const topPanel = scoredPanels[0] || null;
1106
- const topPanelNode = topPanel?.node || null;
1107
- const closeButton =
1108
- closeButtons.find((button) => topPanelNode instanceof HTMLElement && topPanelNode.contains(button)) ||
1109
- closeButtons[0] ||
1110
- null;
1111
-
1112
- return {
1113
- open: Boolean(topPanel || closeButton),
1114
- panelCount: scoredPanels.length,
1115
- closeCount: closeButtons.length,
1116
- topPanelClass: normalize(topPanelNode?.className || ''),
1117
- topPanelScore: Number(topPanel?.score || 0),
1118
- panelRect: topPanel ? rectToJson(topPanel.rect) : null,
1119
- closeRect: closeButton ? rectToJson(closeButton.getBoundingClientRect()) : null,
1120
- };
1121
- };
1122
-
1123
- return collectSnapshot();
1369
+ return browserCollectCandidateDetailSnapshot();
1124
1370
  }
1125
1371
 
1126
1372
  function browserCloseCandidateDetailDomOnce() {
1127
- const collectSnapshot = () => {
1128
- const normalize = (value) => String(value || '').replace(/\s+/g, ' ').trim();
1129
- const isVisible = (el) => {
1130
- if (!(el instanceof HTMLElement)) return false;
1131
- const style = getComputedStyle(el);
1132
- if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.01) {
1133
- return false;
1134
- }
1135
- const rect = el.getBoundingClientRect();
1136
- return rect.width > 2 && rect.height > 2;
1137
- };
1138
- const rectToJson = (rect) => ({
1139
- left: rect.left,
1140
- top: rect.top,
1141
- width: rect.width,
1142
- height: rect.height,
1143
- right: rect.right,
1144
- bottom: rect.bottom,
1145
- });
1146
- const panelSelectors = [
1147
- '.base-info-single-top-detail',
1148
- '.resume-detail-wrap',
1149
- '.geek-card-detail',
1150
- '.candidate-detail-wrap',
1151
- '.chat-detail-wrap',
1152
- ];
1153
- const closeButtons = Array.from(document.querySelectorAll('.close-btn')).filter(isVisible);
1154
- const panelEntries = [];
1155
- const seen = new Set();
1156
- const pushPanel = (node, source) => {
1157
- if (!(node instanceof HTMLElement) || !isVisible(node)) return;
1158
- const rect = node.getBoundingClientRect();
1159
- if (rect.width < 240 || rect.height < 160) return;
1160
- const key = `${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}:${normalize(node.className || '')}`;
1161
- if (seen.has(key)) return;
1162
- seen.add(key);
1163
- panelEntries.push({ node, rect, source });
1164
- };
1165
-
1166
- for (const selector of panelSelectors) {
1167
- for (const node of Array.from(document.querySelectorAll(selector))) {
1168
- pushPanel(node, `selector:${selector}`);
1169
- }
1170
- }
1171
-
1172
- for (const closeButton of closeButtons) {
1173
- let current = closeButton.parentElement;
1174
- let depth = 0;
1175
- while (current instanceof HTMLElement && depth < 10) {
1176
- pushPanel(current, 'close-ancestor');
1177
- current = current.parentElement;
1178
- depth += 1;
1179
- }
1180
- }
1181
-
1182
- const scoredPanels = panelEntries
1183
- .map((entry) => {
1184
- const classText = normalize(entry.node.className || '').toLowerCase();
1185
- const text = normalize(entry.node.textContent || '').slice(0, 240).toLowerCase();
1186
- const containsClose = closeButtons.some((button) => entry.node.contains(button));
1187
- const anchoredRight =
1188
- entry.rect.left >= window.innerWidth * 0.4 ||
1189
- entry.rect.right >= window.innerWidth * 0.72;
1190
- const hasKnownDetailClass =
1191
- classText.includes('base-info-single-top-detail') ||
1192
- classText.includes('resume-detail-wrap') ||
1193
- classText.includes('candidate-detail') ||
1194
- classText.includes('chat-detail') ||
1195
- classText.includes('geek-card-detail');
1196
- const hasDetailHint =
1197
- text.includes('在线简历') ||
1198
- text.includes('附件简历') ||
1199
- text.includes('牛人分析器') ||
1200
- text.includes('活跃');
1201
-
1202
- let score = 0;
1203
- if (containsClose) score += 220;
1204
- if (anchoredRight) score += 140;
1205
- if (hasKnownDetailClass) score += 160;
1206
- if (hasDetailHint) score += 80;
1207
- if (entry.source === 'close-ancestor') score += 40;
1208
- score += Math.min(180, Math.floor((entry.rect.width * entry.rect.height) / 12000));
1209
-
1210
- return {
1211
- ...entry,
1212
- score,
1213
- };
1214
- })
1215
- .sort((a, b) => b.score - a.score);
1216
-
1217
- const topPanel = scoredPanels[0] || null;
1218
- const topPanelNode = topPanel?.node || null;
1219
- const closeButton =
1220
- closeButtons.find((button) => topPanelNode instanceof HTMLElement && topPanelNode.contains(button)) ||
1221
- closeButtons[0] ||
1222
- null;
1223
-
1224
- return {
1225
- open: Boolean(topPanel || closeButton),
1226
- panelCount: scoredPanels.length,
1227
- closeCount: closeButtons.length,
1228
- topPanelClass: normalize(topPanelNode?.className || ''),
1229
- topPanelScore: Number(topPanel?.score || 0),
1230
- panelRect: topPanel ? rectToJson(topPanel.rect) : null,
1231
- closeRect: closeButton ? rectToJson(closeButton.getBoundingClientRect()) : null,
1232
- closeButton,
1233
- };
1234
- };
1235
1373
  const serializeSnapshot = (snapshot = {}) => ({
1236
1374
  open: Boolean(snapshot?.open),
1237
1375
  panelCount: Number(snapshot?.panelCount || 0),
@@ -1240,9 +1378,13 @@ function browserCloseCandidateDetailDomOnce() {
1240
1378
  topPanelScore: Number(snapshot?.topPanelScore || 0),
1241
1379
  panelRect: snapshot?.panelRect || null,
1242
1380
  closeRect: snapshot?.closeRect || null,
1381
+ overlayClass: String(snapshot?.overlayClass || ''),
1382
+ overlayRect: snapshot?.overlayRect || null,
1383
+ contentClass: String(snapshot?.contentClass || ''),
1384
+ contentRect: snapshot?.contentRect || null,
1243
1385
  });
1244
1386
 
1245
- const snapshot = collectSnapshot();
1387
+ const snapshot = browserCollectCandidateDetailSnapshot();
1246
1388
  if (!snapshot?.open || !(snapshot.closeButton instanceof HTMLElement)) {
1247
1389
  return {
1248
1390
  ok: false,
@@ -2699,15 +2841,24 @@ export class BossChatPage {
2699
2841
  async waitForConversationReady(options = {}) {
2700
2842
  const maxAttempts = options.maxAttempts || 12;
2701
2843
  const delayMs = options.delayMs || 260;
2844
+ const requirePanelsClosed = options.requirePanelsClosed === true;
2702
2845
 
2703
2846
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
2704
2847
  const state = await this.chromeClient.callFunction(browserConversationReadyState);
2705
- if (state?.hasOnlineResume || state?.hasAskResume || state?.hasAttachmentResume) {
2848
+ const hasActionControls =
2849
+ Boolean(state?.hasOnlineResume) ||
2850
+ Boolean(state?.hasAskResume) ||
2851
+ Boolean(state?.hasAttachmentResume);
2852
+ const panelsReady = requirePanelsClosed ? Boolean(state?.panelsClosed) : true;
2853
+ const editorReady = requirePanelsClosed
2854
+ ? Boolean(state?.editorVisible) && (Boolean(state?.activeSubmit) || Boolean(state?.hasAnySubmit))
2855
+ : true;
2856
+ if (hasActionControls && panelsReady && editorReady) {
2706
2857
  return state;
2707
2858
  }
2708
2859
  await new Promise((resolve) => setTimeout(resolve, delayMs));
2709
2860
  }
2710
- throw new Error('CONVERSATION_PANEL_NOT_READY');
2861
+ throw new Error(requirePanelsClosed ? 'CONVERSATION_PANEL_NOT_READY_OR_BLOCKED' : 'CONVERSATION_PANEL_NOT_READY');
2711
2862
  }
2712
2863
 
2713
2864
  async waitForCandidateActivated(customer, options = {}) {
@@ -2877,6 +3028,10 @@ export class BossChatPage {
2877
3028
  topPanelScore: Number(result?.topPanelScore || 0),
2878
3029
  panelRect: result?.panelRect || null,
2879
3030
  closeRect: result?.closeRect || null,
3031
+ overlayClass: String(result?.overlayClass || ''),
3032
+ overlayRect: result?.overlayRect || null,
3033
+ contentClass: String(result?.contentClass || ''),
3034
+ contentRect: result?.contentRect || null,
2880
3035
  };
2881
3036
  }
2882
3037
 
@@ -2919,6 +3074,34 @@ export class BossChatPage {
2919
3074
  };
2920
3075
  }
2921
3076
 
3077
+ async clickCandidateDetailOutside() {
3078
+ const result = await this.chromeClient.callFunction(browserFindCandidateDetailOutsideClickPoint);
3079
+ if (!result?.ok || !result?.point) {
3080
+ const finalState = await this.getCandidateDetailState();
3081
+ return {
3082
+ clicked: false,
3083
+ method: `outside-click-miss:${result?.error || 'unknown'}`,
3084
+ finalState,
3085
+ };
3086
+ }
3087
+
3088
+ const point = result.point;
3089
+ const rect = {
3090
+ left: Math.max(0, Number(point.x || 0) - 3),
3091
+ top: Math.max(0, Number(point.y || 0) - 3),
3092
+ width: 6,
3093
+ height: 6,
3094
+ };
3095
+ await this.clickRect(rect);
3096
+ await new Promise((resolve) => setTimeout(resolve, 220));
3097
+ const finalState = await this.getCandidateDetailState();
3098
+ return {
3099
+ clicked: true,
3100
+ method: `outside-click:${result?.strategy || 'unknown'}`,
3101
+ finalState,
3102
+ };
3103
+ }
3104
+
2922
3105
  async closeCandidateDetail({ maxAttempts = 4, ensureDismiss = false } = {}) {
2923
3106
  const drawerOpen = (state) =>
2924
3107
  Boolean(state?.open) ||
@@ -2952,16 +3135,6 @@ export class BossChatPage {
2952
3135
  };
2953
3136
  }
2954
3137
 
2955
- if (midState?.panelRect) {
2956
- await this.clickRect(midState.panelRect);
2957
- methods.push('focus-panel');
2958
- await new Promise((resolve) => setTimeout(resolve, 160));
2959
- } else if (midState?.closeRect) {
2960
- await this.clickRect(midState.closeRect);
2961
- methods.push('focus-close');
2962
- await new Promise((resolve) => setTimeout(resolve, 160));
2963
- }
2964
-
2965
3138
  await this.chromeClient.pressEscape();
2966
3139
  methods.push('escape');
2967
3140
  await new Promise((resolve) => setTimeout(resolve, 220));
@@ -2975,18 +3148,15 @@ export class BossChatPage {
2975
3148
  };
2976
3149
  }
2977
3150
 
2978
- if (midState?.closeRect) {
2979
- await this.clickRect(midState.closeRect);
2980
- methods.push('rect-close');
2981
- await new Promise((resolve) => setTimeout(resolve, 220));
2982
- midState = await this.getCandidateDetailState();
2983
- if (!drawerOpen(midState)) {
2984
- return {
2985
- closed: true,
2986
- method: methods.join('+'),
2987
- finalState: midState,
2988
- };
2989
- }
3151
+ const outsideResult = await this.clickCandidateDetailOutside();
3152
+ methods.push(outsideResult.method || 'outside-click:unknown');
3153
+ midState = outsideResult.finalState || await this.getCandidateDetailState();
3154
+ if (!drawerOpen(midState)) {
3155
+ return {
3156
+ closed: true,
3157
+ method: methods.join('+'),
3158
+ finalState: midState,
3159
+ };
2990
3160
  }
2991
3161
 
2992
3162
  if (ensureDismiss && index >= 1) {