@reconcrap/boss-recommend-mcp 1.3.15 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.15",
3
+ "version": "1.3.16",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -3305,6 +3305,60 @@ class RecommendScreenCli {
3305
3305
  return info;
3306
3306
  }
3307
3307
 
3308
+ async resolveDomResumeFallback(candidate, cardProfile) {
3309
+ let domCandidateInfo = await this.extractResumeTextFromDom(candidate);
3310
+ let networkCandidateInfo = null;
3311
+ if (domCandidateInfo && !isDomProfileConsistentWithCard(cardProfile, domCandidateInfo)) {
3312
+ this.recordResumeNetworkDiagnostic({
3313
+ kind: "dom_profile_mismatch",
3314
+ candidate_key: normalizeText(candidate?.key || candidate?.geek_id || ""),
3315
+ card_name: normalizeText(cardProfile?.name || ""),
3316
+ dom_name: normalizeText(domCandidateInfo?.name || ""),
3317
+ card_school: normalizeText(cardProfile?.school || ""),
3318
+ dom_school: normalizeText(domCandidateInfo?.school || "")
3319
+ });
3320
+ log(
3321
+ `[DOM简历疑似错位] candidate=${candidate?.key || candidate?.geek_id || "unknown"} ` +
3322
+ `card=${normalizeText(cardProfile?.name || "-")} dom=${normalizeText(domCandidateInfo?.name || "-")},尝试重试一次点击+监听。`
3323
+ );
3324
+ try {
3325
+ const retryCaptureStartedAt = Date.now();
3326
+ await this.clickCandidate(candidate);
3327
+ const retryDetailOpen = await this.ensureDetailOpen();
3328
+ if (retryDetailOpen) {
3329
+ networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(
3330
+ candidate,
3331
+ NETWORK_RESUME_RETRY_WAIT_MS,
3332
+ { minTs: retryCaptureStartedAt }
3333
+ );
3334
+ if (!normalizeText(networkCandidateInfo?.resumeText)) {
3335
+ const retryDomCandidateInfo = await this.extractResumeTextFromDom(candidate);
3336
+ if (retryDomCandidateInfo && isDomProfileConsistentWithCard(cardProfile, retryDomCandidateInfo)) {
3337
+ domCandidateInfo = retryDomCandidateInfo;
3338
+ } else {
3339
+ domCandidateInfo = null;
3340
+ }
3341
+ } else {
3342
+ domCandidateInfo = null;
3343
+ }
3344
+ } else {
3345
+ domCandidateInfo = null;
3346
+ }
3347
+ } catch (retryError) {
3348
+ domCandidateInfo = null;
3349
+ this.recordResumeNetworkDiagnostic({
3350
+ kind: "dom_profile_mismatch_retry_failed",
3351
+ candidate_key: normalizeText(candidate?.key || candidate?.geek_id || ""),
3352
+ error: normalizeText(retryError?.message || retryError)
3353
+ });
3354
+ }
3355
+ }
3356
+ return {
3357
+ domCandidateInfo,
3358
+ networkCandidateInfo
3359
+ };
3360
+ }
3361
+
3308
3362
  handleNetworkRequestWillBeSent(params) {
3309
3363
  const url = normalizeText(params?.request?.url || "");
3310
3364
  const postData = params?.request?.postData || "";
@@ -5222,82 +5276,69 @@ class RecommendScreenCli {
5222
5276
  }
5223
5277
  );
5224
5278
  }
5225
- if (!normalizeText(networkCandidateInfo?.resumeText)) {
5226
- domCandidateInfo = await this.extractResumeTextFromDom(nextCandidate);
5227
- if (domCandidateInfo && !isDomProfileConsistentWithCard(cardProfile, domCandidateInfo)) {
5228
- this.recordResumeNetworkDiagnostic({
5229
- kind: "dom_profile_mismatch",
5230
- candidate_key: normalizeText(nextCandidate?.key || nextCandidate?.geek_id || ""),
5231
- card_name: normalizeText(cardProfile?.name || ""),
5232
- dom_name: normalizeText(domCandidateInfo?.name || ""),
5233
- card_school: normalizeText(cardProfile?.school || ""),
5234
- dom_school: normalizeText(domCandidateInfo?.school || "")
5235
- });
5236
- log(
5237
- `[DOM简历疑似错位] candidate=${nextCandidate?.key || nextCandidate?.geek_id || "unknown"} ` +
5238
- `card=${normalizeText(cardProfile?.name || "-")} dom=${normalizeText(domCandidateInfo?.name || "-")},尝试重试一次点击+监听。`
5239
- );
5240
- try {
5241
- const retryCaptureStartedAt = Date.now();
5242
- await this.clickCandidate(nextCandidate);
5243
- const retryDetailOpen = await this.ensureDetailOpen();
5244
- if (retryDetailOpen) {
5245
- networkCandidateInfo = await this.waitForNetworkResumeCandidateInfo(
5246
- nextCandidate,
5247
- NETWORK_RESUME_RETRY_WAIT_MS,
5248
- { minTs: retryCaptureStartedAt }
5249
- );
5250
- if (!normalizeText(networkCandidateInfo?.resumeText)) {
5251
- const retryDomCandidateInfo = await this.extractResumeTextFromDom(nextCandidate);
5252
- if (retryDomCandidateInfo && isDomProfileConsistentWithCard(cardProfile, retryDomCandidateInfo)) {
5253
- domCandidateInfo = retryDomCandidateInfo;
5254
- } else {
5255
- domCandidateInfo = null;
5256
- }
5257
- } else {
5258
- domCandidateInfo = null;
5259
- }
5260
- } else {
5261
- domCandidateInfo = null;
5262
- }
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
- }
5271
- }
5272
- }
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
5279
 
5286
5280
  if (networkCandidateInfo?.resumeText) {
5287
5281
  screening = await this.callTextModel(networkCandidateInfo.resumeText);
5288
5282
  resumeSource = "network";
5289
5283
  resumeTextLength = normalizeText(networkCandidateInfo.resumeText).length;
5290
5284
  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;
5285
+ candidateProfile = mergeCandidateProfiles(
5286
+ networkCandidateInfo || null,
5287
+ cardProfile || null,
5288
+ {
5289
+ name: nextCandidate.name || "",
5290
+ school: nextCandidate.school || "",
5291
+ major: nextCandidate.major || "",
5292
+ company: nextCandidate.last_company || "",
5293
+ position: nextCandidate.last_position || ""
5294
+ }
5295
+ );
5296
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;
5297
+ try {
5298
+ resumeSource = "image_fallback";
5299
+ capture = await this.captureResumeImage(nextCandidate);
5300
+ screening = await this.callVisionModel(capture.stitchedImage);
5301
+ this.resumeSourceStats.image_fallback += 1;
5302
+ } catch (imageFallbackError) {
5303
+ const domFallback = await this.resolveDomResumeFallback(nextCandidate, cardProfile || null);
5304
+ if (domFallback?.networkCandidateInfo?.resumeText) {
5305
+ networkCandidateInfo = domFallback.networkCandidateInfo;
5306
+ screening = await this.callTextModel(networkCandidateInfo.resumeText);
5307
+ resumeSource = "network";
5308
+ resumeTextLength = normalizeText(networkCandidateInfo.resumeText).length;
5309
+ this.resumeSourceStats.network += 1;
5310
+ candidateProfile = mergeCandidateProfiles(
5311
+ networkCandidateInfo || null,
5312
+ cardProfile || null,
5313
+ {
5314
+ name: nextCandidate.name || "",
5315
+ school: nextCandidate.school || "",
5316
+ major: nextCandidate.major || "",
5317
+ company: nextCandidate.last_company || "",
5318
+ position: nextCandidate.last_position || ""
5319
+ }
5320
+ );
5321
+ } else if (domFallback?.domCandidateInfo?.resumeText) {
5322
+ domCandidateInfo = domFallback.domCandidateInfo;
5323
+ screening = await this.callTextModel(domCandidateInfo.resumeText);
5324
+ resumeSource = "dom_fallback";
5325
+ resumeTextLength = normalizeText(domCandidateInfo.resumeText).length;
5326
+ this.resumeSourceStats.dom_fallback += 1;
5327
+ candidateProfile = mergeCandidateProfiles(
5328
+ domCandidateInfo || null,
5329
+ cardProfile || null,
5330
+ {
5331
+ name: nextCandidate.name || "",
5332
+ school: nextCandidate.school || "",
5333
+ major: nextCandidate.major || "",
5334
+ company: nextCandidate.last_company || "",
5335
+ position: nextCandidate.last_position || ""
5336
+ }
5337
+ );
5338
+ } else {
5339
+ throw imageFallbackError;
5340
+ }
5341
+ }
5301
5342
  }
5302
5343
  this.resetResumeCaptureFailureStreak();
5303
5344
  log(`筛选结果: ${screening.passed ? "通过" : "不通过"}`);
@@ -467,7 +467,7 @@ async function testRecommendShouldPreferNetworkResumeWhenAvailable() {
467
467
  assert.equal(result.result.resume_source, "network");
468
468
  }
469
469
 
470
- async function testNetworkMissShouldFallbackToDomBeforeImageCapture() {
470
+ async function testNetworkMissShouldFallbackToImageThenDom() {
471
471
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-network-dom-fallback-"));
472
472
  const candidate = { key: "dom-1", geek_id: "dom-1", name: "dom candidate" };
473
473
  const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
@@ -485,13 +485,15 @@ async function testNetworkMissShouldFallbackToDomBeforeImageCapture() {
485
485
  });
486
486
 
487
487
  cli.waitForNetworkResumeCandidateInfo = async () => null;
488
+ let captureAttempted = false;
488
489
  cli.callTextModel = async (resumeText) => ({
489
490
  passed: true,
490
491
  reason: resumeText.includes("华中科技大学") ? "dom fallback used" : "unexpected",
491
492
  summary: "dom fallback used"
492
493
  });
493
494
  cli.captureResumeImage = async () => {
494
- throw new Error("capture should not be called when dom fallback resume exists");
495
+ captureAttempted = true;
496
+ throw new Error("capture failed, should fallback to dom");
495
497
  };
496
498
 
497
499
  const result = await cli.run();
@@ -501,6 +503,7 @@ async function testNetworkMissShouldFallbackToDomBeforeImageCapture() {
501
503
  assert.equal(cli.passedCandidates.length, 1);
502
504
  assert.equal(cli.passedCandidates[0].school, "华中科技大学");
503
505
  assert.equal(cli.passedCandidates[0].resumeSource, "dom_fallback");
506
+ assert.equal(captureAttempted, true);
504
507
  }
505
508
 
506
509
  async function testNetworkMissShouldFallbackToImageCapture() {
@@ -600,7 +603,7 @@ function testLatestPayloadShouldRemainAvailableWhenCandidateKeyMissing() {
600
603
  key: "",
601
604
  geek_id: ""
602
605
  });
603
- assert.equal(extracted?.resumeText, "recent resume payload");
606
+ assert.equal(extracted?.candidateInfo?.resumeText, "recent resume payload");
604
607
  }
605
608
 
606
609
  async function testVisionModelFailureShouldSkipCandidateAndContinue() {
@@ -638,25 +641,44 @@ async function testVisionModelFailureShouldSkipCandidateAndContinue() {
638
641
  assert.equal(result.result.skipped_count, 1);
639
642
  }
640
643
 
641
- async function testFeaturedNetworkMissShouldSkipWithoutImageCapture() {
644
+ async function testFeaturedNetworkMissShouldFallbackToDomAfterImageFailure() {
642
645
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-network-only-"));
643
646
  const args = createArgs(tempDir);
644
647
  args.pageScope = "featured";
645
648
  const candidate = { key: "featured-no-network", geek_id: "featured-no-network", name: "featured no network" };
646
649
  const cli = new FakeRecommendScreenCli(args, {
647
- candidates: [candidate]
650
+ candidates: [candidate],
651
+ domResumeByKey: new Map([
652
+ ["featured-no-network", {
653
+ name: "featured no network",
654
+ school: "华中科技大学",
655
+ major: "软件工程",
656
+ company: "测试公司",
657
+ position: "后端工程师",
658
+ resumeText: "featured network miss 后应在截图失败后走 DOM 兜底。"
659
+ }]
660
+ ])
648
661
  });
649
662
  cli.waitForNetworkResumeCandidateInfo = async () => null;
663
+ let captureAttempted = false;
664
+ cli.callTextModel = async () => ({
665
+ passed: true,
666
+ reason: "dom fallback used",
667
+ summary: "dom fallback used"
668
+ });
650
669
  cli.captureResumeImage = async () => {
651
- throw new Error("capture should not be called for featured scope");
670
+ captureAttempted = true;
671
+ throw new Error("capture failed for featured scope");
652
672
  };
653
673
 
654
674
  const result = await cli.run();
655
675
  assert.equal(result.status, "COMPLETED");
656
676
  assert.equal(result.result.processed_count, 1);
657
- assert.equal(result.result.passed_count, 0);
658
- assert.equal(result.result.skipped_count, 1);
659
- assert.equal(result.result.resume_source, "network");
677
+ assert.equal(result.result.passed_count, 1);
678
+ assert.equal(result.result.skipped_count, 0);
679
+ assert.equal(result.result.resume_source, "dom_fallback");
680
+ assert.equal(captureAttempted, true);
681
+ assert.equal(cli.passedCandidates[0].resumeSource, "dom_fallback");
660
682
  }
661
683
 
662
684
  async function testFeaturedFavoriteShouldNotUseDomFallback() {
@@ -1461,14 +1483,14 @@ async function main() {
1461
1483
  await testTargetCountShouldNotTreatProcessedCountAsReached();
1462
1484
  await testFeaturedShouldUseNetworkResumeOnly();
1463
1485
  await testRecommendShouldPreferNetworkResumeWhenAvailable();
1464
- await testNetworkMissShouldFallbackToDomBeforeImageCapture();
1486
+ await testNetworkMissShouldFallbackToImageThenDom();
1465
1487
  await testNetworkMissShouldFallbackToImageCapture();
1466
1488
  await testLatestShouldPreferNetworkResumeWhenAvailable();
1467
1489
  await testLatestNetworkMissShouldFallbackToImageCapture();
1468
1490
  testLatestPayloadShouldNotLeakAcrossCandidates();
1469
1491
  testLatestPayloadShouldRemainAvailableWhenCandidateKeyMissing();
1470
1492
  await testVisionModelFailureShouldSkipCandidateAndContinue();
1471
- await testFeaturedNetworkMissShouldSkipWithoutImageCapture();
1493
+ await testFeaturedNetworkMissShouldFallbackToDomAfterImageFailure();
1472
1494
  await testFeaturedFavoriteShouldNotUseDomFallback();
1473
1495
  await testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested();
1474
1496
  await testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd();