@reconcrap/boss-recommend-mcp 2.0.6 → 2.0.7
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 +59 -18
- package/src/core/reporting/legacy-csv.js +2 -2
- package/src/core/screening/index.js +48 -0
- package/src/domains/chat/run-service.js +93 -40
- package/src/domains/recommend/run-service.js +78 -5
- package/src/domains/recruit/run-service.js +87 -12
- package/src/index.js +50 -5
- package/src/recommend-mcp.js +81 -7
- package/src/recruit-mcp.js +103 -8
|
@@ -21,7 +21,13 @@ import {
|
|
|
21
21
|
resetInfiniteListForRefreshRound
|
|
22
22
|
} from "../../core/infinite-list/index.js";
|
|
23
23
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
callScreeningLlm,
|
|
26
|
+
compactScreeningLlmResult,
|
|
27
|
+
createFailedLlmScreeningResult,
|
|
28
|
+
llmResultToScreening,
|
|
29
|
+
screenCandidate
|
|
30
|
+
} from "../../core/screening/index.js";
|
|
25
31
|
import {
|
|
26
32
|
closeRecommendDetail,
|
|
27
33
|
createRecommendDetailNetworkRecorder,
|
|
@@ -165,10 +171,22 @@ function compactDetail(detailResult) {
|
|
|
165
171
|
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
166
172
|
cv_acquisition: detailResult.cv_acquisition || null,
|
|
167
173
|
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
174
|
+
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
168
175
|
close_result: detailResult.close_result
|
|
169
176
|
};
|
|
170
177
|
}
|
|
171
178
|
|
|
179
|
+
function normalizeScreeningMode(value) {
|
|
180
|
+
const normalized = String(value || "llm").trim().toLowerCase();
|
|
181
|
+
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
182
|
+
? "deterministic"
|
|
183
|
+
: "llm";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function createMissingLlmConfigResult() {
|
|
187
|
+
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production recommend runs"));
|
|
188
|
+
}
|
|
189
|
+
|
|
172
190
|
function compactActionDiscovery(discovery) {
|
|
173
191
|
if (!discovery) return null;
|
|
174
192
|
return {
|
|
@@ -354,13 +372,20 @@ export async function runRecommendWorkflow({
|
|
|
354
372
|
executePostAction = true,
|
|
355
373
|
actionTimeoutMs = 8000,
|
|
356
374
|
actionIntervalMs = 500,
|
|
357
|
-
actionAfterClickDelayMs = 900
|
|
375
|
+
actionAfterClickDelayMs = 900,
|
|
376
|
+
screeningMode = "llm",
|
|
377
|
+
llmConfig = null,
|
|
378
|
+
llmTimeoutMs = 120000,
|
|
379
|
+
llmImageLimit = 8,
|
|
380
|
+
llmImageDetail = "high"
|
|
358
381
|
} = {}, runControl) {
|
|
359
382
|
if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
|
|
360
383
|
const normalizedFilter = normalizeFilter(filter);
|
|
361
384
|
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
362
385
|
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
363
386
|
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
387
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
388
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
364
389
|
const postActionEnabled = normalizedPostAction !== "none";
|
|
365
390
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
366
391
|
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
@@ -484,6 +509,8 @@ export async function runRecommendWorkflow({
|
|
|
484
509
|
passed: 0,
|
|
485
510
|
greet_count: 0,
|
|
486
511
|
post_action_clicked: 0,
|
|
512
|
+
screening_mode: normalizedScreeningMode,
|
|
513
|
+
llm_screened: 0,
|
|
487
514
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
488
515
|
scroll_count: 0,
|
|
489
516
|
refresh_rounds: 0,
|
|
@@ -562,6 +589,7 @@ export async function runRecommendWorkflow({
|
|
|
562
589
|
screened: results.length,
|
|
563
590
|
detail_opened: results.filter((item) => item.detail).length,
|
|
564
591
|
passed: results.filter((item) => item.screening.passed).length,
|
|
592
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
565
593
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
566
594
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
567
595
|
refresh_rounds: refreshRounds,
|
|
@@ -696,7 +724,30 @@ export async function runRecommendWorkflow({
|
|
|
696
724
|
await runControl.waitIfPaused();
|
|
697
725
|
runControl.throwIfCanceled();
|
|
698
726
|
runControl.setPhase("recommend:screening");
|
|
699
|
-
|
|
727
|
+
let llmResult = null;
|
|
728
|
+
if (useLlmScreening) {
|
|
729
|
+
if (!llmConfig) {
|
|
730
|
+
llmResult = createMissingLlmConfigResult();
|
|
731
|
+
} else {
|
|
732
|
+
try {
|
|
733
|
+
llmResult = await callScreeningLlm({
|
|
734
|
+
candidate: screeningCandidate,
|
|
735
|
+
criteria,
|
|
736
|
+
config: llmConfig,
|
|
737
|
+
timeoutMs: llmTimeoutMs,
|
|
738
|
+
imageEvidence: detailResult?.image_evidence || null,
|
|
739
|
+
maxImages: llmImageLimit,
|
|
740
|
+
imageDetail: llmImageDetail
|
|
741
|
+
});
|
|
742
|
+
} catch (error) {
|
|
743
|
+
llmResult = createFailedLlmScreeningResult(error);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (detailResult) detailResult.llm_result = llmResult;
|
|
747
|
+
}
|
|
748
|
+
const screening = useLlmScreening
|
|
749
|
+
? llmResultToScreening(llmResult, screeningCandidate)
|
|
750
|
+
: screenCandidate(screeningCandidate, { criteria });
|
|
700
751
|
let actionDiscovery = null;
|
|
701
752
|
let postActionResult = null;
|
|
702
753
|
if (postActionEnabled && detailResult) {
|
|
@@ -731,6 +782,7 @@ export async function runRecommendWorkflow({
|
|
|
731
782
|
card_node_id: cardNodeId,
|
|
732
783
|
candidate: compactCandidate(screeningCandidate),
|
|
733
784
|
detail: compactDetail(detailResult),
|
|
785
|
+
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
734
786
|
screening: compactScreening(screening),
|
|
735
787
|
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
736
788
|
post_action: postActionResult
|
|
@@ -750,6 +802,7 @@ export async function runRecommendWorkflow({
|
|
|
750
802
|
screened: results.length,
|
|
751
803
|
detail_opened: results.filter((item) => item.detail).length,
|
|
752
804
|
passed: results.filter((item) => item.screening.passed).length,
|
|
805
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
753
806
|
greet_count: greetCount,
|
|
754
807
|
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
755
808
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
@@ -764,6 +817,7 @@ export async function runRecommendWorkflow({
|
|
|
764
817
|
last_score: screening.score
|
|
765
818
|
});
|
|
766
819
|
runControl.checkpoint({
|
|
820
|
+
results,
|
|
767
821
|
last_candidate: {
|
|
768
822
|
id: screeningCandidate.id || null,
|
|
769
823
|
key: candidateKey,
|
|
@@ -773,6 +827,7 @@ export async function runRecommendWorkflow({
|
|
|
773
827
|
passed: screening.passed,
|
|
774
828
|
score: screening.score
|
|
775
829
|
},
|
|
830
|
+
llm_screening: compactScreeningLlmResult(llmResult),
|
|
776
831
|
post_action: postActionResult
|
|
777
832
|
}
|
|
778
833
|
});
|
|
@@ -806,6 +861,7 @@ export async function runRecommendWorkflow({
|
|
|
806
861
|
processed: results.length,
|
|
807
862
|
screened: results.length,
|
|
808
863
|
detail_opened: results.filter((item) => item.detail).length,
|
|
864
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
809
865
|
passed: results.filter((item) => item.screening.passed).length,
|
|
810
866
|
greet_count: greetCount,
|
|
811
867
|
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
@@ -851,6 +907,11 @@ export function createRecommendRunService({
|
|
|
851
907
|
actionTimeoutMs = 8000,
|
|
852
908
|
actionIntervalMs = 500,
|
|
853
909
|
actionAfterClickDelayMs = 900,
|
|
910
|
+
screeningMode = "llm",
|
|
911
|
+
llmConfig = null,
|
|
912
|
+
llmTimeoutMs = 120000,
|
|
913
|
+
llmImageLimit = 8,
|
|
914
|
+
llmImageDetail = "high",
|
|
854
915
|
name = "recommend-domain-run"
|
|
855
916
|
} = {}) {
|
|
856
917
|
if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
|
|
@@ -858,6 +919,7 @@ export function createRecommendRunService({
|
|
|
858
919
|
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
859
920
|
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
860
921
|
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
922
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
861
923
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
862
924
|
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
863
925
|
return manager.startRun({
|
|
@@ -888,7 +950,12 @@ export function createRecommendRunService({
|
|
|
888
950
|
post_action: normalizedPostAction,
|
|
889
951
|
max_greet_count: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
890
952
|
execute_post_action: Boolean(executePostAction),
|
|
891
|
-
action_timeout_ms: actionTimeoutMs
|
|
953
|
+
action_timeout_ms: actionTimeoutMs,
|
|
954
|
+
screening_mode: normalizedScreeningMode,
|
|
955
|
+
llm_configured: Boolean(llmConfig),
|
|
956
|
+
llm_timeout_ms: llmTimeoutMs,
|
|
957
|
+
llm_image_limit: llmImageLimit,
|
|
958
|
+
llm_image_detail: llmImageDetail
|
|
892
959
|
},
|
|
893
960
|
progress: {
|
|
894
961
|
card_count: 0,
|
|
@@ -896,6 +963,7 @@ export function createRecommendRunService({
|
|
|
896
963
|
processed: 0,
|
|
897
964
|
screened: 0,
|
|
898
965
|
detail_opened: 0,
|
|
966
|
+
llm_screened: 0,
|
|
899
967
|
passed: 0,
|
|
900
968
|
greet_count: 0,
|
|
901
969
|
post_action_clicked: 0
|
|
@@ -931,7 +999,12 @@ export function createRecommendRunService({
|
|
|
931
999
|
executePostAction,
|
|
932
1000
|
actionTimeoutMs,
|
|
933
1001
|
actionIntervalMs,
|
|
934
|
-
actionAfterClickDelayMs
|
|
1002
|
+
actionAfterClickDelayMs,
|
|
1003
|
+
screeningMode: normalizedScreeningMode,
|
|
1004
|
+
llmConfig,
|
|
1005
|
+
llmTimeoutMs,
|
|
1006
|
+
llmImageLimit,
|
|
1007
|
+
llmImageDetail
|
|
935
1008
|
}, runControl)
|
|
936
1009
|
});
|
|
937
1010
|
}
|
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
resetInfiniteListForRefreshRound
|
|
20
20
|
} from "../../core/infinite-list/index.js";
|
|
21
21
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
callScreeningLlm,
|
|
24
|
+
compactScreeningLlmResult,
|
|
25
|
+
createFailedLlmScreeningResult,
|
|
26
|
+
llmResultToScreening,
|
|
27
|
+
screenCandidate
|
|
28
|
+
} from "../../core/screening/index.js";
|
|
23
29
|
import {
|
|
24
30
|
closeRecruitDetail,
|
|
25
31
|
createRecruitDetailNetworkRecorder,
|
|
@@ -72,10 +78,22 @@ function compactDetail(detailResult) {
|
|
|
72
78
|
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
73
79
|
cv_acquisition: detailResult.cv_acquisition || null,
|
|
74
80
|
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
81
|
+
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
75
82
|
close_result: detailResult.close_result
|
|
76
83
|
};
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
function normalizeScreeningMode(value) {
|
|
87
|
+
const normalized = String(value || "llm").trim().toLowerCase();
|
|
88
|
+
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
89
|
+
? "deterministic"
|
|
90
|
+
: "llm";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createMissingLlmConfigResult() {
|
|
94
|
+
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production search runs"));
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
function normalizeSearchParams(searchParams = {}) {
|
|
80
98
|
return normalizeRecruitSearchParams(searchParams);
|
|
81
99
|
}
|
|
@@ -110,7 +128,7 @@ export async function runRecruitWorkflow({
|
|
|
110
128
|
criteria = "",
|
|
111
129
|
searchParams = {},
|
|
112
130
|
maxCandidates = 5,
|
|
113
|
-
detailLimit =
|
|
131
|
+
detailLimit = null,
|
|
114
132
|
closeDetail = true,
|
|
115
133
|
delayMs = 0,
|
|
116
134
|
cardTimeoutMs = 90000,
|
|
@@ -127,12 +145,19 @@ export async function runRecruitWorkflow({
|
|
|
127
145
|
listFallbackPoint = null,
|
|
128
146
|
refreshOnEnd = true,
|
|
129
147
|
maxRefreshRounds = 2,
|
|
130
|
-
refreshResetSettleMs = 5000
|
|
148
|
+
refreshResetSettleMs = 5000,
|
|
149
|
+
screeningMode = "llm",
|
|
150
|
+
llmConfig = null,
|
|
151
|
+
llmTimeoutMs = 120000,
|
|
152
|
+
llmImageLimit = 8,
|
|
153
|
+
llmImageDetail = "high"
|
|
131
154
|
} = {}, runControl) {
|
|
132
155
|
if (!client) throw new Error("runRecruitWorkflow requires a guarded CDP client");
|
|
133
156
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
157
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
158
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
134
159
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
135
|
-
const detailCountLimit = Math.max(0, Number(detailLimit) || 0);
|
|
160
|
+
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
136
161
|
const networkRecorder = detailCountLimit > 0
|
|
137
162
|
? createRecruitDetailNetworkRecorder(client)
|
|
138
163
|
: null;
|
|
@@ -222,6 +247,8 @@ export async function runRecruitWorkflow({
|
|
|
222
247
|
screened: 0,
|
|
223
248
|
detail_opened: 0,
|
|
224
249
|
passed: 0,
|
|
250
|
+
screening_mode: normalizedScreeningMode,
|
|
251
|
+
llm_screened: 0,
|
|
225
252
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
226
253
|
scroll_count: 0,
|
|
227
254
|
refresh_rounds: 0,
|
|
@@ -298,6 +325,7 @@ export async function runRecruitWorkflow({
|
|
|
298
325
|
screened: results.length,
|
|
299
326
|
detail_opened: results.filter((item) => item.detail).length,
|
|
300
327
|
passed: results.filter((item) => item.screening.passed).length,
|
|
328
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
301
329
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
302
330
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
303
331
|
refresh_rounds: refreshRounds,
|
|
@@ -426,13 +454,37 @@ export async function runRecruitWorkflow({
|
|
|
426
454
|
await runControl.waitIfPaused();
|
|
427
455
|
runControl.throwIfCanceled();
|
|
428
456
|
runControl.setPhase("recruit:screening");
|
|
429
|
-
|
|
457
|
+
let llmResult = null;
|
|
458
|
+
if (useLlmScreening) {
|
|
459
|
+
if (!llmConfig) {
|
|
460
|
+
llmResult = createMissingLlmConfigResult();
|
|
461
|
+
} else {
|
|
462
|
+
try {
|
|
463
|
+
llmResult = await callScreeningLlm({
|
|
464
|
+
candidate: screeningCandidate,
|
|
465
|
+
criteria,
|
|
466
|
+
config: llmConfig,
|
|
467
|
+
timeoutMs: llmTimeoutMs,
|
|
468
|
+
imageEvidence: detailResult?.image_evidence || null,
|
|
469
|
+
maxImages: llmImageLimit,
|
|
470
|
+
imageDetail: llmImageDetail
|
|
471
|
+
});
|
|
472
|
+
} catch (error) {
|
|
473
|
+
llmResult = createFailedLlmScreeningResult(error);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (detailResult) detailResult.llm_result = llmResult;
|
|
477
|
+
}
|
|
478
|
+
const screening = useLlmScreening
|
|
479
|
+
? llmResultToScreening(llmResult, screeningCandidate)
|
|
480
|
+
: screenCandidate(screeningCandidate, { criteria });
|
|
430
481
|
const compactResult = {
|
|
431
482
|
index,
|
|
432
483
|
candidate_key: candidateKey,
|
|
433
484
|
card_node_id: cardNodeId,
|
|
434
485
|
candidate: compactCandidate(screeningCandidate),
|
|
435
486
|
detail: compactDetail(detailResult),
|
|
487
|
+
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
436
488
|
screening: compactScreening(screening)
|
|
437
489
|
};
|
|
438
490
|
results.push(compactResult);
|
|
@@ -450,6 +502,7 @@ export async function runRecruitWorkflow({
|
|
|
450
502
|
screened: results.length,
|
|
451
503
|
detail_opened: results.filter((item) => item.detail).length,
|
|
452
504
|
passed: results.filter((item) => item.screening.passed).length,
|
|
505
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
453
506
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
454
507
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
455
508
|
refresh_rounds: refreshRounds,
|
|
@@ -462,6 +515,7 @@ export async function runRecruitWorkflow({
|
|
|
462
515
|
last_score: screening.score
|
|
463
516
|
});
|
|
464
517
|
runControl.checkpoint({
|
|
518
|
+
results,
|
|
465
519
|
last_candidate: {
|
|
466
520
|
id: screeningCandidate.id || null,
|
|
467
521
|
key: candidateKey,
|
|
@@ -470,7 +524,8 @@ export async function runRecruitWorkflow({
|
|
|
470
524
|
status: screening.status,
|
|
471
525
|
passed: screening.passed,
|
|
472
526
|
score: screening.score
|
|
473
|
-
}
|
|
527
|
+
},
|
|
528
|
+
llm_screening: compactScreeningLlmResult(llmResult)
|
|
474
529
|
}
|
|
475
530
|
});
|
|
476
531
|
|
|
@@ -496,6 +551,7 @@ export async function runRecruitWorkflow({
|
|
|
496
551
|
processed: results.length,
|
|
497
552
|
screened: results.length,
|
|
498
553
|
detail_opened: results.filter((item) => item.detail).length,
|
|
554
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
499
555
|
passed: results.filter((item) => item.screening.passed).length,
|
|
500
556
|
results
|
|
501
557
|
};
|
|
@@ -514,7 +570,7 @@ export function createRecruitRunService({
|
|
|
514
570
|
criteria = "",
|
|
515
571
|
searchParams = {},
|
|
516
572
|
maxCandidates = 5,
|
|
517
|
-
detailLimit =
|
|
573
|
+
detailLimit = null,
|
|
518
574
|
closeDetail = true,
|
|
519
575
|
delayMs = 0,
|
|
520
576
|
cardTimeoutMs = 90000,
|
|
@@ -532,10 +588,18 @@ export function createRecruitRunService({
|
|
|
532
588
|
refreshOnEnd = true,
|
|
533
589
|
maxRefreshRounds = 2,
|
|
534
590
|
refreshResetSettleMs = 5000,
|
|
591
|
+
screeningMode = "llm",
|
|
592
|
+
llmConfig = null,
|
|
593
|
+
llmTimeoutMs = 120000,
|
|
594
|
+
llmImageLimit = 8,
|
|
595
|
+
llmImageDetail = "high",
|
|
535
596
|
name = "recruit-domain-run"
|
|
536
597
|
} = {}) {
|
|
537
598
|
if (!client) throw new Error("startRecruitRun requires a guarded CDP client");
|
|
538
599
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
600
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
601
|
+
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
602
|
+
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
539
603
|
return manager.startRun({
|
|
540
604
|
name,
|
|
541
605
|
context: {
|
|
@@ -544,7 +608,7 @@ export function createRecruitRunService({
|
|
|
544
608
|
criteria_present: Boolean(criteria),
|
|
545
609
|
search_params: normalizedSearchParams,
|
|
546
610
|
max_candidates: maxCandidates,
|
|
547
|
-
detail_limit:
|
|
611
|
+
detail_limit: normalizedDetailLimit,
|
|
548
612
|
close_detail: closeDetail,
|
|
549
613
|
reset_before_search: resetBeforeSearch,
|
|
550
614
|
reset_timeout_ms: resetTimeoutMs,
|
|
@@ -559,14 +623,20 @@ export function createRecruitRunService({
|
|
|
559
623
|
list_fallback_point: listFallbackPoint,
|
|
560
624
|
refresh_on_end: refreshOnEnd,
|
|
561
625
|
max_refresh_rounds: maxRefreshRounds,
|
|
562
|
-
refresh_reset_settle_ms: refreshResetSettleMs
|
|
626
|
+
refresh_reset_settle_ms: refreshResetSettleMs,
|
|
627
|
+
screening_mode: normalizedScreeningMode,
|
|
628
|
+
llm_configured: Boolean(llmConfig),
|
|
629
|
+
llm_timeout_ms: llmTimeoutMs,
|
|
630
|
+
llm_image_limit: llmImageLimit,
|
|
631
|
+
llm_image_detail: llmImageDetail
|
|
563
632
|
},
|
|
564
633
|
progress: {
|
|
565
634
|
card_count: 0,
|
|
566
|
-
target_count:
|
|
635
|
+
target_count: candidateLimit,
|
|
567
636
|
processed: 0,
|
|
568
637
|
screened: 0,
|
|
569
638
|
detail_opened: 0,
|
|
639
|
+
llm_screened: 0,
|
|
570
640
|
passed: 0
|
|
571
641
|
},
|
|
572
642
|
checkpoint: {},
|
|
@@ -576,7 +646,7 @@ export function createRecruitRunService({
|
|
|
576
646
|
criteria,
|
|
577
647
|
searchParams: normalizedSearchParams,
|
|
578
648
|
maxCandidates,
|
|
579
|
-
detailLimit,
|
|
649
|
+
detailLimit: normalizedDetailLimit,
|
|
580
650
|
closeDetail,
|
|
581
651
|
delayMs,
|
|
582
652
|
cardTimeoutMs,
|
|
@@ -593,7 +663,12 @@ export function createRecruitRunService({
|
|
|
593
663
|
listFallbackPoint,
|
|
594
664
|
refreshOnEnd,
|
|
595
665
|
maxRefreshRounds,
|
|
596
|
-
refreshResetSettleMs
|
|
666
|
+
refreshResetSettleMs,
|
|
667
|
+
screeningMode: normalizedScreeningMode,
|
|
668
|
+
llmConfig,
|
|
669
|
+
llmTimeoutMs,
|
|
670
|
+
llmImageLimit,
|
|
671
|
+
llmImageDetail
|
|
597
672
|
}, runControl)
|
|
598
673
|
});
|
|
599
674
|
}
|
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,13 @@ 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",
|
|
1001
1051
|
name: "mcp-recommend-pipeline-run",
|
|
1002
1052
|
parsed
|
|
1003
1053
|
};
|
|
@@ -1054,6 +1104,30 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1054
1104
|
if (gate) return gate;
|
|
1055
1105
|
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1056
1106
|
const normalized = normalizeRecommendStartInput(args, parsed, configResolution);
|
|
1107
|
+
const debugTestOptions = collectRecommendDebugTestOptions(args, normalized);
|
|
1108
|
+
if (debugTestOptions.length && !isDebugTestMode(args)) {
|
|
1109
|
+
return {
|
|
1110
|
+
status: "FAILED",
|
|
1111
|
+
error: {
|
|
1112
|
+
code: "DEBUG_TEST_MODE_REQUIRED",
|
|
1113
|
+
message: `这些参数属于调试/测试路径,正式 live run 不会默认启用:${debugTestOptions.join(", ")}。如确需测试,请显式传 debug_test_mode=true。`,
|
|
1114
|
+
retryable: false
|
|
1115
|
+
},
|
|
1116
|
+
debug_test_options: debugTestOptions
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
if (normalized.screeningMode === "llm" && !configResolution.ok) {
|
|
1120
|
+
return {
|
|
1121
|
+
status: "FAILED",
|
|
1122
|
+
error: {
|
|
1123
|
+
code: "SCREEN_CONFIG_ERROR",
|
|
1124
|
+
message: configResolution.error?.message || "screening-config.json is required for LLM screening.",
|
|
1125
|
+
retryable: true
|
|
1126
|
+
},
|
|
1127
|
+
config_path: configResolution.config_path || null,
|
|
1128
|
+
candidate_paths: configResolution.candidate_paths || []
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1057
1131
|
|
|
1058
1132
|
let session;
|
|
1059
1133
|
try {
|
|
@@ -1085,7 +1159,7 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1085
1159
|
|
|
1086
1160
|
let started;
|
|
1087
1161
|
try {
|
|
1088
|
-
started = recommendRunService.startRecommendRun(getRunOptions(args, parsed, normalized, session));
|
|
1162
|
+
started = recommendRunService.startRecommendRun(getRunOptions(args, parsed, normalized, session, configResolution));
|
|
1089
1163
|
} catch (error) {
|
|
1090
1164
|
await session.close?.();
|
|
1091
1165
|
return {
|