@reconcrap/boss-recommend-mcp 2.1.15 → 2.1.16
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/README.md +29 -0
- package/package.json +8 -7
- package/src/chat-mcp.js +57 -15
- package/src/core/greet-quota/index.js +17 -0
- package/src/core/reporting/legacy-csv.js +5 -1
- package/src/domains/recommend/colleague-contact.js +333 -0
- package/src/domains/recommend/index.js +1 -0
- package/src/domains/recommend/run-service.js +166 -77
- package/src/domains/recruit/constants.js +4 -0
- package/src/domains/recruit/instruction-parser.js +42 -1
- package/src/domains/recruit/run-service.js +42 -4
- package/src/domains/recruit/search.js +40 -6
- package/src/index.js +20 -11
- package/src/parser.js +45 -2
- package/src/recommend-mcp.js +15 -10
- package/src/recruit-mcp.js +8 -0
|
@@ -68,10 +68,11 @@ import {
|
|
|
68
68
|
refreshRecommendListAtEnd
|
|
69
69
|
} from "./refresh.js";
|
|
70
70
|
import { selectRecommendJob } from "./jobs.js";
|
|
71
|
-
import {
|
|
72
|
-
normalizeRecommendPageScope,
|
|
73
|
-
selectRecommendPageScope
|
|
74
|
-
} from "./scopes.js";
|
|
71
|
+
import {
|
|
72
|
+
normalizeRecommendPageScope,
|
|
73
|
+
selectRecommendPageScope
|
|
74
|
+
} from "./scopes.js";
|
|
75
|
+
import { inspectRecentColleagueContact } from "./colleague-contact.js";
|
|
75
76
|
import {
|
|
76
77
|
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
77
78
|
RECOMMEND_CARD_SELECTOR,
|
|
@@ -224,19 +225,20 @@ function compactCandidate(candidate) {
|
|
|
224
225
|
};
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
function compactDetail(detailResult) {
|
|
228
|
-
if (!detailResult) return null;
|
|
229
|
-
return {
|
|
230
|
-
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
231
|
-
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
228
|
+
function compactDetail(detailResult) {
|
|
229
|
+
if (!detailResult) return null;
|
|
230
|
+
return {
|
|
231
|
+
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
232
|
+
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
232
233
|
network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
|
|
233
234
|
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
234
|
-
cv_acquisition: detailResult.cv_acquisition || null,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
235
|
+
cv_acquisition: detailResult.cv_acquisition || null,
|
|
236
|
+
colleague_contact: detailResult.colleague_contact || null,
|
|
237
|
+
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
238
|
+
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
239
|
+
close_result: detailResult.close_result
|
|
240
|
+
};
|
|
241
|
+
}
|
|
240
242
|
|
|
241
243
|
function normalizeScreeningMode(value) {
|
|
242
244
|
const normalized = String(value || "llm").trim().toLowerCase();
|
|
@@ -427,18 +429,19 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
427
429
|
};
|
|
428
430
|
}
|
|
429
431
|
|
|
430
|
-
export function countRecommendResultStatuses(results = [], {
|
|
431
|
-
greetCount = 0
|
|
432
|
-
} = {}) {
|
|
433
|
-
return {
|
|
434
|
-
processed: results.length,
|
|
435
|
-
screened: results.length,
|
|
436
|
-
detail_opened: results.filter((item) => item.detail).length,
|
|
437
|
-
passed: results.filter((item) => item.screening?.passed).length,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
432
|
+
export function countRecommendResultStatuses(results = [], {
|
|
433
|
+
greetCount = 0
|
|
434
|
+
} = {}) {
|
|
435
|
+
return {
|
|
436
|
+
processed: results.length,
|
|
437
|
+
screened: results.length,
|
|
438
|
+
detail_opened: results.filter((item) => item.detail).length,
|
|
439
|
+
passed: results.filter((item) => item.screening?.passed).length,
|
|
440
|
+
skipped: results.filter((item) => item.screening?.passed === false).length,
|
|
441
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
442
|
+
greet_count: greetCount,
|
|
443
|
+
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
444
|
+
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
442
445
|
detail_open_failed: results.filter((item) => (
|
|
443
446
|
item.error?.code === "DETAIL_STALE_NODE"
|
|
444
447
|
|| item.error?.code === "DETAIL_OPEN_FAILED"
|
|
@@ -449,9 +452,17 @@ export function countRecommendResultStatuses(results = [], {
|
|
|
449
452
|
|| item.error?.code === "IMAGE_CAPTURE_STALE_NODE"
|
|
450
453
|
|| item.error?.code === "IMAGE_CAPTURE_TIMEOUT"
|
|
451
454
|
|| item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT"
|
|
452
|
-
)).length
|
|
453
|
-
|
|
454
|
-
|
|
455
|
+
)).length,
|
|
456
|
+
colleague_contact_checked: results.filter((item) => item.detail?.colleague_contact?.checked).length,
|
|
457
|
+
recent_colleague_contact_skipped: results.filter((item) => (
|
|
458
|
+
item.screening?.status === "skip"
|
|
459
|
+
&& item.screening?.reasons?.includes("skipped_recent_colleague_contact")
|
|
460
|
+
)).length,
|
|
461
|
+
colleague_contact_panel_missing: results.filter((item) => (
|
|
462
|
+
item.detail?.colleague_contact?.reason === "panel_missing"
|
|
463
|
+
)).length
|
|
464
|
+
};
|
|
465
|
+
}
|
|
455
466
|
|
|
456
467
|
function countPassedResults(results = []) {
|
|
457
468
|
return countRecommendResultStatuses(results).passed;
|
|
@@ -612,10 +623,10 @@ function compactRecoverableDetailError(error) {
|
|
|
612
623
|
return compactError(error, isStaleRecommendNodeError(error) ? "DETAIL_STALE_NODE" : "DETAIL_OPEN_FAILED");
|
|
613
624
|
}
|
|
614
625
|
|
|
615
|
-
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
616
|
-
return {
|
|
617
|
-
status: "fail",
|
|
618
|
-
passed: false,
|
|
626
|
+
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
627
|
+
return {
|
|
628
|
+
status: "fail",
|
|
629
|
+
passed: false,
|
|
619
630
|
score: 0,
|
|
620
631
|
reasons: isStaleRecommendNodeError(error)
|
|
621
632
|
? ["detail_open_failed", "stale_node"]
|
|
@@ -623,9 +634,22 @@ function createRecoverableDetailFailureScreening(candidate, error) {
|
|
|
623
634
|
? ["detail_open_failed", "detail_open_miss"]
|
|
624
635
|
: ["detail_open_failed"],
|
|
625
636
|
error: compactRecoverableDetailError(error),
|
|
626
|
-
candidate
|
|
627
|
-
};
|
|
628
|
-
}
|
|
637
|
+
candidate
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function createRecentColleagueContactSkipScreening(candidate, colleagueContact) {
|
|
642
|
+
const matched = colleagueContact?.matched_row || null;
|
|
643
|
+
return {
|
|
644
|
+
status: "skip",
|
|
645
|
+
passed: false,
|
|
646
|
+
score: 0,
|
|
647
|
+
reasons: ["skipped_recent_colleague_contact"],
|
|
648
|
+
reason: matched?.text || "Candidate has recent colleague contact history",
|
|
649
|
+
matched_colleague_contact: matched,
|
|
650
|
+
candidate
|
|
651
|
+
};
|
|
652
|
+
}
|
|
629
653
|
|
|
630
654
|
export async function runRecommendWorkflow({
|
|
631
655
|
client,
|
|
@@ -657,15 +681,17 @@ export async function runRecommendWorkflow({
|
|
|
657
681
|
executePostAction = true,
|
|
658
682
|
actionTimeoutMs = 8000,
|
|
659
683
|
actionIntervalMs = 500,
|
|
660
|
-
actionAfterClickDelayMs = 900,
|
|
661
|
-
screeningMode = "llm",
|
|
662
|
-
llmConfig = null,
|
|
684
|
+
actionAfterClickDelayMs = 900,
|
|
685
|
+
screeningMode = "llm",
|
|
686
|
+
llmConfig = null,
|
|
663
687
|
llmTimeoutMs = 120000,
|
|
664
688
|
llmImageLimit = 8,
|
|
665
689
|
llmImageDetail = "high",
|
|
666
690
|
imageOutputDir = "",
|
|
667
691
|
humanRestEnabled = false,
|
|
668
|
-
humanBehavior = null
|
|
692
|
+
humanBehavior = null,
|
|
693
|
+
skipRecentColleagueContacted = true,
|
|
694
|
+
colleagueContactWindowDays = 14
|
|
669
695
|
} = {}, runControl) {
|
|
670
696
|
if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
|
|
671
697
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -689,10 +715,13 @@ export async function runRecommendWorkflow({
|
|
|
689
715
|
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
690
716
|
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
691
717
|
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
692
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
693
|
-
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
694
|
-
const postActionEnabled = normalizedPostAction !== "none";
|
|
695
|
-
const
|
|
718
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
719
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
720
|
+
const postActionEnabled = normalizedPostAction !== "none";
|
|
721
|
+
const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
|
|
722
|
+
const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
|
|
723
|
+
const colleagueContactReferenceDate = new Date();
|
|
724
|
+
const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
|
|
696
725
|
const detailCountLimit = detailLimit == null ? Number.POSITIVE_INFINITY : Math.max(0, Number(detailLimit) || 0);
|
|
697
726
|
const effectiveDetailLimit = postActionEnabled ? Number.POSITIVE_INFINITY : detailCountLimit;
|
|
698
727
|
const networkRecorder = effectiveDetailLimit > 0
|
|
@@ -785,6 +814,8 @@ export async function runRecommendWorkflow({
|
|
|
785
814
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
786
815
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
787
816
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
817
|
+
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
818
|
+
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
788
819
|
human_rest_count: humanRestState.rest_count,
|
|
789
820
|
human_rest_ms: humanRestState.total_rest_ms,
|
|
790
821
|
last_human_event: lastHumanEvent,
|
|
@@ -1109,9 +1140,11 @@ export async function runRecommendWorkflow({
|
|
|
1109
1140
|
let cardCandidate = nextCandidateResult.item.candidate;
|
|
1110
1141
|
|
|
1111
1142
|
let screeningCandidate = cardCandidate;
|
|
1112
|
-
let detailResult = null;
|
|
1113
|
-
let recoverableDetailError = null;
|
|
1114
|
-
let
|
|
1143
|
+
let detailResult = null;
|
|
1144
|
+
let recoverableDetailError = null;
|
|
1145
|
+
let colleagueContact = null;
|
|
1146
|
+
let skipRecentColleagueContact = false;
|
|
1147
|
+
let detailStep = "not_started";
|
|
1115
1148
|
if (index < effectiveDetailLimit) {
|
|
1116
1149
|
try {
|
|
1117
1150
|
await runControl.waitIfPaused();
|
|
@@ -1153,12 +1186,52 @@ export async function runRecommendWorkflow({
|
|
|
1153
1186
|
});
|
|
1154
1187
|
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
1155
1188
|
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
1156
|
-
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1157
|
-
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1158
|
-
screeningCandidate = cardCandidate;
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1189
|
+
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1190
|
+
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1191
|
+
screeningCandidate = cardCandidate;
|
|
1192
|
+
if (shouldSkipRecentColleagueContacted) {
|
|
1193
|
+
detailStep = "check_colleague_contact";
|
|
1194
|
+
try {
|
|
1195
|
+
colleagueContact = await measureTiming(timings, "colleague_contact_check_ms", () => inspectRecentColleagueContact(
|
|
1196
|
+
client,
|
|
1197
|
+
openedDetail.detail_state,
|
|
1198
|
+
{
|
|
1199
|
+
referenceDate: colleagueContactReferenceDate,
|
|
1200
|
+
windowDays: normalizedColleagueContactWindowDays
|
|
1201
|
+
}
|
|
1202
|
+
));
|
|
1203
|
+
if (colleagueContact?.recent) {
|
|
1204
|
+
skipRecentColleagueContact = true;
|
|
1205
|
+
detailResult = {
|
|
1206
|
+
candidate: screeningCandidate,
|
|
1207
|
+
detail: {
|
|
1208
|
+
popup_text: "",
|
|
1209
|
+
resume_text: ""
|
|
1210
|
+
},
|
|
1211
|
+
colleague_contact: colleagueContact,
|
|
1212
|
+
cv_acquisition: {
|
|
1213
|
+
source: "skipped_recent_colleague_contact",
|
|
1214
|
+
skipped: true,
|
|
1215
|
+
reason: "skipped_recent_colleague_contact"
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1219
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1220
|
+
}
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
colleagueContact = {
|
|
1223
|
+
checked: false,
|
|
1224
|
+
recent: false,
|
|
1225
|
+
reason: "inspection_failed",
|
|
1226
|
+
error: error?.message || String(error),
|
|
1227
|
+
window_days: normalizedColleagueContactWindowDays
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
if (!skipRecentColleagueContact) {
|
|
1232
|
+
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1233
|
+
detailStep = "wait_network";
|
|
1234
|
+
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
1162
1235
|
waitForRecommendDetailNetworkEvents,
|
|
1163
1236
|
networkRecorder,
|
|
1164
1237
|
{
|
|
@@ -1182,9 +1255,10 @@ export async function runRecommendWorkflow({
|
|
|
1182
1255
|
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
1183
1256
|
networkParseIntervalMs: 250
|
|
1184
1257
|
});
|
|
1185
|
-
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1186
|
-
|
|
1187
|
-
|
|
1258
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1259
|
+
if (colleagueContact) detailResult.colleague_contact = colleagueContact;
|
|
1260
|
+
|
|
1261
|
+
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
1188
1262
|
let source = "network";
|
|
1189
1263
|
let imageEvidence = null;
|
|
1190
1264
|
let captureTarget = null;
|
|
@@ -1296,9 +1370,10 @@ export async function runRecommendWorkflow({
|
|
|
1296
1370
|
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1297
1371
|
capture_target: captureTarget || null,
|
|
1298
1372
|
capture_target_wait: captureTargetWait
|
|
1299
|
-
};
|
|
1300
|
-
screeningCandidate = detailResult.candidate;
|
|
1301
|
-
|
|
1373
|
+
};
|
|
1374
|
+
screeningCandidate = detailResult.candidate;
|
|
1375
|
+
}
|
|
1376
|
+
} catch (error) {
|
|
1302
1377
|
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
1303
1378
|
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1304
1379
|
if (recoveryCount < 1) {
|
|
@@ -1325,11 +1400,11 @@ export async function runRecommendWorkflow({
|
|
|
1325
1400
|
await runControl.waitIfPaused();
|
|
1326
1401
|
runControl.throwIfCanceled();
|
|
1327
1402
|
runControl.setPhase("recommend:screening");
|
|
1328
|
-
let llmResult = null;
|
|
1329
|
-
if (useLlmScreening) {
|
|
1330
|
-
if (recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
1331
|
-
llmResult = null;
|
|
1332
|
-
} else if (!llmConfig) {
|
|
1403
|
+
let llmResult = null;
|
|
1404
|
+
if (useLlmScreening) {
|
|
1405
|
+
if (skipRecentColleagueContact || recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
1406
|
+
llmResult = null;
|
|
1407
|
+
} else if (!llmConfig) {
|
|
1333
1408
|
llmResult = createMissingLlmConfigResult();
|
|
1334
1409
|
} else {
|
|
1335
1410
|
try {
|
|
@@ -1354,12 +1429,14 @@ export async function runRecommendWorkflow({
|
|
|
1354
1429
|
}
|
|
1355
1430
|
llmResult = createFailedLlmScreeningResult(error);
|
|
1356
1431
|
}
|
|
1357
|
-
}
|
|
1358
|
-
if (detailResult) detailResult.llm_result = llmResult;
|
|
1359
|
-
}
|
|
1360
|
-
const screening =
|
|
1361
|
-
?
|
|
1362
|
-
:
|
|
1432
|
+
}
|
|
1433
|
+
if (detailResult) detailResult.llm_result = llmResult;
|
|
1434
|
+
}
|
|
1435
|
+
const screening = skipRecentColleagueContact
|
|
1436
|
+
? createRecentColleagueContactSkipScreening(screeningCandidate, colleagueContact)
|
|
1437
|
+
: recoverableDetailError
|
|
1438
|
+
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
1439
|
+
: detailResult?.image_evidence?.ok === false
|
|
1363
1440
|
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
1364
1441
|
code: detailResult.image_evidence.error_code,
|
|
1365
1442
|
message: detailResult.image_evidence.error
|
|
@@ -1371,7 +1448,7 @@ export async function runRecommendWorkflow({
|
|
|
1371
1448
|
let postActionResult = null;
|
|
1372
1449
|
let closeFailureError = null;
|
|
1373
1450
|
let closeRecoveryFailure = null;
|
|
1374
|
-
if (postActionEnabled && detailResult) {
|
|
1451
|
+
if (postActionEnabled && detailResult && !skipRecentColleagueContact) {
|
|
1375
1452
|
const postActionStarted = Date.now();
|
|
1376
1453
|
await runControl.waitIfPaused();
|
|
1377
1454
|
runControl.throwIfCanceled();
|
|
@@ -1397,7 +1474,7 @@ export async function runRecommendWorkflow({
|
|
|
1397
1474
|
}
|
|
1398
1475
|
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1399
1476
|
}
|
|
1400
|
-
if (detailResult && closeDetail) {
|
|
1477
|
+
if (detailResult && closeDetail && !detailResult.close_result?.closed) {
|
|
1401
1478
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1402
1479
|
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1403
1480
|
if (!detailResult.close_result?.closed) {
|
|
@@ -1597,6 +1674,8 @@ export function createRecommendRunService({
|
|
|
1597
1674
|
imageOutputDir = "",
|
|
1598
1675
|
humanRestEnabled = false,
|
|
1599
1676
|
humanBehavior = null,
|
|
1677
|
+
skipRecentColleagueContacted = true,
|
|
1678
|
+
colleagueContactWindowDays = 14,
|
|
1600
1679
|
name = "recommend-domain-run"
|
|
1601
1680
|
} = {}) {
|
|
1602
1681
|
if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
|
|
@@ -1605,6 +1684,8 @@ export function createRecommendRunService({
|
|
|
1605
1684
|
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
1606
1685
|
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
1607
1686
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1687
|
+
const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
|
|
1688
|
+
const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
|
|
1608
1689
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
1609
1690
|
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
1610
1691
|
});
|
|
@@ -1649,6 +1730,8 @@ export function createRecommendRunService({
|
|
|
1649
1730
|
llm_image_limit: llmImageLimit,
|
|
1650
1731
|
llm_image_detail: llmImageDetail,
|
|
1651
1732
|
image_output_dir: imageOutputDir || "",
|
|
1733
|
+
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
1734
|
+
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
1652
1735
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1653
1736
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1654
1737
|
human_behavior: effectiveHumanBehavior,
|
|
@@ -1662,13 +1745,17 @@ export function createRecommendRunService({
|
|
|
1662
1745
|
processed: 0,
|
|
1663
1746
|
screened: 0,
|
|
1664
1747
|
detail_opened: 0,
|
|
1665
|
-
llm_screened: 0,
|
|
1666
|
-
passed: 0,
|
|
1667
|
-
|
|
1748
|
+
llm_screened: 0,
|
|
1749
|
+
passed: 0,
|
|
1750
|
+
skipped: 0,
|
|
1751
|
+
greet_count: 0,
|
|
1668
1752
|
post_action_clicked: 0,
|
|
1669
1753
|
image_capture_failed: 0,
|
|
1670
1754
|
detail_open_failed: 0,
|
|
1671
1755
|
transient_recovered: 0,
|
|
1756
|
+
colleague_contact_checked: 0,
|
|
1757
|
+
recent_colleague_contact_skipped: 0,
|
|
1758
|
+
colleague_contact_panel_missing: 0,
|
|
1672
1759
|
context_recoveries: 0,
|
|
1673
1760
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1674
1761
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
@@ -1717,7 +1804,9 @@ export function createRecommendRunService({
|
|
|
1717
1804
|
llmImageDetail,
|
|
1718
1805
|
imageOutputDir,
|
|
1719
1806
|
humanRestEnabled: effectiveHumanRestEnabled,
|
|
1720
|
-
humanBehavior: effectiveHumanBehavior
|
|
1807
|
+
humanBehavior: effectiveHumanBehavior,
|
|
1808
|
+
skipRecentColleagueContacted: shouldSkipRecentColleagueContacted,
|
|
1809
|
+
colleagueContactWindowDays: normalizedColleagueContactWindowDays
|
|
1721
1810
|
}, runControl)
|
|
1722
1811
|
});
|
|
1723
1812
|
}
|
|
@@ -110,6 +110,10 @@ export const RECRUIT_SEARCH_SELECTORS = Object.freeze({
|
|
|
110
110
|
"label.checkbox",
|
|
111
111
|
'[ka="search_change_view_resume"]'
|
|
112
112
|
],
|
|
113
|
+
exchangeResumeLabel: [
|
|
114
|
+
'label.checkbox.high_search_checkbox[ka="search_change_exchange_resume"]',
|
|
115
|
+
'[ka="search_change_exchange_resume"]'
|
|
116
|
+
],
|
|
113
117
|
experienceOption: [
|
|
114
118
|
".experience-select .exp-item",
|
|
115
119
|
".experience-select li",
|
|
@@ -172,6 +172,16 @@ function normalizeRecentViewedOverride(value) {
|
|
|
172
172
|
return null;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
function normalizeBooleanOverride(value) {
|
|
176
|
+
if (typeof value === "boolean") return value;
|
|
177
|
+
if (typeof value === "number") return value !== 0;
|
|
178
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
179
|
+
if (!normalized) return null;
|
|
180
|
+
if (["true", "yes", "y", "1", "on", "enable", "enabled", "需要", "是", "开启"].includes(normalized)) return true;
|
|
181
|
+
if (["false", "no", "n", "0", "off", "disable", "disabled", "不需要", "否", "关闭"].includes(normalized)) return false;
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
175
185
|
function normalizeStringOverride(value) {
|
|
176
186
|
if (typeof value !== "string") return null;
|
|
177
187
|
const normalized = value.trim();
|
|
@@ -483,6 +493,13 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
483
493
|
const rawInstruction = String(instruction || "");
|
|
484
494
|
const text = normalizeText(rawInstruction);
|
|
485
495
|
const finalConfirmed = confirmation?.final_confirmed === true;
|
|
496
|
+
const hasSkipRecentColleagueOverride = Object.prototype.hasOwnProperty.call(
|
|
497
|
+
overrides || {},
|
|
498
|
+
"skip_recent_colleague_contacted"
|
|
499
|
+
);
|
|
500
|
+
const confirmationSkipRecentColleagueContacted = normalizeBooleanOverride(
|
|
501
|
+
confirmation?.skip_recent_colleague_contacted_value
|
|
502
|
+
);
|
|
486
503
|
const explicitSchools = extractSchoolFilterExplicit(rawInstruction);
|
|
487
504
|
const explicitRecentViewed = extractRecentViewedExplicit(rawInstruction);
|
|
488
505
|
const explicitKeyword = extractFieldLineValue(rawInstruction, ["搜索关键词", "关键词", "keyword"]);
|
|
@@ -509,6 +526,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
509
526
|
schools: explicitSchools.explicit ? explicitSchools.schools : extractSchools(text),
|
|
510
527
|
schools_explicit: explicitSchools.explicit,
|
|
511
528
|
filter_recent_viewed: explicitRecentViewed !== null ? explicitRecentViewed : extractRecentViewedFilter(text),
|
|
529
|
+
skip_recent_colleague_contacted: confirmationSkipRecentColleagueContacted ?? true,
|
|
512
530
|
keyword_explicit: explicitKeyword || extractKeywordExplicit(text),
|
|
513
531
|
keyword_auto: extractKeywordAuto(text),
|
|
514
532
|
target_count: explicitTargetCount || extractTargetCount(text),
|
|
@@ -576,6 +594,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
576
594
|
? overrides.filter_recent_viewed
|
|
577
595
|
: overrides.recent_not_view
|
|
578
596
|
);
|
|
597
|
+
const overrideSkipRecentColleagueContacted = normalizeBooleanOverride(overrides.skip_recent_colleague_contacted);
|
|
579
598
|
const overridePostAction = normalizePostAction(overrides.post_action);
|
|
580
599
|
if (overrideCity) parsed.city = overrideCity;
|
|
581
600
|
if (overrideDegree) parsed.degree = overrideDegree;
|
|
@@ -604,6 +623,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
604
623
|
if (overrideJob) parsed.job_override = overrideJob;
|
|
605
624
|
if (overrideCriteria) parsed.criteria_override = overrideCriteria;
|
|
606
625
|
if (overrideRecentViewed !== null) parsed.filter_recent_viewed = overrideRecentViewed;
|
|
626
|
+
if (overrideSkipRecentColleagueContacted !== null) parsed.skip_recent_colleague_contacted = overrideSkipRecentColleagueContacted;
|
|
607
627
|
if (overridePostAction) parsed.post_action_override = overridePostAction;
|
|
608
628
|
if (Number.isFinite(overrides.max_greet_count) && overrides.max_greet_count > 0) {
|
|
609
629
|
parsed.max_greet_count_override = Number.parseInt(String(overrides.max_greet_count), 10);
|
|
@@ -628,6 +648,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
628
648
|
gender: parsed.gender,
|
|
629
649
|
age: parsed.age,
|
|
630
650
|
filter_recent_viewed: parsed.filter_recent_viewed,
|
|
651
|
+
skip_recent_colleague_contacted: parsed.skip_recent_colleague_contacted !== false,
|
|
631
652
|
keyword: keywordResolution.keyword
|
|
632
653
|
};
|
|
633
654
|
const criteria = parsed.criteria_override || confirmationCriteria || parsed.criteria_explicit || null;
|
|
@@ -642,7 +663,9 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
642
663
|
criteria,
|
|
643
664
|
target_count: parsed.target_count,
|
|
644
665
|
post_action: postAction,
|
|
645
|
-
max_greet_count: maxGreetCount
|
|
666
|
+
max_greet_count: maxGreetCount,
|
|
667
|
+
skip_recent_colleague_contacted: parsed.skip_recent_colleague_contacted !== false,
|
|
668
|
+
search_exchange_resume_filter_days: 30
|
|
646
669
|
};
|
|
647
670
|
const missingBeforeDefaults = collectMissingFields(baseSearchParams, baseScreenParams, parsed);
|
|
648
671
|
|
|
@@ -659,6 +682,12 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
659
682
|
const missingAfterDefaults = collectUnresolvedMissingFields(missingBeforeDefaults, appliedDefaults);
|
|
660
683
|
const suspicious_fields = collectSuspiciousFields(searchParams, screenParams);
|
|
661
684
|
const needs_recent_viewed_filter_confirmation = !finalConfirmed && searchParams.filter_recent_viewed === null;
|
|
685
|
+
const needs_skip_recent_colleague_contacted_confirmation = (
|
|
686
|
+
!finalConfirmed
|
|
687
|
+
&& !hasSkipRecentColleagueOverride
|
|
688
|
+
&& confirmationSkipRecentColleagueContacted === null
|
|
689
|
+
&& confirmation?.skip_recent_colleague_contacted_confirmed !== true
|
|
690
|
+
);
|
|
662
691
|
const needs_criteria_confirmation = Boolean(screenParams.criteria) && !finalConfirmed && confirmation?.criteria_confirmed !== true;
|
|
663
692
|
const pending_questions = [
|
|
664
693
|
...buildMissingFieldQuestions(missingAfterDefaults, defaultPreview),
|
|
@@ -672,6 +701,17 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
672
701
|
]
|
|
673
702
|
}]
|
|
674
703
|
: []),
|
|
704
|
+
...(needs_skip_recent_colleague_contacted_confirmation
|
|
705
|
+
? [{
|
|
706
|
+
field: "skip_recent_colleague_contacted",
|
|
707
|
+
question: "是否跳过近期已被同事触达的人选?搜索页会开启 Boss 的“近30天未和同事交换简历”过滤。",
|
|
708
|
+
value: true,
|
|
709
|
+
options: [
|
|
710
|
+
{ label: "跳过(推荐)", value: true },
|
|
711
|
+
{ label: "不跳过", value: false }
|
|
712
|
+
]
|
|
713
|
+
}]
|
|
714
|
+
: []),
|
|
675
715
|
...(needs_criteria_confirmation
|
|
676
716
|
? [{
|
|
677
717
|
field: "criteria",
|
|
@@ -705,6 +745,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
705
745
|
suspicious_fields,
|
|
706
746
|
needs_keyword_confirmation: keywordResolution.needsConfirmation,
|
|
707
747
|
needs_recent_viewed_filter_confirmation,
|
|
748
|
+
needs_skip_recent_colleague_contacted_confirmation,
|
|
708
749
|
needs_criteria_confirmation,
|
|
709
750
|
needs_search_params_confirmation: !finalConfirmed && confirmation?.search_params_confirmed !== true,
|
|
710
751
|
proposed_keyword: keywordResolution.proposedKeyword,
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
configureHumanInteraction,
|
|
13
13
|
createHumanRestController,
|
|
14
14
|
humanDelay,
|
|
15
|
-
normalizeHumanBehaviorOptions
|
|
15
|
+
normalizeHumanBehaviorOptions,
|
|
16
|
+
sleep
|
|
16
17
|
} from "../../core/browser/index.js";
|
|
17
18
|
import {
|
|
18
19
|
compactCvAcquisitionState,
|
|
@@ -74,7 +75,10 @@ import {
|
|
|
74
75
|
RECRUIT_CARD_SELECTOR,
|
|
75
76
|
RECRUIT_LIST_CONTAINER_SELECTORS
|
|
76
77
|
} from "./constants.js";
|
|
77
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
describeGreetQuotaAfterSpend,
|
|
80
|
+
GREET_CREDITS_EXHAUSTED_CODE
|
|
81
|
+
} from "../../core/greet-quota/index.js";
|
|
78
82
|
|
|
79
83
|
function compactScreening(screening) {
|
|
80
84
|
return {
|
|
@@ -176,7 +180,8 @@ async function runRecruitPostAction({
|
|
|
176
180
|
greetCount = 0,
|
|
177
181
|
maxGreetCount = null,
|
|
178
182
|
executePostAction = true,
|
|
179
|
-
afterClickDelayMs = 900
|
|
183
|
+
afterClickDelayMs = 900,
|
|
184
|
+
lastGreetQuotaAfterSpend = null
|
|
180
185
|
} = {}) {
|
|
181
186
|
const plan = resolveRecruitPostAction({
|
|
182
187
|
postAction,
|
|
@@ -206,6 +211,13 @@ async function runRecruitPostAction({
|
|
|
206
211
|
const summary = actionDiscovery?.summary || {};
|
|
207
212
|
const control = summary.greet?.control || summary.greet;
|
|
208
213
|
if (!control?.found && !control?.node_id) {
|
|
214
|
+
if (plan.effective === "greet" && lastGreetQuotaAfterSpend?.exhausted_after_spend) {
|
|
215
|
+
result.reason = "greet_credits_exhausted";
|
|
216
|
+
result.out_of_greet_credits = true;
|
|
217
|
+
result.stop_run = true;
|
|
218
|
+
result.greet_quota_after_last_click = lastGreetQuotaAfterSpend;
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
209
221
|
result.reason = `${plan.effective}_control_not_found`;
|
|
210
222
|
return result;
|
|
211
223
|
}
|
|
@@ -256,6 +268,9 @@ async function runRecruitPostAction({
|
|
|
256
268
|
}
|
|
257
269
|
result.click_result = clickResult;
|
|
258
270
|
result.action_clicked = true;
|
|
271
|
+
result.greet_quota_after_click = describeGreetQuotaAfterSpend(
|
|
272
|
+
clickResult.greet_quota?.found ? clickResult.greet_quota : control.greet_quota || control.label || ""
|
|
273
|
+
);
|
|
259
274
|
result.counted_as_greet = plan.effective === "greet";
|
|
260
275
|
result.reason = "clicked";
|
|
261
276
|
if (afterClickDelayMs > 0) await sleep(afterClickDelayMs);
|
|
@@ -578,6 +593,8 @@ export async function runRecruitWorkflow({
|
|
|
578
593
|
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
579
594
|
const postActionEnabled = normalizedPostAction !== "none";
|
|
580
595
|
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
596
|
+
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted !== false;
|
|
597
|
+
let searchExchangeResumeFilterApplied = false;
|
|
581
598
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
582
599
|
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
583
600
|
const networkRecorder = detailCountLimit > 0
|
|
@@ -603,6 +620,7 @@ export async function runRecruitWorkflow({
|
|
|
603
620
|
const results = [];
|
|
604
621
|
const refreshAttempts = [];
|
|
605
622
|
let greetCount = 0;
|
|
623
|
+
let lastGreetQuotaAfterSpend = null;
|
|
606
624
|
let refreshRounds = 0;
|
|
607
625
|
let contextRecoveryAttempts = 0;
|
|
608
626
|
const candidateRecoveryCounts = new Map();
|
|
@@ -668,6 +686,8 @@ export async function runRecruitWorkflow({
|
|
|
668
686
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
669
687
|
human_rest_count: humanRestState.rest_count,
|
|
670
688
|
human_rest_ms: humanRestState.total_rest_ms,
|
|
689
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
690
|
+
search_exchange_resume_filter_applied: searchExchangeResumeFilterApplied ? 1 : 0,
|
|
671
691
|
last_human_event: lastHumanEvent,
|
|
672
692
|
...extra
|
|
673
693
|
});
|
|
@@ -812,6 +832,12 @@ export async function runRecruitWorkflow({
|
|
|
812
832
|
resetTimeoutMs,
|
|
813
833
|
cityOptionTimeoutMs
|
|
814
834
|
});
|
|
835
|
+
const exchangeResumeStep = searchResult.steps.find((step) => step.step === "exchange_resume");
|
|
836
|
+
searchExchangeResumeFilterApplied = Boolean(
|
|
837
|
+
searchExchangeResumeFilterRequested
|
|
838
|
+
&& exchangeResumeStep?.result?.applied
|
|
839
|
+
&& exchangeResumeStep?.result?.requested === true
|
|
840
|
+
);
|
|
815
841
|
runControl.checkpoint({
|
|
816
842
|
search: {
|
|
817
843
|
search_params: searchResult.search_params,
|
|
@@ -1248,11 +1274,15 @@ export async function runRecruitWorkflow({
|
|
|
1248
1274
|
greetCount,
|
|
1249
1275
|
maxGreetCount: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1250
1276
|
executePostAction,
|
|
1251
|
-
afterClickDelayMs: actionAfterClickDelayMs
|
|
1277
|
+
afterClickDelayMs: actionAfterClickDelayMs,
|
|
1278
|
+
lastGreetQuotaAfterSpend
|
|
1252
1279
|
});
|
|
1253
1280
|
if (postActionResult.counted_as_greet && postActionResult.action_clicked) {
|
|
1254
1281
|
greetCount += 1;
|
|
1255
1282
|
}
|
|
1283
|
+
if (postActionResult.greet_quota_after_click?.found) {
|
|
1284
|
+
lastGreetQuotaAfterSpend = postActionResult.greet_quota_after_click;
|
|
1285
|
+
}
|
|
1256
1286
|
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1257
1287
|
}
|
|
1258
1288
|
if (postActionEnabled && detailResult && closeDetail) {
|
|
@@ -1390,6 +1420,9 @@ export async function runRecruitWorkflow({
|
|
|
1390
1420
|
human_rest: humanRestController.getState(),
|
|
1391
1421
|
last_human_event: lastHumanEvent,
|
|
1392
1422
|
list_end_reason: listEndReason || null,
|
|
1423
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
1424
|
+
search_exchange_resume_filter_applied: searchExchangeResumeFilterApplied ? 1 : 0,
|
|
1425
|
+
last_greet_quota_after_spend: lastGreetQuotaAfterSpend,
|
|
1393
1426
|
refresh_rounds: refreshRounds,
|
|
1394
1427
|
refresh_attempts: refreshAttempts,
|
|
1395
1428
|
context_recoveries: contextRecoveryAttempts,
|
|
@@ -1451,6 +1484,7 @@ export function createRecruitRunService({
|
|
|
1451
1484
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
1452
1485
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1453
1486
|
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
1487
|
+
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted !== false;
|
|
1454
1488
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1455
1489
|
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1456
1490
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -1493,6 +1527,8 @@ export function createRecruitRunService({
|
|
|
1493
1527
|
human_behavior: effectiveHumanBehavior,
|
|
1494
1528
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1495
1529
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1530
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
1531
|
+
search_exchange_resume_filter_applied: 0,
|
|
1496
1532
|
post_action: normalizedPostAction,
|
|
1497
1533
|
max_greet_count: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1498
1534
|
execute_post_action: Boolean(executePostAction),
|
|
@@ -1520,6 +1556,8 @@ export function createRecruitRunService({
|
|
|
1520
1556
|
human_rest_ms: 0,
|
|
1521
1557
|
greet_count: 0,
|
|
1522
1558
|
post_action_clicked: 0,
|
|
1559
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
1560
|
+
search_exchange_resume_filter_applied: 0,
|
|
1523
1561
|
last_human_event: null
|
|
1524
1562
|
},
|
|
1525
1563
|
checkpoint: {},
|