@reconcrap/boss-recommend-mcp 1.3.22 → 1.3.24

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.22",
3
+ "version": "1.3.24",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -4959,30 +4959,18 @@ class RecommendScreenCli {
4959
4959
  applyVisionEvidenceGate(result) {
4960
4960
  const parsed = result && typeof result === "object" ? result : {};
4961
4961
  const rawPassed = parsed?.rawPassed === true || parsed?.passed === true;
4962
- const evidenceGateEligible = parsed?.evidenceGateEligible === true
4963
- || Array.isArray(parsed?.evidence)
4964
- || Number.isFinite(Number(parsed?.evidenceRawCount))
4965
- || Number.isFinite(Number(parsed?.evidenceMatchedCount));
4966
- const parsedEvidence = evidenceGateEligible ? toStringArray(parsed?.evidence) : [];
4967
- const evidenceRawCount = evidenceGateEligible
4968
- ? (Number.isFinite(Number(parsed?.evidenceRawCount))
4969
- ? Number(parsed.evidenceRawCount)
4970
- : parsedEvidence.length)
4971
- : null;
4972
- const evidenceMatchedCount = evidenceGateEligible
4973
- ? (Number.isFinite(Number(parsed?.evidenceMatchedCount))
4974
- ? Number(parsed.evidenceMatchedCount)
4975
- : parsedEvidence.length)
4976
- : null;
4977
- const evidenceGateDemoted = parsed?.evidenceGateDemoted === true
4978
- || (evidenceGateEligible && rawPassed && evidenceMatchedCount <= 0);
4962
+ const parsedEvidence = toStringArray(parsed?.evidence);
4963
+ const evidenceRawCount = Number.isFinite(Number(parsed?.evidenceRawCount))
4964
+ ? Number(parsed.evidenceRawCount)
4965
+ : parsedEvidence.length;
4966
+ const evidenceMatchedCount = Number.isFinite(Number(parsed?.evidenceMatchedCount))
4967
+ ? Number(parsed.evidenceMatchedCount)
4968
+ : parsedEvidence.length;
4979
4969
  const cot = normalizeText(parsed?.cot || parsed?.reason || "");
4980
4970
  const summary = normalizeText(parsed?.summary || cot);
4981
- const finalReason = evidenceGateDemoted
4982
- ? `模型未给出可在简历截图中引用的证据,按安全策略判为不通过。${cot ? ` 原始判断依据(CoT): ${cot}` : ""}`
4983
- : (cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。"));
4971
+ const finalReason = cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
4984
4972
  return {
4985
- passed: evidenceGateDemoted ? false : rawPassed,
4973
+ passed: rawPassed,
4986
4974
  rawPassed,
4987
4975
  cot: finalReason,
4988
4976
  reason: finalReason,
@@ -4990,7 +4978,7 @@ class RecommendScreenCli {
4990
4978
  evidence: parsedEvidence,
4991
4979
  evidenceRawCount,
4992
4980
  evidenceMatchedCount,
4993
- evidenceGateDemoted
4981
+ evidenceGateDemoted: false
4994
4982
  };
4995
4983
  }
4996
4984
 
@@ -5273,22 +5261,15 @@ class RecommendScreenCli {
5273
5261
  const cot = normalizeText(extractCotFromChoice(choice, parsed));
5274
5262
  const reason = cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
5275
5263
  const summary = reason;
5276
- const evidenceGateEligible = Array.isArray(parsed?.evidence)
5277
- || Number.isFinite(Number(parsed?.evidenceRawCount))
5278
- || Number.isFinite(Number(parsed?.evidenceMatchedCount));
5279
- const parsedEvidence = evidenceGateEligible ? toStringArray(parsed?.evidence) : [];
5280
- const evidenceRawCount = evidenceGateEligible
5281
- ? (Number.isFinite(Number(parsed?.evidenceRawCount)) ? Number(parsed.evidenceRawCount) : parsedEvidence.length)
5282
- : null;
5283
- const evidenceMatchedCount = evidenceGateEligible
5284
- ? (Number.isFinite(Number(parsed?.evidenceMatchedCount)) ? Number(parsed.evidenceMatchedCount) : parsedEvidence.length)
5285
- : null;
5286
- const evidenceGateDemoted = evidenceGateEligible && rawPassed && (evidenceMatchedCount ?? 0) <= 0;
5287
- const finalReason = evidenceGateDemoted
5288
- ? `模型未给出可在简历截图中引用的证据,按安全策略判为不通过。${reason ? ` 原始判断依据(CoT): ${reason}` : ""}`
5289
- : reason;
5290
- const passed = evidenceGateDemoted ? false : rawPassed;
5291
- const enrichedReason = enrichReasonWithEvidence(finalReason, summary || finalReason, parsedEvidence, passed);
5264
+ const parsedEvidence = toStringArray(parsed?.evidence);
5265
+ const evidenceRawCount = Number.isFinite(Number(parsed?.evidenceRawCount))
5266
+ ? Number(parsed.evidenceRawCount)
5267
+ : parsedEvidence.length;
5268
+ const evidenceMatchedCount = Number.isFinite(Number(parsed?.evidenceMatchedCount))
5269
+ ? Number(parsed.evidenceMatchedCount)
5270
+ : parsedEvidence.length;
5271
+ const passed = rawPassed;
5272
+ const enrichedReason = enrichReasonWithEvidence(reason, summary || reason, parsedEvidence, passed);
5292
5273
  return {
5293
5274
  passed,
5294
5275
  rawPassed,
@@ -5298,8 +5279,8 @@ class RecommendScreenCli {
5298
5279
  evidence: parsedEvidence,
5299
5280
  evidenceRawCount,
5300
5281
  evidenceMatchedCount,
5301
- evidenceGateEligible,
5302
- evidenceGateDemoted
5282
+ evidenceGateEligible: false,
5283
+ evidenceGateDemoted: false
5303
5284
  };
5304
5285
  }
5305
5286
 
@@ -5440,20 +5421,12 @@ class RecommendScreenCli {
5440
5421
  const cot = normalizeText(extractCotFromChoice(choice, parsed));
5441
5422
  const normalizedResume = normalizeText(safeResumeText);
5442
5423
  const normalizedResumeLower = toLowerSafe(normalizedResume);
5443
- const evidenceGateEligible = Array.isArray(parsed?.evidence)
5444
- || Number.isFinite(Number(parsed?.evidenceRawCount))
5445
- || Number.isFinite(Number(parsed?.evidenceMatchedCount));
5446
- const parsedEvidence = evidenceGateEligible ? toStringArray(parsed.evidence) : [];
5424
+ const parsedEvidence = toStringArray(parsed?.evidence);
5447
5425
  const evidence = [];
5448
- const unmatchedEvidence = [];
5449
- if (evidenceGateEligible) {
5450
- for (const item of parsedEvidence) {
5451
- const matched = matchEvidenceAgainstResume(item, safeResumeText, normalizedResume, normalizedResumeLower);
5452
- if (matched.matched) {
5453
- evidence.push(item);
5454
- } else {
5455
- unmatchedEvidence.push(item);
5456
- }
5426
+ for (const item of parsedEvidence) {
5427
+ const matched = matchEvidenceAgainstResume(item, safeResumeText, normalizedResume, normalizedResumeLower);
5428
+ if (matched.matched) {
5429
+ evidence.push(item);
5457
5430
  }
5458
5431
  }
5459
5432
  const parsedPassed = parsePassedDecision(parsed?.passed);
@@ -5465,19 +5438,8 @@ class RecommendScreenCli {
5465
5438
  `Text model response missing boolean passed decision. content=${truncateText(content, 180)}`
5466
5439
  );
5467
5440
  }
5468
- let passed = rawPassed;
5469
- let finalReason = cot || (passed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
5470
- const evidenceGateDemoted = evidenceGateEligible && rawPassed && evidence.length <= 0;
5471
- if (evidenceGateDemoted) {
5472
- passed = false;
5473
- finalReason = `模型未给出可在简历原文中校验的证据,按安全策略判为不通过。${finalReason ? ` 原始判断依据(CoT): ${finalReason}` : ""}`;
5474
- if (unmatchedEvidence.length > 0) {
5475
- log(
5476
- `[EVIDENCE_GATE] passed=true 但证据未命中简历原文,已降级为不通过;` +
5477
- `chunk=${chunkIndex}/${chunkTotal}; unmatched=${unmatchedEvidence.slice(0, 3).join(" | ")}`
5478
- );
5479
- }
5480
- }
5441
+ const passed = rawPassed;
5442
+ const finalReason = cot || (passed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
5481
5443
  const summary = finalReason;
5482
5444
  const enrichedReason = enrichReasonWithEvidence(finalReason, summary || finalReason, evidence, passed);
5483
5445
  return {
@@ -5487,9 +5449,9 @@ class RecommendScreenCli {
5487
5449
  reason: enrichedReason,
5488
5450
  summary: summary || enrichedReason,
5489
5451
  evidence,
5490
- evidenceRawCount: evidenceGateEligible ? parsedEvidence.length : null,
5491
- evidenceMatchedCount: evidenceGateEligible ? evidence.length : null,
5492
- evidenceGateDemoted,
5452
+ evidenceRawCount: parsedEvidence.length,
5453
+ evidenceMatchedCount: evidence.length,
5454
+ evidenceGateDemoted: false,
5493
5455
  chunkIndex,
5494
5456
  chunkTotal
5495
5457
  };
@@ -1695,7 +1695,7 @@ async function testCloseDetailPageShouldContinueWhenListReady() {
1695
1695
  assert.equal(closed, true);
1696
1696
  }
1697
1697
 
1698
- async function testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence() {
1698
+ async function testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidenceProtocol() {
1699
1699
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-evidence-gate-"));
1700
1700
  const cli = new RecommendScreenCli(createArgs(tempDir));
1701
1701
  cli.prepareVisionImageSegmentsForModel = async () => ({
@@ -1713,12 +1713,75 @@ async function testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence()
1713
1713
  });
1714
1714
  const result = await cli.callVisionModel(path.join(tempDir, "fake.png"));
1715
1715
  assert.equal(result.rawPassed, true);
1716
- assert.equal(result.passed, false);
1717
- assert.equal(result.evidenceGateDemoted, true);
1716
+ assert.equal(result.passed, true);
1717
+ assert.equal(result.evidenceGateDemoted, false);
1718
1718
  assert.equal(result.evidenceRawCount, 0);
1719
1719
  assert.equal(result.evidenceMatchedCount, 0);
1720
1720
  }
1721
1721
 
1722
+ async function testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence() {
1723
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-evidence-gate-explicit-"));
1724
+ const cli = new RecommendScreenCli(createArgs(tempDir));
1725
+ cli.prepareVisionImageSegmentsForModel = async () => ({
1726
+ imagePaths: ["segment-1"],
1727
+ source: "test",
1728
+ sourcePixels: 100,
1729
+ currentPixels: 100
1730
+ });
1731
+ cli.requestVisionModel = async () => ({
1732
+ passed: true,
1733
+ rawPassed: true,
1734
+ reason: "matched",
1735
+ summary: "matched",
1736
+ evidence: [],
1737
+ evidenceGateEligible: true,
1738
+ evidenceRawCount: 0,
1739
+ evidenceMatchedCount: 0
1740
+ });
1741
+ const result = await cli.callVisionModel(path.join(tempDir, "fake.png"));
1742
+ assert.equal(result.rawPassed, true);
1743
+ assert.equal(result.passed, true);
1744
+ assert.equal(result.evidenceGateDemoted, false);
1745
+ assert.equal(result.evidenceRawCount, 0);
1746
+ assert.equal(result.evidenceMatchedCount, 0);
1747
+ }
1748
+
1749
+ async function testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume() {
1750
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-no-demote-"));
1751
+ const cli = new RecommendScreenCli(createArgs(tempDir));
1752
+ const originalFetch = global.fetch;
1753
+ global.fetch = async () => ({
1754
+ ok: true,
1755
+ status: 200,
1756
+ async json() {
1757
+ return {
1758
+ choices: [
1759
+ {
1760
+ message: {
1761
+ content: JSON.stringify({
1762
+ passed: true,
1763
+ reason: "matched",
1764
+ summary: "matched",
1765
+ evidence: ["完全不在简历里的证据"]
1766
+ })
1767
+ }
1768
+ }
1769
+ ]
1770
+ };
1771
+ }
1772
+ });
1773
+ try {
1774
+ const result = await cli.callTextModel("这是简历原文,没有那条证据");
1775
+ assert.equal(result.rawPassed, true);
1776
+ assert.equal(result.passed, true);
1777
+ assert.equal(result.evidenceGateDemoted, false);
1778
+ assert.equal(result.evidenceRawCount, 1);
1779
+ assert.equal(result.evidenceMatchedCount, 0);
1780
+ } finally {
1781
+ global.fetch = originalFetch;
1782
+ }
1783
+ }
1784
+
1722
1785
  async function testVisionModelShouldSendAllOrderedChunks() {
1723
1786
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-all-chunks-"));
1724
1787
  const chunkPaths = [];
@@ -1815,7 +1878,9 @@ async function main() {
1815
1878
  await testTextModelShouldDefaultThinkingLowForVolcengine();
1816
1879
  await testTextModelShouldSupportLowThinkingForVolcengine();
1817
1880
  await testPrepareVisionImageSegmentsShouldSplitLongImage();
1818
- await testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence();
1881
+ await testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidenceProtocol();
1882
+ await testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence();
1883
+ await testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume();
1819
1884
  await testVisionModelShouldSendAllOrderedChunks();
1820
1885
  testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
1821
1886
  await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();