@reconcrap/boss-recommend-mcp 2.0.42 → 2.0.44
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 +7 -1
- package/config/screening-config.example.json +10 -0
- package/package.json +1 -2
- package/src/chat-mcp.js +4 -0
- package/src/chat-runtime-config.js +85 -1
- package/src/cli.js +13 -1
- package/src/core/browser/index.js +651 -4
- package/src/core/capture/index.js +118 -14
- package/src/core/infinite-list/index.js +122 -8
- package/src/domains/chat/run-service.js +121 -5
- package/src/domains/recommend/detail.js +237 -41
- package/src/domains/recommend/run-service.js +225 -106
- package/src/domains/recruit/run-service.js +108 -4
- package/src/index.js +58 -0
- package/src/recommend-mcp.js +22 -18
- package/src/recruit-mcp.js +44 -0
- package/scripts/live-recommend-recovery-smoke.js +0 -305
|
@@ -8,7 +8,13 @@ import {
|
|
|
8
8
|
} from "../../core/run/timing.js";
|
|
9
9
|
import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
|
|
10
10
|
import { waitForCvCaptureTarget } from "../../core/cv-capture-target/index.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
configureHumanInteraction,
|
|
13
|
+
createHumanRestController,
|
|
14
|
+
humanDelay,
|
|
15
|
+
normalizeHumanBehaviorOptions,
|
|
16
|
+
sleep
|
|
17
|
+
} from "../../core/browser/index.js";
|
|
12
18
|
import { GREET_CREDITS_EXHAUSTED_CODE } from "../../core/greet-quota/index.js";
|
|
13
19
|
import {
|
|
14
20
|
compactCvAcquisitionState,
|
|
@@ -420,17 +426,31 @@ export function countRecommendResultStatuses(results = [], {
|
|
|
420
426
|
};
|
|
421
427
|
}
|
|
422
428
|
|
|
423
|
-
function countPassedResults(results = []) {
|
|
424
|
-
return countRecommendResultStatuses(results).passed;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function
|
|
428
|
-
if (!
|
|
429
|
-
return {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
429
|
+
function countPassedResults(results = []) {
|
|
430
|
+
return countRecommendResultStatuses(results).passed;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function compactCloseResult(closeResult) {
|
|
434
|
+
if (!closeResult) return null;
|
|
435
|
+
return {
|
|
436
|
+
closed: Boolean(closeResult.closed),
|
|
437
|
+
reason: closeResult.reason || null,
|
|
438
|
+
attempts: closeResult.attempts || [],
|
|
439
|
+
verification: closeResult.verification || null
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
444
|
+
if (!error) return null;
|
|
445
|
+
const result = {
|
|
446
|
+
code: error.code || fallbackCode,
|
|
447
|
+
message: error.message || String(error)
|
|
448
|
+
};
|
|
449
|
+
if (error.close_result) {
|
|
450
|
+
result.close_result = compactCloseResult(error.close_result);
|
|
451
|
+
}
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
434
454
|
|
|
435
455
|
function createRecommendCloseFailureError(closeResult) {
|
|
436
456
|
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
@@ -554,13 +574,31 @@ export async function runRecommendWorkflow({
|
|
|
554
574
|
actionAfterClickDelayMs = 900,
|
|
555
575
|
screeningMode = "llm",
|
|
556
576
|
llmConfig = null,
|
|
557
|
-
llmTimeoutMs = 120000,
|
|
558
|
-
llmImageLimit = 8,
|
|
559
|
-
llmImageDetail = "high",
|
|
560
|
-
imageOutputDir = ""
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
577
|
+
llmTimeoutMs = 120000,
|
|
578
|
+
llmImageLimit = 8,
|
|
579
|
+
llmImageDetail = "high",
|
|
580
|
+
imageOutputDir = "",
|
|
581
|
+
humanRestEnabled = false,
|
|
582
|
+
humanBehavior = null
|
|
583
|
+
} = {}, runControl) {
|
|
584
|
+
if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
|
|
585
|
+
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
586
|
+
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
587
|
+
});
|
|
588
|
+
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
589
|
+
configureHumanInteraction(client, {
|
|
590
|
+
enabled: effectiveHumanBehavior.enabled,
|
|
591
|
+
clickMovementEnabled: effectiveHumanBehavior.clickMovement,
|
|
592
|
+
textEntryEnabled: effectiveHumanBehavior.textEntry,
|
|
593
|
+
safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
|
|
594
|
+
actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
|
|
595
|
+
});
|
|
596
|
+
const humanRestController = createHumanRestController({
|
|
597
|
+
enabled: effectiveHumanRestEnabled,
|
|
598
|
+
shortRestEnabled: effectiveHumanBehavior.shortRest,
|
|
599
|
+
batchRestEnabled: effectiveHumanBehavior.batchRest
|
|
600
|
+
});
|
|
601
|
+
const normalizedFilter = normalizeFilter(filter);
|
|
564
602
|
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
565
603
|
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
566
604
|
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
@@ -597,26 +635,54 @@ export async function runRecommendWorkflow({
|
|
|
597
635
|
let greetCount = 0;
|
|
598
636
|
const candidateRecoveryCounts = new Map();
|
|
599
637
|
let jobSelection = null;
|
|
600
|
-
let pageScopeSelection = null;
|
|
601
|
-
let filterResult = null;
|
|
602
|
-
let cardNodeIds = [];
|
|
603
|
-
let listEndReason = "";
|
|
604
|
-
|
|
638
|
+
let pageScopeSelection = null;
|
|
639
|
+
let filterResult = null;
|
|
640
|
+
let cardNodeIds = [];
|
|
641
|
+
let listEndReason = "";
|
|
642
|
+
let lastHumanEvent = null;
|
|
643
|
+
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
605
644
|
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
606
645
|
containerSelectors: RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
607
646
|
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
608
647
|
itemSelectors: [RECOMMEND_CARD_SELECTOR],
|
|
609
648
|
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
610
649
|
validateViewportPoint: true
|
|
611
|
-
}));
|
|
612
|
-
|
|
613
|
-
function
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
650
|
+
}));
|
|
651
|
+
|
|
652
|
+
function recordHumanEvent(event = null) {
|
|
653
|
+
if (!event) return lastHumanEvent;
|
|
654
|
+
lastHumanEvent = {
|
|
655
|
+
at: new Date().toISOString(),
|
|
656
|
+
...event
|
|
657
|
+
};
|
|
658
|
+
return lastHumanEvent;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function maybeHumanActionCooldown(phase, timings = {}) {
|
|
662
|
+
if (!effectiveHumanBehavior.actionCooldown) return null;
|
|
663
|
+
const pauseMs = humanDelay(280, 90, {
|
|
664
|
+
minMs: 80,
|
|
665
|
+
maxMs: 720
|
|
666
|
+
});
|
|
667
|
+
if (pauseMs > 0) {
|
|
668
|
+
await runControl.sleep(pauseMs);
|
|
669
|
+
addTiming(timings, `human_${phase}_pause_ms`, pauseMs);
|
|
670
|
+
}
|
|
671
|
+
return recordHumanEvent({
|
|
672
|
+
kind: "action_cooldown",
|
|
673
|
+
phase,
|
|
674
|
+
pause_ms: pauseMs
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function updateRecommendProgress(extra = {}) {
|
|
679
|
+
const counts = countRecommendResultStatuses(results, { greetCount });
|
|
680
|
+
const listSnapshot = compactInfiniteListState(listState);
|
|
681
|
+
const humanRestState = humanRestController.getState();
|
|
682
|
+
runControl.updateProgress({
|
|
683
|
+
card_count: cardNodeIds.length,
|
|
684
|
+
target_count: targetPassCount,
|
|
685
|
+
target_count_semantics: "passed_candidates",
|
|
620
686
|
...counts,
|
|
621
687
|
screening_mode: normalizedScreeningMode,
|
|
622
688
|
unique_seen: listSnapshot.seen_count,
|
|
@@ -624,12 +690,18 @@ export async function runRecommendWorkflow({
|
|
|
624
690
|
refresh_rounds: refreshRounds,
|
|
625
691
|
refresh_attempts: refreshAttempts.length,
|
|
626
692
|
context_recoveries: contextRecoveryAttempts,
|
|
627
|
-
list_end_reason: listEndReason || null,
|
|
628
|
-
viewport_checks: viewportGuard.getStats().checks,
|
|
629
|
-
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
693
|
+
list_end_reason: listEndReason || null,
|
|
694
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
695
|
+
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
696
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
697
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
698
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
699
|
+
human_rest_count: humanRestState.rest_count,
|
|
700
|
+
human_rest_ms: humanRestState.total_rest_ms,
|
|
701
|
+
last_human_event: lastHumanEvent,
|
|
702
|
+
...extra
|
|
703
|
+
});
|
|
704
|
+
}
|
|
633
705
|
|
|
634
706
|
function checkpointInProgressCandidate({
|
|
635
707
|
index = results.length,
|
|
@@ -821,10 +893,11 @@ export async function runRecommendWorkflow({
|
|
|
821
893
|
client,
|
|
822
894
|
state: listState,
|
|
823
895
|
maxScrolls: listMaxScrolls,
|
|
824
|
-
stableSignatureLimit: listStableSignatureLimit,
|
|
825
|
-
wheelDeltaY: listWheelDeltaY,
|
|
826
|
-
settleMs: listSettleMs,
|
|
827
|
-
|
|
896
|
+
stableSignatureLimit: listStableSignatureLimit,
|
|
897
|
+
wheelDeltaY: listWheelDeltaY,
|
|
898
|
+
settleMs: listSettleMs,
|
|
899
|
+
listScrollJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
900
|
+
fallbackPoint: listFallbackResolver,
|
|
828
901
|
findNodeIds: async () => {
|
|
829
902
|
let currentRootState = await getRecommendRoots(client);
|
|
830
903
|
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
@@ -926,10 +999,11 @@ export async function runRecommendWorkflow({
|
|
|
926
999
|
runControl.setPhase("recommend:detail");
|
|
927
1000
|
detailStep = "ensure_viewport";
|
|
928
1001
|
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
929
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
930
|
-
detailStep = "open_detail";
|
|
931
|
-
networkRecorder.clear();
|
|
932
|
-
|
|
1002
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1003
|
+
detailStep = "open_detail";
|
|
1004
|
+
networkRecorder.clear();
|
|
1005
|
+
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1006
|
+
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
933
1007
|
cardNodeId,
|
|
934
1008
|
candidateKey,
|
|
935
1009
|
cardCandidate,
|
|
@@ -1008,11 +1082,12 @@ export async function runRecommendWorkflow({
|
|
|
1008
1082
|
resizeMaxWidth: 1100,
|
|
1009
1083
|
captureViewport: false,
|
|
1010
1084
|
padding: 0,
|
|
1011
|
-
maxScreenshots: maxImagePages,
|
|
1012
|
-
wheelDeltaY: imageWheelDeltaY,
|
|
1013
|
-
settleMs: 350,
|
|
1014
|
-
scrollMethod: "dom-anchor-fallback-input",
|
|
1015
|
-
|
|
1085
|
+
maxScreenshots: maxImagePages,
|
|
1086
|
+
wheelDeltaY: imageWheelDeltaY,
|
|
1087
|
+
settleMs: 350,
|
|
1088
|
+
scrollMethod: "dom-anchor-fallback-input",
|
|
1089
|
+
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
1090
|
+
stepTimeoutMs: 45000,
|
|
1016
1091
|
totalTimeoutMs: 90000,
|
|
1017
1092
|
duplicateStopCount: 1,
|
|
1018
1093
|
skipDuplicateScreenshots: true,
|
|
@@ -1146,9 +1221,10 @@ export async function runRecommendWorkflow({
|
|
|
1146
1221
|
if (postActionEnabled && detailResult) {
|
|
1147
1222
|
const postActionStarted = Date.now();
|
|
1148
1223
|
await runControl.waitIfPaused();
|
|
1149
|
-
runControl.throwIfCanceled();
|
|
1150
|
-
runControl.setPhase("recommend:post-action");
|
|
1151
|
-
|
|
1224
|
+
runControl.throwIfCanceled();
|
|
1225
|
+
runControl.setPhase("recommend:post-action");
|
|
1226
|
+
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1227
|
+
actionDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
1152
1228
|
timeoutMs: actionTimeoutMs,
|
|
1153
1229
|
intervalMs: actionIntervalMs,
|
|
1154
1230
|
requireAny: true
|
|
@@ -1167,10 +1243,11 @@ export async function runRecommendWorkflow({
|
|
|
1167
1243
|
greetCount += 1;
|
|
1168
1244
|
}
|
|
1169
1245
|
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1170
|
-
}
|
|
1171
|
-
if (detailResult && closeDetail) {
|
|
1172
|
-
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1173
|
-
|
|
1246
|
+
}
|
|
1247
|
+
if (detailResult && closeDetail) {
|
|
1248
|
+
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1249
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1250
|
+
if (!detailResult.close_result?.closed) {
|
|
1174
1251
|
const closeError = createRecommendCloseFailureError(detailResult.close_result);
|
|
1175
1252
|
const recovery = await recoverAndReapplyRecommendContext("detail_close_failed", closeError, {
|
|
1176
1253
|
forceRecentNotView: true
|
|
@@ -1239,16 +1316,37 @@ export async function runRecommendWorkflow({
|
|
|
1239
1316
|
});
|
|
1240
1317
|
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
1241
1318
|
|
|
1242
|
-
if (postActionResult?.stop_run) {
|
|
1243
|
-
listEndReason = postActionResult.reason || "post_action_stop";
|
|
1244
|
-
break;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
if (
|
|
1248
|
-
const
|
|
1249
|
-
await
|
|
1250
|
-
|
|
1251
|
-
|
|
1319
|
+
if (postActionResult?.stop_run) {
|
|
1320
|
+
listEndReason = postActionResult.reason || "post_action_stop";
|
|
1321
|
+
break;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
if (effectiveHumanRestEnabled) {
|
|
1325
|
+
const restStarted = Date.now();
|
|
1326
|
+
const restResult = await humanRestController.takeBreakIfNeeded({
|
|
1327
|
+
sleepFn: (ms) => runControl.sleep(ms)
|
|
1328
|
+
});
|
|
1329
|
+
const restElapsed = Date.now() - restStarted;
|
|
1330
|
+
if (restResult.rested) {
|
|
1331
|
+
recordHumanEvent({
|
|
1332
|
+
kind: "rest",
|
|
1333
|
+
pause_ms: restResult.pause_ms || restElapsed,
|
|
1334
|
+
events: restResult.events || []
|
|
1335
|
+
});
|
|
1336
|
+
compactResult.human_rest = restResult;
|
|
1337
|
+
addTiming(compactResult.timings, "human_rest_ms", restElapsed);
|
|
1338
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1339
|
+
updateRecommendProgress({
|
|
1340
|
+
human_rest_last: restResult
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
if (delayMs > 0) {
|
|
1346
|
+
const sleepStarted = Date.now();
|
|
1347
|
+
await runControl.sleep(delayMs);
|
|
1348
|
+
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1349
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1252
1350
|
}
|
|
1253
1351
|
}
|
|
1254
1352
|
|
|
@@ -1261,11 +1359,14 @@ export async function runRecommendWorkflow({
|
|
|
1261
1359
|
filter: compactFilterResult(filterResult),
|
|
1262
1360
|
card_count: cardNodeIds.length,
|
|
1263
1361
|
candidate_list: compactInfiniteListState(listState),
|
|
1264
|
-
viewport_health: {
|
|
1265
|
-
stats: viewportGuard.getStats(),
|
|
1266
|
-
events: viewportGuard.getEvents()
|
|
1267
|
-
},
|
|
1268
|
-
|
|
1362
|
+
viewport_health: {
|
|
1363
|
+
stats: viewportGuard.getStats(),
|
|
1364
|
+
events: viewportGuard.getEvents()
|
|
1365
|
+
},
|
|
1366
|
+
human_behavior: effectiveHumanBehavior,
|
|
1367
|
+
human_rest: humanRestController.getState(),
|
|
1368
|
+
last_human_event: lastHumanEvent,
|
|
1369
|
+
list_end_reason: listEndReason || null,
|
|
1269
1370
|
refresh_rounds: refreshRounds,
|
|
1270
1371
|
refresh_attempts: refreshAttempts,
|
|
1271
1372
|
context_recoveries: contextRecoveryAttempts,
|
|
@@ -1316,20 +1417,26 @@ export function createRecommendRunService({
|
|
|
1316
1417
|
actionIntervalMs = 500,
|
|
1317
1418
|
actionAfterClickDelayMs = 900,
|
|
1318
1419
|
screeningMode = "llm",
|
|
1319
|
-
llmConfig = null,
|
|
1320
|
-
llmTimeoutMs = 120000,
|
|
1321
|
-
llmImageLimit = 8,
|
|
1322
|
-
llmImageDetail = "high",
|
|
1323
|
-
imageOutputDir = "",
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
const
|
|
1332
|
-
const
|
|
1420
|
+
llmConfig = null,
|
|
1421
|
+
llmTimeoutMs = 120000,
|
|
1422
|
+
llmImageLimit = 8,
|
|
1423
|
+
llmImageDetail = "high",
|
|
1424
|
+
imageOutputDir = "",
|
|
1425
|
+
humanRestEnabled = false,
|
|
1426
|
+
humanBehavior = null,
|
|
1427
|
+
name = "recommend-domain-run"
|
|
1428
|
+
} = {}) {
|
|
1429
|
+
if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
|
|
1430
|
+
const normalizedFilter = normalizeFilter(filter);
|
|
1431
|
+
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
1432
|
+
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
1433
|
+
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
1434
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1435
|
+
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
1436
|
+
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
1437
|
+
});
|
|
1438
|
+
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
1439
|
+
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1333
1440
|
const normalizedDetailLimit = detailLimit == null ? null : Math.max(0, Number(detailLimit) || 0);
|
|
1334
1441
|
return manager.startRun({
|
|
1335
1442
|
runId,
|
|
@@ -1365,12 +1472,16 @@ export function createRecommendRunService({
|
|
|
1365
1472
|
action_timeout_ms: actionTimeoutMs,
|
|
1366
1473
|
screening_mode: normalizedScreeningMode,
|
|
1367
1474
|
llm_configured: Boolean(llmConfig),
|
|
1368
|
-
llm_timeout_ms: llmTimeoutMs,
|
|
1369
|
-
llm_image_limit: llmImageLimit,
|
|
1370
|
-
llm_image_detail: llmImageDetail,
|
|
1371
|
-
image_output_dir: imageOutputDir || ""
|
|
1372
|
-
|
|
1373
|
-
|
|
1475
|
+
llm_timeout_ms: llmTimeoutMs,
|
|
1476
|
+
llm_image_limit: llmImageLimit,
|
|
1477
|
+
llm_image_detail: llmImageDetail,
|
|
1478
|
+
image_output_dir: imageOutputDir || "",
|
|
1479
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1480
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1481
|
+
human_behavior: effectiveHumanBehavior,
|
|
1482
|
+
human_rest_enabled: effectiveHumanRestEnabled
|
|
1483
|
+
},
|
|
1484
|
+
progress: {
|
|
1374
1485
|
card_count: 0,
|
|
1375
1486
|
target_count: candidateLimit,
|
|
1376
1487
|
target_count_semantics: "passed_candidates",
|
|
@@ -1381,11 +1492,17 @@ export function createRecommendRunService({
|
|
|
1381
1492
|
passed: 0,
|
|
1382
1493
|
greet_count: 0,
|
|
1383
1494
|
post_action_clicked: 0,
|
|
1384
|
-
image_capture_failed: 0,
|
|
1385
|
-
detail_open_failed: 0,
|
|
1386
|
-
transient_recovered: 0,
|
|
1387
|
-
context_recoveries: 0
|
|
1388
|
-
|
|
1495
|
+
image_capture_failed: 0,
|
|
1496
|
+
detail_open_failed: 0,
|
|
1497
|
+
transient_recovered: 0,
|
|
1498
|
+
context_recoveries: 0,
|
|
1499
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1500
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1501
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1502
|
+
human_rest_count: 0,
|
|
1503
|
+
human_rest_ms: 0,
|
|
1504
|
+
last_human_event: null
|
|
1505
|
+
},
|
|
1389
1506
|
checkpoint: {},
|
|
1390
1507
|
task: (runControl) => workflow({
|
|
1391
1508
|
client,
|
|
@@ -1420,13 +1537,15 @@ export function createRecommendRunService({
|
|
|
1420
1537
|
actionAfterClickDelayMs,
|
|
1421
1538
|
screeningMode: normalizedScreeningMode,
|
|
1422
1539
|
llmConfig,
|
|
1423
|
-
llmTimeoutMs,
|
|
1424
|
-
llmImageLimit,
|
|
1425
|
-
llmImageDetail,
|
|
1426
|
-
imageOutputDir
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1540
|
+
llmTimeoutMs,
|
|
1541
|
+
llmImageLimit,
|
|
1542
|
+
llmImageDetail,
|
|
1543
|
+
imageOutputDir,
|
|
1544
|
+
humanRestEnabled: effectiveHumanRestEnabled,
|
|
1545
|
+
humanBehavior: effectiveHumanBehavior
|
|
1546
|
+
}, runControl)
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1430
1549
|
|
|
1431
1550
|
return {
|
|
1432
1551
|
startRecommendRun,
|
|
@@ -8,6 +8,12 @@ import {
|
|
|
8
8
|
} from "../../core/run/timing.js";
|
|
9
9
|
import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
|
|
10
10
|
import { waitForCvCaptureTarget } from "../../core/cv-capture-target/index.js";
|
|
11
|
+
import {
|
|
12
|
+
configureHumanInteraction,
|
|
13
|
+
createHumanRestController,
|
|
14
|
+
humanDelay,
|
|
15
|
+
normalizeHumanBehaviorOptions
|
|
16
|
+
} from "../../core/browser/index.js";
|
|
11
17
|
import {
|
|
12
18
|
compactCvAcquisitionState,
|
|
13
19
|
countParsedNetworkProfiles,
|
|
@@ -291,9 +297,27 @@ export async function runRecruitWorkflow({
|
|
|
291
297
|
llmTimeoutMs = 120000,
|
|
292
298
|
llmImageLimit = 8,
|
|
293
299
|
llmImageDetail = "high",
|
|
294
|
-
imageOutputDir = ""
|
|
300
|
+
imageOutputDir = "",
|
|
301
|
+
humanRestEnabled = false,
|
|
302
|
+
humanBehavior = null
|
|
295
303
|
} = {}, runControl) {
|
|
296
304
|
if (!client) throw new Error("runRecruitWorkflow requires a guarded CDP client");
|
|
305
|
+
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
306
|
+
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
307
|
+
});
|
|
308
|
+
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
309
|
+
configureHumanInteraction(client, {
|
|
310
|
+
enabled: effectiveHumanBehavior.enabled,
|
|
311
|
+
clickMovementEnabled: effectiveHumanBehavior.clickMovement,
|
|
312
|
+
textEntryEnabled: effectiveHumanBehavior.textEntry,
|
|
313
|
+
safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
|
|
314
|
+
actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
|
|
315
|
+
});
|
|
316
|
+
const humanRestController = createHumanRestController({
|
|
317
|
+
enabled: effectiveHumanRestEnabled,
|
|
318
|
+
shortRestEnabled: effectiveHumanBehavior.shortRest,
|
|
319
|
+
batchRestEnabled: effectiveHumanBehavior.batchRest
|
|
320
|
+
});
|
|
297
321
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
298
322
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
299
323
|
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
@@ -326,6 +350,7 @@ export async function runRecruitWorkflow({
|
|
|
326
350
|
const candidateRecoveryCounts = new Map();
|
|
327
351
|
let cardNodeIds = [];
|
|
328
352
|
let listEndReason = "";
|
|
353
|
+
let lastHumanEvent = null;
|
|
329
354
|
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
330
355
|
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
331
356
|
containerSelectors: RECRUIT_LIST_CONTAINER_SELECTORS,
|
|
@@ -335,9 +360,36 @@ export async function runRecruitWorkflow({
|
|
|
335
360
|
validateViewportPoint: true
|
|
336
361
|
}));
|
|
337
362
|
|
|
363
|
+
function recordHumanEvent(event = null) {
|
|
364
|
+
if (!event) return lastHumanEvent;
|
|
365
|
+
lastHumanEvent = {
|
|
366
|
+
at: new Date().toISOString(),
|
|
367
|
+
...event
|
|
368
|
+
};
|
|
369
|
+
return lastHumanEvent;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function maybeHumanActionCooldown(phase, timings = {}) {
|
|
373
|
+
if (!effectiveHumanBehavior.actionCooldown) return null;
|
|
374
|
+
const pauseMs = humanDelay(280, 90, {
|
|
375
|
+
minMs: 80,
|
|
376
|
+
maxMs: 720
|
|
377
|
+
});
|
|
378
|
+
if (pauseMs > 0) {
|
|
379
|
+
await runControl.sleep(pauseMs);
|
|
380
|
+
addTiming(timings, `human_${phase}_pause_ms`, pauseMs);
|
|
381
|
+
}
|
|
382
|
+
return recordHumanEvent({
|
|
383
|
+
kind: "action_cooldown",
|
|
384
|
+
phase,
|
|
385
|
+
pause_ms: pauseMs
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
338
389
|
function updateRecruitProgress(extra = {}) {
|
|
339
390
|
const counts = countRecruitResultStatuses(results);
|
|
340
391
|
const listSnapshot = compactInfiniteListState(listState);
|
|
392
|
+
const humanRestState = humanRestController.getState();
|
|
341
393
|
runControl.updateProgress({
|
|
342
394
|
card_count: cardNodeIds.length,
|
|
343
395
|
target_count: limit,
|
|
@@ -351,6 +403,12 @@ export async function runRecruitWorkflow({
|
|
|
351
403
|
list_end_reason: listEndReason || null,
|
|
352
404
|
viewport_checks: viewportGuard.getStats().checks,
|
|
353
405
|
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
406
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
407
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
408
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
409
|
+
human_rest_count: humanRestState.rest_count,
|
|
410
|
+
human_rest_ms: humanRestState.total_rest_ms,
|
|
411
|
+
last_human_event: lastHumanEvent,
|
|
354
412
|
...extra
|
|
355
413
|
});
|
|
356
414
|
}
|
|
@@ -519,6 +577,7 @@ export async function runRecruitWorkflow({
|
|
|
519
577
|
stableSignatureLimit: listStableSignatureLimit,
|
|
520
578
|
wheelDeltaY: listWheelDeltaY,
|
|
521
579
|
settleMs: listSettleMs,
|
|
580
|
+
listScrollJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
522
581
|
fallbackPoint: listFallbackResolver,
|
|
523
582
|
findNodeIds: async () => {
|
|
524
583
|
let currentRootState = await getRecruitRoots(client);
|
|
@@ -622,6 +681,7 @@ export async function runRecruitWorkflow({
|
|
|
622
681
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
623
682
|
detailStep = "open_detail";
|
|
624
683
|
networkRecorder.clear();
|
|
684
|
+
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
625
685
|
const openedDetail = await openRecruitCardDetail(client, cardNodeId);
|
|
626
686
|
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
627
687
|
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
@@ -693,6 +753,7 @@ export async function runRecruitWorkflow({
|
|
|
693
753
|
wheelDeltaY: imageWheelDeltaY,
|
|
694
754
|
settleMs: 350,
|
|
695
755
|
scrollMethod: "dom-anchor-fallback-input",
|
|
756
|
+
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
696
757
|
stepTimeoutMs: 45000,
|
|
697
758
|
totalTimeoutMs: 90000,
|
|
698
759
|
duplicateStopCount: 1,
|
|
@@ -765,6 +826,7 @@ export async function runRecruitWorkflow({
|
|
|
765
826
|
screeningCandidate = detailResult.candidate;
|
|
766
827
|
if (closeDetail) {
|
|
767
828
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecruitDetail(client));
|
|
829
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
768
830
|
if (!detailResult.close_result?.closed) {
|
|
769
831
|
const closeError = createRecruitCloseFailureError(detailResult.close_result);
|
|
770
832
|
const recovery = await recoverAndReapplyRecruitContext("detail_close_failed", closeError, {
|
|
@@ -892,6 +954,27 @@ export async function runRecruitWorkflow({
|
|
|
892
954
|
});
|
|
893
955
|
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
894
956
|
|
|
957
|
+
if (effectiveHumanRestEnabled) {
|
|
958
|
+
const restStarted = Date.now();
|
|
959
|
+
const restResult = await humanRestController.takeBreakIfNeeded({
|
|
960
|
+
sleepFn: (ms) => runControl.sleep(ms)
|
|
961
|
+
});
|
|
962
|
+
const restElapsed = Date.now() - restStarted;
|
|
963
|
+
if (restResult.rested) {
|
|
964
|
+
recordHumanEvent({
|
|
965
|
+
kind: "rest",
|
|
966
|
+
pause_ms: restResult.pause_ms || restElapsed,
|
|
967
|
+
events: restResult.events || []
|
|
968
|
+
});
|
|
969
|
+
compactResult.human_rest = restResult;
|
|
970
|
+
addTiming(compactResult.timings, "human_rest_ms", restElapsed);
|
|
971
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
972
|
+
updateRecruitProgress({
|
|
973
|
+
human_rest_last: restResult
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
895
978
|
if (delayMs > 0) {
|
|
896
979
|
const sleepStarted = Date.now();
|
|
897
980
|
await runControl.sleep(delayMs);
|
|
@@ -911,6 +994,9 @@ export async function runRecruitWorkflow({
|
|
|
911
994
|
stats: viewportGuard.getStats(),
|
|
912
995
|
events: viewportGuard.getEvents()
|
|
913
996
|
},
|
|
997
|
+
human_behavior: effectiveHumanBehavior,
|
|
998
|
+
human_rest: humanRestController.getState(),
|
|
999
|
+
last_human_event: lastHumanEvent,
|
|
914
1000
|
list_end_reason: listEndReason || null,
|
|
915
1001
|
refresh_rounds: refreshRounds,
|
|
916
1002
|
refresh_attempts: refreshAttempts,
|
|
@@ -958,6 +1044,8 @@ export function createRecruitRunService({
|
|
|
958
1044
|
llmImageLimit = 8,
|
|
959
1045
|
llmImageDetail = "high",
|
|
960
1046
|
imageOutputDir = "",
|
|
1047
|
+
humanRestEnabled = false,
|
|
1048
|
+
humanBehavior = null,
|
|
961
1049
|
name = "recruit-domain-run"
|
|
962
1050
|
} = {}) {
|
|
963
1051
|
if (!client) throw new Error("startRecruitRun requires a guarded CDP client");
|
|
@@ -965,6 +1053,10 @@ export function createRecruitRunService({
|
|
|
965
1053
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
966
1054
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
967
1055
|
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1056
|
+
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
1057
|
+
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
1058
|
+
});
|
|
1059
|
+
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
968
1060
|
return manager.startRun({
|
|
969
1061
|
name,
|
|
970
1062
|
context: {
|
|
@@ -994,7 +1086,11 @@ export function createRecruitRunService({
|
|
|
994
1086
|
llm_timeout_ms: llmTimeoutMs,
|
|
995
1087
|
llm_image_limit: llmImageLimit,
|
|
996
1088
|
llm_image_detail: llmImageDetail,
|
|
997
|
-
image_output_dir: imageOutputDir || ""
|
|
1089
|
+
image_output_dir: imageOutputDir || "",
|
|
1090
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1091
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1092
|
+
human_behavior: effectiveHumanBehavior,
|
|
1093
|
+
human_rest_enabled: effectiveHumanRestEnabled
|
|
998
1094
|
},
|
|
999
1095
|
progress: {
|
|
1000
1096
|
card_count: 0,
|
|
@@ -1007,7 +1103,13 @@ export function createRecruitRunService({
|
|
|
1007
1103
|
image_capture_failed: 0,
|
|
1008
1104
|
detail_open_failed: 0,
|
|
1009
1105
|
transient_recovered: 0,
|
|
1010
|
-
context_recoveries: 0
|
|
1106
|
+
context_recoveries: 0,
|
|
1107
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1108
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1109
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1110
|
+
human_rest_count: 0,
|
|
1111
|
+
human_rest_ms: 0,
|
|
1112
|
+
last_human_event: null
|
|
1011
1113
|
},
|
|
1012
1114
|
checkpoint: {},
|
|
1013
1115
|
task: (runControl) => workflow({
|
|
@@ -1039,7 +1141,9 @@ export function createRecruitRunService({
|
|
|
1039
1141
|
llmTimeoutMs,
|
|
1040
1142
|
llmImageLimit,
|
|
1041
1143
|
llmImageDetail,
|
|
1042
|
-
imageOutputDir
|
|
1144
|
+
imageOutputDir,
|
|
1145
|
+
humanRestEnabled: effectiveHumanRestEnabled,
|
|
1146
|
+
humanBehavior: effectiveHumanBehavior
|
|
1043
1147
|
}, runControl)
|
|
1044
1148
|
});
|
|
1045
1149
|
}
|