@reconcrap/boss-recommend-mcp 1.2.2 → 1.2.3
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.
|
File without changes
|
package/package.json
CHANGED
|
@@ -690,6 +690,121 @@ function parseGeekIdFromUrl(url) {
|
|
|
690
690
|
return null;
|
|
691
691
|
}
|
|
692
692
|
|
|
693
|
+
function collectGeekIdsFromPayload(payload, fallbackGeekId = null) {
|
|
694
|
+
if (!payload || typeof payload !== "object") return [];
|
|
695
|
+
const geekDetail = payload?.geekDetail || payload;
|
|
696
|
+
const baseInfo = geekDetail?.geekBaseInfo || {};
|
|
697
|
+
const ids = [
|
|
698
|
+
fallbackGeekId,
|
|
699
|
+
baseInfo.geekId,
|
|
700
|
+
baseInfo.encryptGeekId,
|
|
701
|
+
baseInfo.securityId,
|
|
702
|
+
geekDetail?.geekId,
|
|
703
|
+
geekDetail?.encryptGeekId,
|
|
704
|
+
geekDetail?.securityId,
|
|
705
|
+
payload?.geekId,
|
|
706
|
+
payload?.encryptGeekId,
|
|
707
|
+
payload?.securityId
|
|
708
|
+
].map((value) => normalizeText(value)).filter(Boolean);
|
|
709
|
+
return Array.from(new Set(ids));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function hasResumePayloadShape(payload) {
|
|
713
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
714
|
+
const geekDetail = payload?.geekDetail && typeof payload.geekDetail === "object"
|
|
715
|
+
? payload.geekDetail
|
|
716
|
+
: payload;
|
|
717
|
+
const baseInfo = geekDetail?.geekBaseInfo || {};
|
|
718
|
+
const hasIdentity = Boolean(
|
|
719
|
+
normalizeText(
|
|
720
|
+
baseInfo?.name
|
|
721
|
+
|| geekDetail?.geekName
|
|
722
|
+
|| payload?.geekName
|
|
723
|
+
|| baseInfo?.geekId
|
|
724
|
+
|| baseInfo?.encryptGeekId
|
|
725
|
+
|| baseInfo?.securityId
|
|
726
|
+
|| geekDetail?.geekId
|
|
727
|
+
|| geekDetail?.encryptGeekId
|
|
728
|
+
|| geekDetail?.securityId
|
|
729
|
+
|| payload?.geekId
|
|
730
|
+
|| payload?.encryptGeekId
|
|
731
|
+
|| payload?.securityId
|
|
732
|
+
|| ""
|
|
733
|
+
)
|
|
734
|
+
);
|
|
735
|
+
const hasResumeSections = [
|
|
736
|
+
geekDetail?.geekExpectList,
|
|
737
|
+
geekDetail?.geekWorkExpList,
|
|
738
|
+
geekDetail?.geekProjExpList,
|
|
739
|
+
geekDetail?.geekEduExpList,
|
|
740
|
+
geekDetail?.geekEducationList,
|
|
741
|
+
geekDetail?.geekSkillList
|
|
742
|
+
].some((section) => Array.isArray(section) && section.length > 0);
|
|
743
|
+
const hasResumeTextFields = Boolean(
|
|
744
|
+
normalizeText(
|
|
745
|
+
geekDetail?.geekAdvantage
|
|
746
|
+
|| baseInfo?.userDesc
|
|
747
|
+
|| baseInfo?.userDescription
|
|
748
|
+
|| ""
|
|
749
|
+
)
|
|
750
|
+
);
|
|
751
|
+
return hasIdentity && (hasResumeSections || hasResumeTextFields);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function findResumePayloadInObject(root, maxDepth = 4, visited = new Set()) {
|
|
755
|
+
if (root === null || root === undefined || maxDepth < 0) return null;
|
|
756
|
+
if (typeof root !== "object") return null;
|
|
757
|
+
if (visited.has(root)) return null;
|
|
758
|
+
visited.add(root);
|
|
759
|
+
|
|
760
|
+
if (hasResumePayloadShape(root)) {
|
|
761
|
+
return root;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (maxDepth === 0) return null;
|
|
765
|
+
|
|
766
|
+
if (Array.isArray(root)) {
|
|
767
|
+
for (const item of root) {
|
|
768
|
+
const found = findResumePayloadInObject(item, maxDepth - 1, visited);
|
|
769
|
+
if (found) return found;
|
|
770
|
+
}
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const priorityKeys = [
|
|
775
|
+
"zpData",
|
|
776
|
+
"data",
|
|
777
|
+
"result",
|
|
778
|
+
"geekDetail",
|
|
779
|
+
"detail",
|
|
780
|
+
"info"
|
|
781
|
+
];
|
|
782
|
+
for (const key of priorityKeys) {
|
|
783
|
+
if (!(key in root)) continue;
|
|
784
|
+
const found = findResumePayloadInObject(root[key], maxDepth - 1, visited);
|
|
785
|
+
if (found) return found;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
for (const value of Object.values(root)) {
|
|
789
|
+
const found = findResumePayloadInObject(value, maxDepth - 1, visited);
|
|
790
|
+
if (found) return found;
|
|
791
|
+
}
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function extractResumePayloadFromResponseBody(parsedBody) {
|
|
796
|
+
return findResumePayloadInObject(parsedBody, 4) || null;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function isResumeInfoRequestUrl(url) {
|
|
800
|
+
const normalizedUrl = normalizeText(url).toLowerCase();
|
|
801
|
+
if (!normalizedUrl || !normalizedUrl.includes("/wapi/")) return false;
|
|
802
|
+
if (!normalizedUrl.includes("geek") || !normalizedUrl.includes("info")) return false;
|
|
803
|
+
if (/\/boss\/[^?#]*\/geek\/info\b/.test(normalizedUrl)) return true;
|
|
804
|
+
if (/\/geek\/info\b/.test(normalizedUrl)) return true;
|
|
805
|
+
return /[?&](?:geekid|geek_id|encryptgeekid|securityid)=/.test(normalizedUrl);
|
|
806
|
+
}
|
|
807
|
+
|
|
693
808
|
function formatResumeApiData(data) {
|
|
694
809
|
const parts = [];
|
|
695
810
|
const geekDetail = data?.geekDetail || data || {};
|
|
@@ -2074,14 +2189,8 @@ class RecommendScreenCli {
|
|
|
2074
2189
|
if (!payload || typeof payload !== "object") return;
|
|
2075
2190
|
const geekDetail = payload.geekDetail || payload;
|
|
2076
2191
|
const baseInfo = geekDetail.geekBaseInfo || {};
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|| baseInfo.geekId
|
|
2080
|
-
|| baseInfo.encryptGeekId
|
|
2081
|
-
|| geekDetail.geekId
|
|
2082
|
-
|| payload.geekId
|
|
2083
|
-
|| ""
|
|
2084
|
-
) || null;
|
|
2192
|
+
const geekIds = collectGeekIdsFromPayload(payload, fallbackGeekId);
|
|
2193
|
+
const geekId = geekIds[0] || null;
|
|
2085
2194
|
const candidateInfo = {
|
|
2086
2195
|
name: baseInfo.name || geekDetail.geekName || payload.geekName || "",
|
|
2087
2196
|
school: (geekDetail.geekEduExpList && geekDetail.geekEduExpList[0]?.school)
|
|
@@ -2093,17 +2202,18 @@ class RecommendScreenCli {
|
|
|
2093
2202
|
company: (geekDetail.geekWorkExpList && geekDetail.geekWorkExpList[0]?.company) || "",
|
|
2094
2203
|
position: (geekDetail.geekWorkExpList && geekDetail.geekWorkExpList[0]?.positionName) || "",
|
|
2095
2204
|
resumeText: formatResumeApiData(payload),
|
|
2096
|
-
alreadyInterested: payload.alreadyInterested === true
|
|
2205
|
+
alreadyInterested: payload.alreadyInterested === true || geekDetail.alreadyInterested === true
|
|
2097
2206
|
};
|
|
2098
2207
|
const wrapped = {
|
|
2099
2208
|
ts: Date.now(),
|
|
2100
2209
|
geekId: geekId || null,
|
|
2210
|
+
geekIds,
|
|
2101
2211
|
data: payload,
|
|
2102
2212
|
candidateInfo
|
|
2103
2213
|
};
|
|
2104
2214
|
this.latestResumeNetworkPayload = wrapped;
|
|
2105
|
-
|
|
2106
|
-
this.resumeNetworkByGeekId.set(
|
|
2215
|
+
for (const id of geekIds) {
|
|
2216
|
+
this.resumeNetworkByGeekId.set(id, wrapped);
|
|
2107
2217
|
}
|
|
2108
2218
|
}
|
|
2109
2219
|
|
|
@@ -2136,7 +2246,7 @@ class RecommendScreenCli {
|
|
|
2136
2246
|
handleNetworkRequestWillBeSent(params) {
|
|
2137
2247
|
const url = normalizeText(params?.request?.url || "");
|
|
2138
2248
|
if (!url) return;
|
|
2139
|
-
if (url
|
|
2249
|
+
if (isResumeInfoRequestUrl(url)) {
|
|
2140
2250
|
const geekId = parseGeekIdFromUrl(url);
|
|
2141
2251
|
this.resumeNetworkRequests.set(params.requestId, {
|
|
2142
2252
|
ts: Date.now(),
|
|
@@ -2206,9 +2316,13 @@ class RecommendScreenCli {
|
|
|
2206
2316
|
try {
|
|
2207
2317
|
const responseBody = await this.Network.getResponseBody({ requestId: params.requestId });
|
|
2208
2318
|
if (!responseBody?.body) return;
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2211
|
-
|
|
2319
|
+
const rawBody = responseBody.base64Encoded
|
|
2320
|
+
? Buffer.from(responseBody.body, "base64").toString("utf8")
|
|
2321
|
+
: responseBody.body;
|
|
2322
|
+
const parsed = JSON.parse(rawBody);
|
|
2323
|
+
const resumePayload = extractResumePayloadFromResponseBody(parsed);
|
|
2324
|
+
if (resumePayload) {
|
|
2325
|
+
this.cacheResumeNetworkPayload(resumePayload, requestMeta.geekId);
|
|
2212
2326
|
}
|
|
2213
2327
|
} catch {}
|
|
2214
2328
|
}
|
|
@@ -3457,10 +3571,11 @@ class RecommendScreenCli {
|
|
|
3457
3571
|
}
|
|
3458
3572
|
|
|
3459
3573
|
const isFeaturedScope = this.args.pageScope === "featured";
|
|
3574
|
+
const allowImageFallback = !isFeaturedScope;
|
|
3460
3575
|
let capture = null;
|
|
3461
3576
|
let screening = null;
|
|
3462
3577
|
let resumeSource = isFeaturedScope ? "network" : "image_fallback";
|
|
3463
|
-
const networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(nextCandidate, 2400);
|
|
3578
|
+
const networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(nextCandidate, allowImageFallback ? 4200 : 2400);
|
|
3464
3579
|
const candidateProfile = {
|
|
3465
3580
|
name: networkCandidateInfo?.name || nextCandidate.name || "",
|
|
3466
3581
|
school: networkCandidateInfo?.school || nextCandidate.school || "",
|
|
@@ -3479,6 +3594,10 @@ class RecommendScreenCli {
|
|
|
3479
3594
|
screening = await this.callTextModel(networkCandidateInfo.resumeText);
|
|
3480
3595
|
resumeSource = "network";
|
|
3481
3596
|
this.resumeSourceStats.network += 1;
|
|
3597
|
+
} else if (networkCandidateInfo?.resumeText) {
|
|
3598
|
+
screening = await this.callTextModel(networkCandidateInfo.resumeText);
|
|
3599
|
+
resumeSource = "network";
|
|
3600
|
+
this.resumeSourceStats.network += 1;
|
|
3482
3601
|
} else {
|
|
3483
3602
|
capture = await this.captureResumeImage(nextCandidate);
|
|
3484
3603
|
screening = await this.callVisionModel(capture.stitchedImage);
|
|
@@ -338,29 +338,28 @@ async function testFeaturedShouldUseNetworkResumeOnly() {
|
|
|
338
338
|
assert.equal(result.result.resume_source, "network");
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
async function
|
|
342
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-recommend-
|
|
343
|
-
const candidate = { key: "
|
|
341
|
+
async function testRecommendShouldPreferNetworkResumeWhenAvailable() {
|
|
342
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-recommend-network-main-"));
|
|
343
|
+
const candidate = { key: "net-main-1", geek_id: "net-main-1", name: "recommend network main candidate" };
|
|
344
344
|
const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
|
|
345
|
-
candidates: [candidate]
|
|
346
|
-
captureOutcomes: new Map([
|
|
347
|
-
["img-main-1", { stitchedImage: path.join(tempDir, "img-main-1.png") }]
|
|
348
|
-
]),
|
|
349
|
-
screeningByKey: new Map([
|
|
350
|
-
["img-main-1", { passed: true, reason: "image path used", summary: "image path used" }]
|
|
351
|
-
])
|
|
345
|
+
candidates: [candidate]
|
|
352
346
|
});
|
|
353
347
|
cli.waitForNetworkResumeCandidateInfo = async () => ({
|
|
354
|
-
resumeText: "这段 network 文本在 recommend
|
|
348
|
+
resumeText: "这段 network 文本在 recommend 页面应优先用于筛选"
|
|
355
349
|
});
|
|
356
|
-
cli.callTextModel = async () => {
|
|
357
|
-
|
|
350
|
+
cli.callTextModel = async () => ({
|
|
351
|
+
passed: true,
|
|
352
|
+
reason: "network used",
|
|
353
|
+
summary: "network used"
|
|
354
|
+
});
|
|
355
|
+
cli.captureResumeImage = async () => {
|
|
356
|
+
throw new Error("capture should not be called when recommend network resume exists");
|
|
358
357
|
};
|
|
359
358
|
|
|
360
359
|
const result = await cli.run();
|
|
361
360
|
assert.equal(result.status, "COMPLETED");
|
|
362
361
|
assert.equal(result.result.passed_count, 1);
|
|
363
|
-
assert.equal(result.result.resume_source, "
|
|
362
|
+
assert.equal(result.result.resume_source, "network");
|
|
364
363
|
}
|
|
365
364
|
|
|
366
365
|
async function testNetworkMissShouldFallbackToImageCapture() {
|
|
@@ -382,6 +381,53 @@ async function testNetworkMissShouldFallbackToImageCapture() {
|
|
|
382
381
|
assert.equal(result.result.resume_source, "image_fallback");
|
|
383
382
|
}
|
|
384
383
|
|
|
384
|
+
async function testLatestShouldPreferNetworkResumeWhenAvailable() {
|
|
385
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-network-main-"));
|
|
386
|
+
const args = createArgs(tempDir);
|
|
387
|
+
args.pageScope = "latest";
|
|
388
|
+
const candidate = { key: "latest-net-1", geek_id: "latest-net-1", name: "latest network candidate" };
|
|
389
|
+
const cli = new FakeRecommendScreenCli(args, {
|
|
390
|
+
candidates: [candidate]
|
|
391
|
+
});
|
|
392
|
+
cli.waitForNetworkResumeCandidateInfo = async () => ({
|
|
393
|
+
resumeText: "最新页 network 简历可用"
|
|
394
|
+
});
|
|
395
|
+
cli.callTextModel = async () => ({
|
|
396
|
+
passed: true,
|
|
397
|
+
reason: "network used",
|
|
398
|
+
summary: "network used"
|
|
399
|
+
});
|
|
400
|
+
cli.captureResumeImage = async () => {
|
|
401
|
+
throw new Error("capture should not be called when latest network resume exists");
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const result = await cli.run();
|
|
405
|
+
assert.equal(result.status, "COMPLETED");
|
|
406
|
+
assert.equal(result.result.passed_count, 1);
|
|
407
|
+
assert.equal(result.result.resume_source, "network");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function testLatestNetworkMissShouldFallbackToImageCapture() {
|
|
411
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-network-fallback-"));
|
|
412
|
+
const args = createArgs(tempDir);
|
|
413
|
+
args.pageScope = "latest";
|
|
414
|
+
const candidate = { key: "latest-img-1", geek_id: "latest-img-1", name: "latest image candidate" };
|
|
415
|
+
const cli = new FakeRecommendScreenCli(args, {
|
|
416
|
+
candidates: [candidate],
|
|
417
|
+
captureOutcomes: new Map([
|
|
418
|
+
["latest-img-1", { stitchedImage: path.join(tempDir, "latest-img-1.png") }]
|
|
419
|
+
]),
|
|
420
|
+
screeningByKey: new Map([
|
|
421
|
+
["latest-img-1", { passed: false, reason: "image fallback used", summary: "image fallback used" }]
|
|
422
|
+
])
|
|
423
|
+
});
|
|
424
|
+
cli.waitForNetworkResumeCandidateInfo = async () => null;
|
|
425
|
+
|
|
426
|
+
const result = await cli.run();
|
|
427
|
+
assert.equal(result.status, "COMPLETED");
|
|
428
|
+
assert.equal(result.result.resume_source, "image_fallback");
|
|
429
|
+
}
|
|
430
|
+
|
|
385
431
|
async function testVisionModelFailureShouldSkipCandidateAndContinue() {
|
|
386
432
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-vision-failure-skip-"));
|
|
387
433
|
const first = { key: "vision-fail-1", geek_id: "vision-fail-1", name: "vision-fail-1" };
|
|
@@ -803,8 +849,10 @@ async function main() {
|
|
|
803
849
|
await testPageExhaustedBeforeTargetShouldRaiseRecoverableError();
|
|
804
850
|
await testPageExhaustedWithoutTargetShouldStillComplete();
|
|
805
851
|
await testFeaturedShouldUseNetworkResumeOnly();
|
|
806
|
-
await
|
|
852
|
+
await testRecommendShouldPreferNetworkResumeWhenAvailable();
|
|
807
853
|
await testNetworkMissShouldFallbackToImageCapture();
|
|
854
|
+
await testLatestShouldPreferNetworkResumeWhenAvailable();
|
|
855
|
+
await testLatestNetworkMissShouldFallbackToImageCapture();
|
|
808
856
|
await testVisionModelFailureShouldSkipCandidateAndContinue();
|
|
809
857
|
await testFeaturedNetworkMissShouldSkipWithoutImageCapture();
|
|
810
858
|
await testFeaturedFavoriteShouldNotUseDomFallback();
|