@reconcrap/boss-recommend-mcp 1.2.9 → 1.3.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.
Files changed (36) hide show
  1. package/README.md +82 -1
  2. package/package.json +2 -1
  3. package/skills/boss-chat/README.md +5 -0
  4. package/skills/boss-chat/SKILL.md +69 -0
  5. package/skills/boss-recommend-pipeline/SKILL.md +40 -4
  6. package/src/adapters.js +19 -5
  7. package/src/boss-chat.js +436 -0
  8. package/src/cli.js +294 -129
  9. package/src/index.js +459 -108
  10. package/src/parser.js +4 -5
  11. package/src/pipeline.js +605 -8
  12. package/src/run-state.js +5 -0
  13. package/src/test-adapters-runtime.js +69 -0
  14. package/src/test-boss-chat.js +399 -0
  15. package/src/test-index-async.js +238 -4
  16. package/src/test-parser.js +33 -6
  17. package/src/test-pipeline.js +408 -1
  18. package/vendor/boss-chat-cli/README.md +134 -0
  19. package/vendor/boss-chat-cli/package.json +53 -0
  20. package/vendor/boss-chat-cli/src/app.js +769 -0
  21. package/vendor/boss-chat-cli/src/browser/chat-page.js +2681 -0
  22. package/vendor/boss-chat-cli/src/cli.js +1350 -0
  23. package/vendor/boss-chat-cli/src/mcp/server.js +149 -0
  24. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +193 -0
  25. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +260 -0
  26. package/vendor/boss-chat-cli/src/runtime/interaction.js +102 -0
  27. package/vendor/boss-chat-cli/src/runtime/run-control.js +102 -0
  28. package/vendor/boss-chat-cli/src/services/chrome-client.js +97 -0
  29. package/vendor/boss-chat-cli/src/services/llm.js +352 -0
  30. package/vendor/boss-chat-cli/src/services/profile-store.js +157 -0
  31. package/vendor/boss-chat-cli/src/services/report-store.js +19 -0
  32. package/vendor/boss-chat-cli/src/services/resume-capture.js +554 -0
  33. package/vendor/boss-chat-cli/src/services/state-store.js +217 -0
  34. package/vendor/boss-chat-cli/src/utils/customer-key.js +82 -0
  35. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +902 -56
  36. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +387 -1
@@ -13,6 +13,7 @@ class FakeRecommendScreenCli extends RecommendScreenCli {
13
13
  this.testCandidates = options.candidates || [];
14
14
  this.captureOutcomes = options.captureOutcomes || new Map();
15
15
  this.screeningByKey = options.screeningByKey || new Map();
16
+ this.domResumeByKey = options.domResumeByKey || new Map();
16
17
  this.discoveryCalls = 0;
17
18
  this.lastCapturedCandidateKey = null;
18
19
  }
@@ -90,6 +91,10 @@ class FakeRecommendScreenCli extends RecommendScreenCli {
90
91
  return true;
91
92
  }
92
93
 
94
+ async extractResumeTextFromDom(candidate) {
95
+ return this.domResumeByKey.get(candidate.key) || null;
96
+ }
97
+
93
98
  async captureResumeImage(candidate) {
94
99
  const outcome = this.captureOutcomes.get(candidate.key);
95
100
  if (outcome instanceof Error) {
@@ -148,6 +153,31 @@ class FakeDetailCloseProbeCli extends RecommendScreenCli {
148
153
  async pressEsc() {}
149
154
  }
150
155
 
156
+ class FakeRecoverableGreetFailureCli extends FakeRecommendScreenCli {
157
+ constructor(args, options = {}) {
158
+ super(args, options);
159
+ this.greetErrors = options.greetErrors || new Map();
160
+ this.closeFailureKeys = options.closeFailureKeys || new Set();
161
+ }
162
+
163
+ async greetCandidate() {
164
+ const key = this.currentCandidateKey || "";
165
+ const code = this.greetErrors.get(key);
166
+ if (!code) return { actionTaken: "greet" };
167
+ const error = new Error(code);
168
+ error.code = code;
169
+ throw error;
170
+ }
171
+
172
+ async closeDetailPage() {
173
+ const key = this.currentCandidateKey || "";
174
+ if (this.closeFailureKeys.has(key)) {
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ }
180
+
151
181
  function createResumeCaptureError(message = "Resume canvas not found") {
152
182
  const error = new Error(message);
153
183
  error.code = "RESUME_CAPTURE_FAILED";
@@ -166,6 +196,7 @@ function createArgs(tempDir) {
166
196
  pageScope: "recommend",
167
197
  port: 9222,
168
198
  output: path.join(tempDir, "result.csv"),
199
+ inputSummary: null,
169
200
  checkpointPath: path.join(tempDir, "checkpoint.json"),
170
201
  pauseControlPath: path.join(tempDir, "pause.json"),
171
202
  resume: false,
@@ -181,6 +212,7 @@ function createArgs(tempDir) {
181
212
  maxGreetCount: false,
182
213
  pageScope: true,
183
214
  port: true,
215
+ inputSummary: false,
184
216
  postAction: true,
185
217
  postActionConfirmed: true
186
218
  }
@@ -249,7 +281,7 @@ async function testConsecutiveResumeCaptureFailuresStillAbort() {
249
281
  () => cli.run(),
250
282
  (error) => {
251
283
  assert.equal(error.code, "RESUME_CAPTURE_FAILED_CONSECUTIVE_LIMIT");
252
- assert.match(error.message, /连续 .* 位候选人简历(?:捕获失败|获取失败(network \+ 截图))/);
284
+ assert.match(error.message, /连续 .* 位候选人简历(?:捕获失败|获取失败(network \+ (?:DOM \+ )?截图))/);
253
285
  assert.equal(error.rollback?.rollback_count, maxFailures);
254
286
  assert.equal(error.partial_result?.processed_count, 0);
255
287
  assert.equal(error.partial_result?.skipped_count, 0);
@@ -435,6 +467,42 @@ async function testRecommendShouldPreferNetworkResumeWhenAvailable() {
435
467
  assert.equal(result.result.resume_source, "network");
436
468
  }
437
469
 
470
+ async function testNetworkMissShouldFallbackToDomBeforeImageCapture() {
471
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-network-dom-fallback-"));
472
+ const candidate = { key: "dom-1", geek_id: "dom-1", name: "dom candidate" };
473
+ const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
474
+ candidates: [candidate],
475
+ domResumeByKey: new Map([
476
+ ["dom-1", {
477
+ name: "dom candidate",
478
+ school: "华中科技大学",
479
+ major: "智能科学与技术",
480
+ company: "小米科技(武汉)",
481
+ position: "算法工程师",
482
+ resumeText: "教育经历:华中科技大学(本硕),专业智能科学与技术。工作与项目经历包含多模态、大模型微调、Agent 架构设计与落地。"
483
+ }]
484
+ ])
485
+ });
486
+
487
+ cli.waitForNetworkResumeCandidateInfo = async () => null;
488
+ cli.callTextModel = async (resumeText) => ({
489
+ passed: true,
490
+ reason: resumeText.includes("华中科技大学") ? "dom fallback used" : "unexpected",
491
+ summary: "dom fallback used"
492
+ });
493
+ cli.captureResumeImage = async () => {
494
+ throw new Error("capture should not be called when dom fallback resume exists");
495
+ };
496
+
497
+ const result = await cli.run();
498
+ assert.equal(result.status, "COMPLETED");
499
+ assert.equal(result.result.passed_count, 1);
500
+ assert.equal(result.result.resume_source, "dom_fallback");
501
+ assert.equal(cli.passedCandidates.length, 1);
502
+ assert.equal(cli.passedCandidates[0].school, "华中科技大学");
503
+ assert.equal(cli.passedCandidates[0].resumeSource, "dom_fallback");
504
+ }
505
+
438
506
  async function testNetworkMissShouldFallbackToImageCapture() {
439
507
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-network-fallback-"));
440
508
  const candidate = { key: "img-1", geek_id: "img-1", name: "image candidate" };
@@ -501,6 +569,40 @@ async function testLatestNetworkMissShouldFallbackToImageCapture() {
501
569
  assert.equal(result.result.resume_source, "image_fallback");
502
570
  }
503
571
 
572
+ function testLatestPayloadShouldNotLeakAcrossCandidates() {
573
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-payload-leak-"));
574
+ const cli = new RecommendScreenCli(createArgs(tempDir));
575
+ cli.latestResumeNetworkPayload = {
576
+ ts: Date.now(),
577
+ geekIds: ["candidate-a"],
578
+ candidateInfo: {
579
+ resumeText: "candidate-a resume"
580
+ }
581
+ };
582
+ const extracted = cli.tryExtractNetworkResumeForCandidate({
583
+ key: "candidate-b",
584
+ geek_id: "candidate-b"
585
+ });
586
+ assert.equal(extracted, null);
587
+ }
588
+
589
+ function testLatestPayloadShouldRemainAvailableWhenCandidateKeyMissing() {
590
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-payload-no-key-"));
591
+ const cli = new RecommendScreenCli(createArgs(tempDir));
592
+ cli.latestResumeNetworkPayload = {
593
+ ts: Date.now(),
594
+ geekIds: ["candidate-a"],
595
+ candidateInfo: {
596
+ resumeText: "recent resume payload"
597
+ }
598
+ };
599
+ const extracted = cli.tryExtractNetworkResumeForCandidate({
600
+ key: "",
601
+ geek_id: ""
602
+ });
603
+ assert.equal(extracted?.resumeText, "recent resume payload");
604
+ }
605
+
504
606
  async function testVisionModelFailureShouldSkipCandidateAndContinue() {
505
607
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-vision-failure-skip-"));
506
608
  const first = { key: "vision-fail-1", geek_id: "vision-fail-1", name: "vision-fail-1" };
@@ -721,6 +823,193 @@ function testFinishedWrapClassifierShouldNotTreatLoadMoreAsBottom() {
721
823
  assert.equal(refreshOnly.reason, "refresh_button_visible");
722
824
  }
723
825
 
826
+ function testFormatResumeApiDataShouldPreserveEducationTagsAndProjectDescription() {
827
+ const source = {
828
+ geekDetailInfo: {
829
+ geekBaseInfo: {
830
+ name: "测试候选人",
831
+ degreeCategory: "硕士"
832
+ },
833
+ geekEduExpList: [
834
+ {
835
+ school: "南京大学",
836
+ major: "数学",
837
+ degree: 203,
838
+ degreeName: "本科",
839
+ schoolTags: [{ name: "985院校" }, { name: "QS世界大学排名TOP200" }]
840
+ }
841
+ ],
842
+ geekProjExpList: [
843
+ {
844
+ name: "Prompt-to-Prompt 3DEditing via NeRF",
845
+ projectDescription: "采用stable diffusion进行编辑实验"
846
+ }
847
+ ]
848
+ }
849
+ };
850
+ const formatted = __testables.formatResumeApiData(source);
851
+ assert.equal(formatted.includes("学历: 本科"), true);
852
+ assert.equal(formatted.includes("学历: 203"), false);
853
+ assert.equal(formatted.includes("学校标签: 985院校、QS世界大学排名TOP200"), true);
854
+ assert.equal(formatted.includes("描述: 采用stable diffusion进行编辑实验"), true);
855
+ }
856
+
857
+ function testEvidenceTokenMatcherShouldSupportParaphrasedEvidence() {
858
+ const resume = [
859
+ "南京大学 专业: 数学",
860
+ "Prompt-to-Prompt 3DEditing via NeRF",
861
+ "采用 stable diffusion 进行编辑实验"
862
+ ].join(" | ");
863
+ const normalizedResume = resume.replace(/\s+/g, " ").trim();
864
+ const matched = __testables.matchEvidenceAgainstResume(
865
+ "项目经历包含Prompt-to-Prompt 3DEditing via NeRF(stable diffusion)",
866
+ resume,
867
+ normalizedResume,
868
+ normalizedResume.toLowerCase()
869
+ );
870
+ assert.equal(matched.matched, true);
871
+ const unmatched = __testables.matchEvidenceAgainstResume(
872
+ "有十年金融风控投研经历",
873
+ resume,
874
+ normalizedResume,
875
+ normalizedResume.toLowerCase()
876
+ );
877
+ assert.equal(unmatched.matched, false);
878
+ }
879
+
880
+ function testCheckpointPayloadShouldIncludeCandidateAudits() {
881
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-audit-checkpoint-"));
882
+ const cli = new RecommendScreenCli(createArgs(tempDir));
883
+ cli.recordCandidateAudit({
884
+ candidate_key: "candidate-a",
885
+ geek_id: "candidate-a",
886
+ candidate_name: "候选人A",
887
+ outcome: "skipped",
888
+ resume_source: "network",
889
+ raw_passed: true,
890
+ final_passed: false,
891
+ evidence_gate_demoted: true,
892
+ screening_reason: "模型未给出可校验证据"
893
+ });
894
+ const checkpoint = cli.buildCheckpointPayload();
895
+ assert.equal(Array.isArray(checkpoint.candidate_audits), true);
896
+ assert.equal(checkpoint.candidate_audits.length, 1);
897
+ assert.equal(checkpoint.candidate_audits[0].candidate_key, "candidate-a");
898
+ assert.equal(checkpoint.candidate_audits[0].evidence_gate_demoted, true);
899
+ }
900
+
901
+ function testCheckpointShouldPersistAndRestoreInputSummary() {
902
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-input-summary-checkpoint-"));
903
+ const args = createArgs(tempDir);
904
+ args.inputSummary = {
905
+ instruction: "筛选 AI 人选",
906
+ search_params: {
907
+ school_tag: ["985"],
908
+ degree: ["本科"]
909
+ },
910
+ screen_params: {
911
+ criteria: "有 LLM 项目经验"
912
+ },
913
+ baseUrl: "https://should-not-be-stored",
914
+ apiKey: "sk-should-not-be-stored",
915
+ model: "should-not-be-stored"
916
+ };
917
+ const cli = new RecommendScreenCli(args);
918
+ const checkpoint = cli.buildCheckpointPayload();
919
+ assert.equal(checkpoint.input_summary?.instruction, "筛选 AI 人选");
920
+ assert.equal(checkpoint.input_summary?.screen_params?.criteria, "有 LLM 项目经验");
921
+ assert.equal(Object.prototype.hasOwnProperty.call(checkpoint.input_summary || {}, "baseUrl"), false);
922
+ assert.equal(Object.prototype.hasOwnProperty.call(checkpoint.input_summary || {}, "apiKey"), false);
923
+ assert.equal(Object.prototype.hasOwnProperty.call(checkpoint.input_summary || {}, "model"), false);
924
+
925
+ fs.writeFileSync(args.checkpointPath, JSON.stringify(checkpoint, null, 2), "utf8");
926
+ const resumeArgs = createArgs(tempDir);
927
+ resumeArgs.resume = true;
928
+ resumeArgs.inputSummary = null;
929
+ const resumeCli = new RecommendScreenCli(resumeArgs);
930
+ const restored = resumeCli.loadCheckpointIfNeeded();
931
+ assert.equal(restored, true);
932
+ assert.equal(resumeCli.inputSummary?.instruction, "筛选 AI 人选");
933
+ assert.equal(resumeCli.inputSummary?.screen_params?.criteria, "有 LLM 项目经验");
934
+ }
935
+
936
+ function testSaveCsvShouldIncludeAllCandidateOutcomes() {
937
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-csv-audit-"));
938
+ const args = createArgs(tempDir);
939
+ args.output = path.join(tempDir, "result.csv");
940
+ const cli = new RecommendScreenCli(args);
941
+ cli.inputSummary = {
942
+ instruction: "找算法候选人",
943
+ search_params: {
944
+ school_tag: ["985", "211"],
945
+ degree: ["本科"],
946
+ gender: "不限"
947
+ },
948
+ screen_params: {
949
+ criteria: "有 LLM 研究或工程经验",
950
+ post_action: "greet"
951
+ },
952
+ baseUrl: "https://should-not-appear",
953
+ apiKey: "sk-should-not-appear",
954
+ model: "gpt-should-not-appear"
955
+ };
956
+ cli.candidateAudits = [
957
+ {
958
+ candidate_key: "cand-pass",
959
+ geek_id: "cand-pass",
960
+ candidate_name: "通过候选人",
961
+ school: "南京大学",
962
+ major: "数学",
963
+ company: "",
964
+ position: "",
965
+ outcome: "passed",
966
+ screening_reason: "满足全部条件",
967
+ action_taken: "greet",
968
+ resume_source: "network",
969
+ raw_passed: true,
970
+ final_passed: true,
971
+ evidence_raw_count: 4,
972
+ evidence_matched_count: 3,
973
+ evidence_gate_demoted: false,
974
+ error_code: "",
975
+ error_message: ""
976
+ },
977
+ {
978
+ candidate_key: "cand-skip",
979
+ geek_id: "cand-skip",
980
+ candidate_name: "跳过候选人",
981
+ school: "某大学",
982
+ major: "工科",
983
+ company: "",
984
+ position: "",
985
+ outcome: "skipped",
986
+ screening_reason: "证据不足",
987
+ action_taken: "none",
988
+ resume_source: "network",
989
+ raw_passed: false,
990
+ final_passed: false,
991
+ evidence_raw_count: 2,
992
+ evidence_matched_count: 0,
993
+ evidence_gate_demoted: false,
994
+ error_code: "",
995
+ error_message: ""
996
+ }
997
+ ];
998
+ cli.saveCsv();
999
+ const content = fs.readFileSync(args.output, "utf8");
1000
+ assert.equal(content.includes("处理结果"), true);
1001
+ assert.equal(content.includes("运行输入字段"), true);
1002
+ assert.equal(content.includes("instruction"), true);
1003
+ assert.equal(content.includes("screen_params.criteria"), true);
1004
+ assert.equal(content.includes("baseUrl"), false);
1005
+ assert.equal(content.includes("apiKey"), false);
1006
+ assert.equal(content.includes("model"), false);
1007
+ assert.equal((content.match(/运行输入字段/g) || []).length, 1);
1008
+ assert.equal(content.includes("通过候选人"), true);
1009
+ assert.equal(content.includes("跳过候选人"), true);
1010
+ assert.equal(content.includes("cand-skip"), true);
1011
+ }
1012
+
724
1013
  async function testGetCenteredCandidateClickPointShouldSupportLatestSelector() {
725
1014
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-latest-click-locator-"));
726
1015
  const args = createArgs(tempDir);
@@ -915,6 +1204,20 @@ function testParseArgsShouldSupportLatestPageScope() {
915
1204
  assert.equal(parsed.port, 9222);
916
1205
  }
917
1206
 
1207
+ function testParseArgsShouldSupportInputSummaryJson() {
1208
+ const parsed = parseArgs([
1209
+ "--criteria", "test criteria",
1210
+ "--baseurl", "https://example.com/v1",
1211
+ "--apikey", "key",
1212
+ "--model", "test-model",
1213
+ "--post-action", "none",
1214
+ "--post-action-confirmed", "true",
1215
+ "--input-summary-json", "{\"instruction\":\"筛选测试\",\"search_params\":{\"school_tag\":[\"985\"]}}"
1216
+ ]);
1217
+ assert.equal(parsed.inputSummary?.instruction, "筛选测试");
1218
+ assert.equal(parsed.inputSummary?.search_params?.school_tag?.[0], "985");
1219
+ }
1220
+
918
1221
  async function testCallTextModelShouldNotTruncateLongResume() {
919
1222
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-full-"));
920
1223
  const cli = new RecommendScreenCli(createArgs(tempDir));
@@ -1039,6 +1342,77 @@ async function testPrepareVisionImageSegmentsShouldSplitLongImage() {
1039
1342
  }
1040
1343
  }
1041
1344
 
1345
+ function testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable() {
1346
+ assert.equal(
1347
+ __testables.isRecoverablePostActionError({ code: "GREET_CONTINUE_BUTTON_FOUND" }, "greet"),
1348
+ true
1349
+ );
1350
+ assert.equal(
1351
+ __testables.isRecoverablePostActionError({ code: "GREET_BUTTON_NOT_FOUND" }, "greet"),
1352
+ true
1353
+ );
1354
+ }
1355
+
1356
+ async function testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails() {
1357
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-greet-continue-"));
1358
+ const args = createArgs(tempDir);
1359
+ args.postAction = "greet";
1360
+ args.maxGreetCount = 5;
1361
+ args.__provided.maxGreetCount = true;
1362
+
1363
+ const first = { key: "candidate-1", geek_id: "candidate-1", name: "candidate-1" };
1364
+ const second = { key: "candidate-2", geek_id: "candidate-2", name: "candidate-2" };
1365
+ const cli = new FakeRecoverableGreetFailureCli(args, {
1366
+ candidates: [first, second],
1367
+ captureOutcomes: new Map([
1368
+ [first.key, { stitchedImage: path.join(tempDir, "candidate-1.png") }],
1369
+ [second.key, { stitchedImage: path.join(tempDir, "candidate-2.png") }]
1370
+ ]),
1371
+ screeningByKey: new Map([
1372
+ [first.key, { passed: true, reason: "matched", summary: "matched" }],
1373
+ [second.key, { passed: true, reason: "matched", summary: "matched" }]
1374
+ ]),
1375
+ greetErrors: new Map([[first.key, "GREET_CONTINUE_BUTTON_FOUND"]]),
1376
+ closeFailureKeys: new Set([first.key])
1377
+ });
1378
+
1379
+ const result = await cli.run();
1380
+ assert.equal(result.status, "COMPLETED");
1381
+ assert.equal(result.result.processed_count, 2);
1382
+ assert.equal(result.result.passed_count, 2);
1383
+ assert.equal(result.result.greet_count, 1);
1384
+ }
1385
+
1386
+ async function testRecoverableGreetButtonNotFoundShouldNotAbortWhenDetailCloseFails() {
1387
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-greet-not-found-"));
1388
+ const args = createArgs(tempDir);
1389
+ args.postAction = "greet";
1390
+ args.maxGreetCount = 5;
1391
+ args.__provided.maxGreetCount = true;
1392
+
1393
+ const first = { key: "candidate-a", geek_id: "candidate-a", name: "candidate-a" };
1394
+ const second = { key: "candidate-b", geek_id: "candidate-b", name: "candidate-b" };
1395
+ const cli = new FakeRecoverableGreetFailureCli(args, {
1396
+ candidates: [first, second],
1397
+ captureOutcomes: new Map([
1398
+ [first.key, { stitchedImage: path.join(tempDir, "candidate-a.png") }],
1399
+ [second.key, { stitchedImage: path.join(tempDir, "candidate-b.png") }]
1400
+ ]),
1401
+ screeningByKey: new Map([
1402
+ [first.key, { passed: true, reason: "matched", summary: "matched" }],
1403
+ [second.key, { passed: true, reason: "matched", summary: "matched" }]
1404
+ ]),
1405
+ greetErrors: new Map([[first.key, "GREET_BUTTON_NOT_FOUND"]]),
1406
+ closeFailureKeys: new Set([first.key])
1407
+ });
1408
+
1409
+ const result = await cli.run();
1410
+ assert.equal(result.status, "COMPLETED");
1411
+ assert.equal(result.result.processed_count, 2);
1412
+ assert.equal(result.result.passed_count, 2);
1413
+ assert.equal(result.result.greet_count, 1);
1414
+ }
1415
+
1042
1416
  async function testCloseDetailPageShouldFailWhenDetailStillOpenAndListNotReady() {
1043
1417
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-close-detail-fail-"));
1044
1418
  const cli = new FakeDetailCloseProbeCli(createArgs(tempDir), { listReady: false });
@@ -1063,9 +1437,12 @@ async function main() {
1063
1437
  await testTargetCountShouldNotTreatProcessedCountAsReached();
1064
1438
  await testFeaturedShouldUseNetworkResumeOnly();
1065
1439
  await testRecommendShouldPreferNetworkResumeWhenAvailable();
1440
+ await testNetworkMissShouldFallbackToDomBeforeImageCapture();
1066
1441
  await testNetworkMissShouldFallbackToImageCapture();
1067
1442
  await testLatestShouldPreferNetworkResumeWhenAvailable();
1068
1443
  await testLatestNetworkMissShouldFallbackToImageCapture();
1444
+ testLatestPayloadShouldNotLeakAcrossCandidates();
1445
+ testLatestPayloadShouldRemainAvailableWhenCandidateKeyMissing();
1069
1446
  await testVisionModelFailureShouldSkipCandidateAndContinue();
1070
1447
  await testFeaturedNetworkMissShouldSkipWithoutImageCapture();
1071
1448
  await testFeaturedFavoriteShouldNotUseDomFallback();
@@ -1077,6 +1454,11 @@ async function main() {
1077
1454
  testFavoriteActionParserShouldSupportWebSocketPayload();
1078
1455
  testFavoriteActionParserShouldOnlyTrustKnownRequestShapes();
1079
1456
  testFinishedWrapClassifierShouldNotTreatLoadMoreAsBottom();
1457
+ testFormatResumeApiDataShouldPreserveEducationTagsAndProjectDescription();
1458
+ testEvidenceTokenMatcherShouldSupportParaphrasedEvidence();
1459
+ testCheckpointPayloadShouldIncludeCandidateAudits();
1460
+ testCheckpointShouldPersistAndRestoreInputSummary();
1461
+ testSaveCsvShouldIncludeAllCandidateOutcomes();
1080
1462
  await testGetCenteredCandidateClickPointShouldSupportLatestSelector();
1081
1463
  await testFeaturedPostActionFailureShouldStillRecordPassedCandidate();
1082
1464
  await testStitchWithSharpShouldComposeExpectedImage();
@@ -1084,9 +1466,13 @@ async function main() {
1084
1466
  testStitchWithAvailablePythonShouldFailWhenScriptMissing();
1085
1467
  testParseArgsShouldSupportFeaturedAliasesAndInlinePort();
1086
1468
  testParseArgsShouldSupportLatestPageScope();
1469
+ testParseArgsShouldSupportInputSummaryJson();
1087
1470
  await testCallTextModelShouldNotTruncateLongResume();
1088
1471
  await testCallTextModelShouldFallbackToChunkModeOnContextLimit();
1089
1472
  await testPrepareVisionImageSegmentsShouldSplitLongImage();
1473
+ testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
1474
+ await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();
1475
+ await testRecoverableGreetButtonNotFoundShouldNotAbortWhenDetailCloseFails();
1090
1476
  await testCloseDetailPageShouldFailWhenDetailStillOpenAndListNotReady();
1091
1477
  await testCloseDetailPageShouldContinueWhenListReady();
1092
1478
  console.log("recoverable resume failure tests passed");