@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
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
} from "../../core/infinite-list/index.js";
|
|
25
25
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
26
26
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
27
|
+
import {
|
|
28
|
+
addTiming,
|
|
29
|
+
imageEvidenceFilePath,
|
|
30
|
+
measureTiming
|
|
31
|
+
} from "../../core/run/timing.js";
|
|
27
32
|
import {
|
|
28
33
|
callScreeningLlm,
|
|
29
34
|
normalizeText,
|
|
@@ -167,6 +172,17 @@ function createFailedLlmResult(error) {
|
|
|
167
172
|
};
|
|
168
173
|
}
|
|
169
174
|
|
|
175
|
+
function normalizeScreeningMode(value) {
|
|
176
|
+
const normalized = String(value || "llm").trim().toLowerCase();
|
|
177
|
+
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
178
|
+
? "deterministic"
|
|
179
|
+
: "llm";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function createMissingLlmConfigResult() {
|
|
183
|
+
return createFailedLlmResult(new Error("LLM screening config is required for production chat runs"));
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
function createSkippedDetailResult(cardCandidate, reason, error = null) {
|
|
171
187
|
return {
|
|
172
188
|
candidate: cardCandidate,
|
|
@@ -334,7 +350,7 @@ export async function runChatWorkflow({
|
|
|
334
350
|
maxCandidates = 5,
|
|
335
351
|
targetPassCount = null,
|
|
336
352
|
processUntilListEnd = false,
|
|
337
|
-
detailLimit =
|
|
353
|
+
detailLimit = null,
|
|
338
354
|
detailSource = "cascade",
|
|
339
355
|
closeResume = true,
|
|
340
356
|
requestResumeForPassed = false,
|
|
@@ -353,20 +369,24 @@ export async function runChatWorkflow({
|
|
|
353
369
|
llmTimeoutMs = 120000,
|
|
354
370
|
llmImageLimit = 8,
|
|
355
371
|
llmImageDetail = "high",
|
|
372
|
+
screeningMode = "llm",
|
|
356
373
|
listMaxScrolls = 20,
|
|
357
374
|
listStableSignatureLimit = 2,
|
|
358
375
|
listWheelDeltaY = 850,
|
|
359
376
|
listSettleMs = 1200,
|
|
360
|
-
listFallbackPoint = null
|
|
377
|
+
listFallbackPoint = null,
|
|
378
|
+
imageOutputDir = ""
|
|
361
379
|
} = {}, runControl) {
|
|
362
380
|
if (!client) throw new Error("runChatWorkflow requires a guarded CDP client");
|
|
363
381
|
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
382
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
383
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
364
384
|
const processedLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
365
385
|
const passTarget = Number.isFinite(Number(targetPassCount)) && Number(targetPassCount) > 0
|
|
366
386
|
? Number(targetPassCount)
|
|
367
387
|
: null;
|
|
368
388
|
const normalizedStartFrom = normalizeText(startFrom).toLowerCase() === "unread" ? "unread" : "all";
|
|
369
|
-
const detailCountLimit = Math.max(0, Number(detailLimit) || 0);
|
|
389
|
+
const detailCountLimit = detailLimit == null ? processedLimit : Math.max(0, Number(detailLimit) || 0);
|
|
370
390
|
const networkRecorder = detailCountLimit > 0
|
|
371
391
|
? createChatProfileNetworkRecorder(client)
|
|
372
392
|
: null;
|
|
@@ -556,6 +576,7 @@ export async function runChatWorkflow({
|
|
|
556
576
|
requested: 0,
|
|
557
577
|
request_satisfied: 0,
|
|
558
578
|
request_skipped: 0,
|
|
579
|
+
screening_mode: normalizedScreeningMode,
|
|
559
580
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
560
581
|
scroll_count: 0,
|
|
561
582
|
viewport_checks: viewportGuard.getStats().checks,
|
|
@@ -569,6 +590,8 @@ export async function runChatWorkflow({
|
|
|
569
590
|
|| results.filter((item) => item.screening?.passed).length < passTarget
|
|
570
591
|
)
|
|
571
592
|
) {
|
|
593
|
+
const candidateStarted = Date.now();
|
|
594
|
+
const timings = {};
|
|
572
595
|
await runControl.waitIfPaused();
|
|
573
596
|
runControl.throwIfCanceled();
|
|
574
597
|
runControl.setPhase("chat:candidate");
|
|
@@ -581,7 +604,7 @@ export async function runChatWorkflow({
|
|
|
581
604
|
continue;
|
|
582
605
|
}
|
|
583
606
|
|
|
584
|
-
const nextCandidateResult = await getNextInfiniteListCandidate({
|
|
607
|
+
const nextCandidateResult = await measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
585
608
|
client,
|
|
586
609
|
state: listState,
|
|
587
610
|
maxScrolls: listMaxScrolls,
|
|
@@ -608,7 +631,7 @@ export async function runChatWorkflow({
|
|
|
608
631
|
visible_index: visibleIndex
|
|
609
632
|
}
|
|
610
633
|
})
|
|
611
|
-
});
|
|
634
|
+
}));
|
|
612
635
|
if (!nextCandidateResult.ok) {
|
|
613
636
|
const endTopLevelState = await getChatTopLevelState(client);
|
|
614
637
|
if (!endTopLevelState.is_chat_shell) {
|
|
@@ -650,11 +673,11 @@ export async function runChatWorkflow({
|
|
|
650
673
|
|
|
651
674
|
detailStep = "select_candidate";
|
|
652
675
|
networkRecorder.clear();
|
|
653
|
-
const selected = await selectFreshChatCandidate(client, {
|
|
676
|
+
const selected = await measureTiming(timings, "candidate_click_ms", () => selectFreshChatCandidate(client, {
|
|
654
677
|
cardNodeId,
|
|
655
678
|
candidate: cardCandidate,
|
|
656
679
|
timeoutMs: onlineResumeButtonTimeoutMs
|
|
657
|
-
});
|
|
680
|
+
}));
|
|
658
681
|
if (selected.ready?.forbidden_top_level_navigation) {
|
|
659
682
|
throw makeForbiddenChatResumeNavigationError(selected.ready.top_level_state);
|
|
660
683
|
}
|
|
@@ -681,13 +704,13 @@ export async function runChatWorkflow({
|
|
|
681
704
|
if (!detailResult) {
|
|
682
705
|
detailStep = "open_online_resume";
|
|
683
706
|
networkRecorder.clear();
|
|
684
|
-
const openedResume = await openChatOnlineResume(client, {
|
|
707
|
+
const openedResume = await measureTiming(timings, "detail_open_ms", () => openChatOnlineResume(client, {
|
|
685
708
|
timeoutMs: readyTimeoutMs
|
|
686
|
-
});
|
|
709
|
+
}));
|
|
687
710
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
688
711
|
detailStep = "wait_network";
|
|
689
712
|
const networkWait = ["network", "cascade"].includes(normalizedDetailSource)
|
|
690
|
-
? await waitForCvNetworkEvents(
|
|
713
|
+
? await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
691
714
|
waitForChatProfileNetworkEvents,
|
|
692
715
|
networkRecorder,
|
|
693
716
|
{
|
|
@@ -696,8 +719,11 @@ export async function runChatWorkflow({
|
|
|
696
719
|
requireLoaded: true,
|
|
697
720
|
intervalMs: 200
|
|
698
721
|
}
|
|
699
|
-
)
|
|
722
|
+
))
|
|
700
723
|
: null;
|
|
724
|
+
if (networkWait?.elapsed_ms != null) {
|
|
725
|
+
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
726
|
+
}
|
|
701
727
|
let contentWait = {
|
|
702
728
|
ok: false,
|
|
703
729
|
skipped: false,
|
|
@@ -744,10 +770,10 @@ export async function runChatWorkflow({
|
|
|
744
770
|
|
|
745
771
|
if (!detailResult) {
|
|
746
772
|
detailStep = "wait_resume_content";
|
|
747
|
-
contentWait = await waitForChatResumeContent(client, {
|
|
773
|
+
contentWait = await measureTiming(timings, "dom_fallback_ms", () => waitForChatResumeContent(client, {
|
|
748
774
|
timeoutMs: resumeDomTimeoutMs,
|
|
749
775
|
intervalMs: 300
|
|
750
|
-
});
|
|
776
|
+
}));
|
|
751
777
|
resumeState = contentWait.resume_state || openedResume.resume_state;
|
|
752
778
|
resumeHtml = contentWait.resume_html || null;
|
|
753
779
|
resumeNetworkEvents = networkRecorder.events.slice();
|
|
@@ -777,7 +803,13 @@ export async function runChatWorkflow({
|
|
|
777
803
|
if (shouldCaptureImage) {
|
|
778
804
|
if (captureNodeId) {
|
|
779
805
|
detailStep = "capture_image_fallback";
|
|
780
|
-
imageEvidence = await captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
806
|
+
imageEvidence = await measureTiming(timings, "screenshot_capture_ms", () => captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
807
|
+
filePath: imageEvidenceFilePath({
|
|
808
|
+
imageOutputDir,
|
|
809
|
+
domain: "chat",
|
|
810
|
+
runId: runControl?.runId,
|
|
811
|
+
index
|
|
812
|
+
}),
|
|
781
813
|
padding: 8,
|
|
782
814
|
maxScreenshots: maxImagePages,
|
|
783
815
|
wheelDeltaY: imageWheelDeltaY,
|
|
@@ -791,7 +823,7 @@ export async function runChatWorkflow({
|
|
|
791
823
|
run_candidate_index: index,
|
|
792
824
|
candidate_key: candidateKey
|
|
793
825
|
}
|
|
794
|
-
});
|
|
826
|
+
}));
|
|
795
827
|
source = "image";
|
|
796
828
|
recordCvImageFallback(cvAcquisitionState, {
|
|
797
829
|
parsedNetworkProfileCount,
|
|
@@ -799,21 +831,23 @@ export async function runChatWorkflow({
|
|
|
799
831
|
imageEvidence
|
|
800
832
|
});
|
|
801
833
|
if (callLlmOnImage) {
|
|
802
|
-
if (!llmConfig) throw new Error("callLlmOnImage requires llmConfig");
|
|
803
834
|
detailStep = "llm_image_screening";
|
|
804
|
-
|
|
805
|
-
llmResult =
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
835
|
+
if (!llmConfig) {
|
|
836
|
+
llmResult = createMissingLlmConfigResult();
|
|
837
|
+
} else {
|
|
838
|
+
try {
|
|
839
|
+
llmResult = await measureTiming(timings, "vision_model_ms", () => callScreeningLlm({
|
|
840
|
+
candidate: detailResult.candidate,
|
|
841
|
+
criteria,
|
|
842
|
+
config: llmConfig,
|
|
843
|
+
timeoutMs: llmTimeoutMs,
|
|
844
|
+
imageEvidence,
|
|
845
|
+
maxImages: llmImageLimit,
|
|
846
|
+
imageDetail: llmImageDetail
|
|
847
|
+
}));
|
|
848
|
+
} catch (error) {
|
|
849
|
+
llmResult = createFailedLlmResult(error);
|
|
850
|
+
}
|
|
817
851
|
}
|
|
818
852
|
}
|
|
819
853
|
} else {
|
|
@@ -838,28 +872,34 @@ export async function runChatWorkflow({
|
|
|
838
872
|
});
|
|
839
873
|
}
|
|
840
874
|
|
|
841
|
-
if (
|
|
875
|
+
if (useLlmScreening && !llmResult) {
|
|
842
876
|
detailStep = "llm_screening";
|
|
843
|
-
|
|
844
|
-
llmResult =
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
877
|
+
if (!llmConfig) {
|
|
878
|
+
llmResult = createMissingLlmConfigResult();
|
|
879
|
+
} else {
|
|
880
|
+
try {
|
|
881
|
+
const llmTimingKey = imageEvidence?.file_paths?.length
|
|
882
|
+
? "vision_model_ms"
|
|
883
|
+
: "text_model_ms";
|
|
884
|
+
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
885
|
+
candidate: detailResult.candidate,
|
|
886
|
+
criteria,
|
|
887
|
+
config: llmConfig,
|
|
888
|
+
timeoutMs: llmTimeoutMs,
|
|
889
|
+
imageEvidence,
|
|
890
|
+
maxImages: llmImageLimit,
|
|
891
|
+
imageDetail: llmImageDetail
|
|
892
|
+
}));
|
|
893
|
+
} catch (error) {
|
|
894
|
+
llmResult = createFailedLlmResult(error);
|
|
895
|
+
}
|
|
856
896
|
}
|
|
857
897
|
}
|
|
858
898
|
|
|
859
899
|
let closeResult = null;
|
|
860
900
|
if (closeResume) {
|
|
861
901
|
detailStep = "close_resume_modal";
|
|
862
|
-
closeResult = await closeChatResumeModal(client);
|
|
902
|
+
closeResult = await measureTiming(timings, "close_detail_ms", () => closeChatResumeModal(client));
|
|
863
903
|
}
|
|
864
904
|
detailResult.close_result = closeResult;
|
|
865
905
|
detailResult.image_evidence = imageEvidence;
|
|
@@ -901,6 +941,26 @@ export async function runChatWorkflow({
|
|
|
901
941
|
await runControl.waitIfPaused();
|
|
902
942
|
runControl.throwIfCanceled();
|
|
903
943
|
runControl.setPhase("chat:screening");
|
|
944
|
+
let cardOnlyLlmResult = null;
|
|
945
|
+
if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
|
|
946
|
+
if (!llmConfig) {
|
|
947
|
+
cardOnlyLlmResult = createMissingLlmConfigResult();
|
|
948
|
+
} else {
|
|
949
|
+
try {
|
|
950
|
+
cardOnlyLlmResult = await measureTiming(timings, "text_model_ms", () => callScreeningLlm({
|
|
951
|
+
candidate: screeningCandidate,
|
|
952
|
+
criteria,
|
|
953
|
+
config: llmConfig,
|
|
954
|
+
timeoutMs: llmTimeoutMs,
|
|
955
|
+
maxImages: llmImageLimit,
|
|
956
|
+
imageDetail: llmImageDetail
|
|
957
|
+
}));
|
|
958
|
+
} catch (error) {
|
|
959
|
+
cardOnlyLlmResult = createFailedLlmResult(error);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
|
|
904
964
|
const screening = detailUnavailableReason
|
|
905
965
|
? {
|
|
906
966
|
status: "skip",
|
|
@@ -909,15 +969,15 @@ export async function runChatWorkflow({
|
|
|
909
969
|
reasons: [detailUnavailableReason],
|
|
910
970
|
candidate: screeningCandidate
|
|
911
971
|
}
|
|
912
|
-
:
|
|
913
|
-
? llmToScreening(
|
|
972
|
+
: useLlmScreening
|
|
973
|
+
? llmToScreening(effectiveLlmResult, screeningCandidate)
|
|
914
974
|
: screenCandidate(screeningCandidate, { criteria });
|
|
915
975
|
let postAction = null;
|
|
916
976
|
if (requestResumeForPassed && screening.passed) {
|
|
917
|
-
postAction = await requestChatResumeForPassedCandidate(client, {
|
|
977
|
+
postAction = await measureTiming(timings, "post_action_ms", () => requestChatResumeForPassedCandidate(client, {
|
|
918
978
|
greetingText,
|
|
919
979
|
dryRun: dryRunRequestCv
|
|
920
|
-
});
|
|
980
|
+
}));
|
|
921
981
|
if (postAction?.requested) requestSatisfiedCount += 1;
|
|
922
982
|
if (postAction?.skipped) requestSkippedCount += 1;
|
|
923
983
|
if (postAction?.requested && !postAction?.skipped) requestedCount += 1;
|
|
@@ -925,15 +985,18 @@ export async function runChatWorkflow({
|
|
|
925
985
|
throw new Error(`REQUEST_CV_NOT_VERIFIED:${postAction?.reason || "unknown"}`);
|
|
926
986
|
}
|
|
927
987
|
}
|
|
988
|
+
timings.total_ms = Date.now() - candidateStarted;
|
|
928
989
|
const compactResult = {
|
|
929
990
|
index,
|
|
930
991
|
candidate_key: candidateKey,
|
|
931
992
|
card_node_id: effectiveCardNodeId,
|
|
932
993
|
candidate: compactCandidate(screeningCandidate),
|
|
933
994
|
detail: compactDetail(detailResult),
|
|
995
|
+
llm_screening: detailResult ? null : compactLlmResult(cardOnlyLlmResult),
|
|
934
996
|
screening: compactScreening(screening),
|
|
935
997
|
post_action: postAction,
|
|
936
|
-
pre_action_state: preActionState
|
|
998
|
+
pre_action_state: preActionState,
|
|
999
|
+
timings
|
|
937
1000
|
};
|
|
938
1001
|
results.push(compactResult);
|
|
939
1002
|
markInfiniteListCandidateProcessed(listState, candidateKey, {
|
|
@@ -951,7 +1014,7 @@ export async function runChatWorkflow({
|
|
|
951
1014
|
processed: results.length,
|
|
952
1015
|
screened: results.length,
|
|
953
1016
|
detail_opened: results.filter(resultOpenedDetail).length,
|
|
954
|
-
llm_screened: results.filter((item) => item.detail?.llm_screening).length,
|
|
1017
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
955
1018
|
passed: results.filter((item) => item.screening.passed).length,
|
|
956
1019
|
requested: requestedCount,
|
|
957
1020
|
request_satisfied: requestSatisfiedCount,
|
|
@@ -965,7 +1028,9 @@ export async function runChatWorkflow({
|
|
|
965
1028
|
last_candidate_key: candidateKey,
|
|
966
1029
|
last_score: screening.score
|
|
967
1030
|
});
|
|
1031
|
+
const checkpointStarted = Date.now();
|
|
968
1032
|
runControl.checkpoint({
|
|
1033
|
+
results,
|
|
969
1034
|
last_candidate: {
|
|
970
1035
|
id: screeningCandidate.id || null,
|
|
971
1036
|
key: candidateKey,
|
|
@@ -974,12 +1039,17 @@ export async function runChatWorkflow({
|
|
|
974
1039
|
status: screening.status,
|
|
975
1040
|
passed: screening.passed,
|
|
976
1041
|
score: screening.score
|
|
977
|
-
}
|
|
1042
|
+
},
|
|
1043
|
+
llm_screening: compactLlmResult(effectiveLlmResult)
|
|
978
1044
|
}
|
|
979
1045
|
});
|
|
1046
|
+
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
980
1047
|
|
|
981
1048
|
if (delayMs > 0) {
|
|
1049
|
+
const sleepStarted = Date.now();
|
|
982
1050
|
await runControl.sleep(delayMs);
|
|
1051
|
+
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1052
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
983
1053
|
}
|
|
984
1054
|
}
|
|
985
1055
|
|
|
@@ -1002,7 +1072,7 @@ export async function runChatWorkflow({
|
|
|
1002
1072
|
processed: results.length,
|
|
1003
1073
|
screened: results.length,
|
|
1004
1074
|
detail_opened: results.filter(resultOpenedDetail).length,
|
|
1005
|
-
llm_screened: results.filter((item) => item.detail?.llm_screening).length,
|
|
1075
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
1006
1076
|
passed: results.filter((item) => item.screening.passed).length,
|
|
1007
1077
|
requested: requestedCount,
|
|
1008
1078
|
request_satisfied: requestSatisfiedCount,
|
|
@@ -1027,7 +1097,7 @@ export function createChatRunService({
|
|
|
1027
1097
|
maxCandidates = 5,
|
|
1028
1098
|
targetPassCount = null,
|
|
1029
1099
|
processUntilListEnd = false,
|
|
1030
|
-
detailLimit =
|
|
1100
|
+
detailLimit = null,
|
|
1031
1101
|
detailSource = "cascade",
|
|
1032
1102
|
closeResume = true,
|
|
1033
1103
|
requestResumeForPassed = false,
|
|
@@ -1046,15 +1116,20 @@ export function createChatRunService({
|
|
|
1046
1116
|
llmTimeoutMs = 120000,
|
|
1047
1117
|
llmImageLimit = 8,
|
|
1048
1118
|
llmImageDetail = "high",
|
|
1119
|
+
screeningMode = "llm",
|
|
1049
1120
|
listMaxScrolls = 20,
|
|
1050
1121
|
listStableSignatureLimit = 2,
|
|
1051
1122
|
listWheelDeltaY = 850,
|
|
1052
1123
|
listSettleMs = 1200,
|
|
1053
1124
|
listFallbackPoint = null,
|
|
1125
|
+
imageOutputDir = "",
|
|
1054
1126
|
name = "chat-domain-run"
|
|
1055
1127
|
} = {}) {
|
|
1056
1128
|
if (!client) throw new Error("startChatRun requires a guarded CDP client");
|
|
1057
1129
|
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
1130
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1131
|
+
const processedLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1132
|
+
const normalizedDetailLimit = detailLimit == null ? processedLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1058
1133
|
return manager.startRun({
|
|
1059
1134
|
name,
|
|
1060
1135
|
context: {
|
|
@@ -1066,11 +1141,16 @@ export function createChatRunService({
|
|
|
1066
1141
|
max_candidates: maxCandidates,
|
|
1067
1142
|
target_pass_count: targetPassCount,
|
|
1068
1143
|
process_until_list_end: Boolean(processUntilListEnd),
|
|
1069
|
-
detail_limit:
|
|
1144
|
+
detail_limit: normalizedDetailLimit,
|
|
1070
1145
|
detail_source: normalizedDetailSource,
|
|
1071
1146
|
close_resume: closeResume,
|
|
1072
1147
|
cv_acquisition_mode: cvAcquisitionMode,
|
|
1073
1148
|
call_llm_on_image: Boolean(callLlmOnImage),
|
|
1149
|
+
screening_mode: normalizedScreeningMode,
|
|
1150
|
+
llm_configured: Boolean(llmConfig),
|
|
1151
|
+
llm_timeout_ms: llmTimeoutMs,
|
|
1152
|
+
llm_image_limit: llmImageLimit,
|
|
1153
|
+
llm_image_detail: llmImageDetail,
|
|
1074
1154
|
max_image_pages: maxImagePages,
|
|
1075
1155
|
image_wheel_delta_y: imageWheelDeltaY,
|
|
1076
1156
|
list_max_scrolls: listMaxScrolls,
|
|
@@ -1078,13 +1158,14 @@ export function createChatRunService({
|
|
|
1078
1158
|
list_wheel_delta_y: listWheelDeltaY,
|
|
1079
1159
|
list_settle_ms: listSettleMs,
|
|
1080
1160
|
list_fallback_point: listFallbackPoint,
|
|
1081
|
-
online_resume_button_timeout_ms: onlineResumeButtonTimeoutMs
|
|
1161
|
+
online_resume_button_timeout_ms: onlineResumeButtonTimeoutMs,
|
|
1162
|
+
image_output_dir: imageOutputDir || ""
|
|
1082
1163
|
},
|
|
1083
1164
|
progress: {
|
|
1084
1165
|
card_count: 0,
|
|
1085
|
-
target_count: targetPassCount || (processUntilListEnd ? "all" :
|
|
1166
|
+
target_count: targetPassCount || (processUntilListEnd ? "all" : processedLimit),
|
|
1086
1167
|
target_pass_count: targetPassCount,
|
|
1087
|
-
processed_limit:
|
|
1168
|
+
processed_limit: processedLimit,
|
|
1088
1169
|
processed: 0,
|
|
1089
1170
|
screened: 0,
|
|
1090
1171
|
detail_opened: 0,
|
|
@@ -1104,7 +1185,7 @@ export function createChatRunService({
|
|
|
1104
1185
|
maxCandidates,
|
|
1105
1186
|
targetPassCount,
|
|
1106
1187
|
processUntilListEnd,
|
|
1107
|
-
detailLimit,
|
|
1188
|
+
detailLimit: normalizedDetailLimit,
|
|
1108
1189
|
detailSource: normalizedDetailSource,
|
|
1109
1190
|
closeResume,
|
|
1110
1191
|
requestResumeForPassed,
|
|
@@ -1123,11 +1204,13 @@ export function createChatRunService({
|
|
|
1123
1204
|
llmTimeoutMs,
|
|
1124
1205
|
llmImageLimit,
|
|
1125
1206
|
llmImageDetail,
|
|
1207
|
+
screeningMode: normalizedScreeningMode,
|
|
1126
1208
|
listMaxScrolls,
|
|
1127
1209
|
listStableSignatureLimit,
|
|
1128
1210
|
listWheelDeltaY,
|
|
1129
1211
|
listSettleMs,
|
|
1130
|
-
listFallbackPoint
|
|
1212
|
+
listFallbackPoint,
|
|
1213
|
+
imageOutputDir
|
|
1131
1214
|
}, runControl)
|
|
1132
1215
|
});
|
|
1133
1216
|
}
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
querySelectorAll,
|
|
7
7
|
sleep
|
|
8
8
|
} from "../../core/browser/index.js";
|
|
9
|
+
import {
|
|
10
|
+
mergeBossCandidateCardFields,
|
|
11
|
+
parseBossCandidateCardFieldsFromHtml
|
|
12
|
+
} from "../../core/boss-cards/index.js";
|
|
9
13
|
import {
|
|
10
14
|
htmlToText,
|
|
11
15
|
normalizeCandidateFromHtml,
|
|
@@ -24,6 +28,16 @@ function normalizeRefreshButtonLabel(outerHTML = "") {
|
|
|
24
28
|
return normalizeText(htmlToText(outerHTML)).replace(/\s+/g, "");
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
export function parseRecommendCardFieldsFromHtml(html = "") {
|
|
32
|
+
return parseBossCandidateCardFieldsFromHtml(html);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function enrichRecommendCardCandidate(candidate, outerHTML = "") {
|
|
36
|
+
return mergeBossCandidateCardFields(candidate, outerHTML, {
|
|
37
|
+
metadataKey: "recommend_card_fields"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
function isRefreshButtonLabel(label = "") {
|
|
28
42
|
const normalized = String(label || "").trim();
|
|
29
43
|
if (!normalized || normalized.length > 80) return false;
|
|
@@ -91,7 +105,7 @@ export async function readRecommendCardCandidate(client, cardNodeId, {
|
|
|
91
105
|
getAttributesMap(client, cardNodeId),
|
|
92
106
|
getOuterHTML(client, cardNodeId)
|
|
93
107
|
]);
|
|
94
|
-
|
|
108
|
+
const candidate = normalizeCandidateFromHtml({
|
|
95
109
|
domain: "recommend",
|
|
96
110
|
source,
|
|
97
111
|
html: outerHTML,
|
|
@@ -102,6 +116,7 @@ export async function readRecommendCardCandidate(client, cardNodeId, {
|
|
|
102
116
|
...metadata
|
|
103
117
|
}
|
|
104
118
|
});
|
|
119
|
+
return enrichRecommendCardCandidate(candidate, outerHTML);
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
export async function readFirstRecommendCardCandidate(client, frameNodeId, options = {}) {
|
|
@@ -279,15 +279,25 @@ export async function openRecommendCardDetail(client, cardNodeId, {
|
|
|
279
279
|
timeoutMs = 12000,
|
|
280
280
|
scrollIntoView = true
|
|
281
281
|
} = {}) {
|
|
282
|
+
const started = Date.now();
|
|
283
|
+
const clickStarted = Date.now();
|
|
282
284
|
const cardBox = await clickNodeCenter(client, cardNodeId, { scrollIntoView });
|
|
285
|
+
const candidateClickMs = Date.now() - clickStarted;
|
|
286
|
+
const detailStarted = Date.now();
|
|
283
287
|
const detailState = await waitForRecommendDetail(client, { timeoutMs });
|
|
288
|
+
const detailOpenMs = Date.now() - detailStarted;
|
|
284
289
|
if (!detailState?.popup && !detailState?.resumeIframe) {
|
|
285
290
|
throw new Error("Candidate detail did not open or no known detail selectors mounted");
|
|
286
291
|
}
|
|
287
292
|
|
|
288
293
|
return {
|
|
289
294
|
card_box: cardBox,
|
|
290
|
-
detail_state: detailState
|
|
295
|
+
detail_state: detailState,
|
|
296
|
+
timings: {
|
|
297
|
+
candidate_click_ms: candidateClickMs,
|
|
298
|
+
detail_open_ms: detailOpenMs,
|
|
299
|
+
open_total_ms: Date.now() - started
|
|
300
|
+
}
|
|
291
301
|
};
|
|
292
302
|
}
|
|
293
303
|
|