@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.1

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 (88) hide show
  1. package/README.md +86 -33
  2. package/package.json +62 -9
  3. package/skills/boss-chat/SKILL.md +5 -4
  4. package/skills/boss-recommend-pipeline/SKILL.md +21 -31
  5. package/skills/boss-recruit-pipeline/README.md +17 -0
  6. package/skills/boss-recruit-pipeline/SKILL.md +55 -0
  7. package/src/chat-mcp.js +1333 -0
  8. package/src/chat-runtime-config.js +559 -0
  9. package/src/cli.js +1254 -225
  10. package/src/core/browser/index.js +378 -0
  11. package/src/core/capture/index.js +298 -0
  12. package/src/core/cv-acquisition/index.js +219 -0
  13. package/src/core/greet-quota/index.js +54 -0
  14. package/src/core/infinite-list/index.js +459 -0
  15. package/src/core/reporting/legacy-csv.js +332 -0
  16. package/src/core/run/index.js +286 -0
  17. package/src/core/screening/index.js +1166 -0
  18. package/src/core/self-heal/index.js +848 -0
  19. package/src/domains/chat/cards.js +129 -0
  20. package/src/domains/chat/constants.js +183 -0
  21. package/src/domains/chat/detail.js +1369 -0
  22. package/src/domains/chat/index.js +7 -0
  23. package/src/domains/chat/jobs.js +334 -0
  24. package/src/domains/chat/page-guard.js +88 -0
  25. package/src/domains/chat/roots.js +56 -0
  26. package/src/domains/chat/run-service.js +1101 -0
  27. package/src/domains/recommend/actions.js +457 -0
  28. package/src/domains/recommend/cards.js +228 -0
  29. package/src/domains/recommend/constants.js +141 -0
  30. package/src/domains/recommend/detail.js +341 -0
  31. package/src/domains/recommend/filters.js +581 -0
  32. package/src/domains/recommend/index.js +10 -0
  33. package/src/domains/recommend/jobs.js +232 -0
  34. package/src/domains/recommend/refresh.js +204 -0
  35. package/src/domains/recommend/roots.js +78 -0
  36. package/src/domains/recommend/run-service.js +903 -0
  37. package/src/domains/recommend/scopes.js +245 -0
  38. package/src/domains/recruit/actions.js +277 -0
  39. package/src/domains/recruit/cards.js +66 -0
  40. package/src/domains/recruit/constants.js +130 -0
  41. package/src/domains/recruit/detail.js +414 -0
  42. package/src/domains/recruit/index.js +9 -0
  43. package/src/domains/recruit/instruction-parser.js +451 -0
  44. package/src/domains/recruit/refresh.js +40 -0
  45. package/src/domains/recruit/roots.js +67 -0
  46. package/src/domains/recruit/run-service.js +580 -0
  47. package/src/domains/recruit/search.js +1149 -0
  48. package/src/index.js +578 -419
  49. package/src/recommend-mcp.js +1257 -0
  50. package/src/recruit-mcp.js +1035 -0
  51. package/src/adapters.js +0 -3079
  52. package/src/boss-chat.js +0 -1037
  53. package/src/pipeline.js +0 -2249
  54. package/src/recommend-healing-config.js +0 -131
  55. package/src/recommend-healing-rules.json +0 -261
  56. package/src/self-heal.js +0 -2237
  57. package/src/test-adapters-runtime.js +0 -628
  58. package/src/test-boss-chat.js +0 -3196
  59. package/src/test-index-async.js +0 -498
  60. package/src/test-parser.js +0 -742
  61. package/src/test-pipeline.js +0 -2703
  62. package/src/test-run-state.js +0 -152
  63. package/src/test-self-heal.js +0 -224
  64. package/vendor/boss-chat-cli/README.md +0 -134
  65. package/vendor/boss-chat-cli/package.json +0 -53
  66. package/vendor/boss-chat-cli/src/app.js +0 -1501
  67. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  68. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  69. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  70. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  71. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  72. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  73. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  74. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  75. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  76. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  77. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  78. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  79. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  80. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  81. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  82. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  83. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
  84. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  85. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  86. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
  87. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  88. package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
@@ -1,2423 +0,0 @@
1
- const assert = require("node:assert/strict");
2
- const fs = require("node:fs");
3
- const os = require("node:os");
4
- const path = require("node:path");
5
- const sharp = require("sharp");
6
-
7
- const { RecommendScreenCli, parseArgs, __testables } = require("./boss-recommend-screen-cli.cjs");
8
- const { __testables: captureTestables } = require("./scripts/capture-full-resume-canvas.cjs");
9
-
10
- class FakeRecommendScreenCli extends RecommendScreenCli {
11
- constructor(args, options = {}) {
12
- super(args);
13
- this.testCandidates = options.candidates || [];
14
- this.captureOutcomes = options.captureOutcomes || new Map();
15
- this.screeningByKey = options.screeningByKey || new Map();
16
- this.domResumeByKey = options.domResumeByKey || new Map();
17
- this.discoveryCalls = 0;
18
- this.lastCapturedCandidateKey = null;
19
- }
20
-
21
- async connect() {}
22
-
23
- async disconnect() {}
24
-
25
- async getDetailClosedState() {
26
- return { closed: true, reason: "test" };
27
- }
28
-
29
- async closeDetailPage() {
30
- return true;
31
- }
32
-
33
- async waitForListReady() {
34
- return true;
35
- }
36
-
37
- async ensureHealthyListViewport() {
38
- return {
39
- ok: true,
40
- state: { ok: true }
41
- };
42
- }
43
-
44
- async discoverCandidates() {
45
- if (this.discoveryCalls === 0) {
46
- for (const candidate of this.testCandidates) {
47
- this.candidateByKey.set(candidate.key, candidate);
48
- this.discoveredKeys.add(candidate.key);
49
- this.candidateQueue.push(candidate.key);
50
- this.insertCounter += 1;
51
- this.insertedAt.set(candidate.key, this.insertCounter);
52
- }
53
- this.discoveryCalls += 1;
54
- return {
55
- ok: true,
56
- added: this.testCandidates.length,
57
- candidate_count: this.testCandidates.length,
58
- total_cards: this.testCandidates.length
59
- };
60
- }
61
- this.discoveryCalls += 1;
62
- return {
63
- ok: true,
64
- added: 0,
65
- candidate_count: this.testCandidates.length,
66
- total_cards: this.testCandidates.length
67
- };
68
- }
69
-
70
- async scrollAndLoadMore() {
71
- return {
72
- before: {
73
- candidateCount: this.testCandidates.length,
74
- scrollTop: 0,
75
- scrollHeight: 100
76
- },
77
- after: {
78
- candidateCount: this.testCandidates.length,
79
- scrollTop: 0,
80
- scrollHeight: 100
81
- },
82
- bottom: {
83
- isBottom: true
84
- }
85
- };
86
- }
87
-
88
- async clickCandidate() {}
89
-
90
- async ensureDetailOpen() {
91
- return true;
92
- }
93
-
94
- async extractResumeTextFromDom(candidate) {
95
- return this.domResumeByKey.get(candidate.key) || null;
96
- }
97
-
98
- async captureResumeImage(candidate) {
99
- const outcome = this.captureOutcomes.get(candidate.key);
100
- if (outcome instanceof Error) {
101
- throw outcome;
102
- }
103
- this.lastCapturedCandidateKey = candidate.key;
104
- return outcome || {
105
- stitchedImage: path.join(os.tmpdir(), `${candidate.key}.png`)
106
- };
107
- }
108
-
109
- async callVisionModel() {
110
- return this.screeningByKey.get(this.lastCapturedCandidateKey) || {
111
- passed: false,
112
- reason: "not matched",
113
- summary: "not matched"
114
- };
115
- }
116
-
117
- async favoriteCandidate() {
118
- return { actionTaken: "favorite" };
119
- }
120
-
121
- async greetCandidate() {
122
- return { actionTaken: "greet" };
123
- }
124
-
125
- async takeBreakIfNeeded() {}
126
-
127
- saveCsv() {}
128
-
129
- saveCheckpoint() {}
130
- }
131
-
132
- class FakeDetailCloseProbeCli extends RecommendScreenCli {
133
- constructor(args, options = {}) {
134
- super(args);
135
- this.listReady = options.listReady === true;
136
- this.evaluateCallCount = 0;
137
- }
138
-
139
- async getDetailClosedState() {
140
- return { closed: false, reason: "popup visible: .boss-popup__wrapper" };
141
- }
142
-
143
- async evaluate() {
144
- this.evaluateCallCount += 1;
145
- if (this.evaluateCallCount >= 2) {
146
- return this.listReady
147
- ? { ok: true, candidate_count: 1 }
148
- : { ok: false, error: "LIST_NOT_READY" };
149
- }
150
- return { ok: false, error: "CLOSE_ACTION_NOOP" };
151
- }
152
-
153
- async pressEsc() {}
154
- }
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
-
181
- function createResumeCaptureError(message = "Resume canvas not found") {
182
- const error = new Error(message);
183
- error.code = "RESUME_CAPTURE_FAILED";
184
- error.retryable = true;
185
- return error;
186
- }
187
-
188
- function createViewportState(cli, {
189
- actualWidth,
190
- actualHeight = 585,
191
- screenAvailWidth = 1440,
192
- windowState = "maximized",
193
- windowWidth = 1454
194
- }) {
195
- const state = {
196
- ok: true,
197
- clientWidth: actualWidth,
198
- clientHeight: actualHeight,
199
- frameRect: {
200
- width: actualWidth,
201
- height: actualHeight
202
- },
203
- viewport: {
204
- width: actualWidth,
205
- height: actualHeight
206
- },
207
- topViewport: {
208
- innerWidth: actualWidth,
209
- innerHeight: actualHeight,
210
- outerWidth: actualWidth,
211
- outerHeight: actualHeight,
212
- visualWidth: actualWidth,
213
- visualHeight: actualHeight,
214
- screenAvailWidth,
215
- screenAvailHeight: 860,
216
- devicePixelRatio: 2
217
- }
218
- };
219
- state.viewportDiagnostics = cli.buildViewportHealthDiagnostics(
220
- state,
221
- {
222
- bounds: {
223
- left: -7,
224
- top: -7,
225
- width: windowWidth,
226
- height: 874,
227
- windowState
228
- }
229
- },
230
- {
231
- cssVisualViewport: {
232
- clientWidth: actualWidth,
233
- clientHeight: actualHeight
234
- },
235
- cssLayoutViewport: {
236
- clientWidth: actualWidth,
237
- clientHeight: actualHeight
238
- }
239
- }
240
- );
241
- return state;
242
- }
243
-
244
- function createArgs(tempDir) {
245
- return {
246
- baseUrl: "https://example.invalid/v1",
247
- apiKey: "test-key",
248
- model: "test-model",
249
- criteria: "test criteria",
250
- targetCount: null,
251
- maxGreetCount: null,
252
- pageScope: "recommend",
253
- port: 9222,
254
- output: path.join(tempDir, "result.csv"),
255
- inputSummary: null,
256
- checkpointPath: path.join(tempDir, "checkpoint.json"),
257
- pauseControlPath: path.join(tempDir, "pause.json"),
258
- resume: false,
259
- postAction: "none",
260
- postActionConfirmed: true,
261
- help: false,
262
- __provided: {
263
- baseUrl: true,
264
- apiKey: true,
265
- model: true,
266
- criteria: true,
267
- targetCount: true,
268
- maxGreetCount: false,
269
- pageScope: true,
270
- port: true,
271
- inputSummary: false,
272
- postAction: true,
273
- postActionConfirmed: true
274
- }
275
- };
276
- }
277
-
278
- function testViewportCollapseRiskShouldUseRelativeWidth() {
279
- const cli = new RecommendScreenCli(createArgs(os.tmpdir()));
280
- const state = createViewportState(cli, {
281
- actualWidth: 785,
282
- screenAvailWidth: 1440,
283
- windowState: "maximized",
284
- windowWidth: 1454
285
- });
286
- assert.equal(state.viewportDiagnostics.relativeCollapsed, true);
287
- assert.equal(cli.isListViewportCollapsed(state), true);
288
- assert.equal(Math.round(state.viewportDiagnostics.widthRatio * 1000), 545);
289
- }
290
-
291
- function testNormalMaximizedViewportShouldNotCollapse() {
292
- const cli = new RecommendScreenCli(createArgs(os.tmpdir()));
293
- const state = createViewportState(cli, {
294
- actualWidth: 1280,
295
- screenAvailWidth: 1440,
296
- windowState: "maximized",
297
- windowWidth: 1454
298
- });
299
- assert.equal(state.viewportDiagnostics.relativeCollapsed, false);
300
- assert.equal(cli.isListViewportCollapsed(state), false);
301
- }
302
-
303
- function testSmallNormalWindowShouldNotUseScreenWidthRatio() {
304
- const cli = new RecommendScreenCli(createArgs(os.tmpdir()));
305
- const state = createViewportState(cli, {
306
- actualWidth: 785,
307
- screenAvailWidth: 1440,
308
- windowState: "normal",
309
- windowWidth: 800
310
- });
311
- assert.equal(state.viewportDiagnostics.nearFullscreen, false);
312
- assert.equal(state.viewportDiagnostics.relativeCollapsed, false);
313
- assert.equal(cli.isListViewportCollapsed(state), false);
314
- }
315
-
316
- async function testViewportCollapseRiskShouldTriggerRecovery() {
317
- const cli = new RecommendScreenCli(createArgs(os.tmpdir()));
318
- const collapsedState = createViewportState(cli, {
319
- actualWidth: 785,
320
- screenAvailWidth: 1440,
321
- windowState: "maximized",
322
- windowWidth: 1454
323
- });
324
- const healthyState = createViewportState(cli, {
325
- actualWidth: 1280,
326
- screenAvailWidth: 1440,
327
- windowState: "maximized",
328
- windowWidth: 1454
329
- });
330
- let listStateCalls = 0;
331
- let recoveryCalled = false;
332
- cli.getListState = async () => {
333
- listStateCalls += 1;
334
- return listStateCalls === 1 ? collapsedState : healthyState;
335
- };
336
- cli.toggleWindowStateForViewportRecovery = async () => {
337
- recoveryCalled = true;
338
- return true;
339
- };
340
- const result = await cli.ensureHealthyListViewport("test_relative_viewport");
341
- assert.equal(recoveryCalled, true);
342
- assert.equal(result.ok, true);
343
- assert.equal(result.recovered, true);
344
- assert.equal(result.state, healthyState);
345
- }
346
-
347
- async function withLongResumeChunkEnv(overrides, fn) {
348
- const envKeys = [
349
- "BOSS_RECOMMEND_TEXT_CHUNK_SIZE_CHARS",
350
- "BOSS_RECOMMEND_TEXT_CHUNK_OVERLAP_CHARS",
351
- "BOSS_RECOMMEND_TEXT_MAX_CHUNKS"
352
- ];
353
- const previous = Object.fromEntries(envKeys.map((key) => [key, process.env[key]]));
354
- for (const [key, value] of Object.entries(overrides || {})) {
355
- process.env[key] = String(value);
356
- }
357
- try {
358
- return await fn();
359
- } finally {
360
- for (const key of envKeys) {
361
- if (previous[key] === undefined) {
362
- delete process.env[key];
363
- } else {
364
- process.env[key] = previous[key];
365
- }
366
- }
367
- }
368
- }
369
-
370
- function testShouldAbortResumeProbeEarly() {
371
- const probe = {
372
- ok: false,
373
- reason: "NO_CRESUME_IFRAME",
374
- debug: {
375
- activeScopeCount: 0,
376
- totalResumeIframes: 0,
377
- visibleResumeIframes: 0
378
- }
379
- };
380
- const shouldAbort = captureTestables.shouldAbortResumeProbeEarly({
381
- probe,
382
- stableNoResumeIframePolls: captureTestables.EARLY_FAIL_NO_RESUME_IFRAME_STABLE_POLLS,
383
- elapsedMs: captureTestables.EARLY_FAIL_NO_RESUME_IFRAME_MIN_WAIT_MS,
384
- waitResumeMs: 60000
385
- });
386
- assert.equal(shouldAbort, true);
387
- }
388
-
389
- function testResumeViewportStabilityRequiresSettledScrollAndClip() {
390
- const previous = {
391
- ok: true,
392
- scrollTop: 200,
393
- scrollHeight: 1000,
394
- clientHeight: 400,
395
- maxScroll: 600,
396
- clip: { x: 10, y: 20, width: 300, height: 400 }
397
- };
398
- const current = {
399
- ok: true,
400
- scrollTop: 200.5,
401
- scrollHeight: 1000,
402
- clientHeight: 400,
403
- maxScroll: 600,
404
- clip: { x: 10, y: 20, width: 300, height: 400 }
405
- };
406
- assert.equal(captureTestables.isStableResumeViewport(previous, current, 200), true);
407
- assert.equal(captureTestables.isStableResumeViewport(previous, { ...current, scrollTop: 180 }, 200), false);
408
- assert.equal(
409
- captureTestables.isStableResumeViewport(previous, { ...current, clip: { ...current.clip, height: 360 } }, 200),
410
- false
411
- );
412
- }
413
-
414
- async function testSingleResumeCaptureFailureIsSkipped() {
415
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-skip-"));
416
- const badCandidate = { key: "bad", geek_id: "bad", name: "bad candidate" };
417
- const goodCandidate = { key: "good", geek_id: "good", name: "good candidate" };
418
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
419
- candidates: [badCandidate, goodCandidate],
420
- captureOutcomes: new Map([
421
- ["bad", createResumeCaptureError()],
422
- ["good", { stitchedImage: path.join(tempDir, "good.png") }]
423
- ]),
424
- screeningByKey: new Map([
425
- ["good", { passed: true, reason: "matched", summary: "matched" }]
426
- ])
427
- });
428
-
429
- const result = await cli.run();
430
- assert.equal(result.status, "COMPLETED");
431
- assert.equal(result.result.processed_count, 2);
432
- assert.equal(result.result.passed_count, 1);
433
- assert.equal(result.result.skipped_count, 1);
434
- assert.equal(cli.consecutiveResumeCaptureFailures, 0);
435
- }
436
-
437
- async function testConsecutiveResumeCaptureFailuresStillAbort() {
438
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-abort-"));
439
- const maxFailures = __testables.MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES;
440
- const candidates = Array.from({ length: maxFailures }, (_, index) => ({
441
- key: `fail-${index + 1}`,
442
- geek_id: `fail-${index + 1}`,
443
- name: `fail-${index + 1}`
444
- }));
445
- const captureOutcomes = new Map(
446
- candidates.map((candidate) => [candidate.key, createResumeCaptureError(`Resume capture failed for ${candidate.key}`)])
447
- );
448
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
449
- candidates,
450
- captureOutcomes
451
- });
452
-
453
- await assert.rejects(
454
- () => cli.run(),
455
- (error) => {
456
- assert.equal(error.code, "RESUME_CAPTURE_FAILED_CONSECUTIVE_LIMIT");
457
- assert.match(error.message, /连续 .* 位候选人简历(?:捕获失败|获取失败(network \+ (?:DOM \+ )?截图))/);
458
- assert.equal(error.rollback?.rollback_count, maxFailures);
459
- assert.equal(error.partial_result?.processed_count, 0);
460
- assert.equal(error.partial_result?.skipped_count, 0);
461
- assert.deepEqual(Array.from(cli.processedKeys), []);
462
- return true;
463
- }
464
- );
465
- }
466
-
467
- async function testPageExhaustedBeforeTargetShouldRaiseRecoverableError() {
468
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-page-exhausted-"));
469
- const args = createArgs(tempDir);
470
- args.targetCount = 5;
471
- const cli = new FakeRecommendScreenCli(args);
472
- cli.scrollAndLoadMore = async () => ({
473
- before: {
474
- candidateCount: 0,
475
- scrollTop: 120,
476
- scrollHeight: 900
477
- },
478
- after: {
479
- candidateCount: 0,
480
- scrollTop: 900,
481
- scrollHeight: 900
482
- },
483
- bottom: {
484
- isBottom: true,
485
- finished_wrap_visible: true,
486
- refresh_button_visible: true,
487
- refresh_button_text: "刷新"
488
- }
489
- });
490
-
491
- await assert.rejects(
492
- () => cli.run(),
493
- (error) => {
494
- assert.equal(error.code, "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED");
495
- assert.equal(error.retryable, true);
496
- assert.equal(error.partial_result?.processed_count, 0);
497
- assert.equal(error.partial_result?.output_csv, args.output);
498
- assert.equal(error.partial_result?.checkpoint_path, args.checkpointPath);
499
- assert.equal(error.partial_result?.completion_reason, "page_exhausted_before_target_count");
500
- assert.equal(error.page_exhaustion?.reason, "bottom_reached");
501
- assert.equal(error.page_exhaustion?.bottom?.finished_wrap_visible, true);
502
- assert.equal(error.page_exhaustion?.bottom?.refresh_button_visible, true);
503
- return true;
504
- }
505
- );
506
- }
507
-
508
- async function testPageExhaustedWithoutTargetShouldStillComplete() {
509
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-page-complete-"));
510
- const cli = new FakeRecommendScreenCli(createArgs(tempDir));
511
- cli.scrollAndLoadMore = async () => ({
512
- before: {
513
- candidateCount: 0,
514
- scrollTop: 120,
515
- scrollHeight: 900
516
- },
517
- after: {
518
- candidateCount: 0,
519
- scrollTop: 900,
520
- scrollHeight: 900
521
- },
522
- bottom: {
523
- isBottom: true,
524
- finished_wrap_visible: true,
525
- refresh_button_visible: true,
526
- refresh_button_text: "刷新"
527
- }
528
- });
529
-
530
- const result = await cli.run();
531
- assert.equal(result.status, "COMPLETED");
532
- assert.equal(result.result.processed_count, 0);
533
- assert.equal(result.result.output_csv, cli.args.output);
534
- assert.equal(result.result.checkpoint_path, cli.args.checkpointPath);
535
- assert.equal(result.result.completion_reason, "page_exhausted");
536
- }
537
-
538
- async function testTargetCountShouldStopWhenPassedCountReached() {
539
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-target-pass-stop-"));
540
- const args = createArgs(tempDir);
541
- args.targetCount = 1;
542
- const first = { key: "pass-1", geek_id: "pass-1", name: "pass-1" };
543
- const second = { key: "skip-2", geek_id: "skip-2", name: "skip-2" };
544
- const cli = new FakeRecommendScreenCli(args, {
545
- candidates: [first, second],
546
- screeningByKey: new Map([
547
- ["pass-1", { passed: true, reason: "matched", summary: "matched" }],
548
- ["skip-2", { passed: false, reason: "not matched", summary: "not matched" }]
549
- ])
550
- });
551
-
552
- const result = await cli.run();
553
- assert.equal(result.status, "COMPLETED");
554
- assert.equal(result.result.processed_count, 1);
555
- assert.equal(result.result.passed_count, 1);
556
- assert.equal(result.result.skipped_count, 0);
557
- assert.equal(result.result.completion_reason, "target_count_reached");
558
- }
559
-
560
- async function testTargetCountShouldNotTreatProcessedCountAsReached() {
561
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-target-pass-only-"));
562
- const args = createArgs(tempDir);
563
- args.targetCount = 1;
564
- const first = { key: "skip-a", geek_id: "skip-a", name: "skip-a" };
565
- const second = { key: "skip-b", geek_id: "skip-b", name: "skip-b" };
566
- const cli = new FakeRecommendScreenCli(args, {
567
- candidates: [first, second],
568
- screeningByKey: new Map([
569
- ["skip-a", { passed: false, reason: "not matched", summary: "not matched" }],
570
- ["skip-b", { passed: false, reason: "not matched", summary: "not matched" }]
571
- ])
572
- });
573
-
574
- await assert.rejects(
575
- () => cli.run(),
576
- (error) => {
577
- assert.equal(error.code, "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED");
578
- assert.equal(error.retryable, true);
579
- assert.equal(error.partial_result?.processed_count, 2);
580
- assert.equal(error.partial_result?.passed_count, 0);
581
- assert.equal(error.partial_result?.completion_reason, "page_exhausted_before_target_count");
582
- return true;
583
- }
584
- );
585
- }
586
-
587
- async function testFeaturedShouldUseNetworkResumeOnly() {
588
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-network-first-"));
589
- const candidate = { key: "net-1", geek_id: "net-1", name: "network candidate" };
590
- const args = createArgs(tempDir);
591
- args.pageScope = "featured";
592
- const cli = new FakeRecommendScreenCli(args, {
593
- candidates: [candidate]
594
- });
595
-
596
- cli.waitForNetworkResumeCandidateInfo = async () => ({
597
- name: "network candidate",
598
- school: "测试大学",
599
- major: "计算机",
600
- company: "OpenClaw",
601
- position: "工程师",
602
- resumeText: "有丰富 MCP 经验"
603
- });
604
- cli.callTextModel = async () => ({
605
- passed: true,
606
- reason: "network pass",
607
- summary: "network summary"
608
- });
609
- cli.captureResumeImage = async () => {
610
- throw new Error("capture should not be called");
611
- };
612
-
613
- const result = await cli.run();
614
- assert.equal(result.status, "COMPLETED");
615
- assert.equal(result.result.passed_count, 1);
616
- assert.equal(result.result.resume_source, "network");
617
- }
618
-
619
- async function testRecommendShouldPreferNetworkResumeWhenAvailable() {
620
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-recommend-network-main-"));
621
- const candidate = { key: "net-main-1", geek_id: "net-main-1", name: "recommend network main candidate" };
622
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
623
- candidates: [candidate]
624
- });
625
- cli.waitForNetworkResumeCandidateInfo = async () => ({
626
- resumeText: "这段 network 文本在 recommend 页面应优先用于筛选"
627
- });
628
- cli.callTextModel = async () => ({
629
- passed: true,
630
- reason: "network used",
631
- summary: "network used"
632
- });
633
- cli.captureResumeImage = async () => {
634
- throw new Error("capture should not be called when recommend network resume exists");
635
- };
636
-
637
- const result = await cli.run();
638
- assert.equal(result.status, "COMPLETED");
639
- assert.equal(result.result.passed_count, 1);
640
- assert.equal(result.result.resume_source, "network");
641
- }
642
-
643
- async function testNetworkMissShouldFallbackToImageThenDom() {
644
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-network-dom-fallback-"));
645
- const candidate = { key: "dom-1", geek_id: "dom-1", name: "dom candidate" };
646
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
647
- candidates: [candidate],
648
- domResumeByKey: new Map([
649
- ["dom-1", {
650
- name: "dom candidate",
651
- school: "华中科技大学",
652
- major: "智能科学与技术",
653
- company: "小米科技(武汉)",
654
- position: "算法工程师",
655
- resumeText: "教育经历:华中科技大学(本硕),专业智能科学与技术。工作与项目经历包含多模态、大模型微调、Agent 架构设计与落地。"
656
- }]
657
- ])
658
- });
659
-
660
- cli.waitForNetworkResumeCandidateInfo = async () => null;
661
- let captureAttempted = false;
662
- cli.callTextModel = async (resumeText) => ({
663
- passed: true,
664
- reason: resumeText.includes("华中科技大学") ? "dom fallback used" : "unexpected",
665
- summary: "dom fallback used"
666
- });
667
- cli.captureResumeImage = async () => {
668
- captureAttempted = true;
669
- throw new Error("capture failed, should fallback to dom");
670
- };
671
-
672
- const result = await cli.run();
673
- assert.equal(result.status, "COMPLETED");
674
- assert.equal(result.result.passed_count, 1);
675
- assert.equal(result.result.resume_source, "dom_fallback");
676
- assert.equal(cli.passedCandidates.length, 1);
677
- assert.equal(cli.passedCandidates[0].school, "华中科技大学");
678
- assert.equal(cli.passedCandidates[0].resumeSource, "dom_fallback");
679
- assert.equal(captureAttempted, true);
680
- }
681
-
682
- async function testNetworkMissShouldFallbackToImageCapture() {
683
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-network-fallback-"));
684
- const candidate = { key: "img-1", geek_id: "img-1", name: "image candidate" };
685
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
686
- candidates: [candidate],
687
- captureOutcomes: new Map([
688
- ["img-1", { stitchedImage: path.join(tempDir, "img-1.png") }]
689
- ]),
690
- screeningByKey: new Map([
691
- ["img-1", { passed: false, reason: "image path used", summary: "image path used" }]
692
- ])
693
- });
694
- cli.waitForNetworkResumeCandidateInfo = async () => null;
695
-
696
- const result = await cli.run();
697
- assert.equal(result.status, "COMPLETED");
698
- assert.equal(result.result.resume_source, "image_fallback");
699
- }
700
-
701
- async function testImageModeShouldUseShortNetworkGraceWindow() {
702
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-image-mode-grace-"));
703
- const first = { key: "img-mode-1", geek_id: "img-mode-1", name: "image mode one" };
704
- const second = { key: "img-mode-2", geek_id: "img-mode-2", name: "image mode two" };
705
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
706
- candidates: [first, second],
707
- captureOutcomes: new Map([
708
- [first.key, { stitchedImage: path.join(tempDir, "img-mode-1.png") }],
709
- [second.key, { stitchedImage: path.join(tempDir, "img-mode-2.png") }]
710
- ]),
711
- screeningByKey: new Map([
712
- [first.key, { passed: false, reason: "image one", summary: "image one" }],
713
- [second.key, { passed: false, reason: "image two", summary: "image two" }]
714
- ])
715
- });
716
- const waits = [];
717
- cli.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => {
718
- waits.push(timeoutMs);
719
- return null;
720
- };
721
-
722
- const result = await cli.run();
723
- assert.equal(result.status, "COMPLETED");
724
- assert.equal(cli.resumeAcquisitionMode, "image");
725
- assert.deepEqual(waits.slice(-1), [__testables.NETWORK_RESUME_IMAGE_MODE_GRACE_MS]);
726
- }
727
-
728
- async function testImageFailureShouldLateRetryNetworkBeforeDomFallback() {
729
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-image-fail-late-network-"));
730
- const candidate = { key: "late-network-1", geek_id: "late-network-1", name: "late network candidate" };
731
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
732
- candidates: [candidate]
733
- });
734
- let domUsed = false;
735
- cli.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => (
736
- timeoutMs === __testables.NETWORK_RESUME_LATE_RETRY_MS
737
- ? { resumeText: "late network resume text" }
738
- : null
739
- );
740
- cli.captureResumeImage = async () => {
741
- throw createResumeCaptureError("image capture failed before late network");
742
- };
743
- cli.extractResumeTextFromDom = async () => {
744
- domUsed = true;
745
- return null;
746
- };
747
- cli.callTextModel = async () => ({
748
- passed: true,
749
- reason: "late network used",
750
- summary: "late network used"
751
- });
752
-
753
- const result = await cli.run();
754
- assert.equal(result.status, "COMPLETED");
755
- assert.equal(result.result.resume_source, "network");
756
- assert.equal(domUsed, false);
757
- }
758
-
759
- async function testLatestShouldPreferNetworkResumeWhenAvailable() {
760
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-network-main-"));
761
- const args = createArgs(tempDir);
762
- args.pageScope = "latest";
763
- const candidate = { key: "latest-net-1", geek_id: "latest-net-1", name: "latest network candidate" };
764
- const cli = new FakeRecommendScreenCli(args, {
765
- candidates: [candidate]
766
- });
767
- cli.waitForNetworkResumeCandidateInfo = async () => ({
768
- resumeText: "最新页 network 简历可用"
769
- });
770
- cli.callTextModel = async () => ({
771
- passed: true,
772
- reason: "network used",
773
- summary: "network used"
774
- });
775
- cli.captureResumeImage = async () => {
776
- throw new Error("capture should not be called when latest network resume exists");
777
- };
778
-
779
- const result = await cli.run();
780
- assert.equal(result.status, "COMPLETED");
781
- assert.equal(result.result.passed_count, 1);
782
- assert.equal(result.result.resume_source, "network");
783
- }
784
-
785
- async function testLatestNetworkMissShouldFallbackToImageCapture() {
786
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-network-fallback-"));
787
- const args = createArgs(tempDir);
788
- args.pageScope = "latest";
789
- const candidate = { key: "latest-img-1", geek_id: "latest-img-1", name: "latest image candidate" };
790
- const cli = new FakeRecommendScreenCli(args, {
791
- candidates: [candidate],
792
- captureOutcomes: new Map([
793
- ["latest-img-1", { stitchedImage: path.join(tempDir, "latest-img-1.png") }]
794
- ]),
795
- screeningByKey: new Map([
796
- ["latest-img-1", { passed: false, reason: "image fallback used", summary: "image fallback used" }]
797
- ])
798
- });
799
- cli.waitForNetworkResumeCandidateInfo = async () => null;
800
-
801
- const result = await cli.run();
802
- assert.equal(result.status, "COMPLETED");
803
- assert.equal(result.result.resume_source, "image_fallback");
804
- }
805
-
806
- function testLatestPayloadShouldNotLeakAcrossCandidates() {
807
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-payload-leak-"));
808
- const cli = new RecommendScreenCli(createArgs(tempDir));
809
- cli.latestResumeNetworkPayload = {
810
- ts: Date.now(),
811
- geekIds: ["candidate-a"],
812
- candidateInfo: {
813
- resumeText: "candidate-a resume"
814
- }
815
- };
816
- const extracted = cli.tryExtractNetworkResumeForCandidate({
817
- key: "candidate-b",
818
- geek_id: "candidate-b"
819
- });
820
- assert.equal(extracted, null);
821
- }
822
-
823
- function testLatestPayloadShouldRemainAvailableWhenCandidateKeyMissing() {
824
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-payload-no-key-"));
825
- const cli = new RecommendScreenCli(createArgs(tempDir));
826
- cli.latestResumeNetworkPayload = {
827
- ts: Date.now(),
828
- geekIds: ["candidate-a"],
829
- candidateInfo: {
830
- resumeText: "recent resume payload"
831
- }
832
- };
833
- const extracted = cli.tryExtractNetworkResumeForCandidate({
834
- key: "",
835
- geek_id: ""
836
- });
837
- assert.equal(extracted?.candidateInfo?.resumeText, "recent resume payload");
838
- }
839
-
840
- async function testVisionModelFailureShouldSkipCandidateAndContinue() {
841
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-vision-failure-skip-"));
842
- const first = { key: "vision-fail-1", geek_id: "vision-fail-1", name: "vision-fail-1" };
843
- const second = { key: "vision-pass-2", geek_id: "vision-pass-2", name: "vision-pass-2" };
844
- const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
845
- candidates: [first, second],
846
- captureOutcomes: new Map([
847
- ["vision-fail-1", { stitchedImage: path.join(tempDir, "vision-fail-1.png") }],
848
- ["vision-pass-2", { stitchedImage: path.join(tempDir, "vision-pass-2.png") }]
849
- ]),
850
- screeningByKey: new Map([
851
- ["vision-pass-2", { passed: true, reason: "ok", summary: "ok" }]
852
- ])
853
- });
854
-
855
- cli.callVisionModel = async () => {
856
- if (cli.lastCapturedCandidateKey === "vision-fail-1") {
857
- const error = new Error("model backend timeout");
858
- error.code = "VISION_MODEL_FAILED";
859
- throw error;
860
- }
861
- return {
862
- passed: true,
863
- reason: "ok",
864
- summary: "ok"
865
- };
866
- };
867
-
868
- const result = await cli.run();
869
- assert.equal(result.status, "COMPLETED");
870
- assert.equal(result.result.processed_count, 2);
871
- assert.equal(result.result.passed_count, 1);
872
- assert.equal(result.result.skipped_count, 1);
873
- }
874
-
875
- async function testFeaturedNetworkMissShouldFallbackToDomAfterImageFailure() {
876
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-network-only-"));
877
- const args = createArgs(tempDir);
878
- args.pageScope = "featured";
879
- const candidate = { key: "featured-no-network", geek_id: "featured-no-network", name: "featured no network" };
880
- const cli = new FakeRecommendScreenCli(args, {
881
- candidates: [candidate],
882
- domResumeByKey: new Map([
883
- ["featured-no-network", {
884
- name: "featured no network",
885
- school: "华中科技大学",
886
- major: "软件工程",
887
- company: "测试公司",
888
- position: "后端工程师",
889
- resumeText: "featured network miss 后应在截图失败后走 DOM 兜底。"
890
- }]
891
- ])
892
- });
893
- cli.waitForNetworkResumeCandidateInfo = async () => null;
894
- let captureAttempted = false;
895
- cli.callTextModel = async () => ({
896
- passed: true,
897
- reason: "dom fallback used",
898
- summary: "dom fallback used"
899
- });
900
- cli.captureResumeImage = async () => {
901
- captureAttempted = true;
902
- throw new Error("capture failed for featured scope");
903
- };
904
-
905
- const result = await cli.run();
906
- assert.equal(result.status, "COMPLETED");
907
- assert.equal(result.result.processed_count, 1);
908
- assert.equal(result.result.passed_count, 1);
909
- assert.equal(result.result.skipped_count, 0);
910
- assert.equal(result.result.resume_source, "dom_fallback");
911
- assert.equal(captureAttempted, true);
912
- assert.equal(cli.passedCandidates[0].resumeSource, "dom_fallback");
913
- }
914
-
915
- async function testFeaturedFavoriteShouldNotUseDomFallback() {
916
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-"));
917
- const args = createArgs(tempDir);
918
- args.pageScope = "featured";
919
- const calibrationPath = path.join(tempDir, "favorite-calibration.json");
920
- fs.writeFileSync(calibrationPath, JSON.stringify({
921
- favoritePosition: {
922
- pageX: 120,
923
- pageY: 220,
924
- canvasX: 0,
925
- canvasY: 0
926
- }
927
- }, null, 2));
928
- args.calibrationPath = calibrationPath;
929
- const cli = new RecommendScreenCli(args);
930
- let evaluateCalls = 0;
931
- let clickCalls = 0;
932
- cli.evaluate = async () => {
933
- evaluateCalls += 1;
934
- return { ok: true };
935
- };
936
- cli.simulateHumanClick = async () => {
937
- clickCalls += 1;
938
- cli.favoriteActionEvents.push({ action: "add", ts: Date.now(), source: "test", url: "userMark/add" });
939
- };
940
- const result = await cli.favoriteCandidate();
941
- assert.equal(result.actionTaken, "favorite");
942
- assert.equal(clickCalls, 1);
943
- assert.equal(evaluateCalls, 0);
944
- }
945
-
946
- async function testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested() {
947
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-already-"));
948
- const args = createArgs(tempDir);
949
- args.pageScope = "featured";
950
- const calibrationPath = path.join(tempDir, "favorite-calibration.json");
951
- fs.writeFileSync(calibrationPath, JSON.stringify({
952
- favoritePosition: {
953
- pageX: 120,
954
- pageY: 220,
955
- canvasX: 0,
956
- canvasY: 0
957
- }
958
- }, null, 2));
959
- args.calibrationPath = calibrationPath;
960
- const cli = new RecommendScreenCli(args);
961
- let clickCalls = 0;
962
- cli.simulateHumanClick = async () => {
963
- clickCalls += 1;
964
- };
965
- const result = await cli.favoriteCandidate({ alreadyInterested: true });
966
- assert.equal(result.actionTaken, "already_favorited");
967
- assert.equal(result.source, "network_profile");
968
- assert.equal(clickCalls, 0);
969
- }
970
-
971
- async function testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd() {
972
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-del-add-"));
973
- const args = createArgs(tempDir);
974
- args.pageScope = "featured";
975
- const calibrationPath = path.join(tempDir, "favorite-calibration.json");
976
- fs.writeFileSync(calibrationPath, JSON.stringify({
977
- favoritePosition: {
978
- pageX: 120,
979
- pageY: 220,
980
- canvasX: 0,
981
- canvasY: 0
982
- }
983
- }, null, 2));
984
- args.calibrationPath = calibrationPath;
985
- const cli = new RecommendScreenCli(args);
986
- let clickCalls = 0;
987
- cli.simulateHumanClick = async () => {
988
- clickCalls += 1;
989
- cli.favoriteActionEvents.push({
990
- action: clickCalls === 1 ? "del" : "add",
991
- ts: Date.now(),
992
- source: "test",
993
- url: clickCalls === 1 ? "userMark/del" : "userMark/add"
994
- });
995
- };
996
- const result = await cli.favoriteCandidate();
997
- assert.equal(result.actionTaken, "already_favorited");
998
- assert.equal(result.re_favorited, true);
999
- assert.equal(clickCalls, 2);
1000
- }
1001
-
1002
- async function testFeaturedFavoriteWithoutCalibrationShouldFail() {
1003
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-missing-cal-"));
1004
- const args = createArgs(tempDir);
1005
- args.pageScope = "featured";
1006
- args.calibrationPath = path.join(tempDir, "missing-calibration.json");
1007
- const cli = new RecommendScreenCli(args);
1008
- await assert.rejects(
1009
- () => cli.favoriteCandidate(),
1010
- (error) => {
1011
- assert.equal(error.code, "FAVORITE_CALIBRATION_REQUIRED");
1012
- return true;
1013
- }
1014
- );
1015
- }
1016
-
1017
- function testFavoriteActionParserShouldSupportBodySignals() {
1018
- const addFromJson = __testables.parseFavoriteActionFromPostData(JSON.stringify({
1019
- action: "star-interest-click",
1020
- p3: 1
1021
- }));
1022
- const delFromForm = __testables.parseFavoriteActionFromPostData("action=star-interest-click&p3=0");
1023
- assert.equal(addFromJson, "add");
1024
- assert.equal(delFromForm, "del");
1025
- }
1026
-
1027
- function testFavoriteActionParserShouldSupportFallbackRequestShape() {
1028
- const action = __testables.parseFavoriteActionFromRequest(
1029
- "https://www.zhipin.com/wapi/zpgeek/favorite/operate",
1030
- JSON.stringify({ op: "add", geekId: "abc" })
1031
- );
1032
- assert.equal(action, "add");
1033
- }
1034
-
1035
- function testFavoriteActionParserShouldSupportWebSocketPayload() {
1036
- const addFromWsJson = __testables.parseFavoriteActionFromWsPayload(JSON.stringify({
1037
- action: "star-interest-click",
1038
- p3: 1
1039
- }));
1040
- const delFromWsForm = __testables.parseFavoriteActionFromWsPayload("action=star-interest-click&p3=0");
1041
- assert.equal(addFromWsJson, "add");
1042
- assert.equal(delFromWsForm, "del");
1043
- }
1044
-
1045
- function testFavoriteActionParserShouldOnlyTrustKnownRequestShapes() {
1046
- const unknown = __testables.parseFavoriteActionFromKnownRequest(
1047
- "https://www.zhipin.com/wapi/other/metrics",
1048
- JSON.stringify({ action: "add", p3: 1 })
1049
- );
1050
- const actionLog = __testables.parseFavoriteActionFromKnownRequest(
1051
- "https://www.zhipin.com/wapi/zplog/actionLog/common.json",
1052
- JSON.stringify({ action: "star-interest-click", p3: 1 })
1053
- );
1054
- const userMark = __testables.parseFavoriteActionFromKnownRequest(
1055
- "https://www.zhipin.com/wapi/zpgeek/userMark/add",
1056
- ""
1057
- );
1058
- assert.equal(unknown, null);
1059
- assert.equal(actionLog, "add");
1060
- assert.equal(userMark, "add");
1061
- }
1062
-
1063
- function testFinishedWrapClassifierShouldNotTreatLoadMoreAsBottom() {
1064
- const loadMore = __testables.classifyFinishedWrapState("滚动加载更多", false);
1065
- const loading = __testables.classifyFinishedWrapState("正在加载数据...", false);
1066
- const noMore = __testables.classifyFinishedWrapState("没有更多人选", false);
1067
- const refreshOnly = __testables.classifyFinishedWrapState("", true);
1068
-
1069
- assert.equal(loadMore.isBottom, false);
1070
- assert.equal(loadMore.matched_load_more_keyword, "滚动加载更多");
1071
- assert.equal(loading.isBottom, false);
1072
- assert.equal(loading.matched_load_more_keyword, "正在加载");
1073
- assert.equal(noMore.isBottom, true);
1074
- assert.equal(noMore.matched_bottom_keyword, "没有更多");
1075
- assert.equal(refreshOnly.isBottom, true);
1076
- assert.equal(refreshOnly.reason, "refresh_button_visible");
1077
- }
1078
-
1079
- function testFormatResumeApiDataShouldPreserveEducationTagsAndProjectDescription() {
1080
- const source = {
1081
- geekDetailInfo: {
1082
- geekBaseInfo: {
1083
- name: "测试候选人",
1084
- degreeCategory: "硕士"
1085
- },
1086
- geekEduExpList: [
1087
- {
1088
- school: "南京大学",
1089
- major: "数学",
1090
- degree: 203,
1091
- degreeName: "本科",
1092
- schoolTags: [{ name: "985院校" }, { name: "QS世界大学排名TOP200" }]
1093
- }
1094
- ],
1095
- geekProjExpList: [
1096
- {
1097
- name: "Prompt-to-Prompt 3DEditing via NeRF",
1098
- projectDescription: "采用stable diffusion进行编辑实验"
1099
- }
1100
- ]
1101
- }
1102
- };
1103
- const formatted = __testables.formatResumeApiData(source);
1104
- assert.equal(formatted.includes("学历: 本科"), true);
1105
- assert.equal(formatted.includes("学历: 203"), false);
1106
- assert.equal(formatted.includes("学校标签: 985院校、QS世界大学排名TOP200"), true);
1107
- assert.equal(formatted.includes("描述: 采用stable diffusion进行编辑实验"), true);
1108
- }
1109
-
1110
- function testFormatResumeApiDataShouldIncludeStructuredJudgementHints() {
1111
- const source = {
1112
- geekDetailInfo: {
1113
- geekBaseInfo: {
1114
- name: "测试候选人",
1115
- degreeCategory: "硕士"
1116
- },
1117
- geekWorkExpList: [
1118
- {
1119
- company: "中科院",
1120
- positionName: "科研助理",
1121
- startDate: "20241001",
1122
- endDate: "",
1123
- responsibility: "科研以及项目"
1124
- }
1125
- ],
1126
- geekProjExpList: [],
1127
- geekEduExpList: [
1128
- {
1129
- school: "科克大学",
1130
- major: "理学",
1131
- degreeName: "硕士",
1132
- startDateDesc: "2020",
1133
- endDateDesc: "2023"
1134
- },
1135
- {
1136
- school: "东北大学",
1137
- major: "数学与应用数学",
1138
- degreeName: "本科",
1139
- startDate: "20140101",
1140
- endDate: "20180101"
1141
- }
1142
- ],
1143
- workExpCheckRes: [
1144
- {
1145
- desc: "毕业同年未填写工作经历"
1146
- }
1147
- ],
1148
- jobCompetitive: {
1149
- tips: [{ content: "受欢迎程度高" }]
1150
- }
1151
- }
1152
- };
1153
- const formatted = __testables.formatResumeApiData(source);
1154
- assert.equal(formatted.includes("=== 结构化判定线索 ==="), true);
1155
- assert.equal(formatted.includes("最高学历: 硕士"), true);
1156
- assert.equal(formatted.includes("最高学历毕业年份: 2023"), true);
1157
- assert.equal(formatted.includes("是否有工作经历: 是"), true);
1158
- assert.equal(formatted.includes("是否有项目经历: 否"), true);
1159
- assert.equal(formatted.includes("相关经验硬判口径"), true);
1160
- assert.equal(formatted.includes("软风险提示(需追问,不直接淘汰): 毕业同年未填写工作经历"), true);
1161
- assert.equal(formatted.includes("判定忽略项: 活跃度/沟通热度/受欢迎度等运营指标不参与通过判定。"), true);
1162
- }
1163
-
1164
- function testEnrichCandidateInfoWithCardProfileShouldAppendCardFallbackWhenDomInfoMissing() {
1165
- const candidateInfo = {
1166
- name: "",
1167
- school: "",
1168
- major: "",
1169
- company: "",
1170
- position: "",
1171
- resumeText: "=== 基本信息 ===\n姓名: 赵梓轩\n"
1172
- };
1173
- const cardProfile = {
1174
- name: "赵梓轩",
1175
- age: "29岁",
1176
- gender: "男",
1177
- highestDegree: "硕士",
1178
- workYears: "2年",
1179
- company: "中科院",
1180
- position: "科研助理",
1181
- latestWorkStart: "2024.10",
1182
- latestWorkEnd: "至今",
1183
- educationList: [
1184
- { school: "科克大学", major: "理学", degree: "硕士", start: "2020", end: "2023" },
1185
- { school: "东北大学", major: "数学与应用数学", degree: "本科", start: "2014", end: "2018" }
1186
- ]
1187
- };
1188
-
1189
- const enriched = __testables.enrichCandidateInfoWithCardProfile(candidateInfo, cardProfile);
1190
- assert.equal(enriched.name, "赵梓轩");
1191
- assert.equal(enriched.company, "中科院");
1192
- assert.equal(enriched.position, "科研助理");
1193
- assert.equal(enriched.resumeText.includes("=== 人选卡片兜底信息(仅在简历缺失时使用) ==="), true);
1194
- assert.equal(enriched.resumeText.includes("年龄: 29岁"), true);
1195
- assert.equal(enriched.resumeText.includes("性别: 男"), true);
1196
- assert.equal(enriched.resumeText.includes("最近一份工作在职日期: 2024.10 ~ 至今"), true);
1197
- assert.equal(enriched.resumeText.includes("1. 学校=科克大学;专业=理学;学历=硕士;时间=2020 ~ 2023"), true);
1198
- assert.equal(enriched.resumeText.includes("2. 学校=东北大学;专业=数学与应用数学;学历=本科;时间=2014 ~ 2018"), true);
1199
-
1200
- const enrichedAgain = __testables.enrichCandidateInfoWithCardProfile(enriched, cardProfile);
1201
- assert.equal((enrichedAgain.resumeText.match(/人选卡片兜底信息(仅在简历缺失时使用)/g) || []).length, 1);
1202
- }
1203
-
1204
- function testEvidenceTokenMatcherShouldSupportParaphrasedEvidence() {
1205
- const resume = [
1206
- "南京大学 专业: 数学",
1207
- "Prompt-to-Prompt 3DEditing via NeRF",
1208
- "采用 stable diffusion 进行编辑实验"
1209
- ].join(" | ");
1210
- const normalizedResume = resume.replace(/\s+/g, " ").trim();
1211
- const matched = __testables.matchEvidenceAgainstResume(
1212
- "项目经历包含Prompt-to-Prompt 3DEditing via NeRF(stable diffusion)",
1213
- resume,
1214
- normalizedResume,
1215
- normalizedResume.toLowerCase()
1216
- );
1217
- assert.equal(matched.matched, true);
1218
- const unmatched = __testables.matchEvidenceAgainstResume(
1219
- "有十年金融风控投研经历",
1220
- resume,
1221
- normalizedResume,
1222
- normalizedResume.toLowerCase()
1223
- );
1224
- assert.equal(unmatched.matched, false);
1225
- }
1226
-
1227
- function testCheckpointPayloadShouldIncludeCandidateAudits() {
1228
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-audit-checkpoint-"));
1229
- const cli = new RecommendScreenCli(createArgs(tempDir));
1230
- cli.recordCandidateAudit({
1231
- candidate_key: "candidate-a",
1232
- geek_id: "candidate-a",
1233
- candidate_name: "候选人A",
1234
- outcome: "skipped",
1235
- resume_source: "network",
1236
- raw_passed: true,
1237
- final_passed: false,
1238
- evidence_gate_demoted: true,
1239
- screening_reason: "模型未给出可校验证据",
1240
- decision_mode: "text_chunk_aggregate",
1241
- chunk_count: 3,
1242
- aggregate_retry_used: true
1243
- });
1244
- const checkpoint = cli.buildCheckpointPayload();
1245
- assert.equal(Array.isArray(checkpoint.candidate_audits), true);
1246
- assert.equal(checkpoint.candidate_audits.length, 1);
1247
- assert.equal(checkpoint.candidate_audits[0].candidate_key, "candidate-a");
1248
- assert.equal(checkpoint.candidate_audits[0].evidence_gate_demoted, true);
1249
- assert.equal(checkpoint.candidate_audits[0].decision_mode, "text_chunk_aggregate");
1250
- assert.equal(checkpoint.candidate_audits[0].chunk_count, 3);
1251
- assert.equal(checkpoint.candidate_audits[0].aggregate_retry_used, true);
1252
- }
1253
-
1254
- function testCheckpointShouldPersistAndRestoreInputSummary() {
1255
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-input-summary-checkpoint-"));
1256
- const args = createArgs(tempDir);
1257
- args.inputSummary = {
1258
- instruction: "筛选 AI 人选",
1259
- search_params: {
1260
- school_tag: ["985"],
1261
- degree: ["本科"]
1262
- },
1263
- screen_params: {
1264
- criteria: "有 LLM 项目经验"
1265
- },
1266
- baseUrl: "https://should-not-be-stored",
1267
- apiKey: "sk-should-not-be-stored",
1268
- model: "should-not-be-stored"
1269
- };
1270
- const cli = new RecommendScreenCli(args);
1271
- const checkpoint = cli.buildCheckpointPayload();
1272
- assert.equal(checkpoint.input_summary?.instruction, "筛选 AI 人选");
1273
- assert.equal(checkpoint.input_summary?.screen_params?.criteria, "有 LLM 项目经验");
1274
- assert.equal(Object.prototype.hasOwnProperty.call(checkpoint.input_summary || {}, "baseUrl"), false);
1275
- assert.equal(Object.prototype.hasOwnProperty.call(checkpoint.input_summary || {}, "apiKey"), false);
1276
- assert.equal(Object.prototype.hasOwnProperty.call(checkpoint.input_summary || {}, "model"), false);
1277
-
1278
- fs.writeFileSync(args.checkpointPath, JSON.stringify(checkpoint, null, 2), "utf8");
1279
- const resumeArgs = createArgs(tempDir);
1280
- resumeArgs.resume = true;
1281
- resumeArgs.inputSummary = null;
1282
- const resumeCli = new RecommendScreenCli(resumeArgs);
1283
- const restored = resumeCli.loadCheckpointIfNeeded();
1284
- assert.equal(restored, true);
1285
- assert.equal(resumeCli.inputSummary?.instruction, "筛选 AI 人选");
1286
- assert.equal(resumeCli.inputSummary?.screen_params?.criteria, "有 LLM 项目经验");
1287
- }
1288
-
1289
- function testSaveCsvShouldIncludeAllCandidateOutcomes() {
1290
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-csv-audit-"));
1291
- const args = createArgs(tempDir);
1292
- args.output = path.join(tempDir, "result.csv");
1293
- const cli = new RecommendScreenCli(args);
1294
- cli.inputSummary = {
1295
- instruction: "找算法候选人",
1296
- search_params: {
1297
- school_tag: ["985", "211"],
1298
- degree: ["本科"],
1299
- gender: "不限"
1300
- },
1301
- screen_params: {
1302
- criteria: "有 LLM 研究或工程经验",
1303
- post_action: "greet"
1304
- },
1305
- baseUrl: "https://should-not-appear",
1306
- apiKey: "sk-should-not-appear",
1307
- model: "gpt-should-not-appear"
1308
- };
1309
- cli.candidateAudits = [
1310
- {
1311
- candidate_key: "cand-pass",
1312
- geek_id: "cand-pass",
1313
- candidate_name: "通过候选人",
1314
- school: "南京大学",
1315
- major: "数学",
1316
- company: "",
1317
- position: "",
1318
- outcome: "passed",
1319
- screening_reason: "满足全部条件",
1320
- action_taken: "greet",
1321
- resume_source: "network",
1322
- raw_passed: true,
1323
- final_passed: true,
1324
- evidence_raw_count: 4,
1325
- evidence_matched_count: 3,
1326
- evidence_gate_demoted: false,
1327
- error_code: "",
1328
- error_message: ""
1329
- },
1330
- {
1331
- candidate_key: "cand-skip",
1332
- geek_id: "cand-skip",
1333
- candidate_name: "跳过候选人",
1334
- school: "某大学",
1335
- major: "工科",
1336
- company: "",
1337
- position: "",
1338
- outcome: "skipped",
1339
- screening_reason: "证据不足",
1340
- action_taken: "none",
1341
- resume_source: "network",
1342
- raw_passed: false,
1343
- final_passed: false,
1344
- evidence_raw_count: 2,
1345
- evidence_matched_count: 0,
1346
- evidence_gate_demoted: false,
1347
- error_code: "",
1348
- error_message: ""
1349
- }
1350
- ];
1351
- cli.saveCsv();
1352
- const content = fs.readFileSync(args.output, "utf8");
1353
- assert.equal(content.includes("处理结果"), true);
1354
- assert.equal(content.includes("运行输入字段"), true);
1355
- assert.equal(content.includes("instruction"), true);
1356
- assert.equal(content.includes("screen_params.criteria"), true);
1357
- assert.equal(content.includes("baseUrl"), false);
1358
- assert.equal(content.includes("apiKey"), false);
1359
- assert.equal(content.includes("model"), false);
1360
- assert.equal((content.match(/运行输入字段/g) || []).length, 1);
1361
- assert.equal(content.includes("通过候选人"), true);
1362
- assert.equal(content.includes("跳过候选人"), true);
1363
- assert.equal(content.includes("cand-skip"), true);
1364
- }
1365
-
1366
- async function testGetCenteredCandidateClickPointShouldSupportLatestSelector() {
1367
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-latest-click-locator-"));
1368
- const args = createArgs(tempDir);
1369
- args.pageScope = "latest";
1370
- const cli = new RecommendScreenCli(args);
1371
-
1372
- let expressionCaptured = "";
1373
- cli.evaluate = async (expression) => {
1374
- expressionCaptured = String(expression || "");
1375
- return {
1376
- ok: true,
1377
- x: 100,
1378
- y: 100,
1379
- width: 120,
1380
- height: 64
1381
- };
1382
- };
1383
-
1384
- const result = await cli.getCenteredCandidateClickPoint({
1385
- key: "latest-test-key",
1386
- geek_id: "latest-test-key"
1387
- });
1388
-
1389
- assert.equal(result.ok, true);
1390
- assert.equal(expressionCaptured.includes(".candidate-card-wrap .card-inner[data-geek]"), true);
1391
- assert.equal(expressionCaptured.includes("getAttribute('data-geek')"), true);
1392
- }
1393
-
1394
- async function testFeaturedPostActionFailureShouldStillRecordPassedCandidate() {
1395
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-featured-action-failure-"));
1396
- const args = createArgs(tempDir);
1397
- args.pageScope = "featured";
1398
- args.postAction = "favorite";
1399
- const candidate = { key: "featured-fav-fail", geek_id: "featured-fav-fail", name: "featured candidate" };
1400
- const cli = new FakeRecommendScreenCli(args, {
1401
- candidates: [candidate]
1402
- });
1403
-
1404
- cli.waitForNetworkResumeCandidateInfo = async () => ({
1405
- name: "featured candidate",
1406
- school: "测试大学",
1407
- major: "人工智能",
1408
- company: "测试公司",
1409
- position: "算法工程师",
1410
- resumeText: "满足测试标准"
1411
- });
1412
- cli.callTextModel = async () => ({
1413
- passed: true,
1414
- reason: "通过",
1415
- summary: "通过"
1416
- });
1417
- cli.favoriteCandidate = async () => {
1418
- const error = new Error("精选页收藏未检测到 network add 成功信号。");
1419
- error.code = "FAVORITE_BUTTON_FAILED";
1420
- throw error;
1421
- };
1422
-
1423
- const result = await cli.run();
1424
- assert.equal(result.status, "COMPLETED");
1425
- assert.equal(result.result.processed_count, 1);
1426
- assert.equal(result.result.passed_count, 1);
1427
- assert.equal(result.result.skipped_count, 0);
1428
- assert.equal(cli.passedCandidates.length, 1);
1429
- assert.equal(cli.passedCandidates[0].action, "favorite_failed");
1430
- assert.match(cli.passedCandidates[0].reason, /\[favorite失败]/);
1431
- }
1432
-
1433
- async function testRunShouldRecordLongResumeDecisionObservability() {
1434
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-decision-observability-"));
1435
- const args = createArgs(tempDir);
1436
- const candidate = { key: "aggregate-audit", geek_id: "aggregate-audit", name: "aggregate candidate" };
1437
- const cli = new FakeRecommendScreenCli(args, {
1438
- candidates: [candidate]
1439
- });
1440
- cli.waitForNetworkResumeCandidateInfo = async () => ({
1441
- name: "aggregate candidate",
1442
- school: "测试大学",
1443
- major: "人工智能",
1444
- company: "测试公司",
1445
- position: "算法工程师",
1446
- resumeText: "满足测试标准"
1447
- });
1448
- cli.callTextModel = async () => ({
1449
- passed: true,
1450
- rawPassed: true,
1451
- reason: "通过",
1452
- summary: "通过",
1453
- evidence: ["跨 chunk 证据"],
1454
- evidenceRawCount: 1,
1455
- evidenceMatchedCount: 1,
1456
- decisionMode: "text_chunk_aggregate",
1457
- chunkCount: 3,
1458
- aggregateRetryUsed: true,
1459
- chunkIndex: null,
1460
- chunkTotal: 3
1461
- });
1462
-
1463
- const result = await cli.run();
1464
- assert.equal(result.status, "COMPLETED");
1465
- assert.equal(cli.passedCandidates[0]?.decisionMode, "text_chunk_aggregate");
1466
- assert.equal(cli.passedCandidates[0]?.chunkCount, 3);
1467
- assert.equal(cli.passedCandidates[0]?.aggregateRetryUsed, true);
1468
- assert.equal(cli.candidateAudits[0]?.decision_mode, "text_chunk_aggregate");
1469
- assert.equal(cli.candidateAudits[0]?.chunk_count, 3);
1470
- assert.equal(cli.candidateAudits[0]?.aggregate_retry_used, true);
1471
- }
1472
-
1473
- async function testStitchWithSharpShouldComposeExpectedImage() {
1474
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-sharp-stitch-"));
1475
- const chunkA = path.join(tempDir, "chunk_000.png");
1476
- const chunkB = path.join(tempDir, "chunk_001.png");
1477
- const chunkC = path.join(tempDir, "chunk_002.png");
1478
- const metadataPath = path.join(tempDir, "chunks.json");
1479
- const outputPath = path.join(tempDir, "stitched.png");
1480
-
1481
- await sharp({
1482
- create: { width: 20, height: 100, channels: 3, background: { r: 255, g: 0, b: 0 } }
1483
- }).png().toFile(chunkA);
1484
- await sharp({
1485
- create: { width: 20, height: 100, channels: 3, background: { r: 0, g: 255, b: 0 } }
1486
- }).png().toFile(chunkB);
1487
- await sharp({
1488
- create: { width: 20, height: 100, channels: 3, background: { r: 0, g: 0, b: 255 } }
1489
- }).png().toFile(chunkC);
1490
-
1491
- fs.writeFileSync(
1492
- metadataPath,
1493
- JSON.stringify({
1494
- chunks: [
1495
- { index: 0, file: chunkA, scrollTop: 0, clipHeightCss: 100 },
1496
- { index: 1, file: chunkB, scrollTop: 80, clipHeightCss: 100 },
1497
- { index: 2, file: chunkC, scrollTop: 160, clipHeightCss: 100 }
1498
- ]
1499
- }),
1500
- "utf8"
1501
- );
1502
-
1503
- const stitched = await captureTestables.stitchWithSharp(metadataPath, outputPath);
1504
- const outputMeta = await sharp(outputPath).metadata();
1505
-
1506
- assert.equal(stitched.ok, true);
1507
- assert.equal(stitched.engine, "sharp");
1508
- assert.equal(stitched.segments, 3);
1509
- assert.equal(outputMeta.width, 20);
1510
- assert.equal(outputMeta.height, 260);
1511
- assert.equal(Array.isArray(stitched.used), true);
1512
- assert.equal(stitched.used.length, 3);
1513
- }
1514
-
1515
- function testStitchWithAvailablePythonShouldFallbackToPython() {
1516
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-python-fallback-"));
1517
- const stitchScript = path.join(tempDir, "stitch.py");
1518
- fs.writeFileSync(stitchScript, "print('ok')", "utf8");
1519
- const calls = [];
1520
- const result = captureTestables.stitchWithAvailablePython(
1521
- stitchScript,
1522
- path.join(tempDir, "meta.json"),
1523
- path.join(tempDir, "out.png"),
1524
- (command) => {
1525
- calls.push(command);
1526
- if (command === "python3") {
1527
- return {
1528
- status: 1,
1529
- signal: null,
1530
- error: null,
1531
- stderr: "python3 failed",
1532
- stdout: ""
1533
- };
1534
- }
1535
- return {
1536
- status: 0,
1537
- signal: null,
1538
- error: null,
1539
- stderr: "",
1540
- stdout: "ok"
1541
- };
1542
- }
1543
- );
1544
-
1545
- assert.equal(result.ok, true);
1546
- assert.equal(result.command, "python");
1547
- assert.deepEqual(calls, ["python3", "python"]);
1548
- }
1549
-
1550
- function testStitchWithAvailablePythonShouldFailWhenScriptMissing() {
1551
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-python-missing-"));
1552
- const result = captureTestables.stitchWithAvailablePython(
1553
- path.join(tempDir, "missing.py"),
1554
- path.join(tempDir, "meta.json"),
1555
- path.join(tempDir, "out.png")
1556
- );
1557
-
1558
- assert.equal(result.ok, false);
1559
- assert.equal(Array.isArray(result.attempts), true);
1560
- assert.equal(result.attempts.length, 2);
1561
- assert.equal(result.attempts[0].command, "python3");
1562
- }
1563
-
1564
- function testParseArgsShouldSupportFeaturedAliasesAndInlinePort() {
1565
- const parsed = parseArgs([
1566
- "--criteria", "test criteria",
1567
- "--baseurl", "https://example.com/v1",
1568
- "--apikey", "key",
1569
- "--model", "test-model",
1570
- "--target-count", "3",
1571
- "--pageScope", "featured",
1572
- "--port=9222",
1573
- "--postAction", "favorite",
1574
- "--postActionConfirmed", "true"
1575
- ]);
1576
- assert.equal(parsed.pageScope, "featured");
1577
- assert.equal(parsed.port, 9222);
1578
- assert.equal(parsed.targetCount, 3);
1579
- assert.equal(parsed.postAction, "favorite");
1580
- assert.equal(parsed.postActionConfirmed, true);
1581
- assert.equal(parsed.__provided.pageScope, true);
1582
- assert.equal(parsed.__provided.port, true);
1583
- }
1584
-
1585
- function testParseArgsShouldSupportLatestPageScope() {
1586
- const parsed = parseArgs([
1587
- "--criteria", "test criteria",
1588
- "--baseurl", "https://example.com/v1",
1589
- "--apikey", "key",
1590
- "--model", "test-model",
1591
- "--page-scope", "latest",
1592
- "--port", "9222",
1593
- "--post-action", "none",
1594
- "--post-action-confirmed", "true"
1595
- ]);
1596
- assert.equal(parsed.pageScope, "latest");
1597
- assert.equal(parsed.port, 9222);
1598
- }
1599
-
1600
- function testParseArgsShouldSupportInputSummaryJson() {
1601
- const parsed = parseArgs([
1602
- "--criteria", "test criteria",
1603
- "--baseurl", "https://example.com/v1",
1604
- "--apikey", "key",
1605
- "--model", "test-model",
1606
- "--post-action", "none",
1607
- "--post-action-confirmed", "true",
1608
- "--input-summary-json", "{\"instruction\":\"筛选测试\",\"search_params\":{\"school_tag\":[\"985\"]}}"
1609
- ]);
1610
- assert.equal(parsed.inputSummary?.instruction, "筛选测试");
1611
- assert.equal(parsed.inputSummary?.search_params?.school_tag?.[0], "985");
1612
- }
1613
-
1614
- async function testCallTextModelShouldNotTruncateLongResume() {
1615
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-full-"));
1616
- const cli = new RecommendScreenCli(createArgs(tempDir));
1617
- const marker = "__END_OF_RESUME_MARKER__";
1618
- const resumeText = `${"A".repeat(32000)}${marker}`;
1619
- const originalFetch = global.fetch;
1620
- let capturedUserContent = "";
1621
- let callCount = 0;
1622
- global.fetch = async (_url, options = {}) => {
1623
- callCount += 1;
1624
- const payload = JSON.parse(String(options.body || "{}"));
1625
- capturedUserContent = String(payload?.messages?.[1]?.content || "");
1626
- return {
1627
- ok: true,
1628
- status: 200,
1629
- async json() {
1630
- return {
1631
- choices: [
1632
- {
1633
- message: {
1634
- content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"A\"]}"
1635
- }
1636
- }
1637
- ]
1638
- };
1639
- }
1640
- };
1641
- };
1642
- try {
1643
- const result = await cli.callTextModel(resumeText);
1644
- assert.equal(result.passed, false);
1645
- assert.equal(result.decisionMode, "full_text");
1646
- assert.equal(callCount, 1);
1647
- assert.equal(capturedUserContent.includes(marker), true);
1648
- } finally {
1649
- global.fetch = originalFetch;
1650
- }
1651
- }
1652
-
1653
- async function testCallTextModelShouldFallbackToChunkModeOnContextLimit() {
1654
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-chunk-fallback-"));
1655
- const cli = new RecommendScreenCli(createArgs(tempDir));
1656
- const originalFetch = global.fetch;
1657
- const chunkOneMarker = "本科 2025 届,南京大学,研究方向为机器学习";
1658
- const chunkTwoMarker = "主导 AI 项目落地,并有 2 段相关工作经历";
1659
- const resumeText = `${chunkOneMarker}\n${"A".repeat(1300)}\n${chunkTwoMarker}\n${"B".repeat(300)}`;
1660
- const expectedChunkCount = __testables.splitTextByChunks(resumeText, 1000, 1, 6).length;
1661
- let callCount = 0;
1662
- let aggregateUserContent = "";
1663
- global.fetch = async (_url, options = {}) => {
1664
- callCount += 1;
1665
- if (callCount === 1) {
1666
- return {
1667
- ok: false,
1668
- status: 400,
1669
- async text() {
1670
- return "maximum context length exceeded";
1671
- }
1672
- };
1673
- }
1674
-
1675
- const payload = JSON.parse(String(options.body || "{}"));
1676
- const userContent = String(payload?.messages?.[1]?.content || "");
1677
- const chunkMatch = userContent.match(/当前分段:\s*(\d+)\/(\d+)/);
1678
- if (chunkMatch) {
1679
- const chunkIndex = Number(chunkMatch[1]);
1680
- const chunkTotal = Number(chunkMatch[2]);
1681
- const isFirstChunk = chunkIndex === 1;
1682
- const isLastChunk = chunkIndex === chunkTotal;
1683
- return {
1684
- ok: true,
1685
- status: 200,
1686
- async json() {
1687
- return {
1688
- choices: [
1689
- {
1690
- message: {
1691
- content: JSON.stringify({
1692
- chunk_passed: false,
1693
- chunk_summary: isFirstChunk
1694
- ? "教育背景满足应届要求"
1695
- : (isLastChunk ? "项目和工作经历满足相关经验要求" : "中间分段主要是补充描述"),
1696
- hard_evidence: isFirstChunk
1697
- ? ["本科 2025 届", "南京大学"]
1698
- : (isLastChunk ? ["AI 项目落地", "2 段相关工作经历"] : []),
1699
- soft_evidence: [],
1700
- hard_blockers: [],
1701
- missing_or_uncertain: isLastChunk ? [] : ["项目经历细节需要结合后续分段"],
1702
- quoted_spans: isFirstChunk
1703
- ? ["本科 2025 届", "南京大学"]
1704
- : (isLastChunk ? ["AI 项目落地", "2 段相关工作经历"] : []),
1705
- chunk_index: chunkIndex,
1706
- chunk_total: chunkTotal
1707
- })
1708
- }
1709
- }
1710
- ]
1711
- };
1712
- }
1713
- };
1714
- }
1715
- aggregateUserContent = userContent;
1716
- return {
1717
- ok: true,
1718
- status: 200,
1719
- async json() {
1720
- return {
1721
- choices: [
1722
- {
1723
- message: {
1724
- content: JSON.stringify({
1725
- passed: true,
1726
- reason: "综合全部分段后,教育背景与 AI 项目/工作经历共同满足筛选条件。",
1727
- summary: "跨 chunk 证据成立",
1728
- evidence: ["本科 2025 届", "AI 项目落地", "2 段相关工作经历"]
1729
- })
1730
- }
1731
- }
1732
- ]
1733
- };
1734
- }
1735
- };
1736
- };
1737
- try {
1738
- await withLongResumeChunkEnv(
1739
- {
1740
- BOSS_RECOMMEND_TEXT_CHUNK_SIZE_CHARS: "1000",
1741
- BOSS_RECOMMEND_TEXT_CHUNK_OVERLAP_CHARS: "1",
1742
- BOSS_RECOMMEND_TEXT_MAX_CHUNKS: "6"
1743
- },
1744
- async () => {
1745
- const result = await cli.callTextModel(resumeText);
1746
- assert.equal(result.passed, true);
1747
- assert.equal(result.decisionMode, "text_chunk_aggregate");
1748
- assert.equal(result.chunkCount, expectedChunkCount);
1749
- assert.equal(result.aggregateRetryUsed, false);
1750
- assert.equal(callCount, expectedChunkCount + 2);
1751
- assert.equal(aggregateUserContent.includes(`"chunk_count": ${expectedChunkCount}`), true);
1752
- assert.equal(aggregateUserContent.includes("本科 2025 届"), true);
1753
- assert.equal(aggregateUserContent.includes("AI 项目落地"), true);
1754
- }
1755
- );
1756
- } finally {
1757
- global.fetch = originalFetch;
1758
- }
1759
- }
1760
-
1761
- async function testCallTextModelShouldNotUseAnyPassShortcutWhenAggregateRejects() {
1762
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-chunk-aggregate-reject-"));
1763
- const cli = new RecommendScreenCli(createArgs(tempDir));
1764
- const originalFetch = global.fetch;
1765
- const resumeText = `${"A".repeat(1050)}\n局部命中关键词但是整体不满足\n${"B".repeat(1050)}`;
1766
- let aggregateCalled = false;
1767
- global.fetch = async (_url, options = {}) => {
1768
- const payload = JSON.parse(String(options.body || "{}"));
1769
- const userContent = String(payload?.messages?.[1]?.content || "");
1770
- if (userContent.includes("简历内容:")) {
1771
- return {
1772
- ok: false,
1773
- status: 400,
1774
- async text() {
1775
- return "maximum context length exceeded";
1776
- }
1777
- };
1778
- }
1779
- if (userContent.includes("当前分段: 1/3")) {
1780
- return {
1781
- ok: true,
1782
- status: 200,
1783
- async json() {
1784
- return {
1785
- choices: [{ message: { content: JSON.stringify({
1786
- chunk_passed: true,
1787
- chunk_summary: "本段有局部关键词命中",
1788
- hard_evidence: ["局部命中关键词"],
1789
- soft_evidence: [],
1790
- hard_blockers: [],
1791
- missing_or_uncertain: ["缺少完整项目与工作链路"],
1792
- quoted_spans: ["局部命中关键词"],
1793
- chunk_index: 1,
1794
- chunk_total: 3
1795
- }) } }]
1796
- };
1797
- }
1798
- };
1799
- }
1800
- if (userContent.includes("当前分段: 2/3") || userContent.includes("当前分段: 3/3")) {
1801
- return {
1802
- ok: true,
1803
- status: 200,
1804
- async json() {
1805
- return {
1806
- choices: [{ message: { content: JSON.stringify({
1807
- chunk_passed: false,
1808
- chunk_summary: "其他分段未补足必需证据",
1809
- hard_evidence: [],
1810
- soft_evidence: [],
1811
- hard_blockers: ["缺少连续工作/项目证据"],
1812
- missing_or_uncertain: ["完整时间线不足"],
1813
- quoted_spans: ["缺少连续工作/项目证据"],
1814
- chunk_index: userContent.includes("当前分段: 2/3") ? 2 : 3,
1815
- chunk_total: 3
1816
- }) } }]
1817
- };
1818
- }
1819
- };
1820
- }
1821
- aggregateCalled = true;
1822
- return {
1823
- ok: true,
1824
- status: 200,
1825
- async json() {
1826
- return {
1827
- choices: [
1828
- {
1829
- message: {
1830
- content: JSON.stringify({
1831
- passed: false,
1832
- reason: "综合全部分段后仍缺少完整项目与工作链路,不能通过。",
1833
- summary: "聚合后不通过",
1834
- evidence: ["缺少连续工作/项目证据"]
1835
- })
1836
- }
1837
- }
1838
- ]
1839
- };
1840
- }
1841
- };
1842
- };
1843
- try {
1844
- await withLongResumeChunkEnv(
1845
- {
1846
- BOSS_RECOMMEND_TEXT_CHUNK_SIZE_CHARS: "1000",
1847
- BOSS_RECOMMEND_TEXT_CHUNK_OVERLAP_CHARS: "1",
1848
- BOSS_RECOMMEND_TEXT_MAX_CHUNKS: "6"
1849
- },
1850
- async () => {
1851
- const result = await cli.callTextModel(resumeText);
1852
- assert.equal(result.passed, false);
1853
- assert.equal(result.decisionMode, "text_chunk_aggregate");
1854
- assert.equal(aggregateCalled, true);
1855
- }
1856
- );
1857
- } finally {
1858
- global.fetch = originalFetch;
1859
- }
1860
- }
1861
-
1862
- async function testCallTextModelShouldRetryAggregateWithCompressedEvidenceOnce() {
1863
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-chunk-aggregate-retry-"));
1864
- const cli = new RecommendScreenCli(createArgs(tempDir));
1865
- const originalFetch = global.fetch;
1866
- const resumeText = `${"第一段证据 ".repeat(160)}\n${"第二段证据 ".repeat(160)}`;
1867
- const aggregateBodies = [];
1868
- global.fetch = async (_url, options = {}) => {
1869
- const payload = JSON.parse(String(options.body || "{}"));
1870
- const userContent = String(payload?.messages?.[1]?.content || "");
1871
- if (userContent.includes("简历内容:")) {
1872
- return {
1873
- ok: false,
1874
- status: 400,
1875
- async text() {
1876
- return "maximum context length exceeded";
1877
- }
1878
- };
1879
- }
1880
- if (userContent.includes("当前分段:")) {
1881
- const chunkIndex = userContent.includes("当前分段: 1/2") ? 1 : 2;
1882
- return {
1883
- ok: true,
1884
- status: 200,
1885
- async json() {
1886
- return {
1887
- choices: [
1888
- {
1889
- message: {
1890
- content: JSON.stringify({
1891
- chunk_passed: false,
1892
- chunk_summary: `分段 ${chunkIndex} 提取到多条证据`,
1893
- hard_evidence: [
1894
- `关键证据 ${chunkIndex}-1`,
1895
- `关键证据 ${chunkIndex}-2`,
1896
- `关键证据 ${chunkIndex}-3`,
1897
- `关键证据 ${chunkIndex}-4`
1898
- ],
1899
- soft_evidence: [`补充证据 ${chunkIndex}-1`, `补充证据 ${chunkIndex}-2`],
1900
- hard_blockers: [],
1901
- missing_or_uncertain: [`待确认信息 ${chunkIndex}-1`, `待确认信息 ${chunkIndex}-2`],
1902
- quoted_spans: [`原文片段 ${chunkIndex}-1`, `原文片段 ${chunkIndex}-2`],
1903
- chunk_index: chunkIndex,
1904
- chunk_total: 2
1905
- })
1906
- }
1907
- }
1908
- ]
1909
- };
1910
- }
1911
- };
1912
- }
1913
- aggregateBodies.push(userContent);
1914
- if (aggregateBodies.length === 1) {
1915
- return {
1916
- ok: false,
1917
- status: 400,
1918
- async text() {
1919
- return "maximum context length exceeded";
1920
- }
1921
- };
1922
- }
1923
- return {
1924
- ok: true,
1925
- status: 200,
1926
- async json() {
1927
- return {
1928
- choices: [
1929
- {
1930
- message: {
1931
- content: JSON.stringify({
1932
- passed: false,
1933
- reason: "压缩后综合判断仍不通过。",
1934
- summary: "压缩重试完成",
1935
- evidence: ["关键证据 1-1"]
1936
- })
1937
- }
1938
- }
1939
- ]
1940
- };
1941
- }
1942
- };
1943
- };
1944
- try {
1945
- await withLongResumeChunkEnv(
1946
- {
1947
- BOSS_RECOMMEND_TEXT_CHUNK_SIZE_CHARS: "1000",
1948
- BOSS_RECOMMEND_TEXT_CHUNK_OVERLAP_CHARS: "1",
1949
- BOSS_RECOMMEND_TEXT_MAX_CHUNKS: "6"
1950
- },
1951
- async () => {
1952
- const result = await cli.callTextModel(resumeText);
1953
- assert.equal(result.passed, false);
1954
- assert.equal(result.aggregateRetryUsed, true);
1955
- assert.equal(aggregateBodies.length, 2);
1956
- assert.equal(aggregateBodies[1].length < aggregateBodies[0].length, true);
1957
- }
1958
- );
1959
- } finally {
1960
- global.fetch = originalFetch;
1961
- }
1962
- }
1963
-
1964
- async function testCallTextModelShouldFailWhenAggregateResponseMissingPassed() {
1965
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-chunk-aggregate-invalid-"));
1966
- const cli = new RecommendScreenCli(createArgs(tempDir));
1967
- const originalFetch = global.fetch;
1968
- const resumeText = `${"A".repeat(900)}\n${"B".repeat(900)}`;
1969
- global.fetch = async (_url, options = {}) => {
1970
- const payload = JSON.parse(String(options.body || "{}"));
1971
- const userContent = String(payload?.messages?.[1]?.content || "");
1972
- if (userContent.includes("简历内容:")) {
1973
- return {
1974
- ok: false,
1975
- status: 400,
1976
- async text() {
1977
- return "maximum context length exceeded";
1978
- }
1979
- };
1980
- }
1981
- if (userContent.includes("当前分段:")) {
1982
- const chunkIndex = userContent.includes("当前分段: 1/2") ? 1 : 2;
1983
- return {
1984
- ok: true,
1985
- status: 200,
1986
- async json() {
1987
- return {
1988
- choices: [
1989
- {
1990
- message: {
1991
- content: JSON.stringify({
1992
- chunk_passed: false,
1993
- chunk_summary: `分段 ${chunkIndex} 证据不足`,
1994
- hard_evidence: [],
1995
- soft_evidence: [],
1996
- hard_blockers: [],
1997
- missing_or_uncertain: ["缺少关键经历"],
1998
- quoted_spans: [],
1999
- chunk_index: chunkIndex,
2000
- chunk_total: 2
2001
- })
2002
- }
2003
- }
2004
- ]
2005
- };
2006
- }
2007
- };
2008
- }
2009
- return {
2010
- ok: true,
2011
- status: 200,
2012
- async json() {
2013
- return {
2014
- choices: [
2015
- {
2016
- message: {
2017
- content: JSON.stringify({
2018
- reason: "missing passed field",
2019
- summary: "invalid"
2020
- })
2021
- }
2022
- }
2023
- ]
2024
- };
2025
- }
2026
- };
2027
- };
2028
- try {
2029
- await withLongResumeChunkEnv(
2030
- {
2031
- BOSS_RECOMMEND_TEXT_CHUNK_SIZE_CHARS: "1000",
2032
- BOSS_RECOMMEND_TEXT_CHUNK_OVERLAP_CHARS: "1",
2033
- BOSS_RECOMMEND_TEXT_MAX_CHUNKS: "6"
2034
- },
2035
- async () => {
2036
- await assert.rejects(
2037
- () => cli.callTextModel(resumeText),
2038
- (error) => error?.code === "TEXT_MODEL_FAILED"
2039
- );
2040
- }
2041
- );
2042
- } finally {
2043
- global.fetch = originalFetch;
2044
- }
2045
- }
2046
-
2047
- async function testTextModelShouldDefaultThinkingLowForVolcengine() {
2048
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-low-default-"));
2049
- const cli = new RecommendScreenCli(createArgs(tempDir));
2050
- cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
2051
- cli.args.model = "doubao-seed-2-0-mini-260215";
2052
- const originalFetch = global.fetch;
2053
- let capturedPayload = null;
2054
- global.fetch = async (_url, options = {}) => {
2055
- capturedPayload = JSON.parse(String(options.body || "{}"));
2056
- return {
2057
- ok: true,
2058
- status: 200,
2059
- async json() {
2060
- return {
2061
- choices: [
2062
- {
2063
- message: {
2064
- content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
2065
- }
2066
- }
2067
- ]
2068
- };
2069
- }
2070
- };
2071
- };
2072
- try {
2073
- await cli.callTextModel("resume");
2074
- assert.deepEqual(capturedPayload?.thinking, { type: "enabled" });
2075
- assert.equal(capturedPayload?.reasoning_effort, "low");
2076
- } finally {
2077
- global.fetch = originalFetch;
2078
- }
2079
- }
2080
-
2081
- async function testTextModelShouldSupportLowThinkingForVolcengine() {
2082
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-low-"));
2083
- const cli = new RecommendScreenCli(createArgs(tempDir));
2084
- cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
2085
- cli.args.model = "doubao-seed-2-0-mini-260215";
2086
- cli.args.thinkingLevel = "low";
2087
- const originalFetch = global.fetch;
2088
- let capturedPayload = null;
2089
- global.fetch = async (_url, options = {}) => {
2090
- capturedPayload = JSON.parse(String(options.body || "{}"));
2091
- return {
2092
- ok: true,
2093
- status: 200,
2094
- async json() {
2095
- return {
2096
- choices: [
2097
- {
2098
- message: {
2099
- content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
2100
- }
2101
- }
2102
- ]
2103
- };
2104
- }
2105
- };
2106
- };
2107
- try {
2108
- await cli.callTextModel("resume");
2109
- assert.deepEqual(capturedPayload?.thinking, { type: "enabled" });
2110
- assert.equal(capturedPayload?.reasoning_effort, "low");
2111
- } finally {
2112
- global.fetch = originalFetch;
2113
- }
2114
- }
2115
-
2116
- async function testPrepareVisionImageSegmentsShouldSplitLongImage() {
2117
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-segments-"));
2118
- const cli = new RecommendScreenCli(createArgs(tempDir));
2119
- const imagePath = path.join(tempDir, "long.png");
2120
- await sharp({
2121
- create: { width: 400, height: 1200, channels: 3, background: { r: 240, g: 240, b: 240 } }
2122
- }).png().toFile(imagePath);
2123
-
2124
- const prepared = await cli.prepareVisionImageSegmentsForModel(imagePath, 120000, "test");
2125
- assert.equal(Array.isArray(prepared.imagePaths), true);
2126
- assert.equal(prepared.imagePaths.length > 1, true);
2127
- for (const segmentPath of prepared.imagePaths) {
2128
- assert.equal(fs.existsSync(segmentPath), true);
2129
- }
2130
- }
2131
-
2132
- function testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable() {
2133
- assert.equal(
2134
- __testables.isRecoverablePostActionError({ code: "GREET_CONTINUE_BUTTON_FOUND" }, "greet"),
2135
- true
2136
- );
2137
- assert.equal(
2138
- __testables.isRecoverablePostActionError({ code: "GREET_BUTTON_NOT_FOUND" }, "greet"),
2139
- true
2140
- );
2141
- }
2142
-
2143
- async function testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails() {
2144
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-greet-continue-"));
2145
- const args = createArgs(tempDir);
2146
- args.postAction = "greet";
2147
- args.maxGreetCount = 5;
2148
- args.__provided.maxGreetCount = true;
2149
-
2150
- const first = { key: "candidate-1", geek_id: "candidate-1", name: "candidate-1" };
2151
- const second = { key: "candidate-2", geek_id: "candidate-2", name: "candidate-2" };
2152
- const cli = new FakeRecoverableGreetFailureCli(args, {
2153
- candidates: [first, second],
2154
- captureOutcomes: new Map([
2155
- [first.key, { stitchedImage: path.join(tempDir, "candidate-1.png") }],
2156
- [second.key, { stitchedImage: path.join(tempDir, "candidate-2.png") }]
2157
- ]),
2158
- screeningByKey: new Map([
2159
- [first.key, { passed: true, reason: "matched", summary: "matched" }],
2160
- [second.key, { passed: true, reason: "matched", summary: "matched" }]
2161
- ]),
2162
- greetErrors: new Map([[first.key, "GREET_CONTINUE_BUTTON_FOUND"]]),
2163
- closeFailureKeys: new Set([first.key])
2164
- });
2165
-
2166
- const result = await cli.run();
2167
- assert.equal(result.status, "COMPLETED");
2168
- assert.equal(result.result.processed_count, 2);
2169
- assert.equal(result.result.passed_count, 2);
2170
- assert.equal(result.result.greet_count, 1);
2171
- }
2172
-
2173
- async function testRecoverableGreetButtonNotFoundShouldNotAbortWhenDetailCloseFails() {
2174
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-greet-not-found-"));
2175
- const args = createArgs(tempDir);
2176
- args.postAction = "greet";
2177
- args.maxGreetCount = 5;
2178
- args.__provided.maxGreetCount = true;
2179
-
2180
- const first = { key: "candidate-a", geek_id: "candidate-a", name: "candidate-a" };
2181
- const second = { key: "candidate-b", geek_id: "candidate-b", name: "candidate-b" };
2182
- const cli = new FakeRecoverableGreetFailureCli(args, {
2183
- candidates: [first, second],
2184
- captureOutcomes: new Map([
2185
- [first.key, { stitchedImage: path.join(tempDir, "candidate-a.png") }],
2186
- [second.key, { stitchedImage: path.join(tempDir, "candidate-b.png") }]
2187
- ]),
2188
- screeningByKey: new Map([
2189
- [first.key, { passed: true, reason: "matched", summary: "matched" }],
2190
- [second.key, { passed: true, reason: "matched", summary: "matched" }]
2191
- ]),
2192
- greetErrors: new Map([[first.key, "GREET_BUTTON_NOT_FOUND"]]),
2193
- closeFailureKeys: new Set([first.key])
2194
- });
2195
-
2196
- const result = await cli.run();
2197
- assert.equal(result.status, "COMPLETED");
2198
- assert.equal(result.result.processed_count, 2);
2199
- assert.equal(result.result.passed_count, 2);
2200
- assert.equal(result.result.greet_count, 1);
2201
- }
2202
-
2203
- async function testCloseDetailPageShouldFailWhenDetailStillOpenAndListNotReady() {
2204
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-close-detail-fail-"));
2205
- const cli = new FakeDetailCloseProbeCli(createArgs(tempDir), { listReady: false });
2206
- const closed = await cli.closeDetailPage(1);
2207
- assert.equal(closed, false);
2208
- }
2209
-
2210
- async function testCloseDetailPageShouldContinueWhenListReady() {
2211
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-close-detail-list-ready-"));
2212
- const cli = new FakeDetailCloseProbeCli(createArgs(tempDir), { listReady: true });
2213
- const closed = await cli.closeDetailPage(1);
2214
- assert.equal(closed, true);
2215
- }
2216
-
2217
- async function testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidenceProtocol() {
2218
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-evidence-gate-"));
2219
- const cli = new RecommendScreenCli(createArgs(tempDir));
2220
- cli.prepareVisionImageSegmentsForModel = async () => ({
2221
- imagePaths: ["segment-1"],
2222
- source: "test",
2223
- sourcePixels: 100,
2224
- currentPixels: 100
2225
- });
2226
- cli.requestVisionModel = async () => ({
2227
- passed: true,
2228
- rawPassed: true,
2229
- reason: "matched",
2230
- summary: "matched",
2231
- evidence: []
2232
- });
2233
- const result = await cli.callVisionModel(path.join(tempDir, "fake.png"));
2234
- assert.equal(result.rawPassed, true);
2235
- assert.equal(result.passed, true);
2236
- assert.equal(result.evidenceGateDemoted, false);
2237
- assert.equal(result.evidenceRawCount, 0);
2238
- assert.equal(result.evidenceMatchedCount, 0);
2239
- }
2240
-
2241
- async function testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence() {
2242
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-evidence-gate-explicit-"));
2243
- const cli = new RecommendScreenCli(createArgs(tempDir));
2244
- cli.prepareVisionImageSegmentsForModel = async () => ({
2245
- imagePaths: ["segment-1"],
2246
- source: "test",
2247
- sourcePixels: 100,
2248
- currentPixels: 100
2249
- });
2250
- cli.requestVisionModel = async () => ({
2251
- passed: true,
2252
- rawPassed: true,
2253
- reason: "matched",
2254
- summary: "matched",
2255
- evidence: [],
2256
- evidenceGateEligible: true,
2257
- evidenceRawCount: 0,
2258
- evidenceMatchedCount: 0
2259
- });
2260
- const result = await cli.callVisionModel(path.join(tempDir, "fake.png"));
2261
- assert.equal(result.rawPassed, true);
2262
- assert.equal(result.passed, true);
2263
- assert.equal(result.evidenceGateDemoted, false);
2264
- assert.equal(result.evidenceRawCount, 0);
2265
- assert.equal(result.evidenceMatchedCount, 0);
2266
- }
2267
-
2268
- async function testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume() {
2269
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-text-no-demote-"));
2270
- const cli = new RecommendScreenCli(createArgs(tempDir));
2271
- const originalFetch = global.fetch;
2272
- global.fetch = async () => ({
2273
- ok: true,
2274
- status: 200,
2275
- async json() {
2276
- return {
2277
- choices: [
2278
- {
2279
- message: {
2280
- content: JSON.stringify({
2281
- passed: true,
2282
- reason: "matched",
2283
- summary: "matched",
2284
- evidence: ["完全不在简历里的证据"]
2285
- })
2286
- }
2287
- }
2288
- ]
2289
- };
2290
- }
2291
- });
2292
- try {
2293
- const result = await cli.callTextModel("这是简历原文,没有那条证据");
2294
- assert.equal(result.rawPassed, true);
2295
- assert.equal(result.passed, true);
2296
- assert.equal(result.evidenceGateDemoted, false);
2297
- assert.equal(result.evidenceRawCount, 1);
2298
- assert.equal(result.evidenceMatchedCount, 0);
2299
- } finally {
2300
- global.fetch = originalFetch;
2301
- }
2302
- }
2303
-
2304
- async function testVisionModelShouldSendAllOrderedChunks() {
2305
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-all-chunks-"));
2306
- const chunkPaths = [];
2307
- for (let index = 0; index < 3; index += 1) {
2308
- const chunkPath = path.join(tempDir, `chunk-${index + 1}.png`);
2309
- await sharp({
2310
- create: { width: 16, height: 16, channels: 3, background: { r: 255 - index, g: 250, b: 245 } }
2311
- }).png().toFile(chunkPath);
2312
- chunkPaths.push(chunkPath);
2313
- }
2314
- const cli = new RecommendScreenCli(createArgs(tempDir));
2315
- const originalFetch = global.fetch;
2316
- let capturedPayload = null;
2317
- global.fetch = async (_url, options = {}) => {
2318
- capturedPayload = JSON.parse(String(options.body || "{}"));
2319
- return {
2320
- ok: true,
2321
- status: 200,
2322
- async json() {
2323
- return {
2324
- choices: [
2325
- {
2326
- message: {
2327
- content: "{\"passed\": false, \"reason\": \"checked all chunks\", \"summary\": \"checked\", \"evidence\": [\"chunk evidence\", \"more evidence\"]}"
2328
- }
2329
- }
2330
- ]
2331
- };
2332
- }
2333
- };
2334
- };
2335
- try {
2336
- const result = await cli.requestVisionModel(chunkPaths);
2337
- assert.equal(result.passed, false);
2338
- const userContent = capturedPayload?.messages?.[1]?.content || [];
2339
- assert.equal(userContent.filter((item) => item?.type === "image_url").length, 3);
2340
- const text = userContent.map((item) => item?.text || "").join("\n");
2341
- assert.equal(text.includes("简历分段 1/3"), true);
2342
- assert.equal(text.includes("简历分段 2/3"), true);
2343
- assert.equal(text.includes("简历分段 3/3"), true);
2344
- assert.equal(text.includes("不能只根据前几段下结论"), true);
2345
- } finally {
2346
- global.fetch = originalFetch;
2347
- }
2348
- }
2349
-
2350
- async function main() {
2351
- testShouldAbortResumeProbeEarly();
2352
- testResumeViewportStabilityRequiresSettledScrollAndClip();
2353
- testViewportCollapseRiskShouldUseRelativeWidth();
2354
- testNormalMaximizedViewportShouldNotCollapse();
2355
- testSmallNormalWindowShouldNotUseScreenWidthRatio();
2356
- await testViewportCollapseRiskShouldTriggerRecovery();
2357
- await testSingleResumeCaptureFailureIsSkipped();
2358
- await testConsecutiveResumeCaptureFailuresStillAbort();
2359
- await testPageExhaustedBeforeTargetShouldRaiseRecoverableError();
2360
- await testPageExhaustedWithoutTargetShouldStillComplete();
2361
- await testTargetCountShouldStopWhenPassedCountReached();
2362
- await testTargetCountShouldNotTreatProcessedCountAsReached();
2363
- await testFeaturedShouldUseNetworkResumeOnly();
2364
- await testRecommendShouldPreferNetworkResumeWhenAvailable();
2365
- await testNetworkMissShouldFallbackToImageThenDom();
2366
- await testNetworkMissShouldFallbackToImageCapture();
2367
- await testImageModeShouldUseShortNetworkGraceWindow();
2368
- await testImageFailureShouldLateRetryNetworkBeforeDomFallback();
2369
- await testLatestShouldPreferNetworkResumeWhenAvailable();
2370
- await testLatestNetworkMissShouldFallbackToImageCapture();
2371
- testLatestPayloadShouldNotLeakAcrossCandidates();
2372
- testLatestPayloadShouldRemainAvailableWhenCandidateKeyMissing();
2373
- await testVisionModelFailureShouldSkipCandidateAndContinue();
2374
- await testFeaturedNetworkMissShouldFallbackToDomAfterImageFailure();
2375
- await testFeaturedFavoriteShouldNotUseDomFallback();
2376
- await testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested();
2377
- await testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd();
2378
- await testFeaturedFavoriteWithoutCalibrationShouldFail();
2379
- testFavoriteActionParserShouldSupportBodySignals();
2380
- testFavoriteActionParserShouldSupportFallbackRequestShape();
2381
- testFavoriteActionParserShouldSupportWebSocketPayload();
2382
- testFavoriteActionParserShouldOnlyTrustKnownRequestShapes();
2383
- testFinishedWrapClassifierShouldNotTreatLoadMoreAsBottom();
2384
- testFormatResumeApiDataShouldPreserveEducationTagsAndProjectDescription();
2385
- testFormatResumeApiDataShouldIncludeStructuredJudgementHints();
2386
- testEnrichCandidateInfoWithCardProfileShouldAppendCardFallbackWhenDomInfoMissing();
2387
- testEvidenceTokenMatcherShouldSupportParaphrasedEvidence();
2388
- testCheckpointPayloadShouldIncludeCandidateAudits();
2389
- testCheckpointShouldPersistAndRestoreInputSummary();
2390
- testSaveCsvShouldIncludeAllCandidateOutcomes();
2391
- await testGetCenteredCandidateClickPointShouldSupportLatestSelector();
2392
- await testFeaturedPostActionFailureShouldStillRecordPassedCandidate();
2393
- await testRunShouldRecordLongResumeDecisionObservability();
2394
- await testStitchWithSharpShouldComposeExpectedImage();
2395
- testStitchWithAvailablePythonShouldFallbackToPython();
2396
- testStitchWithAvailablePythonShouldFailWhenScriptMissing();
2397
- testParseArgsShouldSupportFeaturedAliasesAndInlinePort();
2398
- testParseArgsShouldSupportLatestPageScope();
2399
- testParseArgsShouldSupportInputSummaryJson();
2400
- await testCallTextModelShouldNotTruncateLongResume();
2401
- await testCallTextModelShouldFallbackToChunkModeOnContextLimit();
2402
- await testCallTextModelShouldNotUseAnyPassShortcutWhenAggregateRejects();
2403
- await testCallTextModelShouldRetryAggregateWithCompressedEvidenceOnce();
2404
- await testCallTextModelShouldFailWhenAggregateResponseMissingPassed();
2405
- await testTextModelShouldDefaultThinkingLowForVolcengine();
2406
- await testTextModelShouldSupportLowThinkingForVolcengine();
2407
- await testPrepareVisionImageSegmentsShouldSplitLongImage();
2408
- await testVisionEvidenceGateShouldKeepRawPassWithoutExplicitEvidenceProtocol();
2409
- await testVisionEvidenceGateShouldNotDemoteWhenExplicitlyArmedWithoutEvidence();
2410
- await testTextModelShouldNotDemoteRawPassWhenEvidenceDoesNotMatchResume();
2411
- await testVisionModelShouldSendAllOrderedChunks();
2412
- testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
2413
- await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();
2414
- await testRecoverableGreetButtonNotFoundShouldNotAbortWhenDetailCloseFails();
2415
- await testCloseDetailPageShouldFailWhenDetailStillOpenAndListNotReady();
2416
- await testCloseDetailPageShouldContinueWhenListReady();
2417
- console.log("recoverable resume failure tests passed");
2418
- }
2419
-
2420
- main().catch((error) => {
2421
- console.error(error);
2422
- process.exit(1);
2423
- });