@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.
- package/README.md +82 -1
- package/package.json +2 -1
- package/skills/boss-chat/README.md +5 -0
- package/skills/boss-chat/SKILL.md +69 -0
- package/skills/boss-recommend-pipeline/SKILL.md +40 -4
- package/src/adapters.js +19 -5
- package/src/boss-chat.js +436 -0
- package/src/cli.js +294 -129
- package/src/index.js +459 -108
- package/src/parser.js +4 -5
- package/src/pipeline.js +605 -8
- package/src/run-state.js +5 -0
- package/src/test-adapters-runtime.js +69 -0
- package/src/test-boss-chat.js +399 -0
- package/src/test-index-async.js +238 -4
- package/src/test-parser.js +33 -6
- package/src/test-pipeline.js +408 -1
- package/vendor/boss-chat-cli/README.md +134 -0
- package/vendor/boss-chat-cli/package.json +53 -0
- package/vendor/boss-chat-cli/src/app.js +769 -0
- package/vendor/boss-chat-cli/src/browser/chat-page.js +2681 -0
- package/vendor/boss-chat-cli/src/cli.js +1350 -0
- package/vendor/boss-chat-cli/src/mcp/server.js +149 -0
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +193 -0
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +260 -0
- package/vendor/boss-chat-cli/src/runtime/interaction.js +102 -0
- package/vendor/boss-chat-cli/src/runtime/run-control.js +102 -0
- package/vendor/boss-chat-cli/src/services/chrome-client.js +97 -0
- package/vendor/boss-chat-cli/src/services/llm.js +352 -0
- package/vendor/boss-chat-cli/src/services/profile-store.js +157 -0
- package/vendor/boss-chat-cli/src/services/report-store.js +19 -0
- package/vendor/boss-chat-cli/src/services/resume-capture.js +554 -0
- package/vendor/boss-chat-cli/src/services/state-store.js +217 -0
- package/vendor/boss-chat-cli/src/utils/customer-key.js +82 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +902 -56
- 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");
|