@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.
@@ -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
- : recommendCandidates.length > 0 && featuredCandidates.length === 0
1463
- ? '0'
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 {