@reconcrap/boss-recommend-mcp 2.1.14 → 2.1.15
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 +5 -5
- package/package.json +1 -1
- package/skills/boss-chat/README.md +2 -2
- package/skills/boss-chat/SKILL.md +7 -7
- package/skills/boss-recruit-pipeline/SKILL.md +23 -1
- package/src/chat-mcp.js +70 -73
- package/src/domains/chat/detail.js +79 -47
- package/src/domains/chat/run-service.js +400 -158
- package/src/domains/recruit/constants.js +65 -0
- package/src/domains/recruit/instruction-parser.js +362 -86
- package/src/domains/recruit/run-service.js +281 -10
- package/src/domains/recruit/search.js +2076 -298
- package/src/index.js +18 -12
- package/src/recommend-mcp.js +77 -8
- package/src/recruit-mcp.js +228 -3
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
captureScrolledNodeScreenshots,
|
|
3
|
+
captureViewportScreenshot
|
|
4
|
+
} from "../../core/capture/index.js";
|
|
2
5
|
import { waitForCvCaptureTarget } from "../../core/cv-capture-target/index.js";
|
|
3
6
|
import {
|
|
4
7
|
clickPoint,
|
|
@@ -32,7 +35,10 @@ import {
|
|
|
32
35
|
resolveInfiniteListFallbackPoint
|
|
33
36
|
} from "../../core/infinite-list/index.js";
|
|
34
37
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
35
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
createRunLifecycleManager,
|
|
40
|
+
RunCanceledError
|
|
41
|
+
} from "../../core/run/index.js";
|
|
36
42
|
import {
|
|
37
43
|
addTiming,
|
|
38
44
|
imageEvidenceFilePath,
|
|
@@ -61,12 +67,14 @@ import {
|
|
|
61
67
|
closeChatBlockingPanels,
|
|
62
68
|
closeChatResumeModal,
|
|
63
69
|
createChatProfileNetworkRecorder,
|
|
64
|
-
extractChatProfileCandidate,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
extractChatProfileCandidate,
|
|
71
|
+
isChatOnlineResumeModalOpenFailureError,
|
|
72
|
+
isUnsafeChatOnlineResumeLinkError,
|
|
73
|
+
openChatOnlineResume,
|
|
74
|
+
quickChatResumeModalOpenProbe,
|
|
75
|
+
readChatActiveCandidateState,
|
|
76
|
+
readChatConversationReadyState,
|
|
77
|
+
requestChatResumeForPassedCandidate,
|
|
70
78
|
selectChatMessageFilter,
|
|
71
79
|
selectChatPrimaryLabel,
|
|
72
80
|
waitForChatOnlineResumeButton,
|
|
@@ -373,12 +381,100 @@ function createFailedLlmResult(error) {
|
|
|
373
381
|
};
|
|
374
382
|
}
|
|
375
383
|
|
|
376
|
-
function normalizeScreeningMode(value) {
|
|
377
|
-
const normalized = String(value || "llm").trim().toLowerCase();
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
384
|
+
function normalizeScreeningMode(value) {
|
|
385
|
+
const normalized = String(value || "llm").trim().toLowerCase();
|
|
386
|
+
if (["collect_cv", "collect-cv", "cv_collection", "request_cv", "request_resume"].includes(normalized)) {
|
|
387
|
+
return "collect_cv";
|
|
388
|
+
}
|
|
389
|
+
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
390
|
+
? "deterministic"
|
|
391
|
+
: "llm";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function isCvAcquiredOrAvailable(detailResult = null, preActionState = null) {
|
|
395
|
+
return Boolean(
|
|
396
|
+
preActionState?.attachment_resume_enabled
|
|
397
|
+
|| detailResult?.cv_acquisition?.full_cv_evidence?.full_cv_acquired
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function isChatResumeRequestAvailable(preActionState = null) {
|
|
402
|
+
return Boolean(preActionState?.ask_resume?.node_id && !preActionState.ask_resume.disabled);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function shouldSkipCvCollectionForDetailReason(reason = "") {
|
|
406
|
+
const normalized = normalizeText(reason);
|
|
407
|
+
return [
|
|
408
|
+
"active_candidate_mismatch",
|
|
409
|
+
"forbidden_top_level_resume_navigation",
|
|
410
|
+
"online_resume_modal_did_not_open",
|
|
411
|
+
"unsafe_online_resume_navigation_link"
|
|
412
|
+
].includes(normalized)
|
|
413
|
+
|| normalized.startsWith("recoverable_cdp_node_stale:")
|
|
414
|
+
|| normalized.startsWith("resume_modal_close_failed:");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function createCvCollectionScreening(screeningCandidate, {
|
|
418
|
+
detailResult = null,
|
|
419
|
+
detailUnavailableReason = "",
|
|
420
|
+
preActionState = null
|
|
421
|
+
} = {}) {
|
|
422
|
+
if (preActionState?.already_requested_resume) {
|
|
423
|
+
return {
|
|
424
|
+
status: "skip",
|
|
425
|
+
passed: false,
|
|
426
|
+
score: 0,
|
|
427
|
+
reasons: ["resume_request_already_pending"],
|
|
428
|
+
candidate: screeningCandidate
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (isCvAcquiredOrAvailable(detailResult, preActionState)) {
|
|
432
|
+
const reason = preActionState?.attachment_resume_enabled
|
|
433
|
+
? "attachment_resume_already_available"
|
|
434
|
+
: "online_cv_already_available";
|
|
435
|
+
return {
|
|
436
|
+
status: "skip",
|
|
437
|
+
passed: false,
|
|
438
|
+
score: 0,
|
|
439
|
+
reasons: [reason],
|
|
440
|
+
candidate: screeningCandidate
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (isChatResumeRequestAvailable(preActionState)) {
|
|
444
|
+
const reason = detailUnavailableReason || "request_cv_available";
|
|
445
|
+
return {
|
|
446
|
+
status: "pass",
|
|
447
|
+
passed: true,
|
|
448
|
+
score: 100,
|
|
449
|
+
reasons: [`collect_cv:${reason}`],
|
|
450
|
+
candidate: screeningCandidate
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
if (shouldSkipCvCollectionForDetailReason(detailUnavailableReason)) {
|
|
454
|
+
return {
|
|
455
|
+
status: "skip",
|
|
456
|
+
passed: false,
|
|
457
|
+
score: 0,
|
|
458
|
+
reasons: [detailUnavailableReason],
|
|
459
|
+
candidate: screeningCandidate
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const reason = detailUnavailableReason || "cv_collection_missing_online_cv";
|
|
463
|
+
return {
|
|
464
|
+
status: "pass",
|
|
465
|
+
passed: true,
|
|
466
|
+
score: 100,
|
|
467
|
+
reasons: [`collect_cv:${reason}`],
|
|
468
|
+
candidate: screeningCandidate
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function shouldOpenOnlineResumeForChatDetail({
|
|
473
|
+
collectCvOnly = false,
|
|
474
|
+
detailResult = null
|
|
475
|
+
} = {}) {
|
|
476
|
+
return !collectCvOnly && !detailResult;
|
|
477
|
+
}
|
|
382
478
|
|
|
383
479
|
function createMissingLlmConfigResult() {
|
|
384
480
|
return createFailedLlmResult(new Error("LLM screening config is required for production chat runs"));
|
|
@@ -400,17 +496,81 @@ function createSkippedDetailResult(cardCandidate, reason, error = null) {
|
|
|
400
496
|
};
|
|
401
497
|
}
|
|
402
498
|
|
|
403
|
-
function compactChatRuntimeError(error) {
|
|
404
|
-
if (!error) return null;
|
|
405
|
-
return {
|
|
406
|
-
name: error.name || "Error",
|
|
407
|
-
code: error.code || null,
|
|
408
|
-
message: error.message || String(error),
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
499
|
+
function compactChatRuntimeError(error) {
|
|
500
|
+
if (!error) return null;
|
|
501
|
+
return {
|
|
502
|
+
name: error.name || "Error",
|
|
503
|
+
code: error.code || null,
|
|
504
|
+
message: error.message || String(error),
|
|
505
|
+
retryable: typeof error.retryable === "boolean" ? error.retryable : null,
|
|
506
|
+
attempts: Array.isArray(error.attempts) ? error.attempts : null,
|
|
507
|
+
close_result: error.close_result || null,
|
|
508
|
+
selection_ready_state: error.selection_ready_state || null,
|
|
509
|
+
page_state: error.page_state || null
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function captureChatFinalFailureArtifact(client, {
|
|
514
|
+
runControl,
|
|
515
|
+
imageOutputDir = "",
|
|
516
|
+
error = null
|
|
517
|
+
} = {}) {
|
|
518
|
+
if (!client || !imageOutputDir || !runControl?.runId) return null;
|
|
519
|
+
const artifact = {
|
|
520
|
+
schema_version: 1,
|
|
521
|
+
kind: "chat_final_failure_page",
|
|
522
|
+
captured_at: new Date().toISOString(),
|
|
523
|
+
run_id: runControl.runId,
|
|
524
|
+
error: compactChatRuntimeError(error),
|
|
525
|
+
page_state: null,
|
|
526
|
+
active_candidate_state: null,
|
|
527
|
+
conversation_ready_state: null,
|
|
528
|
+
screenshot: null,
|
|
529
|
+
screenshot_error: null
|
|
530
|
+
};
|
|
531
|
+
try {
|
|
532
|
+
artifact.page_state = await getChatTopLevelState(client);
|
|
533
|
+
} catch (pageError) {
|
|
534
|
+
artifact.page_state = {
|
|
535
|
+
error: pageError?.message || String(pageError)
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
artifact.active_candidate_state = await readChatActiveCandidateState(client);
|
|
540
|
+
} catch (activeCandidateError) {
|
|
541
|
+
artifact.active_candidate_state = {
|
|
542
|
+
error: activeCandidateError?.message || String(activeCandidateError)
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
artifact.conversation_ready_state = await readChatConversationReadyState(client);
|
|
547
|
+
} catch (conversationError) {
|
|
548
|
+
artifact.conversation_ready_state = {
|
|
549
|
+
error: conversationError?.message || String(conversationError)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
artifact.screenshot = await captureViewportScreenshot(client, {
|
|
554
|
+
filePath: imageEvidenceFilePath({
|
|
555
|
+
imageOutputDir,
|
|
556
|
+
domain: "chat-final-failure",
|
|
557
|
+
runId: runControl.runId,
|
|
558
|
+
index: 0,
|
|
559
|
+
extension: "jpg"
|
|
560
|
+
}),
|
|
561
|
+
format: "jpeg",
|
|
562
|
+
quality: 72,
|
|
563
|
+
metadata: {
|
|
564
|
+
domain: "chat",
|
|
565
|
+
run_id: runControl.runId,
|
|
566
|
+
reason: "final_failure"
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
} catch (screenshotError) {
|
|
570
|
+
artifact.screenshot_error = screenshotError?.message || String(screenshotError);
|
|
571
|
+
}
|
|
572
|
+
return artifact;
|
|
573
|
+
}
|
|
414
574
|
|
|
415
575
|
const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
|
|
416
576
|
const CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH = 180;
|
|
@@ -578,14 +738,15 @@ async function resolveFreshChatCardNodeId(client, {
|
|
|
578
738
|
return freshNodeId || fallbackNodeId;
|
|
579
739
|
}
|
|
580
740
|
|
|
581
|
-
async function selectFreshChatCandidate(client, {
|
|
582
|
-
cardNodeId,
|
|
583
|
-
candidate,
|
|
584
|
-
timeoutMs,
|
|
585
|
-
settleMs = 1200
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
741
|
+
async function selectFreshChatCandidate(client, {
|
|
742
|
+
cardNodeId,
|
|
743
|
+
candidate,
|
|
744
|
+
timeoutMs,
|
|
745
|
+
settleMs = 1200,
|
|
746
|
+
onlineResumeProbe = true
|
|
747
|
+
} = {}) {
|
|
748
|
+
let lastError = null;
|
|
749
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
589
750
|
const modalGuard = await ensureNoOpenChatResumeModalBeforeCandidateClick(client);
|
|
590
751
|
const rootState = await getChatRoots(client);
|
|
591
752
|
const freshNodeId = await resolveFreshChatCardNodeId(client, {
|
|
@@ -596,16 +757,18 @@ async function selectFreshChatCandidate(client, {
|
|
|
596
757
|
try {
|
|
597
758
|
await scrollNodeIntoView(client, freshNodeId);
|
|
598
759
|
await sleep(250);
|
|
599
|
-
const box = await getNodeBox(client, freshNodeId);
|
|
600
|
-
await clickPoint(client, box.center.x, box.center.y);
|
|
601
|
-
if (settleMs > 0) await sleep(settleMs);
|
|
602
|
-
const ready =
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
760
|
+
const box = await getNodeBox(client, freshNodeId);
|
|
761
|
+
await clickPoint(client, box.center.x, box.center.y);
|
|
762
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
763
|
+
const ready = onlineResumeProbe
|
|
764
|
+
? await waitForChatOnlineResumeButton(client, {
|
|
765
|
+
timeoutMs,
|
|
766
|
+
expectedCandidateId: candidate?.id || ""
|
|
767
|
+
})
|
|
768
|
+
: await readSelectedChatCandidateState(client, candidate);
|
|
769
|
+
return {
|
|
770
|
+
card_box: box,
|
|
771
|
+
ready,
|
|
609
772
|
card_node_id: freshNodeId,
|
|
610
773
|
refreshed_node: freshNodeId !== cardNodeId,
|
|
611
774
|
modal_guard: modalGuard,
|
|
@@ -617,8 +780,35 @@ async function selectFreshChatCandidate(client, {
|
|
|
617
780
|
await sleep(350);
|
|
618
781
|
}
|
|
619
782
|
}
|
|
620
|
-
throw lastError || new Error("Chat candidate selection failed");
|
|
621
|
-
}
|
|
783
|
+
throw lastError || new Error("Chat candidate selection failed");
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function readSelectedChatCandidateState(client, candidate = null) {
|
|
787
|
+
const topLevelState = await getChatTopLevelState(client);
|
|
788
|
+
if (topLevelState.is_forbidden_resume_top_level) {
|
|
789
|
+
return {
|
|
790
|
+
forbidden_top_level_navigation: true,
|
|
791
|
+
top_level_state: topLevelState
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const activeState = await readChatActiveCandidateState(client);
|
|
795
|
+
const expectedId = normalizeText(candidate?.id || "");
|
|
796
|
+
const activeCandidateId = normalizeText(activeState?.active_candidate?.candidate_id || "");
|
|
797
|
+
const candidateSelectionVerified = expectedId
|
|
798
|
+
? activeCandidateId === expectedId
|
|
799
|
+
: undefined;
|
|
800
|
+
return {
|
|
801
|
+
ok: !expectedId || candidateSelectionVerified === true,
|
|
802
|
+
reason: expectedId && candidateSelectionVerified !== true
|
|
803
|
+
? "active_candidate_mismatch"
|
|
804
|
+
: "online_resume_probe_skipped",
|
|
805
|
+
roots: activeState.roots,
|
|
806
|
+
activeCandidate: activeState.active_candidate,
|
|
807
|
+
expected_candidate_id: expectedId || null,
|
|
808
|
+
active_candidate_id: activeCandidateId || null,
|
|
809
|
+
candidate_selection_verified: candidateSelectionVerified
|
|
810
|
+
};
|
|
811
|
+
}
|
|
622
812
|
|
|
623
813
|
function selectedDetailNetworkEvents(detailSource, selectionEvents, resumeEvents) {
|
|
624
814
|
if (detailSource !== "network" && detailSource !== "cascade") return [];
|
|
@@ -760,15 +950,16 @@ export async function runChatWorkflow({
|
|
|
760
950
|
safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
|
|
761
951
|
actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
|
|
762
952
|
});
|
|
763
|
-
const humanRestController = createHumanRestController({
|
|
764
|
-
enabled: effectiveHumanRestEnabled,
|
|
765
|
-
shortRestEnabled: effectiveHumanBehavior.shortRest,
|
|
766
|
-
batchRestEnabled: effectiveHumanBehavior.batchRest,
|
|
767
|
-
restLevel: effectiveHumanBehavior.restLevel
|
|
768
|
-
});
|
|
769
|
-
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
770
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
771
|
-
const
|
|
953
|
+
const humanRestController = createHumanRestController({
|
|
954
|
+
enabled: effectiveHumanRestEnabled,
|
|
955
|
+
shortRestEnabled: effectiveHumanBehavior.shortRest,
|
|
956
|
+
batchRestEnabled: effectiveHumanBehavior.batchRest,
|
|
957
|
+
restLevel: effectiveHumanBehavior.restLevel
|
|
958
|
+
});
|
|
959
|
+
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
960
|
+
const normalizedScreeningMode = normalizeText(criteria) ? normalizeScreeningMode(screeningMode) : "collect_cv";
|
|
961
|
+
const collectCvOnly = normalizedScreeningMode === "collect_cv" || !normalizeText(criteria);
|
|
962
|
+
const useLlmScreening = normalizedScreeningMode === "llm" && !collectCvOnly;
|
|
772
963
|
const processedLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
773
964
|
const passTarget = Number.isFinite(Number(targetPassCount)) && Number(targetPassCount) > 0
|
|
774
965
|
? Number(targetPassCount)
|
|
@@ -1214,11 +1405,12 @@ export async function runChatWorkflow({
|
|
|
1214
1405
|
detailStep = "select_candidate";
|
|
1215
1406
|
networkRecorder.clear();
|
|
1216
1407
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1217
|
-
const selected = await measureTiming(timings, "candidate_click_ms", () => selectFreshChatCandidate(client, {
|
|
1218
|
-
cardNodeId,
|
|
1219
|
-
candidate: cardCandidate,
|
|
1220
|
-
timeoutMs: onlineResumeButtonTimeoutMs
|
|
1221
|
-
|
|
1408
|
+
const selected = await measureTiming(timings, "candidate_click_ms", () => selectFreshChatCandidate(client, {
|
|
1409
|
+
cardNodeId,
|
|
1410
|
+
candidate: cardCandidate,
|
|
1411
|
+
timeoutMs: onlineResumeButtonTimeoutMs,
|
|
1412
|
+
onlineResumeProbe: !collectCvOnly
|
|
1413
|
+
}));
|
|
1222
1414
|
if (selected.ready?.forbidden_top_level_navigation) {
|
|
1223
1415
|
throw makeForbiddenChatResumeNavigationError(selected.ready.top_level_state);
|
|
1224
1416
|
}
|
|
@@ -1238,11 +1430,11 @@ export async function runChatWorkflow({
|
|
|
1238
1430
|
detailResult.cv_acquisition.pre_detail_state = preActionState;
|
|
1239
1431
|
detailResult.cv_acquisition.selection_ready_state = selected.ready;
|
|
1240
1432
|
}
|
|
1241
|
-
if (!selected.ready?.ok) {
|
|
1242
|
-
if (detailResult) {
|
|
1243
|
-
// Already classified by the pre-detail conversation state.
|
|
1244
|
-
} else if (selected.ready?.reason === "active_candidate_mismatch") {
|
|
1245
|
-
throw makeChatCandidateSelectionMismatchError(selected, cardCandidate);
|
|
1433
|
+
if (!selected.ready?.ok) {
|
|
1434
|
+
if (detailResult) {
|
|
1435
|
+
// Already classified by the pre-detail conversation state.
|
|
1436
|
+
} else if (selected.ready?.reason === "active_candidate_mismatch") {
|
|
1437
|
+
throw makeChatCandidateSelectionMismatchError(selected, cardCandidate);
|
|
1246
1438
|
} else {
|
|
1247
1439
|
detailStep = "read_conversation_ready_state";
|
|
1248
1440
|
if (preActionState.attachment_resume_enabled) {
|
|
@@ -1252,13 +1444,18 @@ export async function runChatWorkflow({
|
|
|
1252
1444
|
} else {
|
|
1253
1445
|
detailUnavailableReason = "online_resume_button_unavailable";
|
|
1254
1446
|
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason);
|
|
1255
|
-
detailResult.cv_acquisition.pre_detail_state = preActionState;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1447
|
+
detailResult.cv_acquisition.pre_detail_state = preActionState;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
if (collectCvOnly && !detailResult) {
|
|
1452
|
+
detailUnavailableReason = preActionState?.has_online_resume
|
|
1453
|
+
? "collect_cv_request_candidate"
|
|
1454
|
+
: "collect_cv_missing_online_resume";
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (shouldOpenOnlineResumeForChatDetail({ collectCvOnly, detailResult })) {
|
|
1458
|
+
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1262
1459
|
let networkWait = null;
|
|
1263
1460
|
let contentWait = {
|
|
1264
1461
|
ok: false,
|
|
@@ -1688,11 +1885,11 @@ export async function runChatWorkflow({
|
|
|
1688
1885
|
recovery
|
|
1689
1886
|
});
|
|
1690
1887
|
continue;
|
|
1691
|
-
} else if (isChatCandidateSelectionMismatchError(error)) {
|
|
1692
|
-
const retryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1693
|
-
if (retryCount < 1) {
|
|
1694
|
-
candidateRecoveryCounts.set(candidateKey, retryCount + 1);
|
|
1695
|
-
const recovery = await recoverAndReapplyChatContext(
|
|
1888
|
+
} else if (isChatCandidateSelectionMismatchError(error)) {
|
|
1889
|
+
const retryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1890
|
+
if (retryCount < 1) {
|
|
1891
|
+
candidateRecoveryCounts.set(candidateKey, retryCount + 1);
|
|
1892
|
+
const recovery = await recoverAndReapplyChatContext(
|
|
1696
1893
|
"active_candidate_mismatch",
|
|
1697
1894
|
error,
|
|
1698
1895
|
{ forceRefresh: true }
|
|
@@ -1704,15 +1901,35 @@ export async function runChatWorkflow({
|
|
|
1704
1901
|
continue;
|
|
1705
1902
|
}
|
|
1706
1903
|
detailUnavailableReason = "active_candidate_mismatch";
|
|
1707
|
-
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1708
|
-
detailResult.cv_acquisition.selection_ready_state = error.selection_ready_state || null;
|
|
1709
|
-
detailResult.cv_acquisition.recovery_attempted = true;
|
|
1710
|
-
detailResult.cv_acquisition.recovery_attempt_count = retryCount;
|
|
1711
|
-
} else if (
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1904
|
+
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1905
|
+
detailResult.cv_acquisition.selection_ready_state = error.selection_ready_state || null;
|
|
1906
|
+
detailResult.cv_acquisition.recovery_attempted = true;
|
|
1907
|
+
detailResult.cv_acquisition.recovery_attempt_count = retryCount;
|
|
1908
|
+
} else if (isChatOnlineResumeModalOpenFailureError(error)) {
|
|
1909
|
+
const retryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1910
|
+
if (retryCount < 1) {
|
|
1911
|
+
candidateRecoveryCounts.set(candidateKey, retryCount + 1);
|
|
1912
|
+
const recovery = await recoverAndReapplyChatContext(
|
|
1913
|
+
"online_resume_modal_did_not_open",
|
|
1914
|
+
error,
|
|
1915
|
+
{ forceRefresh: true }
|
|
1916
|
+
);
|
|
1917
|
+
checkpointInProgressCandidate({
|
|
1918
|
+
event: "retry_after_online_resume_modal_open_failure",
|
|
1919
|
+
recovery
|
|
1920
|
+
});
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
detailUnavailableReason = "online_resume_modal_did_not_open";
|
|
1924
|
+
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1925
|
+
detailResult.cv_acquisition.attempts = error.attempts || null;
|
|
1926
|
+
detailResult.cv_acquisition.recovery_attempted = true;
|
|
1927
|
+
detailResult.cv_acquisition.recovery_attempt_count = retryCount;
|
|
1928
|
+
} else if (isUnsafeChatOnlineResumeLinkError(error)) {
|
|
1929
|
+
detailUnavailableReason = "unsafe_online_resume_navigation_link";
|
|
1930
|
+
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1931
|
+
detailResult.cv_acquisition.blocked_pre_click = true;
|
|
1932
|
+
detailResult.cv_acquisition.button_href = error.href || null;
|
|
1716
1933
|
detailResult.cv_acquisition.button_selector = error.button_selector || null;
|
|
1717
1934
|
detailResult.cv_acquisition.attempts = error.attempts || null;
|
|
1718
1935
|
} else {
|
|
@@ -1723,37 +1940,43 @@ export async function runChatWorkflow({
|
|
|
1723
1940
|
await closeChatBlockingPanels(client, { attemptsLimit: 2 });
|
|
1724
1941
|
}
|
|
1725
1942
|
}
|
|
1726
|
-
screeningCandidate = detailResult
|
|
1727
|
-
}
|
|
1943
|
+
screeningCandidate = detailResult?.candidate || cardCandidate;
|
|
1944
|
+
}
|
|
1728
1945
|
|
|
1729
1946
|
await runControl.waitIfPaused();
|
|
1730
1947
|
runControl.throwIfCanceled();
|
|
1731
1948
|
runControl.setPhase("chat:screening");
|
|
1732
1949
|
let cardOnlyLlmResult = null;
|
|
1733
|
-
if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
|
|
1734
|
-
detailUnavailableReason = detailResult
|
|
1735
|
-
? "full_cv_not_acquired"
|
|
1736
|
-
: "detail_not_opened_full_cv_required";
|
|
1737
|
-
}
|
|
1738
|
-
const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
|
|
1739
|
-
const screening =
|
|
1740
|
-
? {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1950
|
+
if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
|
|
1951
|
+
detailUnavailableReason = detailResult
|
|
1952
|
+
? "full_cv_not_acquired"
|
|
1953
|
+
: "detail_not_opened_full_cv_required";
|
|
1954
|
+
}
|
|
1955
|
+
const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
|
|
1956
|
+
const screening = collectCvOnly
|
|
1957
|
+
? createCvCollectionScreening(screeningCandidate, {
|
|
1958
|
+
detailResult,
|
|
1959
|
+
detailUnavailableReason,
|
|
1960
|
+
preActionState
|
|
1961
|
+
})
|
|
1962
|
+
: detailUnavailableReason
|
|
1963
|
+
? {
|
|
1964
|
+
status: "skip",
|
|
1965
|
+
passed: false,
|
|
1966
|
+
score: 0,
|
|
1967
|
+
reasons: [detailUnavailableReason],
|
|
1968
|
+
candidate: screeningCandidate
|
|
1969
|
+
}
|
|
1970
|
+
: useLlmScreening
|
|
1971
|
+
? llmToScreening(effectiveLlmResult, screeningCandidate)
|
|
1972
|
+
: screenCandidate(screeningCandidate, { criteria });
|
|
1750
1973
|
let postAction = null;
|
|
1751
1974
|
if (requestResumeForPassed && screening.passed) {
|
|
1752
1975
|
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1753
|
-
postAction = await measureTiming(timings, "post_action_ms", () => requestChatResumeForPassedCandidate(client, {
|
|
1754
|
-
greetingText,
|
|
1755
|
-
dryRun: dryRunRequestCv
|
|
1756
|
-
}));
|
|
1976
|
+
postAction = await measureTiming(timings, "post_action_ms", () => requestChatResumeForPassedCandidate(client, {
|
|
1977
|
+
greetingText,
|
|
1978
|
+
dryRun: dryRunRequestCv
|
|
1979
|
+
}));
|
|
1757
1980
|
if (postAction?.requested) requestSatisfiedCount += 1;
|
|
1758
1981
|
if (postAction?.skipped) requestSkippedCount += 1;
|
|
1759
1982
|
if (postAction?.requested && !postAction?.skipped) requestedCount += 1;
|
|
@@ -1950,8 +2173,8 @@ export function createChatRunService({
|
|
|
1950
2173
|
name = "chat-domain-run"
|
|
1951
2174
|
} = {}) {
|
|
1952
2175
|
if (!client) throw new Error("startChatRun requires a guarded CDP client");
|
|
1953
|
-
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
1954
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
2176
|
+
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
2177
|
+
const normalizedScreeningMode = normalizeText(criteria) ? normalizeScreeningMode(screeningMode) : "collect_cv";
|
|
1955
2178
|
const processedLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1956
2179
|
const normalizedDetailLimit = detailLimit == null ? processedLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1957
2180
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -1977,9 +2200,10 @@ export function createChatRunService({
|
|
|
1977
2200
|
dry_run_request_cv: Boolean(dryRunRequestCv),
|
|
1978
2201
|
greeting_text: greetingText,
|
|
1979
2202
|
cv_acquisition_mode: cvAcquisitionMode,
|
|
1980
|
-
call_llm_on_image: Boolean(callLlmOnImage),
|
|
1981
|
-
screening_mode: normalizedScreeningMode,
|
|
1982
|
-
|
|
2203
|
+
call_llm_on_image: Boolean(callLlmOnImage),
|
|
2204
|
+
screening_mode: normalizedScreeningMode,
|
|
2205
|
+
cv_collection_mode: normalizedScreeningMode === "collect_cv",
|
|
2206
|
+
llm_configured: Boolean(llmConfig),
|
|
1983
2207
|
llm_timeout_ms: llmTimeoutMs,
|
|
1984
2208
|
llm_image_limit: llmImageLimit,
|
|
1985
2209
|
llm_image_detail: llmImageDetail,
|
|
@@ -1994,10 +2218,11 @@ export function createChatRunService({
|
|
|
1994
2218
|
image_output_dir: imageOutputDir || "",
|
|
1995
2219
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1996
2220
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1997
|
-
human_behavior: effectiveHumanBehavior,
|
|
1998
|
-
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1999
|
-
human_rest_enabled: effectiveHumanRestEnabled
|
|
2000
|
-
|
|
2221
|
+
human_behavior: effectiveHumanBehavior,
|
|
2222
|
+
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
2223
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
2224
|
+
cv_collection_mode: normalizedScreeningMode === "collect_cv"
|
|
2225
|
+
},
|
|
2001
2226
|
progress: {
|
|
2002
2227
|
card_count: 0,
|
|
2003
2228
|
target_count: targetPassCount || (processUntilListEnd ? "all" : processedLimit),
|
|
@@ -2021,47 +2246,64 @@ export function createChatRunService({
|
|
|
2021
2246
|
human_rest_ms: 0,
|
|
2022
2247
|
last_human_event: null
|
|
2023
2248
|
},
|
|
2024
|
-
checkpoint: {},
|
|
2025
|
-
task: (runControl) =>
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2249
|
+
checkpoint: {},
|
|
2250
|
+
task: async (runControl) => {
|
|
2251
|
+
try {
|
|
2252
|
+
return await workflow({
|
|
2253
|
+
client,
|
|
2254
|
+
targetUrl,
|
|
2255
|
+
job,
|
|
2256
|
+
startFrom,
|
|
2257
|
+
criteria,
|
|
2258
|
+
maxCandidates,
|
|
2259
|
+
targetPassCount,
|
|
2260
|
+
processUntilListEnd,
|
|
2261
|
+
detailLimit: normalizedDetailLimit,
|
|
2262
|
+
detailSource: normalizedDetailSource,
|
|
2263
|
+
closeResume,
|
|
2264
|
+
requestResumeForPassed,
|
|
2265
|
+
dryRunRequestCv,
|
|
2266
|
+
greetingText,
|
|
2267
|
+
delayMs,
|
|
2268
|
+
cardTimeoutMs,
|
|
2269
|
+
readyTimeoutMs,
|
|
2270
|
+
onlineResumeButtonTimeoutMs,
|
|
2271
|
+
resumeDomTimeoutMs,
|
|
2272
|
+
maxImagePages,
|
|
2273
|
+
imageWheelDeltaY,
|
|
2274
|
+
cvAcquisitionMode,
|
|
2275
|
+
callLlmOnImage,
|
|
2276
|
+
llmConfig,
|
|
2277
|
+
llmTimeoutMs,
|
|
2278
|
+
llmImageLimit,
|
|
2279
|
+
llmImageDetail,
|
|
2280
|
+
screeningMode: normalizedScreeningMode,
|
|
2281
|
+
listMaxScrolls,
|
|
2282
|
+
listStableSignatureLimit,
|
|
2283
|
+
listWheelDeltaY,
|
|
2284
|
+
listSettleMs,
|
|
2285
|
+
listFallbackPoint,
|
|
2286
|
+
imageOutputDir,
|
|
2287
|
+
humanRestEnabled: effectiveHumanRestEnabled,
|
|
2288
|
+
humanBehavior: effectiveHumanBehavior
|
|
2289
|
+
}, runControl);
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
if (error instanceof RunCanceledError) throw error;
|
|
2292
|
+
const finalFailureArtifact = await captureChatFinalFailureArtifact(client, {
|
|
2293
|
+
runControl,
|
|
2294
|
+
imageOutputDir,
|
|
2295
|
+
error
|
|
2296
|
+
});
|
|
2297
|
+
if (finalFailureArtifact) {
|
|
2298
|
+
runControl.checkpoint({
|
|
2299
|
+
final_failure_artifact: finalFailureArtifact
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
throw error;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2065
2307
|
|
|
2066
2308
|
return {
|
|
2067
2309
|
startChatRun,
|