@reconcrap/boss-recommend-mcp 2.1.13 → 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 +17 -6
- package/config/screening-config.example.json +5 -0
- 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/chat-runtime-config.js +26 -0
- package/src/core/reporting/legacy-csv.js +9 -1
- package/src/core/screening/index.js +351 -20
- package/src/domains/chat/detail.js +79 -47
- package/src/domains/chat/run-service.js +456 -185
- package/src/domains/recommend/run-service.js +11 -3
- package/src/domains/recruit/constants.js +65 -0
- package/src/domains/recruit/instruction-parser.js +362 -86
- package/src/domains/recruit/run-service.js +289 -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,
|
|
@@ -40,6 +46,8 @@ import {
|
|
|
40
46
|
} from "../../core/run/timing.js";
|
|
41
47
|
import {
|
|
42
48
|
callScreeningLlm,
|
|
49
|
+
createFatalLlmRunError,
|
|
50
|
+
isFatalLlmProviderError,
|
|
43
51
|
normalizeText,
|
|
44
52
|
screenCandidate
|
|
45
53
|
} from "../../core/screening/index.js";
|
|
@@ -59,12 +67,14 @@ import {
|
|
|
59
67
|
closeChatBlockingPanels,
|
|
60
68
|
closeChatResumeModal,
|
|
61
69
|
createChatProfileNetworkRecorder,
|
|
62
|
-
extractChatProfileCandidate,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
extractChatProfileCandidate,
|
|
71
|
+
isChatOnlineResumeModalOpenFailureError,
|
|
72
|
+
isUnsafeChatOnlineResumeLinkError,
|
|
73
|
+
openChatOnlineResume,
|
|
74
|
+
quickChatResumeModalOpenProbe,
|
|
75
|
+
readChatActiveCandidateState,
|
|
76
|
+
readChatConversationReadyState,
|
|
77
|
+
requestChatResumeForPassedCandidate,
|
|
68
78
|
selectChatMessageFilter,
|
|
69
79
|
selectChatPrimaryLabel,
|
|
70
80
|
waitForChatOnlineResumeButton,
|
|
@@ -108,19 +118,31 @@ function compactLlmResult(llmResult) {
|
|
|
108
118
|
ok: Boolean(llmResult.ok),
|
|
109
119
|
provider: llmResult.provider || null,
|
|
110
120
|
passed: llmResult.passed,
|
|
121
|
+
review_required: typeof llmResult.review_required === "boolean" ? llmResult.review_required : null,
|
|
111
122
|
cot: llmResult.cot || llmResult.decision_cot || "",
|
|
112
|
-
reasoning_content: llmResult.reasoning_content || "",
|
|
113
|
-
raw_model_output: llmResult.raw_model_output || "",
|
|
114
|
-
evidence_count: llmResult.evidence?.length || 0,
|
|
115
|
-
usage: llmResult.usage || null,
|
|
123
|
+
reasoning_content: llmResult.reasoning_content || "",
|
|
124
|
+
raw_model_output: llmResult.raw_model_output || "",
|
|
125
|
+
evidence_count: llmResult.evidence?.length || 0,
|
|
126
|
+
usage: llmResult.usage || null,
|
|
116
127
|
finish_reason: llmResult.finish_reason || null,
|
|
117
|
-
image_input_count: llmResult.image_input_count || 0,
|
|
118
|
-
attempt_count: llmResult.attempt_count || 0,
|
|
119
|
-
fallback_count: llmResult.fallback_count || 0,
|
|
120
|
-
llm_model_failures: Array.isArray(llmResult.llm_model_failures) ? llmResult.llm_model_failures : [],
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
image_input_count: llmResult.image_input_count || 0,
|
|
129
|
+
attempt_count: llmResult.attempt_count || 0,
|
|
130
|
+
fallback_count: llmResult.fallback_count || 0,
|
|
131
|
+
llm_model_failures: Array.isArray(llmResult.llm_model_failures) ? llmResult.llm_model_failures : [],
|
|
132
|
+
screening_strategy: llmResult.screening_strategy || "",
|
|
133
|
+
fast_thinking_level: llmResult.fast_thinking_level || "",
|
|
134
|
+
verify_thinking_level: llmResult.verify_thinking_level || "",
|
|
135
|
+
verified: typeof llmResult.verified === "boolean" ? llmResult.verified : null,
|
|
136
|
+
verification_reason: llmResult.verification_reason || "",
|
|
137
|
+
decision_source: llmResult.decision_source || "",
|
|
138
|
+
fast_result: llmResult.fast_result || null,
|
|
139
|
+
verify_result: llmResult.verify_result || null,
|
|
140
|
+
error_code: llmResult.error_code || null,
|
|
141
|
+
fatal: Boolean(llmResult.fatal),
|
|
142
|
+
fatal_reason: llmResult.fatal_reason || "",
|
|
143
|
+
error: llmResult.error || null
|
|
144
|
+
};
|
|
145
|
+
}
|
|
124
146
|
|
|
125
147
|
function compactCandidate(candidate) {
|
|
126
148
|
return {
|
|
@@ -339,9 +361,9 @@ function isRecoverableLlmScreeningError(error) {
|
|
|
339
361
|
}
|
|
340
362
|
|
|
341
363
|
function createFailedLlmResult(error) {
|
|
342
|
-
return {
|
|
343
|
-
ok: false,
|
|
344
|
-
passed: false,
|
|
364
|
+
return {
|
|
365
|
+
ok: false,
|
|
366
|
+
passed: false,
|
|
345
367
|
reason: "",
|
|
346
368
|
evidence: [],
|
|
347
369
|
cot: "",
|
|
@@ -349,19 +371,110 @@ function createFailedLlmResult(error) {
|
|
|
349
371
|
reasoning_content: "",
|
|
350
372
|
raw_model_output: "",
|
|
351
373
|
attempt_count: Number(error?.llm_attempt_count) || 0,
|
|
352
|
-
fallback_count: Array.isArray(error?.llm_model_failures) ? error.llm_model_failures.length : 0,
|
|
353
|
-
llm_model_failures: Array.isArray(error?.llm_model_failures) ? error.llm_model_failures : [],
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
374
|
+
fallback_count: Array.isArray(error?.llm_model_failures) ? error.llm_model_failures.length : 0,
|
|
375
|
+
llm_model_failures: Array.isArray(error?.llm_model_failures) ? error.llm_model_failures : [],
|
|
376
|
+
error_code: error?.code || null,
|
|
377
|
+
fatal: Boolean(isFatalLlmProviderError(error)),
|
|
378
|
+
fatal_reason: error?.llm_fatal_reason || "",
|
|
379
|
+
error: error?.message || String(error || "unknown"),
|
|
380
|
+
screened_at: new Date().toISOString()
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
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
|
+
}
|
|
365
478
|
|
|
366
479
|
function createMissingLlmConfigResult() {
|
|
367
480
|
return createFailedLlmResult(new Error("LLM screening config is required for production chat runs"));
|
|
@@ -383,17 +496,81 @@ function createSkippedDetailResult(cardCandidate, reason, error = null) {
|
|
|
383
496
|
};
|
|
384
497
|
}
|
|
385
498
|
|
|
386
|
-
function compactChatRuntimeError(error) {
|
|
387
|
-
if (!error) return null;
|
|
388
|
-
return {
|
|
389
|
-
name: error.name || "Error",
|
|
390
|
-
code: error.code || null,
|
|
391
|
-
message: error.message || String(error),
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
+
}
|
|
397
574
|
|
|
398
575
|
const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
|
|
399
576
|
const CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH = 180;
|
|
@@ -561,14 +738,15 @@ async function resolveFreshChatCardNodeId(client, {
|
|
|
561
738
|
return freshNodeId || fallbackNodeId;
|
|
562
739
|
}
|
|
563
740
|
|
|
564
|
-
async function selectFreshChatCandidate(client, {
|
|
565
|
-
cardNodeId,
|
|
566
|
-
candidate,
|
|
567
|
-
timeoutMs,
|
|
568
|
-
settleMs = 1200
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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) {
|
|
572
750
|
const modalGuard = await ensureNoOpenChatResumeModalBeforeCandidateClick(client);
|
|
573
751
|
const rootState = await getChatRoots(client);
|
|
574
752
|
const freshNodeId = await resolveFreshChatCardNodeId(client, {
|
|
@@ -579,16 +757,18 @@ async function selectFreshChatCandidate(client, {
|
|
|
579
757
|
try {
|
|
580
758
|
await scrollNodeIntoView(client, freshNodeId);
|
|
581
759
|
await sleep(250);
|
|
582
|
-
const box = await getNodeBox(client, freshNodeId);
|
|
583
|
-
await clickPoint(client, box.center.x, box.center.y);
|
|
584
|
-
if (settleMs > 0) await sleep(settleMs);
|
|
585
|
-
const ready =
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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,
|
|
592
772
|
card_node_id: freshNodeId,
|
|
593
773
|
refreshed_node: freshNodeId !== cardNodeId,
|
|
594
774
|
modal_guard: modalGuard,
|
|
@@ -600,8 +780,35 @@ async function selectFreshChatCandidate(client, {
|
|
|
600
780
|
await sleep(350);
|
|
601
781
|
}
|
|
602
782
|
}
|
|
603
|
-
throw lastError || new Error("Chat candidate selection failed");
|
|
604
|
-
}
|
|
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
|
+
}
|
|
605
812
|
|
|
606
813
|
function selectedDetailNetworkEvents(detailSource, selectionEvents, resumeEvents) {
|
|
607
814
|
if (detailSource !== "network" && detailSource !== "cascade") return [];
|
|
@@ -743,15 +950,16 @@ export async function runChatWorkflow({
|
|
|
743
950
|
safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
|
|
744
951
|
actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
|
|
745
952
|
});
|
|
746
|
-
const humanRestController = createHumanRestController({
|
|
747
|
-
enabled: effectiveHumanRestEnabled,
|
|
748
|
-
shortRestEnabled: effectiveHumanBehavior.shortRest,
|
|
749
|
-
batchRestEnabled: effectiveHumanBehavior.batchRest,
|
|
750
|
-
restLevel: effectiveHumanBehavior.restLevel
|
|
751
|
-
});
|
|
752
|
-
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
753
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
754
|
-
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;
|
|
755
963
|
const processedLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
756
964
|
const passTarget = Number.isFinite(Number(targetPassCount)) && Number(targetPassCount) > 0
|
|
757
965
|
? Number(targetPassCount)
|
|
@@ -1197,11 +1405,12 @@ export async function runChatWorkflow({
|
|
|
1197
1405
|
detailStep = "select_candidate";
|
|
1198
1406
|
networkRecorder.clear();
|
|
1199
1407
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1200
|
-
const selected = await measureTiming(timings, "candidate_click_ms", () => selectFreshChatCandidate(client, {
|
|
1201
|
-
cardNodeId,
|
|
1202
|
-
candidate: cardCandidate,
|
|
1203
|
-
timeoutMs: onlineResumeButtonTimeoutMs
|
|
1204
|
-
|
|
1408
|
+
const selected = await measureTiming(timings, "candidate_click_ms", () => selectFreshChatCandidate(client, {
|
|
1409
|
+
cardNodeId,
|
|
1410
|
+
candidate: cardCandidate,
|
|
1411
|
+
timeoutMs: onlineResumeButtonTimeoutMs,
|
|
1412
|
+
onlineResumeProbe: !collectCvOnly
|
|
1413
|
+
}));
|
|
1205
1414
|
if (selected.ready?.forbidden_top_level_navigation) {
|
|
1206
1415
|
throw makeForbiddenChatResumeNavigationError(selected.ready.top_level_state);
|
|
1207
1416
|
}
|
|
@@ -1221,11 +1430,11 @@ export async function runChatWorkflow({
|
|
|
1221
1430
|
detailResult.cv_acquisition.pre_detail_state = preActionState;
|
|
1222
1431
|
detailResult.cv_acquisition.selection_ready_state = selected.ready;
|
|
1223
1432
|
}
|
|
1224
|
-
if (!selected.ready?.ok) {
|
|
1225
|
-
if (detailResult) {
|
|
1226
|
-
// Already classified by the pre-detail conversation state.
|
|
1227
|
-
} else if (selected.ready?.reason === "active_candidate_mismatch") {
|
|
1228
|
-
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);
|
|
1229
1438
|
} else {
|
|
1230
1439
|
detailStep = "read_conversation_ready_state";
|
|
1231
1440
|
if (preActionState.attachment_resume_enabled) {
|
|
@@ -1235,13 +1444,18 @@ export async function runChatWorkflow({
|
|
|
1235
1444
|
} else {
|
|
1236
1445
|
detailUnavailableReason = "online_resume_button_unavailable";
|
|
1237
1446
|
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason);
|
|
1238
|
-
detailResult.cv_acquisition.pre_detail_state = preActionState;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
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);
|
|
1245
1459
|
let networkWait = null;
|
|
1246
1460
|
let contentWait = {
|
|
1247
1461
|
ok: false,
|
|
@@ -1510,9 +1724,15 @@ export async function runChatWorkflow({
|
|
|
1510
1724
|
maxImages: llmImageLimit,
|
|
1511
1725
|
imageDetail: llmImageDetail
|
|
1512
1726
|
}));
|
|
1513
|
-
} catch (error) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1727
|
+
} catch (error) {
|
|
1728
|
+
if (isFatalLlmProviderError(error)) {
|
|
1729
|
+
throw createFatalLlmRunError(error, {
|
|
1730
|
+
domain: "chat",
|
|
1731
|
+
candidate: detailResult.candidate
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
llmResult = createFailedLlmResult(error);
|
|
1735
|
+
}
|
|
1516
1736
|
}
|
|
1517
1737
|
}
|
|
1518
1738
|
} else {
|
|
@@ -1583,9 +1803,15 @@ export async function runChatWorkflow({
|
|
|
1583
1803
|
maxImages: llmImageLimit,
|
|
1584
1804
|
imageDetail: llmImageDetail
|
|
1585
1805
|
}));
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
|
|
1588
|
-
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
if (isFatalLlmProviderError(error)) {
|
|
1808
|
+
throw createFatalLlmRunError(error, {
|
|
1809
|
+
domain: "chat",
|
|
1810
|
+
candidate: detailResult.candidate
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
llmResult = createFailedLlmResult(error);
|
|
1814
|
+
}
|
|
1589
1815
|
}
|
|
1590
1816
|
}
|
|
1591
1817
|
}
|
|
@@ -1659,11 +1885,11 @@ export async function runChatWorkflow({
|
|
|
1659
1885
|
recovery
|
|
1660
1886
|
});
|
|
1661
1887
|
continue;
|
|
1662
|
-
} else if (isChatCandidateSelectionMismatchError(error)) {
|
|
1663
|
-
const retryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1664
|
-
if (retryCount < 1) {
|
|
1665
|
-
candidateRecoveryCounts.set(candidateKey, retryCount + 1);
|
|
1666
|
-
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(
|
|
1667
1893
|
"active_candidate_mismatch",
|
|
1668
1894
|
error,
|
|
1669
1895
|
{ forceRefresh: true }
|
|
@@ -1675,15 +1901,35 @@ export async function runChatWorkflow({
|
|
|
1675
1901
|
continue;
|
|
1676
1902
|
}
|
|
1677
1903
|
detailUnavailableReason = "active_candidate_mismatch";
|
|
1678
|
-
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1679
|
-
detailResult.cv_acquisition.selection_ready_state = error.selection_ready_state || null;
|
|
1680
|
-
detailResult.cv_acquisition.recovery_attempted = true;
|
|
1681
|
-
detailResult.cv_acquisition.recovery_attempt_count = retryCount;
|
|
1682
|
-
} else if (
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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;
|
|
1687
1933
|
detailResult.cv_acquisition.button_selector = error.button_selector || null;
|
|
1688
1934
|
detailResult.cv_acquisition.attempts = error.attempts || null;
|
|
1689
1935
|
} else {
|
|
@@ -1694,37 +1940,43 @@ export async function runChatWorkflow({
|
|
|
1694
1940
|
await closeChatBlockingPanels(client, { attemptsLimit: 2 });
|
|
1695
1941
|
}
|
|
1696
1942
|
}
|
|
1697
|
-
screeningCandidate = detailResult
|
|
1698
|
-
}
|
|
1943
|
+
screeningCandidate = detailResult?.candidate || cardCandidate;
|
|
1944
|
+
}
|
|
1699
1945
|
|
|
1700
1946
|
await runControl.waitIfPaused();
|
|
1701
1947
|
runControl.throwIfCanceled();
|
|
1702
1948
|
runControl.setPhase("chat:screening");
|
|
1703
1949
|
let cardOnlyLlmResult = null;
|
|
1704
|
-
if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
|
|
1705
|
-
detailUnavailableReason = detailResult
|
|
1706
|
-
? "full_cv_not_acquired"
|
|
1707
|
-
: "detail_not_opened_full_cv_required";
|
|
1708
|
-
}
|
|
1709
|
-
const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
|
|
1710
|
-
const screening =
|
|
1711
|
-
? {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
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 });
|
|
1721
1973
|
let postAction = null;
|
|
1722
1974
|
if (requestResumeForPassed && screening.passed) {
|
|
1723
1975
|
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1724
|
-
postAction = await measureTiming(timings, "post_action_ms", () => requestChatResumeForPassedCandidate(client, {
|
|
1725
|
-
greetingText,
|
|
1726
|
-
dryRun: dryRunRequestCv
|
|
1727
|
-
}));
|
|
1976
|
+
postAction = await measureTiming(timings, "post_action_ms", () => requestChatResumeForPassedCandidate(client, {
|
|
1977
|
+
greetingText,
|
|
1978
|
+
dryRun: dryRunRequestCv
|
|
1979
|
+
}));
|
|
1728
1980
|
if (postAction?.requested) requestSatisfiedCount += 1;
|
|
1729
1981
|
if (postAction?.skipped) requestSkippedCount += 1;
|
|
1730
1982
|
if (postAction?.requested && !postAction?.skipped) requestedCount += 1;
|
|
@@ -1921,8 +2173,8 @@ export function createChatRunService({
|
|
|
1921
2173
|
name = "chat-domain-run"
|
|
1922
2174
|
} = {}) {
|
|
1923
2175
|
if (!client) throw new Error("startChatRun requires a guarded CDP client");
|
|
1924
|
-
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
1925
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
2176
|
+
const normalizedDetailSource = normalizeDetailSource(detailSource);
|
|
2177
|
+
const normalizedScreeningMode = normalizeText(criteria) ? normalizeScreeningMode(screeningMode) : "collect_cv";
|
|
1926
2178
|
const processedLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1927
2179
|
const normalizedDetailLimit = detailLimit == null ? processedLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1928
2180
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -1948,9 +2200,10 @@ export function createChatRunService({
|
|
|
1948
2200
|
dry_run_request_cv: Boolean(dryRunRequestCv),
|
|
1949
2201
|
greeting_text: greetingText,
|
|
1950
2202
|
cv_acquisition_mode: cvAcquisitionMode,
|
|
1951
|
-
call_llm_on_image: Boolean(callLlmOnImage),
|
|
1952
|
-
screening_mode: normalizedScreeningMode,
|
|
1953
|
-
|
|
2203
|
+
call_llm_on_image: Boolean(callLlmOnImage),
|
|
2204
|
+
screening_mode: normalizedScreeningMode,
|
|
2205
|
+
cv_collection_mode: normalizedScreeningMode === "collect_cv",
|
|
2206
|
+
llm_configured: Boolean(llmConfig),
|
|
1954
2207
|
llm_timeout_ms: llmTimeoutMs,
|
|
1955
2208
|
llm_image_limit: llmImageLimit,
|
|
1956
2209
|
llm_image_detail: llmImageDetail,
|
|
@@ -1965,10 +2218,11 @@ export function createChatRunService({
|
|
|
1965
2218
|
image_output_dir: imageOutputDir || "",
|
|
1966
2219
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1967
2220
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1968
|
-
human_behavior: effectiveHumanBehavior,
|
|
1969
|
-
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1970
|
-
human_rest_enabled: effectiveHumanRestEnabled
|
|
1971
|
-
|
|
2221
|
+
human_behavior: effectiveHumanBehavior,
|
|
2222
|
+
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
2223
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
2224
|
+
cv_collection_mode: normalizedScreeningMode === "collect_cv"
|
|
2225
|
+
},
|
|
1972
2226
|
progress: {
|
|
1973
2227
|
card_count: 0,
|
|
1974
2228
|
target_count: targetPassCount || (processUntilListEnd ? "all" : processedLimit),
|
|
@@ -1992,47 +2246,64 @@ export function createChatRunService({
|
|
|
1992
2246
|
human_rest_ms: 0,
|
|
1993
2247
|
last_human_event: null
|
|
1994
2248
|
},
|
|
1995
|
-
checkpoint: {},
|
|
1996
|
-
task: (runControl) =>
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
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
|
+
}
|
|
2036
2307
|
|
|
2037
2308
|
return {
|
|
2038
2309
|
startChatRun,
|