@reconcrap/boss-recommend-mcp 2.0.6 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/chat-mcp.js +60 -18
- package/src/core/boss-cards/index.js +199 -0
- package/src/core/capture/index.js +4 -0
- package/src/core/cv-acquisition/index.js +1 -0
- package/src/core/reporting/legacy-csv.js +12 -3
- package/src/core/run/timing.js +33 -0
- package/src/core/screening/index.js +95 -5
- package/src/domains/chat/cards.js +9 -1
- package/src/domains/chat/run-service.js +142 -59
- package/src/domains/recommend/cards.js +16 -1
- package/src/domains/recommend/detail.js +11 -1
- package/src/domains/recommend/run-service.js +120 -13
- package/src/domains/recruit/cards.js +9 -1
- package/src/domains/recruit/detail.js +12 -1
- package/src/domains/recruit/run-service.js +127 -20
- package/src/index.js +50 -5
- package/src/recommend-mcp.js +82 -7
- package/src/recruit-mcp.js +104 -8
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
2
|
+
import {
|
|
3
|
+
addTiming,
|
|
4
|
+
imageEvidenceFilePath,
|
|
5
|
+
measureTiming
|
|
6
|
+
} from "../../core/run/timing.js";
|
|
2
7
|
import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
|
|
3
8
|
import {
|
|
4
9
|
compactCvAcquisitionState,
|
|
@@ -19,7 +24,13 @@ import {
|
|
|
19
24
|
resetInfiniteListForRefreshRound
|
|
20
25
|
} from "../../core/infinite-list/index.js";
|
|
21
26
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
22
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
callScreeningLlm,
|
|
29
|
+
compactScreeningLlmResult,
|
|
30
|
+
createFailedLlmScreeningResult,
|
|
31
|
+
llmResultToScreening,
|
|
32
|
+
screenCandidate
|
|
33
|
+
} from "../../core/screening/index.js";
|
|
23
34
|
import {
|
|
24
35
|
closeRecruitDetail,
|
|
25
36
|
createRecruitDetailNetworkRecorder,
|
|
@@ -72,10 +83,22 @@ function compactDetail(detailResult) {
|
|
|
72
83
|
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
73
84
|
cv_acquisition: detailResult.cv_acquisition || null,
|
|
74
85
|
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
86
|
+
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
75
87
|
close_result: detailResult.close_result
|
|
76
88
|
};
|
|
77
89
|
}
|
|
78
90
|
|
|
91
|
+
function normalizeScreeningMode(value) {
|
|
92
|
+
const normalized = String(value || "llm").trim().toLowerCase();
|
|
93
|
+
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
94
|
+
? "deterministic"
|
|
95
|
+
: "llm";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createMissingLlmConfigResult() {
|
|
99
|
+
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production search runs"));
|
|
100
|
+
}
|
|
101
|
+
|
|
79
102
|
function normalizeSearchParams(searchParams = {}) {
|
|
80
103
|
return normalizeRecruitSearchParams(searchParams);
|
|
81
104
|
}
|
|
@@ -110,7 +133,7 @@ export async function runRecruitWorkflow({
|
|
|
110
133
|
criteria = "",
|
|
111
134
|
searchParams = {},
|
|
112
135
|
maxCandidates = 5,
|
|
113
|
-
detailLimit =
|
|
136
|
+
detailLimit = null,
|
|
114
137
|
closeDetail = true,
|
|
115
138
|
delayMs = 0,
|
|
116
139
|
cardTimeoutMs = 90000,
|
|
@@ -127,12 +150,20 @@ export async function runRecruitWorkflow({
|
|
|
127
150
|
listFallbackPoint = null,
|
|
128
151
|
refreshOnEnd = true,
|
|
129
152
|
maxRefreshRounds = 2,
|
|
130
|
-
refreshResetSettleMs = 5000
|
|
153
|
+
refreshResetSettleMs = 5000,
|
|
154
|
+
screeningMode = "llm",
|
|
155
|
+
llmConfig = null,
|
|
156
|
+
llmTimeoutMs = 120000,
|
|
157
|
+
llmImageLimit = 8,
|
|
158
|
+
llmImageDetail = "high",
|
|
159
|
+
imageOutputDir = ""
|
|
131
160
|
} = {}, runControl) {
|
|
132
161
|
if (!client) throw new Error("runRecruitWorkflow requires a guarded CDP client");
|
|
133
162
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
163
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
164
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
134
165
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
135
|
-
const detailCountLimit = Math.max(0, Number(detailLimit) || 0);
|
|
166
|
+
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
136
167
|
const networkRecorder = detailCountLimit > 0
|
|
137
168
|
? createRecruitDetailNetworkRecorder(client)
|
|
138
169
|
: null;
|
|
@@ -222,6 +253,8 @@ export async function runRecruitWorkflow({
|
|
|
222
253
|
screened: 0,
|
|
223
254
|
detail_opened: 0,
|
|
224
255
|
passed: 0,
|
|
256
|
+
screening_mode: normalizedScreeningMode,
|
|
257
|
+
llm_screened: 0,
|
|
225
258
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
226
259
|
scroll_count: 0,
|
|
227
260
|
refresh_rounds: 0,
|
|
@@ -231,12 +264,14 @@ export async function runRecruitWorkflow({
|
|
|
231
264
|
});
|
|
232
265
|
|
|
233
266
|
while (results.length < limit) {
|
|
267
|
+
const candidateStarted = Date.now();
|
|
268
|
+
const timings = {};
|
|
234
269
|
await runControl.waitIfPaused();
|
|
235
270
|
runControl.throwIfCanceled();
|
|
236
271
|
runControl.setPhase("recruit:candidate");
|
|
237
272
|
rootState = await ensureRecruitViewport(rootState, "candidate_loop");
|
|
238
273
|
|
|
239
|
-
const nextCandidateResult = await getNextInfiniteListCandidate({
|
|
274
|
+
const nextCandidateResult = await measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
240
275
|
client,
|
|
241
276
|
state: listState,
|
|
242
277
|
maxScrolls: listMaxScrolls,
|
|
@@ -264,7 +299,7 @@ export async function runRecruitWorkflow({
|
|
|
264
299
|
search_params: normalizedSearchParams
|
|
265
300
|
}
|
|
266
301
|
})
|
|
267
|
-
});
|
|
302
|
+
}));
|
|
268
303
|
if (!nextCandidateResult.ok) {
|
|
269
304
|
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
270
305
|
if (
|
|
@@ -298,6 +333,7 @@ export async function runRecruitWorkflow({
|
|
|
298
333
|
screened: results.length,
|
|
299
334
|
detail_opened: results.filter((item) => item.detail).length,
|
|
300
335
|
passed: results.filter((item) => item.screening.passed).length,
|
|
336
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
301
337
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
302
338
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
303
339
|
refresh_rounds: refreshRounds,
|
|
@@ -345,8 +381,10 @@ export async function runRecruitWorkflow({
|
|
|
345
381
|
rootState = await ensureRecruitViewport(rootState, "detail");
|
|
346
382
|
networkRecorder.clear();
|
|
347
383
|
const openedDetail = await openRecruitCardDetail(client, cardNodeId);
|
|
384
|
+
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
385
|
+
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
348
386
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
349
|
-
const networkWait = await waitForCvNetworkEvents(
|
|
387
|
+
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
350
388
|
waitForRecruitDetailNetworkEvents,
|
|
351
389
|
networkRecorder,
|
|
352
390
|
{
|
|
@@ -355,7 +393,10 @@ export async function runRecruitWorkflow({
|
|
|
355
393
|
requireLoaded: true,
|
|
356
394
|
intervalMs: 120
|
|
357
395
|
}
|
|
358
|
-
);
|
|
396
|
+
));
|
|
397
|
+
if (networkWait?.elapsed_ms != null) {
|
|
398
|
+
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
399
|
+
}
|
|
359
400
|
detailResult = await extractRecruitDetailCandidate(client, {
|
|
360
401
|
cardCandidate,
|
|
361
402
|
cardNodeId,
|
|
@@ -377,7 +418,13 @@ export async function runRecruitWorkflow({
|
|
|
377
418
|
|| openedDetail.detail_state?.resumeIframe?.node_id
|
|
378
419
|
|| null;
|
|
379
420
|
if (captureNodeId) {
|
|
380
|
-
imageEvidence = await captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
421
|
+
imageEvidence = await measureTiming(timings, "screenshot_capture_ms", () => captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
422
|
+
filePath: imageEvidenceFilePath({
|
|
423
|
+
imageOutputDir,
|
|
424
|
+
domain: "recruit",
|
|
425
|
+
runId: runControl?.runId,
|
|
426
|
+
index
|
|
427
|
+
}),
|
|
381
428
|
padding: 4,
|
|
382
429
|
maxScreenshots: maxImagePages,
|
|
383
430
|
wheelDeltaY: imageWheelDeltaY,
|
|
@@ -389,7 +436,7 @@ export async function runRecruitWorkflow({
|
|
|
389
436
|
run_candidate_index: index,
|
|
390
437
|
candidate_key: candidateKey
|
|
391
438
|
}
|
|
392
|
-
});
|
|
439
|
+
}));
|
|
393
440
|
source = "image";
|
|
394
441
|
recordCvImageFallback(cvAcquisitionState, {
|
|
395
442
|
parsedNetworkProfileCount,
|
|
@@ -408,7 +455,7 @@ export async function runRecruitWorkflow({
|
|
|
408
455
|
|
|
409
456
|
let closeResult = null;
|
|
410
457
|
if (closeDetail) {
|
|
411
|
-
closeResult = await closeRecruitDetail(client);
|
|
458
|
+
closeResult = await measureTiming(timings, "close_detail_ms", () => closeRecruitDetail(client));
|
|
412
459
|
}
|
|
413
460
|
detailResult.close_result = closeResult;
|
|
414
461
|
detailResult.image_evidence = imageEvidence;
|
|
@@ -426,14 +473,43 @@ export async function runRecruitWorkflow({
|
|
|
426
473
|
await runControl.waitIfPaused();
|
|
427
474
|
runControl.throwIfCanceled();
|
|
428
475
|
runControl.setPhase("recruit:screening");
|
|
429
|
-
|
|
476
|
+
let llmResult = null;
|
|
477
|
+
if (useLlmScreening) {
|
|
478
|
+
if (!llmConfig) {
|
|
479
|
+
llmResult = createMissingLlmConfigResult();
|
|
480
|
+
} else {
|
|
481
|
+
try {
|
|
482
|
+
const llmTimingKey = detailResult?.image_evidence?.file_paths?.length
|
|
483
|
+
? "vision_model_ms"
|
|
484
|
+
: "text_model_ms";
|
|
485
|
+
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
486
|
+
candidate: screeningCandidate,
|
|
487
|
+
criteria,
|
|
488
|
+
config: llmConfig,
|
|
489
|
+
timeoutMs: llmTimeoutMs,
|
|
490
|
+
imageEvidence: detailResult?.image_evidence || null,
|
|
491
|
+
maxImages: llmImageLimit,
|
|
492
|
+
imageDetail: llmImageDetail
|
|
493
|
+
}));
|
|
494
|
+
} catch (error) {
|
|
495
|
+
llmResult = createFailedLlmScreeningResult(error);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (detailResult) detailResult.llm_result = llmResult;
|
|
499
|
+
}
|
|
500
|
+
const screening = useLlmScreening
|
|
501
|
+
? llmResultToScreening(llmResult, screeningCandidate)
|
|
502
|
+
: screenCandidate(screeningCandidate, { criteria });
|
|
503
|
+
timings.total_ms = Date.now() - candidateStarted;
|
|
430
504
|
const compactResult = {
|
|
431
505
|
index,
|
|
432
506
|
candidate_key: candidateKey,
|
|
433
507
|
card_node_id: cardNodeId,
|
|
434
508
|
candidate: compactCandidate(screeningCandidate),
|
|
435
509
|
detail: compactDetail(detailResult),
|
|
436
|
-
|
|
510
|
+
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
511
|
+
screening: compactScreening(screening),
|
|
512
|
+
timings
|
|
437
513
|
};
|
|
438
514
|
results.push(compactResult);
|
|
439
515
|
markInfiniteListCandidateProcessed(listState, candidateKey, {
|
|
@@ -450,6 +526,7 @@ export async function runRecruitWorkflow({
|
|
|
450
526
|
screened: results.length,
|
|
451
527
|
detail_opened: results.filter((item) => item.detail).length,
|
|
452
528
|
passed: results.filter((item) => item.screening.passed).length,
|
|
529
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
453
530
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
454
531
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
455
532
|
refresh_rounds: refreshRounds,
|
|
@@ -461,7 +538,9 @@ export async function runRecruitWorkflow({
|
|
|
461
538
|
last_candidate_key: candidateKey,
|
|
462
539
|
last_score: screening.score
|
|
463
540
|
});
|
|
541
|
+
const checkpointStarted = Date.now();
|
|
464
542
|
runControl.checkpoint({
|
|
543
|
+
results,
|
|
465
544
|
last_candidate: {
|
|
466
545
|
id: screeningCandidate.id || null,
|
|
467
546
|
key: candidateKey,
|
|
@@ -470,12 +549,17 @@ export async function runRecruitWorkflow({
|
|
|
470
549
|
status: screening.status,
|
|
471
550
|
passed: screening.passed,
|
|
472
551
|
score: screening.score
|
|
473
|
-
}
|
|
552
|
+
},
|
|
553
|
+
llm_screening: compactScreeningLlmResult(llmResult)
|
|
474
554
|
}
|
|
475
555
|
});
|
|
556
|
+
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
476
557
|
|
|
477
558
|
if (delayMs > 0) {
|
|
559
|
+
const sleepStarted = Date.now();
|
|
478
560
|
await runControl.sleep(delayMs);
|
|
561
|
+
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
562
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
479
563
|
}
|
|
480
564
|
}
|
|
481
565
|
|
|
@@ -496,6 +580,7 @@ export async function runRecruitWorkflow({
|
|
|
496
580
|
processed: results.length,
|
|
497
581
|
screened: results.length,
|
|
498
582
|
detail_opened: results.filter((item) => item.detail).length,
|
|
583
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
499
584
|
passed: results.filter((item) => item.screening.passed).length,
|
|
500
585
|
results
|
|
501
586
|
};
|
|
@@ -514,7 +599,7 @@ export function createRecruitRunService({
|
|
|
514
599
|
criteria = "",
|
|
515
600
|
searchParams = {},
|
|
516
601
|
maxCandidates = 5,
|
|
517
|
-
detailLimit =
|
|
602
|
+
detailLimit = null,
|
|
518
603
|
closeDetail = true,
|
|
519
604
|
delayMs = 0,
|
|
520
605
|
cardTimeoutMs = 90000,
|
|
@@ -532,10 +617,19 @@ export function createRecruitRunService({
|
|
|
532
617
|
refreshOnEnd = true,
|
|
533
618
|
maxRefreshRounds = 2,
|
|
534
619
|
refreshResetSettleMs = 5000,
|
|
620
|
+
screeningMode = "llm",
|
|
621
|
+
llmConfig = null,
|
|
622
|
+
llmTimeoutMs = 120000,
|
|
623
|
+
llmImageLimit = 8,
|
|
624
|
+
llmImageDetail = "high",
|
|
625
|
+
imageOutputDir = "",
|
|
535
626
|
name = "recruit-domain-run"
|
|
536
627
|
} = {}) {
|
|
537
628
|
if (!client) throw new Error("startRecruitRun requires a guarded CDP client");
|
|
538
629
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
630
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
631
|
+
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
632
|
+
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
539
633
|
return manager.startRun({
|
|
540
634
|
name,
|
|
541
635
|
context: {
|
|
@@ -544,7 +638,7 @@ export function createRecruitRunService({
|
|
|
544
638
|
criteria_present: Boolean(criteria),
|
|
545
639
|
search_params: normalizedSearchParams,
|
|
546
640
|
max_candidates: maxCandidates,
|
|
547
|
-
detail_limit:
|
|
641
|
+
detail_limit: normalizedDetailLimit,
|
|
548
642
|
close_detail: closeDetail,
|
|
549
643
|
reset_before_search: resetBeforeSearch,
|
|
550
644
|
reset_timeout_ms: resetTimeoutMs,
|
|
@@ -559,14 +653,21 @@ export function createRecruitRunService({
|
|
|
559
653
|
list_fallback_point: listFallbackPoint,
|
|
560
654
|
refresh_on_end: refreshOnEnd,
|
|
561
655
|
max_refresh_rounds: maxRefreshRounds,
|
|
562
|
-
refresh_reset_settle_ms: refreshResetSettleMs
|
|
656
|
+
refresh_reset_settle_ms: refreshResetSettleMs,
|
|
657
|
+
screening_mode: normalizedScreeningMode,
|
|
658
|
+
llm_configured: Boolean(llmConfig),
|
|
659
|
+
llm_timeout_ms: llmTimeoutMs,
|
|
660
|
+
llm_image_limit: llmImageLimit,
|
|
661
|
+
llm_image_detail: llmImageDetail,
|
|
662
|
+
image_output_dir: imageOutputDir || ""
|
|
563
663
|
},
|
|
564
664
|
progress: {
|
|
565
665
|
card_count: 0,
|
|
566
|
-
target_count:
|
|
666
|
+
target_count: candidateLimit,
|
|
567
667
|
processed: 0,
|
|
568
668
|
screened: 0,
|
|
569
669
|
detail_opened: 0,
|
|
670
|
+
llm_screened: 0,
|
|
570
671
|
passed: 0
|
|
571
672
|
},
|
|
572
673
|
checkpoint: {},
|
|
@@ -576,7 +677,7 @@ export function createRecruitRunService({
|
|
|
576
677
|
criteria,
|
|
577
678
|
searchParams: normalizedSearchParams,
|
|
578
679
|
maxCandidates,
|
|
579
|
-
detailLimit,
|
|
680
|
+
detailLimit: normalizedDetailLimit,
|
|
580
681
|
closeDetail,
|
|
581
682
|
delayMs,
|
|
582
683
|
cardTimeoutMs,
|
|
@@ -593,7 +694,13 @@ export function createRecruitRunService({
|
|
|
593
694
|
listFallbackPoint,
|
|
594
695
|
refreshOnEnd,
|
|
595
696
|
maxRefreshRounds,
|
|
596
|
-
refreshResetSettleMs
|
|
697
|
+
refreshResetSettleMs,
|
|
698
|
+
screeningMode: normalizedScreeningMode,
|
|
699
|
+
llmConfig,
|
|
700
|
+
llmTimeoutMs,
|
|
701
|
+
llmImageLimit,
|
|
702
|
+
llmImageDetail,
|
|
703
|
+
imageOutputDir
|
|
597
704
|
}, runControl)
|
|
598
705
|
});
|
|
599
706
|
}
|
package/src/index.js
CHANGED
|
@@ -541,11 +541,38 @@ function createRunInputSchema() {
|
|
|
541
541
|
detail_limit: {
|
|
542
542
|
type: "integer",
|
|
543
543
|
minimum: 0,
|
|
544
|
-
description: "打开详情/CV 的人数上限;默认跟随 target_count/max_candidates。生产筛选不应传 0
|
|
544
|
+
description: "打开详情/CV 的人数上限;默认跟随 target_count/max_candidates。生产筛选不应传 0;detail_limit=0 需要 debug_test_mode=true 且 allow_card_only_screening=true"
|
|
545
545
|
},
|
|
546
546
|
allow_card_only_screening: {
|
|
547
547
|
type: "boolean",
|
|
548
|
-
description: "高级调试开关;默认 false
|
|
548
|
+
description: "高级调试开关;默认 false。只有同时显式 debug_test_mode=true 时,recommend 才会尊重 detail_limit=0"
|
|
549
|
+
},
|
|
550
|
+
debug_test_mode: {
|
|
551
|
+
type: "boolean",
|
|
552
|
+
description: "高级测试开关;默认 false。只有显式为 true 时才允许 deterministic/local scorer、跳过筛选器、card-only、dry-run 后置动作等调试路径"
|
|
553
|
+
},
|
|
554
|
+
screening_mode: {
|
|
555
|
+
type: "string",
|
|
556
|
+
enum: ["llm", "deterministic"],
|
|
557
|
+
description: "筛选引擎;默认 llm。deterministic 仅限 debug_test_mode=true 的明确测试场景"
|
|
558
|
+
},
|
|
559
|
+
use_llm: {
|
|
560
|
+
type: "boolean",
|
|
561
|
+
description: "兼容字段;默认 true。use_llm=false 等同 deterministic,仅限 debug_test_mode=true"
|
|
562
|
+
},
|
|
563
|
+
llm_timeout_ms: {
|
|
564
|
+
type: "integer",
|
|
565
|
+
minimum: 1000,
|
|
566
|
+
description: "可选,单个候选人的 LLM 调用超时"
|
|
567
|
+
},
|
|
568
|
+
llm_image_limit: {
|
|
569
|
+
type: "integer",
|
|
570
|
+
minimum: 1,
|
|
571
|
+
description: "可选,传给 LLM 的图片简历截图页数上限"
|
|
572
|
+
},
|
|
573
|
+
llm_image_detail: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description: "可选,图片输入 detail,默认 high"
|
|
549
576
|
},
|
|
550
577
|
delay_ms: {
|
|
551
578
|
type: "integer",
|
|
@@ -577,11 +604,11 @@ function createRunInputSchema() {
|
|
|
577
604
|
},
|
|
578
605
|
no_filter: {
|
|
579
606
|
type: "boolean",
|
|
580
|
-
description: "开发/live gate 专用:跳过本次筛选器点击,默认 false"
|
|
607
|
+
description: "开发/live gate 专用:跳过本次筛选器点击,默认 false;正式 run 需要 debug_test_mode=true 才允许"
|
|
581
608
|
},
|
|
582
609
|
filter_enabled: {
|
|
583
610
|
type: "boolean",
|
|
584
|
-
description: "开发/live gate 专用:false
|
|
611
|
+
description: "开发/live gate 专用:false 时跳过本次筛选器点击;正式 run 需要 debug_test_mode=true 才允许"
|
|
585
612
|
},
|
|
586
613
|
refresh_on_end: {
|
|
587
614
|
type: "boolean",
|
|
@@ -672,7 +699,16 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
|
|
|
672
699
|
},
|
|
673
700
|
use_llm: {
|
|
674
701
|
type: "boolean",
|
|
675
|
-
description: "
|
|
702
|
+
description: "可选,默认 true。use_llm=false 等同 deterministic/local scorer,仅限 debug_test_mode=true 的明确测试场景"
|
|
703
|
+
},
|
|
704
|
+
screening_mode: {
|
|
705
|
+
type: "string",
|
|
706
|
+
enum: ["llm", "deterministic"],
|
|
707
|
+
description: "筛选引擎;默认 llm。deterministic 仅限 debug_test_mode=true"
|
|
708
|
+
},
|
|
709
|
+
debug_test_mode: {
|
|
710
|
+
type: "boolean",
|
|
711
|
+
description: "高级测试开关;默认 false。只有显式为 true 时才允许 deterministic/local scorer、detail_limit=0、dry-run 求简历等调试路径"
|
|
676
712
|
},
|
|
677
713
|
request_cv: {
|
|
678
714
|
type: "boolean",
|
|
@@ -703,6 +739,15 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
|
|
|
703
739
|
minimum: 1000,
|
|
704
740
|
description: "可选,单个候选人的 LLM 调用超时"
|
|
705
741
|
},
|
|
742
|
+
llm_image_limit: {
|
|
743
|
+
type: "integer",
|
|
744
|
+
minimum: 1,
|
|
745
|
+
description: "可选,传给 LLM 的图片简历截图页数上限"
|
|
746
|
+
},
|
|
747
|
+
llm_image_detail: {
|
|
748
|
+
type: "string",
|
|
749
|
+
description: "可选,图片输入 detail,默认 high"
|
|
750
|
+
},
|
|
706
751
|
online_resume_button_timeout_ms: {
|
|
707
752
|
type: "integer",
|
|
708
753
|
minimum: 1000,
|
package/src/recommend-mcp.js
CHANGED
|
@@ -84,9 +84,38 @@ function parseNonNegativeInteger(raw, fallback) {
|
|
|
84
84
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function isDebugTestMode(args = {}) {
|
|
88
|
+
return args.debug_test_mode === true || args.allow_debug_test_mode === true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeScreeningModeArg(args = {}) {
|
|
92
|
+
const raw = normalizeText(args.screening_mode || args.screeningMode || "");
|
|
93
|
+
if (args.use_llm === false) return "deterministic";
|
|
94
|
+
return ["deterministic", "local", "local_scorer"].includes(raw.toLowerCase())
|
|
95
|
+
? "deterministic"
|
|
96
|
+
: "llm";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectRecommendDebugTestOptions(args = {}, normalized = {}) {
|
|
100
|
+
const reasons = [];
|
|
101
|
+
if (normalizeScreeningModeArg(args) === "deterministic") reasons.push("deterministic_screening");
|
|
102
|
+
if (args.allow_card_only_screening === true) reasons.push("allow_card_only_screening");
|
|
103
|
+
if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
|
|
104
|
+
if (args.no_filter === true) reasons.push("no_filter");
|
|
105
|
+
if (args.filter_enabled === false) reasons.push("filter_enabled=false");
|
|
106
|
+
if (args.dry_run_post_action === true) reasons.push("dry_run_post_action");
|
|
107
|
+
if (args.execute_post_action === false && normalized.postAction && normalized.postAction !== "none") {
|
|
108
|
+
reasons.push("execute_post_action=false");
|
|
109
|
+
}
|
|
110
|
+
return reasons;
|
|
111
|
+
}
|
|
112
|
+
|
|
87
113
|
function resolveRecommendDetailLimit(args = {}, normalized = {}) {
|
|
88
114
|
const fallback = parsePositiveInteger(normalized.targetCount, 5);
|
|
89
115
|
const requested = parseNonNegativeInteger(args.detail_limit, fallback);
|
|
116
|
+
if (requested === 0 && !isDebugTestMode(args)) {
|
|
117
|
+
return fallback;
|
|
118
|
+
}
|
|
90
119
|
if (requested === 0 && args.allow_card_only_screening !== true) {
|
|
91
120
|
return fallback;
|
|
92
121
|
}
|
|
@@ -270,8 +299,15 @@ function ensureRecommendRunArtifacts(snapshot) {
|
|
|
270
299
|
if (meta) meta.checkpointPath = artifacts.checkpoint_path;
|
|
271
300
|
|
|
272
301
|
const summary = snapshot?.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
|
|
273
|
-
|
|
274
|
-
|
|
302
|
+
const checkpointResults = Array.isArray(checkpoint.results) ? checkpoint.results : [];
|
|
303
|
+
const artifactSummary = summary || (checkpointResults.length ? {
|
|
304
|
+
domain: "recommend",
|
|
305
|
+
partial: true,
|
|
306
|
+
partial_reason: snapshot?.status || snapshot?.state || "non_terminal",
|
|
307
|
+
results: checkpointResults
|
|
308
|
+
} : null);
|
|
309
|
+
if (artifactSummary) {
|
|
310
|
+
const rows = Array.isArray(artifactSummary.results) ? artifactSummary.results : [];
|
|
275
311
|
writeRecommendLegacyCsvAtomic(artifacts.output_csv, rows, snapshot, meta);
|
|
276
312
|
writeJsonAtomic(artifacts.report_json, {
|
|
277
313
|
run_id: snapshot.runId || snapshot.run_id,
|
|
@@ -280,7 +316,7 @@ function ensureRecommendRunArtifacts(snapshot) {
|
|
|
280
316
|
progress: snapshot.progress || {},
|
|
281
317
|
context: snapshot.context || {},
|
|
282
318
|
checkpoint,
|
|
283
|
-
summary,
|
|
319
|
+
summary: artifactSummary,
|
|
284
320
|
generated_at: new Date().toISOString()
|
|
285
321
|
});
|
|
286
322
|
if (meta) {
|
|
@@ -297,6 +333,12 @@ function buildLegacyRecommendResult(snapshot) {
|
|
|
297
333
|
const artifacts = ensureRecommendRunArtifacts(snapshot);
|
|
298
334
|
const meta = getRecommendRunMeta(snapshot.runId);
|
|
299
335
|
const summary = snapshot.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
|
|
336
|
+
const checkpoint = snapshot.checkpoint && typeof snapshot.checkpoint === "object" ? snapshot.checkpoint : {};
|
|
337
|
+
const resultRows = Array.isArray(summary?.results)
|
|
338
|
+
? summary.results
|
|
339
|
+
: Array.isArray(checkpoint.results)
|
|
340
|
+
? checkpoint.results
|
|
341
|
+
: [];
|
|
300
342
|
const progress = normalizeLegacyProgress(snapshot.progress, summary);
|
|
301
343
|
const targetCount = Number.isInteger(progress.target_count)
|
|
302
344
|
? progress.target_count
|
|
@@ -341,7 +383,7 @@ function buildLegacyRecommendResult(snapshot) {
|
|
|
341
383
|
screen_params: clonePlain(meta.parsed?.screenParams || {}, {}),
|
|
342
384
|
target_count_semantics: TARGET_COUNT_SEMANTICS,
|
|
343
385
|
error: snapshot.error || null,
|
|
344
|
-
results:
|
|
386
|
+
results: resultRows
|
|
345
387
|
};
|
|
346
388
|
}
|
|
347
389
|
|
|
@@ -958,11 +1000,12 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
|
|
|
958
1000
|
postAction: parsed.screenParams?.post_action || "none",
|
|
959
1001
|
maxGreetCount: Number.isInteger(parsed.screenParams?.max_greet_count)
|
|
960
1002
|
? parsed.screenParams.max_greet_count
|
|
961
|
-
: null
|
|
1003
|
+
: null,
|
|
1004
|
+
screeningMode: normalizeScreeningModeArg(args)
|
|
962
1005
|
};
|
|
963
1006
|
}
|
|
964
1007
|
|
|
965
|
-
function getRunOptions(args, parsed, normalized, session) {
|
|
1008
|
+
function getRunOptions(args, parsed, normalized, session, configResolution = null) {
|
|
966
1009
|
const slowLive = args.slow_live === true;
|
|
967
1010
|
const executePostAction = args.dry_run_post_action === true
|
|
968
1011
|
? false
|
|
@@ -998,6 +1041,14 @@ function getRunOptions(args, parsed, normalized, session) {
|
|
|
998
1041
|
actionTimeoutMs: parsePositiveInteger(args.action_timeout_ms, slowLive ? 12000 : 8000),
|
|
999
1042
|
actionIntervalMs: parsePositiveInteger(args.action_interval_ms, 500),
|
|
1000
1043
|
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
1044
|
+
screeningMode: normalized.screeningMode,
|
|
1045
|
+
llmConfig: normalized.screeningMode === "llm" && configResolution?.ok ? {
|
|
1046
|
+
...configResolution.config
|
|
1047
|
+
} : null,
|
|
1048
|
+
llmTimeoutMs: parsePositiveInteger(args.llm_timeout_ms, slowLive ? 180000 : 120000),
|
|
1049
|
+
llmImageLimit: parsePositiveInteger(args.llm_image_limit, 8),
|
|
1050
|
+
llmImageDetail: normalizeText(args.llm_image_detail) || "high",
|
|
1051
|
+
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1001
1052
|
name: "mcp-recommend-pipeline-run",
|
|
1002
1053
|
parsed
|
|
1003
1054
|
};
|
|
@@ -1054,6 +1105,30 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1054
1105
|
if (gate) return gate;
|
|
1055
1106
|
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1056
1107
|
const normalized = normalizeRecommendStartInput(args, parsed, configResolution);
|
|
1108
|
+
const debugTestOptions = collectRecommendDebugTestOptions(args, normalized);
|
|
1109
|
+
if (debugTestOptions.length && !isDebugTestMode(args)) {
|
|
1110
|
+
return {
|
|
1111
|
+
status: "FAILED",
|
|
1112
|
+
error: {
|
|
1113
|
+
code: "DEBUG_TEST_MODE_REQUIRED",
|
|
1114
|
+
message: `这些参数属于调试/测试路径,正式 live run 不会默认启用:${debugTestOptions.join(", ")}。如确需测试,请显式传 debug_test_mode=true。`,
|
|
1115
|
+
retryable: false
|
|
1116
|
+
},
|
|
1117
|
+
debug_test_options: debugTestOptions
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
if (normalized.screeningMode === "llm" && !configResolution.ok) {
|
|
1121
|
+
return {
|
|
1122
|
+
status: "FAILED",
|
|
1123
|
+
error: {
|
|
1124
|
+
code: "SCREEN_CONFIG_ERROR",
|
|
1125
|
+
message: configResolution.error?.message || "screening-config.json is required for LLM screening.",
|
|
1126
|
+
retryable: true
|
|
1127
|
+
},
|
|
1128
|
+
config_path: configResolution.config_path || null,
|
|
1129
|
+
candidate_paths: configResolution.candidate_paths || []
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1057
1132
|
|
|
1058
1133
|
let session;
|
|
1059
1134
|
try {
|
|
@@ -1085,7 +1160,7 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1085
1160
|
|
|
1086
1161
|
let started;
|
|
1087
1162
|
try {
|
|
1088
|
-
started = recommendRunService.startRecommendRun(getRunOptions(args, parsed, normalized, session));
|
|
1163
|
+
started = recommendRunService.startRecommendRun(getRunOptions(args, parsed, normalized, session, configResolution));
|
|
1089
1164
|
} catch (error) {
|
|
1090
1165
|
await session.close?.();
|
|
1091
1166
|
return {
|