@reconcrap/boss-recommend-mcp 2.0.22 → 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 +1 -1
- package/src/domains/chat/detail.js +159 -39
package/package.json
CHANGED
|
@@ -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
|
}
|
|
@@ -336,6 +363,53 @@ function isRequestedResumeText(text = "") {
|
|
|
336
363
|
);
|
|
337
364
|
}
|
|
338
365
|
|
|
366
|
+
function isResumeRequestSentMessageText(text = "") {
|
|
367
|
+
const normalized = normalizeDetailText(text);
|
|
368
|
+
return Boolean(
|
|
369
|
+
normalized.includes("简历请求已发送")
|
|
370
|
+
|| normalized.includes("已发送简历")
|
|
371
|
+
|| normalized.includes("已求简历")
|
|
372
|
+
|| normalized.includes("已索要简历")
|
|
373
|
+
|| normalized.includes("已发送")
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function countTextOccurrences(text = "", needle = "") {
|
|
378
|
+
if (!needle) return 0;
|
|
379
|
+
let count = 0;
|
|
380
|
+
let index = 0;
|
|
381
|
+
while (index < text.length) {
|
|
382
|
+
const found = text.indexOf(needle, index);
|
|
383
|
+
if (found < 0) break;
|
|
384
|
+
count += 1;
|
|
385
|
+
index = found + needle.length;
|
|
386
|
+
}
|
|
387
|
+
return count;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function countResumeRequestSentMessageMarkers(lines = []) {
|
|
391
|
+
const markers = ["简历请求已发送", "已发送简历", "已求简历", "已索要简历", "已发送"];
|
|
392
|
+
return lines.reduce((total, line) => (
|
|
393
|
+
total + markers.reduce((lineTotal, marker) => (
|
|
394
|
+
lineTotal + countTextOccurrences(line, marker)
|
|
395
|
+
), 0)
|
|
396
|
+
), 0);
|
|
397
|
+
}
|
|
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
|
+
|
|
339
413
|
function isRequestedResumeControlTarget(target = {}) {
|
|
340
414
|
const label = normalizeDetailText(target.label);
|
|
341
415
|
const className = String(target.attributes?.class || "");
|
|
@@ -369,7 +443,6 @@ function isConfirmText(text = "") {
|
|
|
369
443
|
normalized === "确定"
|
|
370
444
|
|| normalized === "确认"
|
|
371
445
|
|| normalized === "提交"
|
|
372
|
-
|| normalized === "发送"
|
|
373
446
|
|| normalized === "继续"
|
|
374
447
|
|| normalized.includes("确定")
|
|
375
448
|
|| normalized.includes("确认")
|
|
@@ -435,6 +508,35 @@ async function findVisibleMatchingTarget(client, roots, selectors, predicate) {
|
|
|
435
508
|
return null;
|
|
436
509
|
}
|
|
437
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
|
+
|
|
438
540
|
export async function selectChatPrimaryLabel(client, {
|
|
439
541
|
label = "全部",
|
|
440
542
|
timeoutMs = 8000,
|
|
@@ -852,39 +954,53 @@ export async function openChatOnlineResume(client, {
|
|
|
852
954
|
|
|
853
955
|
export async function readChatConversationReadyState(client) {
|
|
854
956
|
const rootState = await getChatRoots(client);
|
|
855
|
-
const
|
|
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(
|
|
856
964
|
client,
|
|
857
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,
|
|
858
974
|
CHAT_ONLINE_RESUME_BUTTON_SELECTORS,
|
|
859
975
|
(target) => target.label.includes("在线简历") && !target.disabled
|
|
860
976
|
);
|
|
861
977
|
const attachmentResume = await findVisibleMatchingTarget(
|
|
862
978
|
client,
|
|
863
|
-
|
|
979
|
+
controlRoots,
|
|
864
980
|
CHAT_ATTACHMENT_RESUME_BUTTON_SELECTORS,
|
|
865
981
|
(target) => isAttachmentResumeText(target.label)
|
|
866
982
|
);
|
|
867
983
|
const askResume = await findVisibleMatchingTarget(
|
|
868
984
|
client,
|
|
869
|
-
|
|
985
|
+
controlRoots,
|
|
870
986
|
CHAT_ASK_RESUME_BUTTON_SELECTORS,
|
|
871
987
|
(target) => isAskResumeText(target.label) && !isAttachmentResumeTarget(target)
|
|
872
988
|
);
|
|
873
989
|
const requestedResume = await findVisibleMatchingTarget(
|
|
874
990
|
client,
|
|
875
|
-
|
|
991
|
+
requestedRoots,
|
|
876
992
|
CHAT_ASK_RESUME_BUTTON_SELECTORS,
|
|
877
993
|
(target) => isRequestedResumeControlTarget(target)
|
|
878
994
|
);
|
|
879
995
|
const editor = await findVisibleMatchingTarget(
|
|
880
996
|
client,
|
|
881
|
-
|
|
997
|
+
controlRoots,
|
|
882
998
|
CHAT_EDITOR_SELECTORS,
|
|
883
999
|
() => true
|
|
884
1000
|
);
|
|
885
1001
|
const sendButton = await findVisibleMatchingTarget(
|
|
886
1002
|
client,
|
|
887
|
-
|
|
1003
|
+
controlRoots,
|
|
888
1004
|
CHAT_SEND_BUTTON_SELECTORS,
|
|
889
1005
|
(target) => isSendText(target.label) || /submit/i.test(String(target.attributes?.class || ""))
|
|
890
1006
|
);
|
|
@@ -1034,13 +1150,6 @@ export async function clickChatAskResume(client, {
|
|
|
1034
1150
|
control: state.attachment_resume
|
|
1035
1151
|
};
|
|
1036
1152
|
}
|
|
1037
|
-
if (state.already_requested_resume) {
|
|
1038
|
-
return {
|
|
1039
|
-
ok: true,
|
|
1040
|
-
already_requested: true,
|
|
1041
|
-
control: state.requested_resume
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
1153
|
if (state.ask_resume?.node_id && !state.ask_resume.disabled) {
|
|
1045
1154
|
try {
|
|
1046
1155
|
if (state.ask_resume.center) {
|
|
@@ -1063,6 +1172,13 @@ export async function clickChatAskResume(client, {
|
|
|
1063
1172
|
};
|
|
1064
1173
|
}
|
|
1065
1174
|
}
|
|
1175
|
+
if (state.already_requested_resume) {
|
|
1176
|
+
return {
|
|
1177
|
+
ok: true,
|
|
1178
|
+
already_requested: true,
|
|
1179
|
+
control: state.requested_resume
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1066
1182
|
await sleep(250);
|
|
1067
1183
|
}
|
|
1068
1184
|
return {
|
|
@@ -1078,19 +1194,18 @@ export async function clickChatConfirmRequestResume(client, {
|
|
|
1078
1194
|
} = {}) {
|
|
1079
1195
|
const started = Date.now();
|
|
1080
1196
|
let lastTarget = null;
|
|
1197
|
+
let lastState = null;
|
|
1081
1198
|
while (Date.now() - started <= timeoutMs) {
|
|
1082
|
-
|
|
1083
|
-
if (state.already_requested_resume) {
|
|
1084
|
-
return {
|
|
1085
|
-
confirmed: true,
|
|
1086
|
-
assumed_requested: true,
|
|
1087
|
-
state
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1199
|
+
lastState = await readChatConversationReadyState(client);
|
|
1090
1200
|
const rootState = await getChatRoots(client);
|
|
1091
|
-
const
|
|
1201
|
+
const confirmRoots = await resolveScopedRoots(
|
|
1092
1202
|
client,
|
|
1093
1203
|
rootState.roots,
|
|
1204
|
+
CHAT_CONVERSATION_CONTROL_SCOPE_SELECTORS
|
|
1205
|
+
);
|
|
1206
|
+
const target = await findVisibleMatchingTarget(
|
|
1207
|
+
client,
|
|
1208
|
+
confirmRoots,
|
|
1094
1209
|
CHAT_CONFIRM_REQUEST_RESUME_SELECTORS,
|
|
1095
1210
|
(item) => isConfirmText(item.label) && !item.disabled
|
|
1096
1211
|
);
|
|
@@ -1124,7 +1239,8 @@ export async function clickChatConfirmRequestResume(client, {
|
|
|
1124
1239
|
return {
|
|
1125
1240
|
confirmed: false,
|
|
1126
1241
|
error: "CONFIRM_BUTTON_NOT_FOUND",
|
|
1127
|
-
control: lastTarget
|
|
1242
|
+
control: lastTarget,
|
|
1243
|
+
state: lastState
|
|
1128
1244
|
};
|
|
1129
1245
|
}
|
|
1130
1246
|
|
|
@@ -1151,18 +1267,26 @@ export async function getChatResumeRequestMessageState(client) {
|
|
|
1151
1267
|
text = htmlToText(await getOuterHTML(client, nodeId));
|
|
1152
1268
|
} catch {}
|
|
1153
1269
|
const lines = text.split(/\r?\n/).map(normalizeDetailText).filter(Boolean);
|
|
1154
|
-
const matching = lines.filter((line) => line
|
|
1270
|
+
const matching = lines.filter((line) => isResumeRequestSentMessageText(line));
|
|
1271
|
+
const attachmentMatching = lines.filter((line) => isResumeAttachmentMessageText(line));
|
|
1272
|
+
const count = countResumeRequestSentMessageMarkers(lines);
|
|
1273
|
+
const resumeAttachmentCount = countResumeAttachmentMessageMarkers(lines);
|
|
1155
1274
|
return {
|
|
1156
1275
|
ok: Boolean(text),
|
|
1157
1276
|
selector: messageRoot?.selector || "top",
|
|
1158
|
-
count
|
|
1277
|
+
count,
|
|
1278
|
+
resume_attachment_count: resumeAttachmentCount,
|
|
1279
|
+
success_count: count + resumeAttachmentCount,
|
|
1159
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] || "",
|
|
1160
1283
|
recent: lines.slice(-12)
|
|
1161
1284
|
};
|
|
1162
1285
|
}
|
|
1163
1286
|
|
|
1164
1287
|
export async function waitForChatResumeRequestMessage(client, {
|
|
1165
1288
|
baselineCount = 0,
|
|
1289
|
+
baselineResumeAttachmentCount = 0,
|
|
1166
1290
|
timeoutMs = 6500,
|
|
1167
1291
|
intervalMs = 260
|
|
1168
1292
|
} = {}) {
|
|
@@ -1170,9 +1294,10 @@ export async function waitForChatResumeRequestMessage(client, {
|
|
|
1170
1294
|
let state = null;
|
|
1171
1295
|
while (Date.now() - started <= timeoutMs) {
|
|
1172
1296
|
state = await getChatResumeRequestMessageState(client);
|
|
1173
|
-
const observed =
|
|
1174
|
-
|
|
1175
|
-
|| state.
|
|
1297
|
+
const observed = (
|
|
1298
|
+
state.count > baselineCount
|
|
1299
|
+
|| state.resume_attachment_count > baselineResumeAttachmentCount
|
|
1300
|
+
);
|
|
1176
1301
|
if (observed) {
|
|
1177
1302
|
return {
|
|
1178
1303
|
observed: true,
|
|
@@ -1204,14 +1329,6 @@ export async function requestChatResumeForPassedCandidate(client, {
|
|
|
1204
1329
|
initial_state: initialState
|
|
1205
1330
|
};
|
|
1206
1331
|
}
|
|
1207
|
-
if (initialState.already_requested_resume) {
|
|
1208
|
-
return {
|
|
1209
|
-
requested: true,
|
|
1210
|
-
skipped: true,
|
|
1211
|
-
reason: "resume_already_requested",
|
|
1212
|
-
initial_state: initialState
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
1332
|
if (dryRun) {
|
|
1216
1333
|
return {
|
|
1217
1334
|
requested: false,
|
|
@@ -1281,7 +1398,8 @@ export async function requestChatResumeForPassedCandidate(client, {
|
|
|
1281
1398
|
confirmResult = await clickChatConfirmRequestResume(client);
|
|
1282
1399
|
}
|
|
1283
1400
|
const messageCheck = await waitForChatResumeRequestMessage(client, {
|
|
1284
|
-
baselineCount: before.count
|
|
1401
|
+
baselineCount: before.count,
|
|
1402
|
+
baselineResumeAttachmentCount: before.resume_attachment_count
|
|
1285
1403
|
});
|
|
1286
1404
|
const messageObserved = Boolean(messageCheck.observed);
|
|
1287
1405
|
attempts.push({
|
|
@@ -1290,8 +1408,10 @@ export async function requestChatResumeForPassedCandidate(client, {
|
|
|
1290
1408
|
confirm_result: confirmResult,
|
|
1291
1409
|
message_before_count: before.count,
|
|
1292
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,
|
|
1293
1413
|
message_observed: messageObserved,
|
|
1294
|
-
message_last_text: messageCheck.state?.last_text || ""
|
|
1414
|
+
message_last_text: messageCheck.state?.last_success_text || messageCheck.state?.last_text || ""
|
|
1295
1415
|
});
|
|
1296
1416
|
if (messageObserved) {
|
|
1297
1417
|
return {
|