@reconcrap/boss-recommend-mcp 1.3.15 → 1.3.17
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 +2 -1
- package/config/screening-config.example.json +1 -0
- package/package.json +1 -1
- package/src/adapters.js +28 -9
- package/src/boss-chat.js +22 -0
- package/src/cli.js +6 -1
- package/src/test-boss-chat.js +89 -0
- package/vendor/boss-chat-cli/src/cli.js +10 -0
- package/vendor/boss-chat-cli/src/mcp/server.js +1 -1
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +1 -1
- package/vendor/boss-chat-cli/src/services/llm.js +119 -24
- package/vendor/boss-chat-cli/src/services/profile-store.js +5 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +482 -110
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +109 -41
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +237 -11
|
@@ -26,7 +26,21 @@ const CSV_HEADER = [
|
|
|
26
26
|
"证据门控降级",
|
|
27
27
|
"错误码",
|
|
28
28
|
"错误信息",
|
|
29
|
-
"候选人ID"
|
|
29
|
+
"候选人ID",
|
|
30
|
+
"总耗时ms",
|
|
31
|
+
"候选卡片读取ms",
|
|
32
|
+
"点击候选人ms",
|
|
33
|
+
"详情打开ms",
|
|
34
|
+
"network简历等待ms",
|
|
35
|
+
"文本模型ms",
|
|
36
|
+
"截图获取ms",
|
|
37
|
+
"视觉模型ms",
|
|
38
|
+
"late network retry ms",
|
|
39
|
+
"DOM fallback ms",
|
|
40
|
+
"通过后动作ms",
|
|
41
|
+
"关闭详情ms",
|
|
42
|
+
"休息ms",
|
|
43
|
+
"checkpoint保存ms"
|
|
30
44
|
].join(",");
|
|
31
45
|
const INPUT_SUMMARY_HEADER = ["运行输入字段", "运行输入值"].join(",");
|
|
32
46
|
const RESUME_CAPTURE_WAIT_MS = 60000;
|
|
@@ -34,6 +48,8 @@ const RESUME_CAPTURE_MAX_ATTEMPTS = 4;
|
|
|
34
48
|
const RESUME_CAPTURE_RETRY_DELAY_MS = 1200;
|
|
35
49
|
const NETWORK_RESUME_WAIT_MS = 4200;
|
|
36
50
|
const NETWORK_RESUME_RETRY_WAIT_MS = 2000;
|
|
51
|
+
const NETWORK_RESUME_IMAGE_MODE_GRACE_MS = 1000;
|
|
52
|
+
const NETWORK_RESUME_LATE_RETRY_MS = 3000;
|
|
37
53
|
const MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES = 10;
|
|
38
54
|
const DEFAULT_VISION_MAX_IMAGE_PIXELS = 36000000;
|
|
39
55
|
const DEFAULT_VISION_RETRY_MAX_IMAGE_PIXELS = 30000000;
|
|
@@ -962,12 +978,69 @@ function shouldBringChromeToFront() {
|
|
|
962
978
|
}
|
|
963
979
|
|
|
964
980
|
const SHOULD_BRING_TO_FRONT = shouldBringChromeToFront();
|
|
981
|
+
const LLM_THINKING_ENV_KEYS = [
|
|
982
|
+
"BOSS_RECOMMEND_LLM_THINKING_LEVEL",
|
|
983
|
+
"BOSS_LLM_THINKING_LEVEL",
|
|
984
|
+
"LLM_THINKING_LEVEL"
|
|
985
|
+
];
|
|
986
|
+
|
|
987
|
+
function normalizeLlmThinkingLevel(value) {
|
|
988
|
+
const normalized = normalizeText(value).toLowerCase().replace(/[_\s]+/g, "-");
|
|
989
|
+
if (!normalized) return "";
|
|
990
|
+
if (["off", "disabled", "disable", "minimal", "none", "false", "0"].includes(normalized)) return "off";
|
|
991
|
+
if (["low", "medium", "high", "auto", "current", "default", "provider-default", "unchanged", "inherit"].includes(normalized)) {
|
|
992
|
+
return normalized;
|
|
993
|
+
}
|
|
994
|
+
return "";
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function getEnvLlmThinkingLevel() {
|
|
998
|
+
for (const key of LLM_THINKING_ENV_KEYS) {
|
|
999
|
+
const normalized = normalizeLlmThinkingLevel(process.env[key]);
|
|
1000
|
+
if (normalized) return normalized;
|
|
1001
|
+
}
|
|
1002
|
+
return "";
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function resolveLlmThinkingLevel(value) {
|
|
1006
|
+
return normalizeLlmThinkingLevel(value) || getEnvLlmThinkingLevel() || "off";
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function isVolcengineModel(baseUrl, model) {
|
|
1010
|
+
const combined = `${baseUrl || ""} ${model || ""}`;
|
|
1011
|
+
return /volces\.com|volcengine|ark\.cn-|doubao|seed/i.test(combined);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function applyChatCompletionThinking(payload, { baseUrl = "", model = "", thinkingLevel = "" } = {}) {
|
|
1015
|
+
const level = resolveLlmThinkingLevel(thinkingLevel);
|
|
1016
|
+
if (["current", "default", "provider-default", "unchanged", "inherit"].includes(level)) return payload;
|
|
1017
|
+
const isVolc = isVolcengineModel(baseUrl, model);
|
|
1018
|
+
if (isVolc) {
|
|
1019
|
+
if (level === "auto") {
|
|
1020
|
+
payload.thinking = { type: "auto" };
|
|
1021
|
+
return payload;
|
|
1022
|
+
}
|
|
1023
|
+
if (level === "off") {
|
|
1024
|
+
payload.thinking = { type: "disabled" };
|
|
1025
|
+
payload.reasoning_effort = "minimal";
|
|
1026
|
+
return payload;
|
|
1027
|
+
}
|
|
1028
|
+
payload.thinking = { type: "enabled" };
|
|
1029
|
+
payload.reasoning_effort = level;
|
|
1030
|
+
return payload;
|
|
1031
|
+
}
|
|
1032
|
+
if (level !== "auto") {
|
|
1033
|
+
payload.reasoning_effort = level === "off" ? "minimal" : level;
|
|
1034
|
+
}
|
|
1035
|
+
return payload;
|
|
1036
|
+
}
|
|
965
1037
|
|
|
966
1038
|
function parseArgs(argv) {
|
|
967
1039
|
const parsed = {
|
|
968
1040
|
baseUrl: null,
|
|
969
1041
|
apiKey: null,
|
|
970
1042
|
model: null,
|
|
1043
|
+
thinkingLevel: null,
|
|
971
1044
|
openaiOrganization: null,
|
|
972
1045
|
openaiProject: null,
|
|
973
1046
|
criteria: null,
|
|
@@ -988,6 +1061,7 @@ function parseArgs(argv) {
|
|
|
988
1061
|
baseUrl: false,
|
|
989
1062
|
apiKey: false,
|
|
990
1063
|
model: false,
|
|
1064
|
+
thinkingLevel: false,
|
|
991
1065
|
criteria: false,
|
|
992
1066
|
targetCount: false,
|
|
993
1067
|
maxGreetCount: false,
|
|
@@ -1016,6 +1090,10 @@ function parseArgs(argv) {
|
|
|
1016
1090
|
parsed.model = inlineValue || next;
|
|
1017
1091
|
parsed.__provided.model = true;
|
|
1018
1092
|
if (!inlineValue) index += 1;
|
|
1093
|
+
} else if ((token === "--thinking-level" || token === "--thinkingLevel" || token === "--llm-thinking-level" || token === "--reasoning-effort") && (inlineValue || next)) {
|
|
1094
|
+
parsed.thinkingLevel = inlineValue || next;
|
|
1095
|
+
parsed.__provided.thinkingLevel = true;
|
|
1096
|
+
if (!inlineValue) index += 1;
|
|
1019
1097
|
} else if (token === "--openai-organization" && (inlineValue || next)) {
|
|
1020
1098
|
parsed.openaiOrganization = inlineValue || next;
|
|
1021
1099
|
if (!inlineValue) index += 1;
|
|
@@ -1208,6 +1286,30 @@ function csvEscape(value) {
|
|
|
1208
1286
|
return `"${String(value || "").replace(/"/g, '""')}"`;
|
|
1209
1287
|
}
|
|
1210
1288
|
|
|
1289
|
+
function normalizeTimingMs(value) {
|
|
1290
|
+
const parsed = Number(value);
|
|
1291
|
+
if (!Number.isFinite(parsed) || parsed < 0) return null;
|
|
1292
|
+
return Math.round(parsed);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function sanitizeTimingBreakdown(value) {
|
|
1296
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
1297
|
+
const result = {};
|
|
1298
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
1299
|
+
const normalizedKey = normalizeText(key);
|
|
1300
|
+
if (!normalizedKey) continue;
|
|
1301
|
+
const normalizedValue = normalizeTimingMs(raw);
|
|
1302
|
+
if (normalizedValue === null) continue;
|
|
1303
|
+
result[normalizedKey] = normalizedValue;
|
|
1304
|
+
}
|
|
1305
|
+
return result;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function getTimingMs(timing, key) {
|
|
1309
|
+
const normalized = normalizeTimingMs(timing?.[key]);
|
|
1310
|
+
return normalized === null ? "" : normalized;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1211
1313
|
function stringifyInputSummaryValue(value) {
|
|
1212
1314
|
if (value === null) return "null";
|
|
1213
1315
|
if (value === undefined) return "";
|
|
@@ -2844,6 +2946,8 @@ class RecommendScreenCli {
|
|
|
2844
2946
|
dom_fallback: 0,
|
|
2845
2947
|
image_fallback: 0
|
|
2846
2948
|
};
|
|
2949
|
+
this.resumeAcquisitionMode = "unknown";
|
|
2950
|
+
this.resumeAcquisitionModeReason = "";
|
|
2847
2951
|
this.lastActiveTabStatus = PAGE_SCOPE_TAB_STATUS[this.args.pageScope] || null;
|
|
2848
2952
|
this.featuredCalibration = this.args.pageScope === "featured"
|
|
2849
2953
|
? loadCalibrationPosition(this.args.calibrationPath)
|
|
@@ -2890,6 +2994,8 @@ class RecommendScreenCli {
|
|
|
2890
2994
|
skipped_count: this.skippedCount,
|
|
2891
2995
|
greet_count: this.greetCount,
|
|
2892
2996
|
greet_limit_fallback_count: this.greetLimitFallbackCount,
|
|
2997
|
+
resume_acquisition_mode: this.resumeAcquisitionMode,
|
|
2998
|
+
resume_acquisition_mode_reason: this.resumeAcquisitionModeReason,
|
|
2893
2999
|
processed_keys: Array.from(this.processedKeys),
|
|
2894
3000
|
passed_candidates: this.passedCandidates.map((item) => ({
|
|
2895
3001
|
name: item?.name || "",
|
|
@@ -2926,7 +3032,8 @@ class RecommendScreenCli {
|
|
|
2926
3032
|
error_code: item?.error_code || "",
|
|
2927
3033
|
error_message: item?.error_message || "",
|
|
2928
3034
|
chunk_index: Number.isFinite(Number(item?.chunk_index)) ? Number(item.chunk_index) : null,
|
|
2929
|
-
chunk_total: Number.isFinite(Number(item?.chunk_total)) ? Number(item.chunk_total) : null
|
|
3035
|
+
chunk_total: Number.isFinite(Number(item?.chunk_total)) ? Number(item.chunk_total) : null,
|
|
3036
|
+
timing_ms: sanitizeTimingBreakdown(item?.timing_ms)
|
|
2930
3037
|
})),
|
|
2931
3038
|
input_summary: sanitizeInputSummary(this.inputSummary)
|
|
2932
3039
|
};
|
|
@@ -2942,6 +3049,7 @@ class RecommendScreenCli {
|
|
|
2942
3049
|
checkpoint_path: this.checkpointPath,
|
|
2943
3050
|
selected_page: this.args.pageScope || "recommend",
|
|
2944
3051
|
active_tab_status: this.lastActiveTabStatus || PAGE_SCOPE_TAB_STATUS[this.args.pageScope] || null,
|
|
3052
|
+
resume_acquisition_mode: this.resumeAcquisitionMode,
|
|
2945
3053
|
resume_source: this.resumeSourceStats.image_fallback > 0
|
|
2946
3054
|
? "image_fallback"
|
|
2947
3055
|
: this.resumeSourceStats.dom_fallback > 0
|
|
@@ -3092,7 +3200,8 @@ class RecommendScreenCli {
|
|
|
3092
3200
|
error_code: normalizeText(entry?.error_code || "") || "",
|
|
3093
3201
|
error_message: normalizeText(entry?.error_message || "") || "",
|
|
3094
3202
|
chunk_index: Number.isFinite(Number(entry?.chunk_index)) ? Number(entry.chunk_index) : null,
|
|
3095
|
-
chunk_total: Number.isFinite(Number(entry?.chunk_total)) ? Number(entry.chunk_total) : null
|
|
3203
|
+
chunk_total: Number.isFinite(Number(entry?.chunk_total)) ? Number(entry.chunk_total) : null,
|
|
3204
|
+
timing_ms: sanitizeTimingBreakdown(entry?.timing_ms)
|
|
3096
3205
|
};
|
|
3097
3206
|
this.candidateAudits.push(normalized);
|
|
3098
3207
|
const maxItems = parsePositiveInteger(process.env.BOSS_RECOMMEND_MAX_CANDIDATE_AUDITS);
|
|
@@ -3101,6 +3210,22 @@ class RecommendScreenCli {
|
|
|
3101
3210
|
}
|
|
3102
3211
|
}
|
|
3103
3212
|
|
|
3213
|
+
updateCandidateAuditTiming(candidateKey, timing = {}) {
|
|
3214
|
+
const normalizedKey = normalizeText(candidateKey || "");
|
|
3215
|
+
if (!normalizedKey) return;
|
|
3216
|
+
const timingMs = sanitizeTimingBreakdown(timing);
|
|
3217
|
+
for (let index = this.candidateAudits.length - 1; index >= 0; index -= 1) {
|
|
3218
|
+
const audit = this.candidateAudits[index];
|
|
3219
|
+
if (
|
|
3220
|
+
normalizeText(audit?.candidate_key || "") === normalizedKey
|
|
3221
|
+
|| normalizeText(audit?.geek_id || "") === normalizedKey
|
|
3222
|
+
) {
|
|
3223
|
+
audit.timing_ms = timingMs;
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3104
3229
|
logResumeNetworkMissDiagnostics(candidate, options = {}) {
|
|
3105
3230
|
const candidateKey = normalizeText(candidate?.key || candidate?.geek_id || "");
|
|
3106
3231
|
const candidateName = normalizeText(candidate?.name || "");
|
|
@@ -3240,6 +3365,60 @@ class RecommendScreenCli {
|
|
|
3240
3365
|
return null;
|
|
3241
3366
|
}
|
|
3242
3367
|
|
|
3368
|
+
setResumeAcquisitionMode(mode, reason = "") {
|
|
3369
|
+
if (!["unknown", "network", "image"].includes(mode)) return;
|
|
3370
|
+
if (this.resumeAcquisitionMode === mode) return;
|
|
3371
|
+
this.resumeAcquisitionMode = mode;
|
|
3372
|
+
this.resumeAcquisitionModeReason = normalizeText(reason || "");
|
|
3373
|
+
log(`[简历获取模式] mode=${mode}${this.resumeAcquisitionModeReason ? ` reason=${this.resumeAcquisitionModeReason}` : ""}`);
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
async waitForResumeNetworkByMode(candidate, options = {}) {
|
|
3377
|
+
const minTs = Number.isFinite(Number(options?.minTs)) ? Number(options.minTs) : 0;
|
|
3378
|
+
const mode = this.resumeAcquisitionMode || "unknown";
|
|
3379
|
+
const firstWaitMs = mode === "image" ? NETWORK_RESUME_IMAGE_MODE_GRACE_MS : NETWORK_RESUME_WAIT_MS;
|
|
3380
|
+
const waitStartedAt = Date.now();
|
|
3381
|
+
let networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(candidate, firstWaitMs, { minTs });
|
|
3382
|
+
if (normalizeText(networkCandidateInfo?.resumeText)) {
|
|
3383
|
+
this.setResumeAcquisitionMode("network", "network_resume_hit");
|
|
3384
|
+
return networkCandidateInfo;
|
|
3385
|
+
}
|
|
3386
|
+
if (typeof this.logResumeNetworkMissDiagnostics === "function") {
|
|
3387
|
+
this.logResumeNetworkMissDiagnostics(candidate, {
|
|
3388
|
+
timeoutMs: firstWaitMs,
|
|
3389
|
+
waitStartedAt
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
if (mode === "image") {
|
|
3393
|
+
return null;
|
|
3394
|
+
}
|
|
3395
|
+
await sleep(NETWORK_RESUME_RETRY_WAIT_MS);
|
|
3396
|
+
networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(
|
|
3397
|
+
candidate,
|
|
3398
|
+
NETWORK_RESUME_RETRY_WAIT_MS,
|
|
3399
|
+
{ minTs }
|
|
3400
|
+
);
|
|
3401
|
+
if (normalizeText(networkCandidateInfo?.resumeText)) {
|
|
3402
|
+
this.setResumeAcquisitionMode("network", "network_resume_retry_hit");
|
|
3403
|
+
return networkCandidateInfo;
|
|
3404
|
+
}
|
|
3405
|
+
return null;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
async waitForLateNetworkResumeCandidateInfo(candidate, options = {}) {
|
|
3409
|
+
const minTs = Number.isFinite(Number(options?.minTs)) ? Number(options.minTs) : 0;
|
|
3410
|
+
const networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(
|
|
3411
|
+
candidate,
|
|
3412
|
+
NETWORK_RESUME_LATE_RETRY_MS,
|
|
3413
|
+
{ minTs }
|
|
3414
|
+
);
|
|
3415
|
+
if (normalizeText(networkCandidateInfo?.resumeText)) {
|
|
3416
|
+
this.setResumeAcquisitionMode("network", "late_network_resume_hit");
|
|
3417
|
+
return networkCandidateInfo;
|
|
3418
|
+
}
|
|
3419
|
+
return null;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3243
3422
|
async extractResumeTextFromDom(candidate) {
|
|
3244
3423
|
const candidateKey = normalizeText(candidate?.key || candidate?.geek_id || "");
|
|
3245
3424
|
const candidateLabel = normalizeText(candidate?.name || candidateKey || "unknown");
|
|
@@ -3305,6 +3484,60 @@ class RecommendScreenCli {
|
|
|
3305
3484
|
return info;
|
|
3306
3485
|
}
|
|
3307
3486
|
|
|
3487
|
+
async resolveDomResumeFallback(candidate, cardProfile) {
|
|
3488
|
+
let domCandidateInfo = await this.extractResumeTextFromDom(candidate);
|
|
3489
|
+
let networkCandidateInfo = null;
|
|
3490
|
+
if (domCandidateInfo && !isDomProfileConsistentWithCard(cardProfile, domCandidateInfo)) {
|
|
3491
|
+
this.recordResumeNetworkDiagnostic({
|
|
3492
|
+
kind: "dom_profile_mismatch",
|
|
3493
|
+
candidate_key: normalizeText(candidate?.key || candidate?.geek_id || ""),
|
|
3494
|
+
card_name: normalizeText(cardProfile?.name || ""),
|
|
3495
|
+
dom_name: normalizeText(domCandidateInfo?.name || ""),
|
|
3496
|
+
card_school: normalizeText(cardProfile?.school || ""),
|
|
3497
|
+
dom_school: normalizeText(domCandidateInfo?.school || "")
|
|
3498
|
+
});
|
|
3499
|
+
log(
|
|
3500
|
+
`[DOM简历疑似错位] candidate=${candidate?.key || candidate?.geek_id || "unknown"} ` +
|
|
3501
|
+
`card=${normalizeText(cardProfile?.name || "-")} dom=${normalizeText(domCandidateInfo?.name || "-")},尝试重试一次点击+监听。`
|
|
3502
|
+
);
|
|
3503
|
+
try {
|
|
3504
|
+
const retryCaptureStartedAt = Date.now();
|
|
3505
|
+
await this.clickCandidate(candidate);
|
|
3506
|
+
const retryDetailOpen = await this.ensureDetailOpen();
|
|
3507
|
+
if (retryDetailOpen) {
|
|
3508
|
+
networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(
|
|
3509
|
+
candidate,
|
|
3510
|
+
NETWORK_RESUME_RETRY_WAIT_MS,
|
|
3511
|
+
{ minTs: retryCaptureStartedAt }
|
|
3512
|
+
);
|
|
3513
|
+
if (!normalizeText(networkCandidateInfo?.resumeText)) {
|
|
3514
|
+
const retryDomCandidateInfo = await this.extractResumeTextFromDom(candidate);
|
|
3515
|
+
if (retryDomCandidateInfo && isDomProfileConsistentWithCard(cardProfile, retryDomCandidateInfo)) {
|
|
3516
|
+
domCandidateInfo = retryDomCandidateInfo;
|
|
3517
|
+
} else {
|
|
3518
|
+
domCandidateInfo = null;
|
|
3519
|
+
}
|
|
3520
|
+
} else {
|
|
3521
|
+
domCandidateInfo = null;
|
|
3522
|
+
}
|
|
3523
|
+
} else {
|
|
3524
|
+
domCandidateInfo = null;
|
|
3525
|
+
}
|
|
3526
|
+
} catch (retryError) {
|
|
3527
|
+
domCandidateInfo = null;
|
|
3528
|
+
this.recordResumeNetworkDiagnostic({
|
|
3529
|
+
kind: "dom_profile_mismatch_retry_failed",
|
|
3530
|
+
candidate_key: normalizeText(candidate?.key || candidate?.geek_id || ""),
|
|
3531
|
+
error: normalizeText(retryError?.message || retryError)
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
return {
|
|
3536
|
+
domCandidateInfo,
|
|
3537
|
+
networkCandidateInfo
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3308
3541
|
handleNetworkRequestWillBeSent(params) {
|
|
3309
3542
|
const url = normalizeText(params?.request?.url || "");
|
|
3310
3543
|
const postData = params?.request?.postData || "";
|
|
@@ -3583,7 +3816,8 @@ class RecommendScreenCli {
|
|
|
3583
3816
|
error_code: normalizeText(item?.error_code || "") || "",
|
|
3584
3817
|
error_message: normalizeText(item?.error_message || "") || "",
|
|
3585
3818
|
chunk_index: Number.isFinite(Number(item?.chunk_index)) ? Number(item.chunk_index) : null,
|
|
3586
|
-
chunk_total: Number.isFinite(Number(item?.chunk_total)) ? Number(item.chunk_total) : null
|
|
3819
|
+
chunk_total: Number.isFinite(Number(item?.chunk_total)) ? Number(item.chunk_total) : null,
|
|
3820
|
+
timing_ms: sanitizeTimingBreakdown(item?.timing_ms)
|
|
3587
3821
|
}))
|
|
3588
3822
|
: [];
|
|
3589
3823
|
if (!this.inputSummary) {
|
|
@@ -3611,6 +3845,17 @@ class RecommendScreenCli {
|
|
|
3611
3845
|
this.resumeSourceStats.image_fallback = 1;
|
|
3612
3846
|
}
|
|
3613
3847
|
}
|
|
3848
|
+
const checkpointMode = normalizeText(parsed.resume_acquisition_mode || "").toLowerCase();
|
|
3849
|
+
if (["network", "image"].includes(checkpointMode)) {
|
|
3850
|
+
this.resumeAcquisitionMode = checkpointMode;
|
|
3851
|
+
this.resumeAcquisitionModeReason = normalizeText(parsed.resume_acquisition_mode_reason || "checkpoint");
|
|
3852
|
+
} else if (this.resumeSourceStats.network > 0) {
|
|
3853
|
+
this.resumeAcquisitionMode = "network";
|
|
3854
|
+
this.resumeAcquisitionModeReason = "checkpoint_source_stats";
|
|
3855
|
+
} else if (this.resumeSourceStats.image_fallback > 0) {
|
|
3856
|
+
this.resumeAcquisitionMode = "image";
|
|
3857
|
+
this.resumeAcquisitionModeReason = "checkpoint_source_stats";
|
|
3858
|
+
}
|
|
3614
3859
|
|
|
3615
3860
|
return true;
|
|
3616
3861
|
}
|
|
@@ -4137,7 +4382,8 @@ class RecommendScreenCli {
|
|
|
4137
4382
|
outPrefix,
|
|
4138
4383
|
targetPattern: RECOMMEND_URL_FRAGMENT,
|
|
4139
4384
|
waitResumeMs: RESUME_CAPTURE_WAIT_MS,
|
|
4140
|
-
scrollSettleMs: 500
|
|
4385
|
+
scrollSettleMs: 500,
|
|
4386
|
+
stitchFullImage: false
|
|
4141
4387
|
});
|
|
4142
4388
|
} catch (error) {
|
|
4143
4389
|
lastError = error;
|
|
@@ -4164,7 +4410,7 @@ class RecommendScreenCli {
|
|
|
4164
4410
|
DEFAULT_VISION_MAX_IMAGE_PIXELS
|
|
4165
4411
|
);
|
|
4166
4412
|
const retryLimit = resolveVisionRetryPixelLimit(primaryLimit);
|
|
4167
|
-
const preparedPrimary = await this.
|
|
4413
|
+
const preparedPrimary = await this.prepareVisionInputsForModel(imagePath, primaryLimit, "primary");
|
|
4168
4414
|
try {
|
|
4169
4415
|
const primaryResult = await this.requestVisionModel(preparedPrimary.imagePaths);
|
|
4170
4416
|
return this.applyVisionEvidenceGate(primaryResult);
|
|
@@ -4179,7 +4425,7 @@ class RecommendScreenCli {
|
|
|
4179
4425
|
`segments=${preparedPrimary.imagePaths?.length || 1}`
|
|
4180
4426
|
);
|
|
4181
4427
|
}
|
|
4182
|
-
const preparedRetry = await this.
|
|
4428
|
+
const preparedRetry = await this.prepareVisionInputsForModel(imagePath, retryLimit, "retry");
|
|
4183
4429
|
try {
|
|
4184
4430
|
const retryResult = await this.requestVisionModel(preparedRetry.imagePaths);
|
|
4185
4431
|
return this.applyVisionEvidenceGate(retryResult);
|
|
@@ -4199,6 +4445,37 @@ class RecommendScreenCli {
|
|
|
4199
4445
|
}
|
|
4200
4446
|
}
|
|
4201
4447
|
|
|
4448
|
+
async prepareVisionInputsForModel(imageInput, maxPixels, attemptTag = "primary") {
|
|
4449
|
+
const sourcePaths = Array.isArray(imageInput) ? imageInput.filter(Boolean) : [imageInput].filter(Boolean);
|
|
4450
|
+
if (sourcePaths.length <= 0) {
|
|
4451
|
+
return {
|
|
4452
|
+
imagePaths: [],
|
|
4453
|
+
source: "empty",
|
|
4454
|
+
sourcePixels: null,
|
|
4455
|
+
currentPixels: null
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
const preparedItems = [];
|
|
4459
|
+
for (let index = 0; index < sourcePaths.length; index += 1) {
|
|
4460
|
+
const prepared = await this.prepareVisionImageSegmentsForModel(
|
|
4461
|
+
sourcePaths[index],
|
|
4462
|
+
maxPixels,
|
|
4463
|
+
`${attemptTag}.input${String(index + 1).padStart(3, "0")}`
|
|
4464
|
+
);
|
|
4465
|
+
preparedItems.push(prepared);
|
|
4466
|
+
}
|
|
4467
|
+
return {
|
|
4468
|
+
imagePaths: preparedItems.flatMap((item) => item.imagePaths || []),
|
|
4469
|
+
source: sourcePaths.length > 1 ? "ordered_chunks" : (preparedItems[0]?.source || "single"),
|
|
4470
|
+
sourcePixels: preparedItems.reduce((acc, item) => (
|
|
4471
|
+
Number.isFinite(Number(item?.sourcePixels)) ? acc + Number(item.sourcePixels) : acc
|
|
4472
|
+
), 0) || null,
|
|
4473
|
+
currentPixels: preparedItems.reduce((acc, item) => (
|
|
4474
|
+
Number.isFinite(Number(item?.currentPixels)) ? acc + Number(item.currentPixels) : acc
|
|
4475
|
+
), 0) || null
|
|
4476
|
+
};
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4202
4479
|
applyVisionEvidenceGate(result) {
|
|
4203
4480
|
const parsed = result && typeof result === "object" ? result : {};
|
|
4204
4481
|
const rawPassed = parsed?.rawPassed === true || parsed?.passed === true;
|
|
@@ -4420,6 +4697,7 @@ class RecommendScreenCli {
|
|
|
4420
4697
|
"请根据以下标准判断候选人是否通过筛选。\n\n" +
|
|
4421
4698
|
`筛选标准:\n${this.args.criteria}\n\n` +
|
|
4422
4699
|
"你将收到候选人完整简历的一个或多个顺序分段图片。必须完整阅读全部分段后再判断," +
|
|
4700
|
+
"不能只根据前几段下结论;后续分段中的教育、项目、经历或否定信息必须纳入最终判断。" +
|
|
4423
4701
|
"严禁编造任何不存在的经历、项目、学校、公司或时间线;证据不足时必须判定为不通过。\n\n" +
|
|
4424
4702
|
"要求:\n" +
|
|
4425
4703
|
"1) reason 必须写出可审计的判定依据,至少包含 2 条与筛选标准直接相关的事实。\n" +
|
|
@@ -4464,6 +4742,11 @@ class RecommendScreenCli {
|
|
|
4464
4742
|
}
|
|
4465
4743
|
]
|
|
4466
4744
|
};
|
|
4745
|
+
applyChatCompletionThinking(payload, {
|
|
4746
|
+
baseUrl,
|
|
4747
|
+
model: this.args.model,
|
|
4748
|
+
thinkingLevel: this.args.thinkingLevel
|
|
4749
|
+
});
|
|
4467
4750
|
const headers = {
|
|
4468
4751
|
"Content-Type": "application/json",
|
|
4469
4752
|
Authorization: `Bearer ${this.args.apiKey}`
|
|
@@ -4611,6 +4894,11 @@ class RecommendScreenCli {
|
|
|
4611
4894
|
}
|
|
4612
4895
|
]
|
|
4613
4896
|
};
|
|
4897
|
+
applyChatCompletionThinking(payload, {
|
|
4898
|
+
baseUrl,
|
|
4899
|
+
model: this.args.model,
|
|
4900
|
+
thinkingLevel: this.args.thinkingLevel
|
|
4901
|
+
});
|
|
4614
4902
|
const headers = {
|
|
4615
4903
|
"Content-Type": "application/json",
|
|
4616
4904
|
Authorization: `Bearer ${this.args.apiKey}`
|
|
@@ -4990,6 +5278,7 @@ class RecommendScreenCli {
|
|
|
4990
5278
|
const finalPassed = audit?.final_passed === true || normalizeText(audit?.outcome || "") === "passed";
|
|
4991
5279
|
const screeningReason = normalizeText(audit?.screening_reason || passedItem?.reason || "");
|
|
4992
5280
|
const passReason = finalPassed ? screeningReason : "";
|
|
5281
|
+
const timing = sanitizeTimingBreakdown(audit?.timing_ms);
|
|
4993
5282
|
lines.push([
|
|
4994
5283
|
csvEscape(audit?.candidate_name || passedItem?.name || ""),
|
|
4995
5284
|
csvEscape(audit?.school || passedItem?.school || ""),
|
|
@@ -5008,7 +5297,21 @@ class RecommendScreenCli {
|
|
|
5008
5297
|
csvEscape(audit?.evidence_gate_demoted === true ? "true" : "false"),
|
|
5009
5298
|
csvEscape(audit?.error_code || ""),
|
|
5010
5299
|
csvEscape(audit?.error_message || ""),
|
|
5011
|
-
csvEscape(auditGeekId || passedItem?.geekId || "")
|
|
5300
|
+
csvEscape(auditGeekId || passedItem?.geekId || ""),
|
|
5301
|
+
csvEscape(getTimingMs(timing, "total_ms")),
|
|
5302
|
+
csvEscape(getTimingMs(timing, "card_profile_ms")),
|
|
5303
|
+
csvEscape(getTimingMs(timing, "click_candidate_ms")),
|
|
5304
|
+
csvEscape(getTimingMs(timing, "detail_open_ms")),
|
|
5305
|
+
csvEscape(getTimingMs(timing, "network_resume_wait_ms")),
|
|
5306
|
+
csvEscape(getTimingMs(timing, "text_model_ms")),
|
|
5307
|
+
csvEscape(getTimingMs(timing, "image_capture_ms")),
|
|
5308
|
+
csvEscape(getTimingMs(timing, "vision_model_ms")),
|
|
5309
|
+
csvEscape(getTimingMs(timing, "late_network_retry_ms")),
|
|
5310
|
+
csvEscape(getTimingMs(timing, "dom_fallback_ms")),
|
|
5311
|
+
csvEscape(getTimingMs(timing, "post_action_ms")),
|
|
5312
|
+
csvEscape(getTimingMs(timing, "close_detail_ms")),
|
|
5313
|
+
csvEscape(getTimingMs(timing, "rest_ms")),
|
|
5314
|
+
csvEscape(getTimingMs(timing, "checkpoint_save_ms"))
|
|
5012
5315
|
].join(","));
|
|
5013
5316
|
}
|
|
5014
5317
|
fs.mkdirSync(path.dirname(this.args.output), { recursive: true });
|
|
@@ -5164,6 +5467,29 @@ class RecommendScreenCli {
|
|
|
5164
5467
|
this.scrollRetryCount = 0;
|
|
5165
5468
|
this.processedCount += 1;
|
|
5166
5469
|
log(`处理第 ${this.processedCount} 位候选人: ${nextCandidate.name || nextCandidate.geek_id}`);
|
|
5470
|
+
const candidateStartedAt = Date.now();
|
|
5471
|
+
const candidateTiming = {};
|
|
5472
|
+
const candidateKeyForTiming = nextCandidate.key || nextCandidate.geek_id || "";
|
|
5473
|
+
const addCandidateTiming = (key, startedAt) => {
|
|
5474
|
+
const elapsed = Math.max(0, Date.now() - startedAt);
|
|
5475
|
+
candidateTiming[key] = Math.round((Number(candidateTiming[key]) || 0) + elapsed);
|
|
5476
|
+
};
|
|
5477
|
+
const timeCandidateStage = async (key, fn) => {
|
|
5478
|
+
const startedAt = Date.now();
|
|
5479
|
+
try {
|
|
5480
|
+
return await fn();
|
|
5481
|
+
} finally {
|
|
5482
|
+
addCandidateTiming(key, startedAt);
|
|
5483
|
+
}
|
|
5484
|
+
};
|
|
5485
|
+
const timeCandidateStageSync = (key, fn) => {
|
|
5486
|
+
const startedAt = Date.now();
|
|
5487
|
+
try {
|
|
5488
|
+
return fn();
|
|
5489
|
+
} finally {
|
|
5490
|
+
addCandidateTiming(key, startedAt);
|
|
5491
|
+
}
|
|
5492
|
+
};
|
|
5167
5493
|
let shouldMarkProcessed = true;
|
|
5168
5494
|
let resumeSource = "";
|
|
5169
5495
|
let resumeTextLength = null;
|
|
@@ -5181,7 +5507,10 @@ class RecommendScreenCli {
|
|
|
5181
5507
|
|
|
5182
5508
|
try {
|
|
5183
5509
|
this.currentCandidateKey = nextCandidate.key || nextCandidate.geek_id || null;
|
|
5184
|
-
const cardProfile = await
|
|
5510
|
+
const cardProfile = await timeCandidateStage(
|
|
5511
|
+
"card_profile_ms",
|
|
5512
|
+
() => this.extractCandidateProfileFromCard(nextCandidate)
|
|
5513
|
+
);
|
|
5185
5514
|
candidateProfile = mergeCandidateProfiles(
|
|
5186
5515
|
cardProfile || null,
|
|
5187
5516
|
{
|
|
@@ -5193,112 +5522,131 @@ class RecommendScreenCli {
|
|
|
5193
5522
|
}
|
|
5194
5523
|
);
|
|
5195
5524
|
const candidateCaptureStartedAt = Date.now();
|
|
5196
|
-
await this.clickCandidate(nextCandidate);
|
|
5197
|
-
const detailOpen = await this.ensureDetailOpen();
|
|
5525
|
+
await timeCandidateStage("click_candidate_ms", () => this.clickCandidate(nextCandidate));
|
|
5526
|
+
const detailOpen = await timeCandidateStage("detail_open_ms", () => this.ensureDetailOpen());
|
|
5198
5527
|
if (!detailOpen) {
|
|
5199
5528
|
throw this.buildError("DETAIL_OPEN_FAILED", "详情页打开超时");
|
|
5200
5529
|
}
|
|
5201
5530
|
|
|
5202
5531
|
let capture = null;
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5532
|
+
let networkCandidateInfo = await timeCandidateStage(
|
|
5533
|
+
"network_resume_wait_ms",
|
|
5534
|
+
() => this.waitForResumeNetworkByMode(nextCandidate, {
|
|
5535
|
+
minTs: candidateCaptureStartedAt
|
|
5536
|
+
})
|
|
5537
|
+
);
|
|
5208
5538
|
let domCandidateInfo = null;
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5539
|
+
|
|
5540
|
+
if (networkCandidateInfo?.resumeText) {
|
|
5541
|
+
screening = await timeCandidateStage(
|
|
5542
|
+
"text_model_ms",
|
|
5543
|
+
() => this.callTextModel(networkCandidateInfo.resumeText)
|
|
5544
|
+
);
|
|
5545
|
+
resumeSource = "network";
|
|
5546
|
+
resumeTextLength = normalizeText(networkCandidateInfo.resumeText).length;
|
|
5547
|
+
this.resumeSourceStats.network += 1;
|
|
5548
|
+
candidateProfile = mergeCandidateProfiles(
|
|
5549
|
+
networkCandidateInfo || null,
|
|
5550
|
+
cardProfile || null,
|
|
5220
5551
|
{
|
|
5221
|
-
|
|
5552
|
+
name: nextCandidate.name || "",
|
|
5553
|
+
school: nextCandidate.school || "",
|
|
5554
|
+
major: nextCandidate.major || "",
|
|
5555
|
+
company: nextCandidate.last_company || "",
|
|
5556
|
+
position: nextCandidate.last_position || ""
|
|
5222
5557
|
}
|
|
5223
5558
|
);
|
|
5224
|
-
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
});
|
|
5236
|
-
log(
|
|
5237
|
-
`[DOM简历疑似错位] candidate=${nextCandidate?.key || nextCandidate?.geek_id || "unknown"} ` +
|
|
5238
|
-
`card=${normalizeText(cardProfile?.name || "-")} dom=${normalizeText(domCandidateInfo?.name || "-")},尝试重试一次点击+监听。`
|
|
5559
|
+
} else {
|
|
5560
|
+
try {
|
|
5561
|
+
resumeSource = "image_fallback";
|
|
5562
|
+
capture = await timeCandidateStage(
|
|
5563
|
+
"image_capture_ms",
|
|
5564
|
+
() => this.captureResumeImage(nextCandidate)
|
|
5565
|
+
);
|
|
5566
|
+
this.setResumeAcquisitionMode("image", "image_capture_success");
|
|
5567
|
+
screening = await timeCandidateStage(
|
|
5568
|
+
"vision_model_ms",
|
|
5569
|
+
() => this.callVisionModel(capture.modelImagePaths || capture.stitchedImage)
|
|
5239
5570
|
);
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5571
|
+
this.resumeSourceStats.image_fallback += 1;
|
|
5572
|
+
} catch (imageFallbackError) {
|
|
5573
|
+
const lateNetworkCandidateInfo = await timeCandidateStage(
|
|
5574
|
+
"late_network_retry_ms",
|
|
5575
|
+
() => this.waitForLateNetworkResumeCandidateInfo(nextCandidate, {
|
|
5576
|
+
minTs: candidateCaptureStartedAt
|
|
5577
|
+
})
|
|
5578
|
+
);
|
|
5579
|
+
if (lateNetworkCandidateInfo?.resumeText) {
|
|
5580
|
+
networkCandidateInfo = lateNetworkCandidateInfo;
|
|
5581
|
+
screening = await timeCandidateStage(
|
|
5582
|
+
"text_model_ms",
|
|
5583
|
+
() => this.callTextModel(networkCandidateInfo.resumeText)
|
|
5584
|
+
);
|
|
5585
|
+
resumeSource = "network";
|
|
5586
|
+
resumeTextLength = normalizeText(networkCandidateInfo.resumeText).length;
|
|
5587
|
+
this.resumeSourceStats.network += 1;
|
|
5588
|
+
candidateProfile = mergeCandidateProfiles(
|
|
5589
|
+
networkCandidateInfo || null,
|
|
5590
|
+
cardProfile || null,
|
|
5591
|
+
{
|
|
5592
|
+
name: nextCandidate.name || "",
|
|
5593
|
+
school: nextCandidate.school || "",
|
|
5594
|
+
major: nextCandidate.major || "",
|
|
5595
|
+
company: nextCandidate.last_company || "",
|
|
5596
|
+
position: nextCandidate.last_position || ""
|
|
5597
|
+
}
|
|
5598
|
+
);
|
|
5599
|
+
} else {
|
|
5600
|
+
const domFallback = await timeCandidateStage(
|
|
5601
|
+
"dom_fallback_ms",
|
|
5602
|
+
() => this.resolveDomResumeFallback(nextCandidate, cardProfile || null)
|
|
5603
|
+
);
|
|
5604
|
+
if (domFallback?.networkCandidateInfo?.resumeText) {
|
|
5605
|
+
networkCandidateInfo = domFallback.networkCandidateInfo;
|
|
5606
|
+
screening = await timeCandidateStage(
|
|
5607
|
+
"text_model_ms",
|
|
5608
|
+
() => this.callTextModel(networkCandidateInfo.resumeText)
|
|
5249
5609
|
);
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5610
|
+
resumeSource = "network";
|
|
5611
|
+
resumeTextLength = normalizeText(networkCandidateInfo.resumeText).length;
|
|
5612
|
+
this.resumeSourceStats.network += 1;
|
|
5613
|
+
candidateProfile = mergeCandidateProfiles(
|
|
5614
|
+
networkCandidateInfo || null,
|
|
5615
|
+
cardProfile || null,
|
|
5616
|
+
{
|
|
5617
|
+
name: nextCandidate.name || "",
|
|
5618
|
+
school: nextCandidate.school || "",
|
|
5619
|
+
major: nextCandidate.major || "",
|
|
5620
|
+
company: nextCandidate.last_company || "",
|
|
5621
|
+
position: nextCandidate.last_position || ""
|
|
5256
5622
|
}
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5623
|
+
);
|
|
5624
|
+
} else if (domFallback?.domCandidateInfo?.resumeText) {
|
|
5625
|
+
domCandidateInfo = domFallback.domCandidateInfo;
|
|
5626
|
+
screening = await timeCandidateStage(
|
|
5627
|
+
"text_model_ms",
|
|
5628
|
+
() => this.callTextModel(domCandidateInfo.resumeText)
|
|
5629
|
+
);
|
|
5630
|
+
resumeSource = "dom_fallback";
|
|
5631
|
+
resumeTextLength = normalizeText(domCandidateInfo.resumeText).length;
|
|
5632
|
+
this.resumeSourceStats.dom_fallback += 1;
|
|
5633
|
+
candidateProfile = mergeCandidateProfiles(
|
|
5634
|
+
domCandidateInfo || null,
|
|
5635
|
+
cardProfile || null,
|
|
5636
|
+
{
|
|
5637
|
+
name: nextCandidate.name || "",
|
|
5638
|
+
school: nextCandidate.school || "",
|
|
5639
|
+
major: nextCandidate.major || "",
|
|
5640
|
+
company: nextCandidate.last_company || "",
|
|
5641
|
+
position: nextCandidate.last_position || ""
|
|
5642
|
+
}
|
|
5643
|
+
);
|
|
5260
5644
|
} else {
|
|
5261
|
-
|
|
5645
|
+
throw imageFallbackError;
|
|
5262
5646
|
}
|
|
5263
|
-
} catch (retryError) {
|
|
5264
|
-
domCandidateInfo = null;
|
|
5265
|
-
this.recordResumeNetworkDiagnostic({
|
|
5266
|
-
kind: "dom_profile_mismatch_retry_failed",
|
|
5267
|
-
candidate_key: normalizeText(nextCandidate?.key || nextCandidate?.geek_id || ""),
|
|
5268
|
-
error: normalizeText(retryError?.message || retryError)
|
|
5269
|
-
});
|
|
5270
5647
|
}
|
|
5271
5648
|
}
|
|
5272
5649
|
}
|
|
5273
|
-
const resumeCandidateInfo = networkCandidateInfo?.resumeText ? networkCandidateInfo : domCandidateInfo;
|
|
5274
|
-
candidateProfile = mergeCandidateProfiles(
|
|
5275
|
-
resumeCandidateInfo || null,
|
|
5276
|
-
cardProfile || null,
|
|
5277
|
-
{
|
|
5278
|
-
name: nextCandidate.name || "",
|
|
5279
|
-
school: nextCandidate.school || "",
|
|
5280
|
-
major: nextCandidate.major || "",
|
|
5281
|
-
company: nextCandidate.last_company || "",
|
|
5282
|
-
position: nextCandidate.last_position || ""
|
|
5283
|
-
}
|
|
5284
|
-
);
|
|
5285
|
-
|
|
5286
|
-
if (networkCandidateInfo?.resumeText) {
|
|
5287
|
-
screening = await this.callTextModel(networkCandidateInfo.resumeText);
|
|
5288
|
-
resumeSource = "network";
|
|
5289
|
-
resumeTextLength = normalizeText(networkCandidateInfo.resumeText).length;
|
|
5290
|
-
this.resumeSourceStats.network += 1;
|
|
5291
|
-
} else if (domCandidateInfo?.resumeText) {
|
|
5292
|
-
screening = await this.callTextModel(domCandidateInfo.resumeText);
|
|
5293
|
-
resumeSource = "dom_fallback";
|
|
5294
|
-
resumeTextLength = normalizeText(domCandidateInfo.resumeText).length;
|
|
5295
|
-
this.resumeSourceStats.dom_fallback += 1;
|
|
5296
|
-
} else {
|
|
5297
|
-
resumeSource = "image_fallback";
|
|
5298
|
-
capture = await this.captureResumeImage(nextCandidate);
|
|
5299
|
-
screening = await this.callVisionModel(capture.stitchedImage);
|
|
5300
|
-
this.resumeSourceStats.image_fallback += 1;
|
|
5301
|
-
}
|
|
5302
5650
|
this.resetResumeCaptureFailureStreak();
|
|
5303
5651
|
log(`筛选结果: ${screening.passed ? "通过" : "不通过"}`);
|
|
5304
5652
|
|
|
@@ -5315,13 +5663,16 @@ class RecommendScreenCli {
|
|
|
5315
5663
|
}
|
|
5316
5664
|
let actionResult = { actionTaken: "none" };
|
|
5317
5665
|
try {
|
|
5318
|
-
actionResult =
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
:
|
|
5666
|
+
actionResult = await timeCandidateStage(
|
|
5667
|
+
"post_action_ms",
|
|
5668
|
+
() => effectiveAction === "favorite"
|
|
5669
|
+
? this.favoriteCandidate({
|
|
5670
|
+
alreadyInterested: networkCandidateInfo?.alreadyInterested === true
|
|
5671
|
+
})
|
|
5672
|
+
: effectiveAction === "greet"
|
|
5673
|
+
? this.greetCandidate()
|
|
5674
|
+
: Promise.resolve({ actionTaken: "none" })
|
|
5675
|
+
);
|
|
5325
5676
|
} catch (postActionError) {
|
|
5326
5677
|
if (!isRecoverablePostActionError(postActionError, effectiveAction)) {
|
|
5327
5678
|
throw postActionError;
|
|
@@ -5354,7 +5705,7 @@ class RecommendScreenCli {
|
|
|
5354
5705
|
action: actionResult.actionTaken,
|
|
5355
5706
|
geekId: nextCandidate.geek_id,
|
|
5356
5707
|
summary: screening.summary,
|
|
5357
|
-
imagePath: capture?.stitchedImage || "",
|
|
5708
|
+
imagePath: capture?.stitchedImage || capture?.modelImagePaths?.[0] || capture?.chunkFiles?.[0] || "",
|
|
5358
5709
|
resumeSource
|
|
5359
5710
|
});
|
|
5360
5711
|
this.recordCandidateAudit({
|
|
@@ -5486,7 +5837,7 @@ class RecommendScreenCli {
|
|
|
5486
5837
|
);
|
|
5487
5838
|
}
|
|
5488
5839
|
} finally {
|
|
5489
|
-
const closed = await this.closeDetailPage();
|
|
5840
|
+
const closed = await timeCandidateStage("close_detail_ms", () => this.closeDetailPage());
|
|
5490
5841
|
if (!closed) {
|
|
5491
5842
|
if (allowDetailCloseFailure) {
|
|
5492
5843
|
log("[详情关闭兜底] 本候选人 post_action 失败后详情页关闭未确认,已记录错误并继续下一位候选人。");
|
|
@@ -5499,12 +5850,31 @@ class RecommendScreenCli {
|
|
|
5499
5850
|
}
|
|
5500
5851
|
}
|
|
5501
5852
|
|
|
5502
|
-
await this.takeBreakIfNeeded();
|
|
5853
|
+
await timeCandidateStage("rest_ms", () => this.takeBreakIfNeeded());
|
|
5854
|
+
candidateTiming.total_ms = Math.max(0, Date.now() - candidateStartedAt);
|
|
5855
|
+
this.updateCandidateAuditTiming(candidateKeyForTiming, candidateTiming);
|
|
5503
5856
|
try {
|
|
5504
|
-
this.saveCheckpoint();
|
|
5857
|
+
timeCandidateStageSync("checkpoint_save_ms", () => this.saveCheckpoint());
|
|
5858
|
+
candidateTiming.total_ms = Math.max(0, Date.now() - candidateStartedAt);
|
|
5859
|
+
this.updateCandidateAuditTiming(candidateKeyForTiming, candidateTiming);
|
|
5505
5860
|
} catch (checkpointError) {
|
|
5506
5861
|
log(`[保存checkpoint失败] ${checkpointError.message || checkpointError}`);
|
|
5507
5862
|
}
|
|
5863
|
+
try {
|
|
5864
|
+
this.saveCsv();
|
|
5865
|
+
} catch (csvError) {
|
|
5866
|
+
log(`[增量保存CSV失败] ${csvError.message || csvError}`);
|
|
5867
|
+
}
|
|
5868
|
+
log(
|
|
5869
|
+
`[TIMING] candidate=${candidateKeyForTiming || nextCandidate.name || "unknown"} ` +
|
|
5870
|
+
`total_ms=${candidateTiming.total_ms ?? ""} ` +
|
|
5871
|
+
`network_ms=${candidateTiming.network_resume_wait_ms ?? 0} ` +
|
|
5872
|
+
`text_model_ms=${candidateTiming.text_model_ms ?? 0} ` +
|
|
5873
|
+
`image_capture_ms=${candidateTiming.image_capture_ms ?? 0} ` +
|
|
5874
|
+
`vision_model_ms=${candidateTiming.vision_model_ms ?? 0} ` +
|
|
5875
|
+
`post_action_ms=${candidateTiming.post_action_ms ?? 0} ` +
|
|
5876
|
+
`close_ms=${candidateTiming.close_detail_ms ?? 0}`
|
|
5877
|
+
);
|
|
5508
5878
|
}
|
|
5509
5879
|
|
|
5510
5880
|
if (this.args.targetCount && this.passedCandidates.length < this.args.targetCount) {
|
|
@@ -5565,7 +5935,7 @@ async function main() {
|
|
|
5565
5935
|
console.log(JSON.stringify({
|
|
5566
5936
|
status: "COMPLETED",
|
|
5567
5937
|
result: {
|
|
5568
|
-
usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --page-scope recommend|latest|featured --calibration <favorite-calibration.json> --port 9222 --output <csv-path> [--input-summary-json <json>] --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
|
|
5938
|
+
usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --thinking-level off|low|medium|high|current --page-scope recommend|latest|featured --calibration <favorite-calibration.json> --port 9222 --output <csv-path> [--input-summary-json <json>] --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
|
|
5569
5939
|
}
|
|
5570
5940
|
}));
|
|
5571
5941
|
return;
|
|
@@ -5605,6 +5975,8 @@ if (require.main === module) {
|
|
|
5605
5975
|
MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES,
|
|
5606
5976
|
RESUME_CAPTURE_MAX_ATTEMPTS,
|
|
5607
5977
|
RESUME_CAPTURE_WAIT_MS,
|
|
5978
|
+
NETWORK_RESUME_IMAGE_MODE_GRACE_MS,
|
|
5979
|
+
NETWORK_RESUME_LATE_RETRY_MS,
|
|
5608
5980
|
parseFavoriteActionFromPostData,
|
|
5609
5981
|
parseFavoriteActionFromRequest,
|
|
5610
5982
|
parseFavoriteActionFromKnownRequest,
|