@reconcrap/boss-recommend-mcp 1.3.23 → 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
|
@@ -475,15 +475,6 @@ function toStringArray(value, maxItems = 8) {
|
|
|
475
475
|
return normalized;
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
function hasEvidenceGateSignal(parsed, parsedEvidence = null) {
|
|
479
|
-
if (!parsed || typeof parsed !== "object") return false;
|
|
480
|
-
if (parsed?.evidenceGateEligible === true) return true;
|
|
481
|
-
if (Number.isFinite(Number(parsed?.evidenceRawCount))) return true;
|
|
482
|
-
if (Number.isFinite(Number(parsed?.evidenceMatchedCount))) return true;
|
|
483
|
-
const normalizedEvidence = Array.isArray(parsedEvidence) ? parsedEvidence : toStringArray(parsed?.evidence);
|
|
484
|
-
return normalizedEvidence.length > 0;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
478
|
function toLowerSafe(text) {
|
|
488
479
|
return String(text || "").toLowerCase();
|
|
489
480
|
}
|
|
@@ -4969,26 +4960,17 @@ class RecommendScreenCli {
|
|
|
4969
4960
|
const parsed = result && typeof result === "object" ? result : {};
|
|
4970
4961
|
const rawPassed = parsed?.rawPassed === true || parsed?.passed === true;
|
|
4971
4962
|
const parsedEvidence = toStringArray(parsed?.evidence);
|
|
4972
|
-
const
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
:
|
|
4978
|
-
const evidenceMatchedCount = evidenceGateEligible
|
|
4979
|
-
? (Number.isFinite(Number(parsed?.evidenceMatchedCount))
|
|
4980
|
-
? Number(parsed.evidenceMatchedCount)
|
|
4981
|
-
: parsedEvidence.length)
|
|
4982
|
-
: null;
|
|
4983
|
-
const evidenceGateDemoted = parsed?.evidenceGateDemoted === true
|
|
4984
|
-
|| (evidenceGateEligible && rawPassed && evidenceMatchedCount <= 0);
|
|
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;
|
|
4985
4969
|
const cot = normalizeText(parsed?.cot || parsed?.reason || "");
|
|
4986
4970
|
const summary = normalizeText(parsed?.summary || cot);
|
|
4987
|
-
const finalReason =
|
|
4988
|
-
? `模型未给出可在简历截图中引用的证据,按安全策略判为不通过。${cot ? ` 原始判断依据(CoT): ${cot}` : ""}`
|
|
4989
|
-
: (cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。"));
|
|
4971
|
+
const finalReason = cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
|
|
4990
4972
|
return {
|
|
4991
|
-
passed:
|
|
4973
|
+
passed: rawPassed,
|
|
4992
4974
|
rawPassed,
|
|
4993
4975
|
cot: finalReason,
|
|
4994
4976
|
reason: finalReason,
|
|
@@ -4996,7 +4978,7 @@ class RecommendScreenCli {
|
|
|
4996
4978
|
evidence: parsedEvidence,
|
|
4997
4979
|
evidenceRawCount,
|
|
4998
4980
|
evidenceMatchedCount,
|
|
4999
|
-
evidenceGateDemoted
|
|
4981
|
+
evidenceGateDemoted: false
|
|
5000
4982
|
};
|
|
5001
4983
|
}
|
|
5002
4984
|
|
|
@@ -5280,19 +5262,14 @@ class RecommendScreenCli {
|
|
|
5280
5262
|
const reason = cot || (rawPassed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
|
|
5281
5263
|
const summary = reason;
|
|
5282
5264
|
const parsedEvidence = toStringArray(parsed?.evidence);
|
|
5283
|
-
const
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
const
|
|
5291
|
-
const finalReason = evidenceGateDemoted
|
|
5292
|
-
? `模型未给出可在简历截图中引用的证据,按安全策略判为不通过。${reason ? ` 原始判断依据(CoT): ${reason}` : ""}`
|
|
5293
|
-
: reason;
|
|
5294
|
-
const passed = evidenceGateDemoted ? false : rawPassed;
|
|
5295
|
-
const enrichedReason = enrichReasonWithEvidence(finalReason, summary || finalReason, parsedEvidence, passed);
|
|
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);
|
|
5296
5273
|
return {
|
|
5297
5274
|
passed,
|
|
5298
5275
|
rawPassed,
|
|
@@ -5302,8 +5279,8 @@ class RecommendScreenCli {
|
|
|
5302
5279
|
evidence: parsedEvidence,
|
|
5303
5280
|
evidenceRawCount,
|
|
5304
5281
|
evidenceMatchedCount,
|
|
5305
|
-
evidenceGateEligible,
|
|
5306
|
-
evidenceGateDemoted
|
|
5282
|
+
evidenceGateEligible: false,
|
|
5283
|
+
evidenceGateDemoted: false
|
|
5307
5284
|
};
|
|
5308
5285
|
}
|
|
5309
5286
|
|
|
@@ -5445,17 +5422,11 @@ class RecommendScreenCli {
|
|
|
5445
5422
|
const normalizedResume = normalizeText(safeResumeText);
|
|
5446
5423
|
const normalizedResumeLower = toLowerSafe(normalizedResume);
|
|
5447
5424
|
const parsedEvidence = toStringArray(parsed?.evidence);
|
|
5448
|
-
const evidenceGateEligible = hasEvidenceGateSignal(parsed, parsedEvidence);
|
|
5449
5425
|
const evidence = [];
|
|
5450
|
-
const
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
if (matched.matched) {
|
|
5455
|
-
evidence.push(item);
|
|
5456
|
-
} else {
|
|
5457
|
-
unmatchedEvidence.push(item);
|
|
5458
|
-
}
|
|
5426
|
+
for (const item of parsedEvidence) {
|
|
5427
|
+
const matched = matchEvidenceAgainstResume(item, safeResumeText, normalizedResume, normalizedResumeLower);
|
|
5428
|
+
if (matched.matched) {
|
|
5429
|
+
evidence.push(item);
|
|
5459
5430
|
}
|
|
5460
5431
|
}
|
|
5461
5432
|
const parsedPassed = parsePassedDecision(parsed?.passed);
|
|
@@ -5467,19 +5438,8 @@ class RecommendScreenCli {
|
|
|
5467
5438
|
`Text model response missing boolean passed decision. content=${truncateText(content, 180)}`
|
|
5468
5439
|
);
|
|
5469
5440
|
}
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
const evidenceGateDemoted = evidenceGateEligible && rawPassed && evidence.length <= 0;
|
|
5473
|
-
if (evidenceGateDemoted) {
|
|
5474
|
-
passed = false;
|
|
5475
|
-
finalReason = `模型未给出可在简历原文中校验的证据,按安全策略判为不通过。${finalReason ? ` 原始判断依据(CoT): ${finalReason}` : ""}`;
|
|
5476
|
-
if (unmatchedEvidence.length > 0) {
|
|
5477
|
-
log(
|
|
5478
|
-
`[EVIDENCE_GATE] passed=true 但证据未命中简历原文,已降级为不通过;` +
|
|
5479
|
-
`chunk=${chunkIndex}/${chunkTotal}; unmatched=${unmatchedEvidence.slice(0, 3).join(" | ")}`
|
|
5480
|
-
);
|
|
5481
|
-
}
|
|
5482
|
-
}
|
|
5441
|
+
const passed = rawPassed;
|
|
5442
|
+
const finalReason = cot || (passed ? "模型判定符合筛选标准。" : "模型判定不符合筛选标准。");
|
|
5483
5443
|
const summary = finalReason;
|
|
5484
5444
|
const enrichedReason = enrichReasonWithEvidence(finalReason, summary || finalReason, evidence, passed);
|
|
5485
5445
|
return {
|
|
@@ -5489,9 +5449,9 @@ class RecommendScreenCli {
|
|
|
5489
5449
|
reason: enrichedReason,
|
|
5490
5450
|
summary: summary || enrichedReason,
|
|
5491
5451
|
evidence,
|
|
5492
|
-
evidenceRawCount:
|
|
5493
|
-
evidenceMatchedCount:
|
|
5494
|
-
evidenceGateDemoted,
|
|
5452
|
+
evidenceRawCount: parsedEvidence.length,
|
|
5453
|
+
evidenceMatchedCount: evidence.length,
|
|
5454
|
+
evidenceGateDemoted: false,
|
|
5495
5455
|
chunkIndex,
|
|
5496
5456
|
chunkTotal
|
|
5497
5457
|
};
|
|
@@ -1715,11 +1715,11 @@ async function testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidencePro
|
|
|
1715
1715
|
assert.equal(result.rawPassed, true);
|
|
1716
1716
|
assert.equal(result.passed, true);
|
|
1717
1717
|
assert.equal(result.evidenceGateDemoted, false);
|
|
1718
|
-
assert.equal(result.evidenceRawCount,
|
|
1719
|
-
assert.equal(result.evidenceMatchedCount,
|
|
1718
|
+
assert.equal(result.evidenceRawCount, 0);
|
|
1719
|
+
assert.equal(result.evidenceMatchedCount, 0);
|
|
1720
1720
|
}
|
|
1721
1721
|
|
|
1722
|
-
async function
|
|
1722
|
+
async function testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence() {
|
|
1723
1723
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-evidence-gate-explicit-"));
|
|
1724
1724
|
const cli = new RecommendScreenCli(createArgs(tempDir));
|
|
1725
1725
|
cli.prepareVisionImageSegmentsForModel = async () => ({
|
|
@@ -1740,12 +1740,48 @@ async function testVisionEvidenceGateShouldDemoteWhenExplicitlyArmedWithoutEvide
|
|
|
1740
1740
|
});
|
|
1741
1741
|
const result = await cli.callVisionModel(path.join(tempDir, "fake.png"));
|
|
1742
1742
|
assert.equal(result.rawPassed, true);
|
|
1743
|
-
assert.equal(result.passed,
|
|
1744
|
-
assert.equal(result.evidenceGateDemoted,
|
|
1743
|
+
assert.equal(result.passed, true);
|
|
1744
|
+
assert.equal(result.evidenceGateDemoted, false);
|
|
1745
1745
|
assert.equal(result.evidenceRawCount, 0);
|
|
1746
1746
|
assert.equal(result.evidenceMatchedCount, 0);
|
|
1747
1747
|
}
|
|
1748
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
|
+
|
|
1749
1785
|
async function testVisionModelShouldSendAllOrderedChunks() {
|
|
1750
1786
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-all-chunks-"));
|
|
1751
1787
|
const chunkPaths = [];
|
|
@@ -1843,7 +1879,8 @@ async function main() {
|
|
|
1843
1879
|
await testTextModelShouldSupportLowThinkingForVolcengine();
|
|
1844
1880
|
await testPrepareVisionImageSegmentsShouldSplitLongImage();
|
|
1845
1881
|
await testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidenceProtocol();
|
|
1846
|
-
await
|
|
1882
|
+
await testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence();
|
|
1883
|
+
await testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume();
|
|
1847
1884
|
await testVisionModelShouldSendAllOrderedChunks();
|
|
1848
1885
|
testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
|
|
1849
1886
|
await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();
|