@reconcrap/boss-recommend-mcp 2.0.23 → 2.0.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.23",
3
+ "version": "2.0.24",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -46,6 +46,33 @@ import {
46
46
 
47
47
  export const CHAT_UNSAFE_ONLINE_RESUME_LINK_CODE = "CHAT_UNSAFE_ONLINE_RESUME_LINK";
48
48
 
49
+ const CHAT_CONVERSATION_CONTROL_SCOPE_SELECTORS = Object.freeze([
50
+ ".conversation-main",
51
+ ".conversation-editor",
52
+ ".chat-message-list",
53
+ ".toolbar-box-right",
54
+ ".operate-exchange-left",
55
+ ".operate-icon-item",
56
+ ".exchange-tooltip",
57
+ ".boss-popup__wrapper",
58
+ ".boss-dialog",
59
+ ".dialog-wrap.active",
60
+ ".geek-detail-modal"
61
+ ]);
62
+
63
+ const CHAT_REQUESTED_RESUME_SCOPE_SELECTORS = Object.freeze([
64
+ ".chat-message-list",
65
+ ".conversation-editor",
66
+ ".conversation-main",
67
+ ".toolbar-box-right",
68
+ ".operate-exchange-left",
69
+ ".operate-icon-item",
70
+ ".exchange-tooltip",
71
+ ".boss-popup__wrapper",
72
+ ".boss-dialog",
73
+ ".dialog-wrap.active"
74
+ ]);
75
+
49
76
  export function matchesChatProfileNetwork(url) {
50
77
  return CHAT_PROFILE_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
51
78
  }
@@ -369,6 +396,20 @@ function countResumeRequestSentMessageMarkers(lines = []) {
369
396
  ), 0);
370
397
  }
371
398
 
399
+ function isResumeAttachmentMessageText(text = "") {
400
+ const normalized = normalizeDetailText(text);
401
+ return Boolean(
402
+ /点击.*附件简历/.test(normalized)
403
+ || /预览附件简历/.test(normalized)
404
+ || /查看附件简历/.test(normalized)
405
+ || /(?:简历|resume)[^\s]*\.(?:pdf|docx?|jpg|jpeg|png)\b/i.test(normalized)
406
+ );
407
+ }
408
+
409
+ function countResumeAttachmentMessageMarkers(lines = []) {
410
+ return lines.reduce((total, line) => total + (isResumeAttachmentMessageText(line) ? 1 : 0), 0);
411
+ }
412
+
372
413
  function isRequestedResumeControlTarget(target = {}) {
373
414
  const label = normalizeDetailText(target.label);
374
415
  const className = String(target.attributes?.class || "");
@@ -402,7 +443,6 @@ function isConfirmText(text = "") {
402
443
  normalized === "确定"
403
444
  || normalized === "确认"
404
445
  || normalized === "提交"
405
- || normalized === "发送"
406
446
  || normalized === "继续"
407
447
  || normalized.includes("确定")
408
448
  || normalized.includes("确认")
@@ -468,6 +508,35 @@ async function findVisibleMatchingTarget(client, roots, selectors, predicate) {
468
508
  return null;
469
509
  }
470
510
 
511
+ async function resolveScopedRoots(client, roots = [], selectors = [], {
512
+ fallbackToRoots = true
513
+ } = {}) {
514
+ const scoped = [];
515
+ const seen = new Set();
516
+ for (const root of roots) {
517
+ if (!root?.nodeId) continue;
518
+ for (const selector of selectors) {
519
+ let nodeIds = [];
520
+ try {
521
+ nodeIds = await querySelectorAll(client, root.nodeId, selector);
522
+ } catch {
523
+ nodeIds = [];
524
+ }
525
+ for (const nodeId of nodeIds) {
526
+ const key = `${root.name}:${nodeId}`;
527
+ if (seen.has(key)) continue;
528
+ seen.add(key);
529
+ scoped.push({
530
+ name: `${root.name}:${selector}`,
531
+ nodeId
532
+ });
533
+ }
534
+ }
535
+ }
536
+ if (scoped.length || !fallbackToRoots) return scoped;
537
+ return roots;
538
+ }
539
+
471
540
  export async function selectChatPrimaryLabel(client, {
472
541
  label = "全部",
473
542
  timeoutMs = 8000,
@@ -885,39 +954,53 @@ export async function openChatOnlineResume(client, {
885
954
 
886
955
  export async function readChatConversationReadyState(client) {
887
956
  const rootState = await getChatRoots(client);
888
- const onlineResume = await findVisibleMatchingTarget(
957
+ const scopedControlRoots = await resolveScopedRoots(
958
+ client,
959
+ rootState.roots,
960
+ CHAT_CONVERSATION_CONTROL_SCOPE_SELECTORS,
961
+ { fallbackToRoots: false }
962
+ );
963
+ const scopedRequestedRoots = await resolveScopedRoots(
889
964
  client,
890
965
  rootState.roots,
966
+ CHAT_REQUESTED_RESUME_SCOPE_SELECTORS,
967
+ { fallbackToRoots: false }
968
+ );
969
+ const controlRoots = scopedControlRoots.length ? scopedControlRoots : rootState.roots;
970
+ const requestedRoots = scopedRequestedRoots.length ? scopedRequestedRoots : rootState.roots;
971
+ const onlineResume = await findVisibleMatchingTarget(
972
+ client,
973
+ controlRoots,
891
974
  CHAT_ONLINE_RESUME_BUTTON_SELECTORS,
892
975
  (target) => target.label.includes("在线简历") && !target.disabled
893
976
  );
894
977
  const attachmentResume = await findVisibleMatchingTarget(
895
978
  client,
896
- rootState.roots,
979
+ controlRoots,
897
980
  CHAT_ATTACHMENT_RESUME_BUTTON_SELECTORS,
898
981
  (target) => isAttachmentResumeText(target.label)
899
982
  );
900
983
  const askResume = await findVisibleMatchingTarget(
901
984
  client,
902
- rootState.roots,
985
+ controlRoots,
903
986
  CHAT_ASK_RESUME_BUTTON_SELECTORS,
904
987
  (target) => isAskResumeText(target.label) && !isAttachmentResumeTarget(target)
905
988
  );
906
989
  const requestedResume = await findVisibleMatchingTarget(
907
990
  client,
908
- rootState.roots,
991
+ requestedRoots,
909
992
  CHAT_ASK_RESUME_BUTTON_SELECTORS,
910
993
  (target) => isRequestedResumeControlTarget(target)
911
994
  );
912
995
  const editor = await findVisibleMatchingTarget(
913
996
  client,
914
- rootState.roots,
997
+ controlRoots,
915
998
  CHAT_EDITOR_SELECTORS,
916
999
  () => true
917
1000
  );
918
1001
  const sendButton = await findVisibleMatchingTarget(
919
1002
  client,
920
- rootState.roots,
1003
+ controlRoots,
921
1004
  CHAT_SEND_BUTTON_SELECTORS,
922
1005
  (target) => isSendText(target.label) || /submit/i.test(String(target.attributes?.class || ""))
923
1006
  );
@@ -1111,19 +1194,18 @@ export async function clickChatConfirmRequestResume(client, {
1111
1194
  } = {}) {
1112
1195
  const started = Date.now();
1113
1196
  let lastTarget = null;
1197
+ let lastState = null;
1114
1198
  while (Date.now() - started <= timeoutMs) {
1115
- const state = await readChatConversationReadyState(client);
1116
- if (state.already_requested_resume) {
1117
- return {
1118
- confirmed: true,
1119
- assumed_requested: true,
1120
- state
1121
- };
1122
- }
1199
+ lastState = await readChatConversationReadyState(client);
1123
1200
  const rootState = await getChatRoots(client);
1124
- const target = await findVisibleMatchingTarget(
1201
+ const confirmRoots = await resolveScopedRoots(
1125
1202
  client,
1126
1203
  rootState.roots,
1204
+ CHAT_CONVERSATION_CONTROL_SCOPE_SELECTORS
1205
+ );
1206
+ const target = await findVisibleMatchingTarget(
1207
+ client,
1208
+ confirmRoots,
1127
1209
  CHAT_CONFIRM_REQUEST_RESUME_SELECTORS,
1128
1210
  (item) => isConfirmText(item.label) && !item.disabled
1129
1211
  );
@@ -1157,7 +1239,8 @@ export async function clickChatConfirmRequestResume(client, {
1157
1239
  return {
1158
1240
  confirmed: false,
1159
1241
  error: "CONFIRM_BUTTON_NOT_FOUND",
1160
- control: lastTarget
1242
+ control: lastTarget,
1243
+ state: lastState
1161
1244
  };
1162
1245
  }
1163
1246
 
@@ -1185,18 +1268,25 @@ export async function getChatResumeRequestMessageState(client) {
1185
1268
  } catch {}
1186
1269
  const lines = text.split(/\r?\n/).map(normalizeDetailText).filter(Boolean);
1187
1270
  const matching = lines.filter((line) => isResumeRequestSentMessageText(line));
1271
+ const attachmentMatching = lines.filter((line) => isResumeAttachmentMessageText(line));
1188
1272
  const count = countResumeRequestSentMessageMarkers(lines);
1273
+ const resumeAttachmentCount = countResumeAttachmentMessageMarkers(lines);
1189
1274
  return {
1190
1275
  ok: Boolean(text),
1191
1276
  selector: messageRoot?.selector || "top",
1192
1277
  count,
1278
+ resume_attachment_count: resumeAttachmentCount,
1279
+ success_count: count + resumeAttachmentCount,
1193
1280
  last_text: matching[matching.length - 1] || lines[lines.length - 1] || "",
1281
+ last_resume_attachment_text: attachmentMatching[attachmentMatching.length - 1] || "",
1282
+ last_success_text: matching[matching.length - 1] || attachmentMatching[attachmentMatching.length - 1] || "",
1194
1283
  recent: lines.slice(-12)
1195
1284
  };
1196
1285
  }
1197
1286
 
1198
1287
  export async function waitForChatResumeRequestMessage(client, {
1199
1288
  baselineCount = 0,
1289
+ baselineResumeAttachmentCount = 0,
1200
1290
  timeoutMs = 6500,
1201
1291
  intervalMs = 260
1202
1292
  } = {}) {
@@ -1204,7 +1294,10 @@ export async function waitForChatResumeRequestMessage(client, {
1204
1294
  let state = null;
1205
1295
  while (Date.now() - started <= timeoutMs) {
1206
1296
  state = await getChatResumeRequestMessageState(client);
1207
- const observed = state.count > baselineCount;
1297
+ const observed = (
1298
+ state.count > baselineCount
1299
+ || state.resume_attachment_count > baselineResumeAttachmentCount
1300
+ );
1208
1301
  if (observed) {
1209
1302
  return {
1210
1303
  observed: true,
@@ -1305,7 +1398,8 @@ export async function requestChatResumeForPassedCandidate(client, {
1305
1398
  confirmResult = await clickChatConfirmRequestResume(client);
1306
1399
  }
1307
1400
  const messageCheck = await waitForChatResumeRequestMessage(client, {
1308
- baselineCount: before.count
1401
+ baselineCount: before.count,
1402
+ baselineResumeAttachmentCount: before.resume_attachment_count
1309
1403
  });
1310
1404
  const messageObserved = Boolean(messageCheck.observed);
1311
1405
  attempts.push({
@@ -1314,8 +1408,10 @@ export async function requestChatResumeForPassedCandidate(client, {
1314
1408
  confirm_result: confirmResult,
1315
1409
  message_before_count: before.count,
1316
1410
  message_after_count: messageCheck.state?.count || 0,
1411
+ resume_attachment_before_count: before.resume_attachment_count || 0,
1412
+ resume_attachment_after_count: messageCheck.state?.resume_attachment_count || 0,
1317
1413
  message_observed: messageObserved,
1318
- message_last_text: messageCheck.state?.last_text || ""
1414
+ message_last_text: messageCheck.state?.last_success_text || messageCheck.state?.last_text || ""
1319
1415
  });
1320
1416
  if (messageObserved) {
1321
1417
  return {