@reconcrap/boss-recommend-mcp 1.1.11 → 1.2.0
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 +1 -1
- package/skills/boss-recommend-pipeline/SKILL.md +4 -3
- package/src/adapters.js +33 -19
- package/src/cli.js +1 -0
- package/src/index.js +2 -2
- package/src/parser.js +9 -4
- package/src/pipeline.js +7 -3
- package/src/test-adapters-runtime.js +107 -0
- package/src/test-parser.js +16 -0
- package/src/test-pipeline.js +78 -1
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +528 -104
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +187 -1
- package/vendor/boss-recommend-search-cli/src/cli.js +14 -6
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +2 -0
|
@@ -4,7 +4,7 @@ const os = require("node:os");
|
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const sharp = require("sharp");
|
|
6
6
|
|
|
7
|
-
const { RecommendScreenCli, __testables } = require("./boss-recommend-screen-cli.cjs");
|
|
7
|
+
const { RecommendScreenCli, parseArgs, __testables } = require("./boss-recommend-screen-cli.cjs");
|
|
8
8
|
const { __testables: captureTestables } = require("./scripts/capture-full-resume-canvas.cjs");
|
|
9
9
|
|
|
10
10
|
class FakeRecommendScreenCli extends RecommendScreenCli {
|
|
@@ -434,6 +434,62 @@ async function testFeaturedFavoriteShouldNotUseDomFallback() {
|
|
|
434
434
|
assert.equal(evaluateCalls, 0);
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
async function testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested() {
|
|
438
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-already-"));
|
|
439
|
+
const args = createArgs(tempDir);
|
|
440
|
+
args.pageScope = "featured";
|
|
441
|
+
const calibrationPath = path.join(tempDir, "favorite-calibration.json");
|
|
442
|
+
fs.writeFileSync(calibrationPath, JSON.stringify({
|
|
443
|
+
favoritePosition: {
|
|
444
|
+
pageX: 120,
|
|
445
|
+
pageY: 220,
|
|
446
|
+
canvasX: 0,
|
|
447
|
+
canvasY: 0
|
|
448
|
+
}
|
|
449
|
+
}, null, 2));
|
|
450
|
+
args.calibrationPath = calibrationPath;
|
|
451
|
+
const cli = new RecommendScreenCli(args);
|
|
452
|
+
let clickCalls = 0;
|
|
453
|
+
cli.simulateHumanClick = async () => {
|
|
454
|
+
clickCalls += 1;
|
|
455
|
+
};
|
|
456
|
+
const result = await cli.favoriteCandidate({ alreadyInterested: true });
|
|
457
|
+
assert.equal(result.actionTaken, "already_favorited");
|
|
458
|
+
assert.equal(result.source, "network_profile");
|
|
459
|
+
assert.equal(clickCalls, 0);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd() {
|
|
463
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-del-add-"));
|
|
464
|
+
const args = createArgs(tempDir);
|
|
465
|
+
args.pageScope = "featured";
|
|
466
|
+
const calibrationPath = path.join(tempDir, "favorite-calibration.json");
|
|
467
|
+
fs.writeFileSync(calibrationPath, JSON.stringify({
|
|
468
|
+
favoritePosition: {
|
|
469
|
+
pageX: 120,
|
|
470
|
+
pageY: 220,
|
|
471
|
+
canvasX: 0,
|
|
472
|
+
canvasY: 0
|
|
473
|
+
}
|
|
474
|
+
}, null, 2));
|
|
475
|
+
args.calibrationPath = calibrationPath;
|
|
476
|
+
const cli = new RecommendScreenCli(args);
|
|
477
|
+
let clickCalls = 0;
|
|
478
|
+
cli.simulateHumanClick = async () => {
|
|
479
|
+
clickCalls += 1;
|
|
480
|
+
cli.favoriteActionEvents.push({
|
|
481
|
+
action: clickCalls === 1 ? "del" : "add",
|
|
482
|
+
ts: Date.now(),
|
|
483
|
+
source: "test",
|
|
484
|
+
url: clickCalls === 1 ? "userMark/del" : "userMark/add"
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
const result = await cli.favoriteCandidate();
|
|
488
|
+
assert.equal(result.actionTaken, "already_favorited");
|
|
489
|
+
assert.equal(result.re_favorited, true);
|
|
490
|
+
assert.equal(clickCalls, 2);
|
|
491
|
+
}
|
|
492
|
+
|
|
437
493
|
async function testFeaturedFavoriteWithoutCalibrationShouldFail() {
|
|
438
494
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-missing-cal-"));
|
|
439
495
|
const args = createArgs(tempDir);
|
|
@@ -449,6 +505,91 @@ async function testFeaturedFavoriteWithoutCalibrationShouldFail() {
|
|
|
449
505
|
);
|
|
450
506
|
}
|
|
451
507
|
|
|
508
|
+
function testFavoriteActionParserShouldSupportBodySignals() {
|
|
509
|
+
const addFromJson = __testables.parseFavoriteActionFromPostData(JSON.stringify({
|
|
510
|
+
action: "star-interest-click",
|
|
511
|
+
p3: 1
|
|
512
|
+
}));
|
|
513
|
+
const delFromForm = __testables.parseFavoriteActionFromPostData("action=star-interest-click&p3=0");
|
|
514
|
+
assert.equal(addFromJson, "add");
|
|
515
|
+
assert.equal(delFromForm, "del");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function testFavoriteActionParserShouldSupportFallbackRequestShape() {
|
|
519
|
+
const action = __testables.parseFavoriteActionFromRequest(
|
|
520
|
+
"https://www.zhipin.com/wapi/zpgeek/favorite/operate",
|
|
521
|
+
JSON.stringify({ op: "add", geekId: "abc" })
|
|
522
|
+
);
|
|
523
|
+
assert.equal(action, "add");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function testFavoriteActionParserShouldSupportWebSocketPayload() {
|
|
527
|
+
const addFromWsJson = __testables.parseFavoriteActionFromWsPayload(JSON.stringify({
|
|
528
|
+
action: "star-interest-click",
|
|
529
|
+
p3: 1
|
|
530
|
+
}));
|
|
531
|
+
const delFromWsForm = __testables.parseFavoriteActionFromWsPayload("action=star-interest-click&p3=0");
|
|
532
|
+
assert.equal(addFromWsJson, "add");
|
|
533
|
+
assert.equal(delFromWsForm, "del");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function testFavoriteActionParserShouldOnlyTrustKnownRequestShapes() {
|
|
537
|
+
const unknown = __testables.parseFavoriteActionFromKnownRequest(
|
|
538
|
+
"https://www.zhipin.com/wapi/other/metrics",
|
|
539
|
+
JSON.stringify({ action: "add", p3: 1 })
|
|
540
|
+
);
|
|
541
|
+
const actionLog = __testables.parseFavoriteActionFromKnownRequest(
|
|
542
|
+
"https://www.zhipin.com/wapi/zplog/actionLog/common.json",
|
|
543
|
+
JSON.stringify({ action: "star-interest-click", p3: 1 })
|
|
544
|
+
);
|
|
545
|
+
const userMark = __testables.parseFavoriteActionFromKnownRequest(
|
|
546
|
+
"https://www.zhipin.com/wapi/zpgeek/userMark/add",
|
|
547
|
+
""
|
|
548
|
+
);
|
|
549
|
+
assert.equal(unknown, null);
|
|
550
|
+
assert.equal(actionLog, "add");
|
|
551
|
+
assert.equal(userMark, "add");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function testFeaturedPostActionFailureShouldStillRecordPassedCandidate() {
|
|
555
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-featured-action-failure-"));
|
|
556
|
+
const args = createArgs(tempDir);
|
|
557
|
+
args.pageScope = "featured";
|
|
558
|
+
args.postAction = "favorite";
|
|
559
|
+
const candidate = { key: "featured-fav-fail", geek_id: "featured-fav-fail", name: "featured candidate" };
|
|
560
|
+
const cli = new FakeRecommendScreenCli(args, {
|
|
561
|
+
candidates: [candidate]
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
cli.waitForNetworkResumeCandidateInfo = async () => ({
|
|
565
|
+
name: "featured candidate",
|
|
566
|
+
school: "测试大学",
|
|
567
|
+
major: "人工智能",
|
|
568
|
+
company: "测试公司",
|
|
569
|
+
position: "算法工程师",
|
|
570
|
+
resumeText: "满足测试标准"
|
|
571
|
+
});
|
|
572
|
+
cli.callTextModel = async () => ({
|
|
573
|
+
passed: true,
|
|
574
|
+
reason: "通过",
|
|
575
|
+
summary: "通过"
|
|
576
|
+
});
|
|
577
|
+
cli.favoriteCandidate = async () => {
|
|
578
|
+
const error = new Error("精选页收藏未检测到 network add 成功信号。");
|
|
579
|
+
error.code = "FAVORITE_BUTTON_FAILED";
|
|
580
|
+
throw error;
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const result = await cli.run();
|
|
584
|
+
assert.equal(result.status, "COMPLETED");
|
|
585
|
+
assert.equal(result.result.processed_count, 1);
|
|
586
|
+
assert.equal(result.result.passed_count, 1);
|
|
587
|
+
assert.equal(result.result.skipped_count, 0);
|
|
588
|
+
assert.equal(cli.passedCandidates.length, 1);
|
|
589
|
+
assert.equal(cli.passedCandidates[0].action, "favorite_failed");
|
|
590
|
+
assert.match(cli.passedCandidates[0].reason, /\[favorite失败]/);
|
|
591
|
+
}
|
|
592
|
+
|
|
452
593
|
async function testStitchWithSharpShouldComposeExpectedImage() {
|
|
453
594
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-sharp-stitch-"));
|
|
454
595
|
const chunkA = path.join(tempDir, "chunk_000.png");
|
|
@@ -540,6 +681,42 @@ function testStitchWithAvailablePythonShouldFailWhenScriptMissing() {
|
|
|
540
681
|
assert.equal(result.attempts[0].command, "python3");
|
|
541
682
|
}
|
|
542
683
|
|
|
684
|
+
function testParseArgsShouldSupportFeaturedAliasesAndInlinePort() {
|
|
685
|
+
const parsed = parseArgs([
|
|
686
|
+
"--criteria", "test criteria",
|
|
687
|
+
"--baseurl", "https://example.com/v1",
|
|
688
|
+
"--apikey", "key",
|
|
689
|
+
"--model", "test-model",
|
|
690
|
+
"--target-count", "3",
|
|
691
|
+
"--pageScope", "featured",
|
|
692
|
+
"--port=9222",
|
|
693
|
+
"--postAction", "favorite",
|
|
694
|
+
"--postActionConfirmed", "true"
|
|
695
|
+
]);
|
|
696
|
+
assert.equal(parsed.pageScope, "featured");
|
|
697
|
+
assert.equal(parsed.port, 9222);
|
|
698
|
+
assert.equal(parsed.targetCount, 3);
|
|
699
|
+
assert.equal(parsed.postAction, "favorite");
|
|
700
|
+
assert.equal(parsed.postActionConfirmed, true);
|
|
701
|
+
assert.equal(parsed.__provided.pageScope, true);
|
|
702
|
+
assert.equal(parsed.__provided.port, true);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function testParseArgsShouldSupportLatestPageScope() {
|
|
706
|
+
const parsed = parseArgs([
|
|
707
|
+
"--criteria", "test criteria",
|
|
708
|
+
"--baseurl", "https://example.com/v1",
|
|
709
|
+
"--apikey", "key",
|
|
710
|
+
"--model", "test-model",
|
|
711
|
+
"--page-scope", "latest",
|
|
712
|
+
"--port", "9222",
|
|
713
|
+
"--post-action", "none",
|
|
714
|
+
"--post-action-confirmed", "true"
|
|
715
|
+
]);
|
|
716
|
+
assert.equal(parsed.pageScope, "latest");
|
|
717
|
+
assert.equal(parsed.port, 9222);
|
|
718
|
+
}
|
|
719
|
+
|
|
543
720
|
async function main() {
|
|
544
721
|
testShouldAbortResumeProbeEarly();
|
|
545
722
|
await testSingleResumeCaptureFailureIsSkipped();
|
|
@@ -551,10 +728,19 @@ async function main() {
|
|
|
551
728
|
await testNetworkMissShouldFallbackToImageCapture();
|
|
552
729
|
await testFeaturedNetworkMissShouldSkipWithoutImageCapture();
|
|
553
730
|
await testFeaturedFavoriteShouldNotUseDomFallback();
|
|
731
|
+
await testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested();
|
|
732
|
+
await testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd();
|
|
554
733
|
await testFeaturedFavoriteWithoutCalibrationShouldFail();
|
|
734
|
+
testFavoriteActionParserShouldSupportBodySignals();
|
|
735
|
+
testFavoriteActionParserShouldSupportFallbackRequestShape();
|
|
736
|
+
testFavoriteActionParserShouldSupportWebSocketPayload();
|
|
737
|
+
testFavoriteActionParserShouldOnlyTrustKnownRequestShapes();
|
|
738
|
+
await testFeaturedPostActionFailureShouldStillRecordPassedCandidate();
|
|
555
739
|
await testStitchWithSharpShouldComposeExpectedImage();
|
|
556
740
|
testStitchWithAvailablePythonShouldFallbackToPython();
|
|
557
741
|
testStitchWithAvailablePythonShouldFailWhenScriptMissing();
|
|
742
|
+
testParseArgsShouldSupportFeaturedAliasesAndInlinePort();
|
|
743
|
+
testParseArgsShouldSupportLatestPageScope();
|
|
558
744
|
console.log("recoverable resume failure tests passed");
|
|
559
745
|
}
|
|
560
746
|
|
|
@@ -41,6 +41,7 @@ function normalizePageScope(value) {
|
|
|
41
41
|
const normalized = normalizeText(value).toLowerCase();
|
|
42
42
|
if (!normalized) return null;
|
|
43
43
|
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
44
|
+
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
44
45
|
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
@@ -1449,6 +1450,8 @@ class RecommendSearchCli {
|
|
|
1449
1450
|
const recommendCandidates = cards.filter((card) => card.querySelector('.card-inner[data-geekid]'));
|
|
1450
1451
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
|
|
1451
1452
|
const featuredCandidates = featuredCards.filter((card) => card.querySelector('a[data-geekid]'));
|
|
1453
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
|
|
1454
|
+
const latestCandidates = latestCards.filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
|
|
1452
1455
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
1453
1456
|
const activeTab = tabs.find((node) => {
|
|
1454
1457
|
const className = String(node.className || '');
|
|
@@ -1457,22 +1460,27 @@ class RecommendSearchCli {
|
|
|
1457
1460
|
}) || null;
|
|
1458
1461
|
const activeTabStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
|
|
1459
1462
|
const inferredStatus = activeTabStatus
|
|
1460
|
-
|| (featuredCandidates.length > 0 && recommendCandidates.length === 0
|
|
1463
|
+
|| (featuredCandidates.length > 0 && recommendCandidates.length === 0 && latestCandidates.length === 0
|
|
1461
1464
|
? '3'
|
|
1462
|
-
:
|
|
1463
|
-
? '
|
|
1464
|
-
:
|
|
1465
|
+
: latestCandidates.length > 0 && recommendCandidates.length === 0 && featuredCandidates.length === 0
|
|
1466
|
+
? '1'
|
|
1467
|
+
: recommendCandidates.length > 0 && featuredCandidates.length === 0 && latestCandidates.length === 0
|
|
1468
|
+
? '0'
|
|
1469
|
+
: '');
|
|
1465
1470
|
const effectiveCount = inferredStatus === '3'
|
|
1466
1471
|
? featuredCandidates.length
|
|
1472
|
+
: inferredStatus === '1'
|
|
1473
|
+
? latestCandidates.length
|
|
1467
1474
|
: inferredStatus === '0'
|
|
1468
1475
|
? recommendCandidates.length
|
|
1469
|
-
: Math.max(recommendCandidates.length, featuredCandidates.length);
|
|
1476
|
+
: Math.max(recommendCandidates.length, featuredCandidates.length, latestCandidates.length);
|
|
1470
1477
|
const body = doc.body;
|
|
1471
1478
|
return {
|
|
1472
1479
|
ok: true,
|
|
1473
1480
|
candidateCount: effectiveCount,
|
|
1474
1481
|
recommendCandidateCount: recommendCandidates.length,
|
|
1475
1482
|
featuredCandidateCount: featuredCandidates.length,
|
|
1483
|
+
latestCandidateCount: latestCandidates.length,
|
|
1476
1484
|
activeTabStatus: inferredStatus || null,
|
|
1477
1485
|
totalCardCount: cards.length,
|
|
1478
1486
|
scrollTop: body ? body.scrollTop : 0,
|
|
@@ -1508,7 +1516,7 @@ class RecommendSearchCli {
|
|
|
1508
1516
|
console.log(JSON.stringify({
|
|
1509
1517
|
status: "COMPLETED",
|
|
1510
1518
|
result: {
|
|
1511
|
-
usage: "node src/cli.js --school-tag 985/211 --degree 本科及以上 --gender 男 --recent-not-view 近14天没有 --job \"算法工程师(视频/图像模型方向) _ 杭州\" --page-scope recommend|featured --port 9222",
|
|
1519
|
+
usage: "node src/cli.js --school-tag 985/211 --degree 本科及以上 --gender 男 --recent-not-view 近14天没有 --job \"算法工程师(视频/图像模型方向) _ 杭州\" --page-scope recommend|latest|featured --port 9222",
|
|
1512
1520
|
list_jobs_usage: "node src/cli.js --list-jobs --port 9222"
|
|
1513
1521
|
}
|
|
1514
1522
|
}));
|
|
@@ -43,6 +43,8 @@ function createArgs(overrides = {}) {
|
|
|
43
43
|
function testParseArgsPageScope() {
|
|
44
44
|
const parsed = parseArgs(["--page-scope", "featured"]);
|
|
45
45
|
assert.equal(parsed.pageScope, "featured");
|
|
46
|
+
const latestParsed = parseArgs(["--page-scope", "latest"]);
|
|
47
|
+
assert.equal(latestParsed.pageScope, "latest");
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
class SelectJobCliMock extends RecommendSearchCli {
|