@reconcrap/boss-recommend-mcp 2.0.23 → 2.0.25
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 -21
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
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
997
|
+
controlRoots,
|
|
915
998
|
CHAT_EDITOR_SELECTORS,
|
|
916
999
|
() => true
|
|
917
1000
|
);
|
|
918
1001
|
const sendButton = await findVisibleMatchingTarget(
|
|
919
1002
|
client,
|
|
920
|
-
|
|
1003
|
+
controlRoots,
|
|
921
1004
|
CHAT_SEND_BUTTON_SELECTORS,
|
|
922
1005
|
(target) => isSendText(target.label) || /submit/i.test(String(target.attributes?.class || ""))
|
|
923
1006
|
);
|
|
@@ -1056,6 +1139,7 @@ export async function clickChatAskResume(client, {
|
|
|
1056
1139
|
} = {}) {
|
|
1057
1140
|
const started = Date.now();
|
|
1058
1141
|
let lastState = null;
|
|
1142
|
+
let lastDisabledAskResume = null;
|
|
1059
1143
|
while (Date.now() - started <= timeoutMs) {
|
|
1060
1144
|
const state = await readChatConversationReadyState(client);
|
|
1061
1145
|
lastState = state;
|
|
@@ -1089,6 +1173,9 @@ export async function clickChatAskResume(client, {
|
|
|
1089
1173
|
};
|
|
1090
1174
|
}
|
|
1091
1175
|
}
|
|
1176
|
+
if (state.ask_resume?.node_id && state.ask_resume.disabled) {
|
|
1177
|
+
lastDisabledAskResume = state.ask_resume;
|
|
1178
|
+
}
|
|
1092
1179
|
if (state.already_requested_resume) {
|
|
1093
1180
|
return {
|
|
1094
1181
|
ok: true,
|
|
@@ -1098,6 +1185,16 @@ export async function clickChatAskResume(client, {
|
|
|
1098
1185
|
}
|
|
1099
1186
|
await sleep(250);
|
|
1100
1187
|
}
|
|
1188
|
+
if (lastDisabledAskResume) {
|
|
1189
|
+
return {
|
|
1190
|
+
ok: false,
|
|
1191
|
+
already_requested: true,
|
|
1192
|
+
request_pending: true,
|
|
1193
|
+
error: "ASK_RESUME_BUTTON_DISABLED",
|
|
1194
|
+
control: lastDisabledAskResume,
|
|
1195
|
+
state: lastState
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1101
1198
|
return {
|
|
1102
1199
|
ok: false,
|
|
1103
1200
|
error: "ASK_RESUME_BUTTON_NOT_FOUND",
|
|
@@ -1111,19 +1208,18 @@ export async function clickChatConfirmRequestResume(client, {
|
|
|
1111
1208
|
} = {}) {
|
|
1112
1209
|
const started = Date.now();
|
|
1113
1210
|
let lastTarget = null;
|
|
1211
|
+
let lastState = null;
|
|
1114
1212
|
while (Date.now() - started <= timeoutMs) {
|
|
1115
|
-
|
|
1116
|
-
if (state.already_requested_resume) {
|
|
1117
|
-
return {
|
|
1118
|
-
confirmed: true,
|
|
1119
|
-
assumed_requested: true,
|
|
1120
|
-
state
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1213
|
+
lastState = await readChatConversationReadyState(client);
|
|
1123
1214
|
const rootState = await getChatRoots(client);
|
|
1124
|
-
const
|
|
1215
|
+
const confirmRoots = await resolveScopedRoots(
|
|
1125
1216
|
client,
|
|
1126
1217
|
rootState.roots,
|
|
1218
|
+
CHAT_CONVERSATION_CONTROL_SCOPE_SELECTORS
|
|
1219
|
+
);
|
|
1220
|
+
const target = await findVisibleMatchingTarget(
|
|
1221
|
+
client,
|
|
1222
|
+
confirmRoots,
|
|
1127
1223
|
CHAT_CONFIRM_REQUEST_RESUME_SELECTORS,
|
|
1128
1224
|
(item) => isConfirmText(item.label) && !item.disabled
|
|
1129
1225
|
);
|
|
@@ -1157,7 +1253,8 @@ export async function clickChatConfirmRequestResume(client, {
|
|
|
1157
1253
|
return {
|
|
1158
1254
|
confirmed: false,
|
|
1159
1255
|
error: "CONFIRM_BUTTON_NOT_FOUND",
|
|
1160
|
-
control: lastTarget
|
|
1256
|
+
control: lastTarget,
|
|
1257
|
+
state: lastState
|
|
1161
1258
|
};
|
|
1162
1259
|
}
|
|
1163
1260
|
|
|
@@ -1185,18 +1282,25 @@ export async function getChatResumeRequestMessageState(client) {
|
|
|
1185
1282
|
} catch {}
|
|
1186
1283
|
const lines = text.split(/\r?\n/).map(normalizeDetailText).filter(Boolean);
|
|
1187
1284
|
const matching = lines.filter((line) => isResumeRequestSentMessageText(line));
|
|
1285
|
+
const attachmentMatching = lines.filter((line) => isResumeAttachmentMessageText(line));
|
|
1188
1286
|
const count = countResumeRequestSentMessageMarkers(lines);
|
|
1287
|
+
const resumeAttachmentCount = countResumeAttachmentMessageMarkers(lines);
|
|
1189
1288
|
return {
|
|
1190
1289
|
ok: Boolean(text),
|
|
1191
1290
|
selector: messageRoot?.selector || "top",
|
|
1192
1291
|
count,
|
|
1292
|
+
resume_attachment_count: resumeAttachmentCount,
|
|
1293
|
+
success_count: count + resumeAttachmentCount,
|
|
1193
1294
|
last_text: matching[matching.length - 1] || lines[lines.length - 1] || "",
|
|
1295
|
+
last_resume_attachment_text: attachmentMatching[attachmentMatching.length - 1] || "",
|
|
1296
|
+
last_success_text: matching[matching.length - 1] || attachmentMatching[attachmentMatching.length - 1] || "",
|
|
1194
1297
|
recent: lines.slice(-12)
|
|
1195
1298
|
};
|
|
1196
1299
|
}
|
|
1197
1300
|
|
|
1198
1301
|
export async function waitForChatResumeRequestMessage(client, {
|
|
1199
1302
|
baselineCount = 0,
|
|
1303
|
+
baselineResumeAttachmentCount = 0,
|
|
1200
1304
|
timeoutMs = 6500,
|
|
1201
1305
|
intervalMs = 260
|
|
1202
1306
|
} = {}) {
|
|
@@ -1204,7 +1308,10 @@ export async function waitForChatResumeRequestMessage(client, {
|
|
|
1204
1308
|
let state = null;
|
|
1205
1309
|
while (Date.now() - started <= timeoutMs) {
|
|
1206
1310
|
state = await getChatResumeRequestMessageState(client);
|
|
1207
|
-
const observed =
|
|
1311
|
+
const observed = (
|
|
1312
|
+
state.count > baselineCount
|
|
1313
|
+
|| state.resume_attachment_count > baselineResumeAttachmentCount
|
|
1314
|
+
);
|
|
1208
1315
|
if (observed) {
|
|
1209
1316
|
return {
|
|
1210
1317
|
observed: true,
|
|
@@ -1224,6 +1331,7 @@ export async function waitForChatResumeRequestMessage(client, {
|
|
|
1224
1331
|
export async function requestChatResumeForPassedCandidate(client, {
|
|
1225
1332
|
greetingText = "Hi同学,能麻烦发下简历吗?",
|
|
1226
1333
|
maxAttempts = 3,
|
|
1334
|
+
askResumeTimeoutMs = 8000,
|
|
1227
1335
|
dryRun = false
|
|
1228
1336
|
} = {}) {
|
|
1229
1337
|
const effectiveGreetingText = normalizeDetailText(greetingText) || "Hi同学,能麻烦发下简历吗?";
|
|
@@ -1269,13 +1377,17 @@ export async function requestChatResumeForPassedCandidate(client, {
|
|
|
1269
1377
|
const attempts = [];
|
|
1270
1378
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
1271
1379
|
const before = await getChatResumeRequestMessageState(client);
|
|
1272
|
-
const askResult = await clickChatAskResume(client
|
|
1380
|
+
const askResult = await clickChatAskResume(client, {
|
|
1381
|
+
timeoutMs: askResumeTimeoutMs
|
|
1382
|
+
});
|
|
1273
1383
|
let confirmResult = {
|
|
1274
1384
|
confirmed: false,
|
|
1275
1385
|
assumed_requested: Boolean(askResult.already_requested),
|
|
1276
1386
|
skipped: true,
|
|
1277
1387
|
reason: askResult.attachment_resume_available
|
|
1278
1388
|
? "attachment_resume_already_available"
|
|
1389
|
+
: askResult.request_pending
|
|
1390
|
+
? "resume_request_already_pending"
|
|
1279
1391
|
: askResult.ok
|
|
1280
1392
|
? "already_requested"
|
|
1281
1393
|
: (askResult.error || "ask_resume_not_clicked")
|
|
@@ -1301,11 +1413,35 @@ export async function requestChatResumeForPassedCandidate(client, {
|
|
|
1301
1413
|
attempts
|
|
1302
1414
|
};
|
|
1303
1415
|
}
|
|
1416
|
+
if (askResult.request_pending || askResult.already_requested) {
|
|
1417
|
+
attempts.push({
|
|
1418
|
+
attempt: attempt + 1,
|
|
1419
|
+
ask_result: askResult,
|
|
1420
|
+
confirm_result: confirmResult,
|
|
1421
|
+
message_before_count: before.count,
|
|
1422
|
+
message_after_count: before.count,
|
|
1423
|
+
resume_attachment_before_count: before.resume_attachment_count || 0,
|
|
1424
|
+
resume_attachment_after_count: before.resume_attachment_count || 0,
|
|
1425
|
+
message_observed: false,
|
|
1426
|
+
message_last_text: before.last_success_text || before.last_text || ""
|
|
1427
|
+
});
|
|
1428
|
+
return {
|
|
1429
|
+
requested: false,
|
|
1430
|
+
skipped: true,
|
|
1431
|
+
reason: "resume_request_already_pending",
|
|
1432
|
+
initial_state: initialState,
|
|
1433
|
+
close_before_greeting: closeBeforeGreeting,
|
|
1434
|
+
greeting_sent: true,
|
|
1435
|
+
greeting_send_result: sendResult,
|
|
1436
|
+
attempts
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1304
1439
|
if (askResult.ok && !askResult.already_requested) {
|
|
1305
1440
|
confirmResult = await clickChatConfirmRequestResume(client);
|
|
1306
1441
|
}
|
|
1307
1442
|
const messageCheck = await waitForChatResumeRequestMessage(client, {
|
|
1308
|
-
baselineCount: before.count
|
|
1443
|
+
baselineCount: before.count,
|
|
1444
|
+
baselineResumeAttachmentCount: before.resume_attachment_count
|
|
1309
1445
|
});
|
|
1310
1446
|
const messageObserved = Boolean(messageCheck.observed);
|
|
1311
1447
|
attempts.push({
|
|
@@ -1314,8 +1450,10 @@ export async function requestChatResumeForPassedCandidate(client, {
|
|
|
1314
1450
|
confirm_result: confirmResult,
|
|
1315
1451
|
message_before_count: before.count,
|
|
1316
1452
|
message_after_count: messageCheck.state?.count || 0,
|
|
1453
|
+
resume_attachment_before_count: before.resume_attachment_count || 0,
|
|
1454
|
+
resume_attachment_after_count: messageCheck.state?.resume_attachment_count || 0,
|
|
1317
1455
|
message_observed: messageObserved,
|
|
1318
|
-
message_last_text: messageCheck.state?.last_text || ""
|
|
1456
|
+
message_last_text: messageCheck.state?.last_success_text || messageCheck.state?.last_text || ""
|
|
1319
1457
|
});
|
|
1320
1458
|
if (messageObserved) {
|
|
1321
1459
|
return {
|