@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
|
@@ -39,7 +39,9 @@ import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
|
39
39
|
import {
|
|
40
40
|
callScreeningLlm,
|
|
41
41
|
compactScreeningLlmResult,
|
|
42
|
+
createFatalLlmRunError,
|
|
42
43
|
createFailedLlmScreeningResult,
|
|
44
|
+
isFatalLlmProviderError,
|
|
43
45
|
llmResultToScreening,
|
|
44
46
|
screenCandidate
|
|
45
47
|
} from "../../core/screening/index.js";
|
|
@@ -51,6 +53,10 @@ import {
|
|
|
51
53
|
openRecruitCardDetail,
|
|
52
54
|
waitForRecruitDetailNetworkEvents
|
|
53
55
|
} from "./detail.js";
|
|
56
|
+
import {
|
|
57
|
+
clickRecruitActionControl,
|
|
58
|
+
waitForRecruitDetailActionControls
|
|
59
|
+
} from "./actions.js";
|
|
54
60
|
import {
|
|
55
61
|
readRecruitCardCandidate,
|
|
56
62
|
waitForRecruitCardNodeIds
|
|
@@ -68,6 +74,7 @@ import {
|
|
|
68
74
|
RECRUIT_CARD_SELECTOR,
|
|
69
75
|
RECRUIT_LIST_CONTAINER_SELECTORS
|
|
70
76
|
} from "./constants.js";
|
|
77
|
+
import { GREET_CREDITS_EXHAUSTED_CODE } from "../../core/greet-quota/index.js";
|
|
71
78
|
|
|
72
79
|
function compactScreening(screening) {
|
|
73
80
|
return {
|
|
@@ -118,6 +125,164 @@ function createMissingLlmConfigResult() {
|
|
|
118
125
|
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production search runs"));
|
|
119
126
|
}
|
|
120
127
|
|
|
128
|
+
function normalizeRecruitPostAction(value) {
|
|
129
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
130
|
+
if (["", "none", "skip", "no", "不执行", "无", "什么也不做"].includes(normalized)) return "none";
|
|
131
|
+
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
132
|
+
return "none";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveRecruitPostAction({
|
|
136
|
+
postAction = "none",
|
|
137
|
+
greetCount = 0,
|
|
138
|
+
maxGreetCount = null
|
|
139
|
+
} = {}) {
|
|
140
|
+
const requested = normalizeRecruitPostAction(postAction);
|
|
141
|
+
const currentGreetCount = Number.isInteger(greetCount) && greetCount >= 0 ? greetCount : 0;
|
|
142
|
+
const limit = Number.isInteger(maxGreetCount) && maxGreetCount > 0 ? maxGreetCount : null;
|
|
143
|
+
if (requested === "greet" && limit !== null && currentGreetCount >= limit) {
|
|
144
|
+
return {
|
|
145
|
+
requested,
|
|
146
|
+
effective: "none",
|
|
147
|
+
reason: "greet_limit_reached",
|
|
148
|
+
greet_count: currentGreetCount,
|
|
149
|
+
max_greet_count: limit
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
requested,
|
|
154
|
+
effective: requested,
|
|
155
|
+
reason: "requested_action",
|
|
156
|
+
greet_count: currentGreetCount,
|
|
157
|
+
max_greet_count: limit
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function compactActionDiscovery(discovery) {
|
|
162
|
+
if (!discovery) return null;
|
|
163
|
+
return {
|
|
164
|
+
ok: Boolean(discovery.ok),
|
|
165
|
+
elapsed_ms: discovery.elapsed_ms,
|
|
166
|
+
summary: discovery.summary || null
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function runRecruitPostAction({
|
|
171
|
+
client,
|
|
172
|
+
rootNodeIds = [],
|
|
173
|
+
screening,
|
|
174
|
+
actionDiscovery,
|
|
175
|
+
postAction = "none",
|
|
176
|
+
greetCount = 0,
|
|
177
|
+
maxGreetCount = null,
|
|
178
|
+
executePostAction = true,
|
|
179
|
+
afterClickDelayMs = 900
|
|
180
|
+
} = {}) {
|
|
181
|
+
const plan = resolveRecruitPostAction({
|
|
182
|
+
postAction,
|
|
183
|
+
greetCount,
|
|
184
|
+
maxGreetCount
|
|
185
|
+
});
|
|
186
|
+
const result = {
|
|
187
|
+
requested: postAction,
|
|
188
|
+
execute_post_action: Boolean(executePostAction),
|
|
189
|
+
plan,
|
|
190
|
+
eligible: Boolean(screening?.passed),
|
|
191
|
+
action_attempted: false,
|
|
192
|
+
action_clicked: false,
|
|
193
|
+
counted_as_greet: false,
|
|
194
|
+
reason: ""
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
if (!screening?.passed) {
|
|
198
|
+
result.reason = "screening_not_passed";
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
if (plan.effective === "none") {
|
|
202
|
+
result.reason = plan.reason === "greet_limit_reached" ? "greet_limit_reached" : "post_action_none";
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const summary = actionDiscovery?.summary || {};
|
|
207
|
+
const control = summary.greet?.control || summary.greet;
|
|
208
|
+
if (!control?.found && !control?.node_id) {
|
|
209
|
+
result.reason = `${plan.effective}_control_not_found`;
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
result.control = control;
|
|
213
|
+
|
|
214
|
+
if (plan.effective === "greet" && control.continue_chat) {
|
|
215
|
+
result.reason = "already_connected_continue_chat";
|
|
216
|
+
result.already_connected = true;
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
if (plan.effective === "greet" && control.greet_quota?.exhausted) {
|
|
220
|
+
result.reason = "greet_credits_exhausted";
|
|
221
|
+
result.out_of_greet_credits = true;
|
|
222
|
+
result.stop_run = true;
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
if (plan.effective === "greet" && control.available === false) {
|
|
226
|
+
result.reason = "greet_control_not_available";
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
if (control.disabled) {
|
|
230
|
+
result.reason = `${plan.effective}_control_disabled`;
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
if (!executePostAction) {
|
|
234
|
+
result.reason = "dry_run_post_action";
|
|
235
|
+
result.would_click = true;
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
result.action_attempted = true;
|
|
240
|
+
result.control_before = control;
|
|
241
|
+
let clickResult;
|
|
242
|
+
try {
|
|
243
|
+
clickResult = await clickRecruitActionControl(client, {
|
|
244
|
+
...control,
|
|
245
|
+
kind: plan.effective
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (error?.code === GREET_CREDITS_EXHAUSTED_CODE) {
|
|
249
|
+
result.reason = "greet_credits_exhausted";
|
|
250
|
+
result.out_of_greet_credits = true;
|
|
251
|
+
result.stop_run = true;
|
|
252
|
+
result.greet_quota = error.greet_quota || control.greet_quota || null;
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
result.click_result = clickResult;
|
|
258
|
+
result.action_clicked = true;
|
|
259
|
+
result.counted_as_greet = plan.effective === "greet";
|
|
260
|
+
result.reason = "clicked";
|
|
261
|
+
if (afterClickDelayMs > 0) await sleep(afterClickDelayMs);
|
|
262
|
+
try {
|
|
263
|
+
const afterDiscovery = await waitForRecruitDetailActionControls(client, {
|
|
264
|
+
rootNodeIds,
|
|
265
|
+
timeoutMs: 2500,
|
|
266
|
+
intervalMs: 300,
|
|
267
|
+
requireAny: false
|
|
268
|
+
});
|
|
269
|
+
const afterControl = afterDiscovery?.summary?.greet?.control || afterDiscovery?.summary?.greet || null;
|
|
270
|
+
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
271
|
+
result.control_after = afterControl;
|
|
272
|
+
if (plan.effective === "greet") {
|
|
273
|
+
result.verified_after_click = Boolean(
|
|
274
|
+
afterControl?.continue_chat
|
|
275
|
+
|| String(afterControl?.label || "").includes("继续沟通")
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
result.verify_error = {
|
|
280
|
+
message: error?.message || String(error)
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
121
286
|
function normalizeSearchParams(searchParams = {}) {
|
|
122
287
|
return normalizeRecruitSearchParams(searchParams);
|
|
123
288
|
}
|
|
@@ -326,13 +491,17 @@ function createRecoverableDetailFailureScreening(candidate, error) {
|
|
|
326
491
|
};
|
|
327
492
|
}
|
|
328
493
|
|
|
329
|
-
export function countRecruitResultStatuses(results = []
|
|
494
|
+
export function countRecruitResultStatuses(results = [], {
|
|
495
|
+
greetCount = 0
|
|
496
|
+
} = {}) {
|
|
330
497
|
return {
|
|
331
498
|
processed: results.length,
|
|
332
499
|
screened: results.length,
|
|
333
500
|
detail_opened: results.filter((item) => item.detail).length,
|
|
334
501
|
passed: results.filter((item) => item.screening?.passed).length,
|
|
335
502
|
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
503
|
+
greet_count: greetCount,
|
|
504
|
+
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
336
505
|
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
337
506
|
detail_open_failed: results.filter((item) => (
|
|
338
507
|
item.error?.code === "DETAIL_STALE_NODE"
|
|
@@ -378,7 +547,13 @@ export async function runRecruitWorkflow({
|
|
|
378
547
|
llmImageDetail = "high",
|
|
379
548
|
imageOutputDir = "",
|
|
380
549
|
humanRestEnabled = false,
|
|
381
|
-
humanBehavior = null
|
|
550
|
+
humanBehavior = null,
|
|
551
|
+
postAction = "none",
|
|
552
|
+
maxGreetCount = null,
|
|
553
|
+
executePostAction = true,
|
|
554
|
+
actionTimeoutMs = 8000,
|
|
555
|
+
actionIntervalMs = 400,
|
|
556
|
+
actionAfterClickDelayMs = 900
|
|
382
557
|
} = {}, runControl) {
|
|
383
558
|
if (!client) throw new Error("runRecruitWorkflow requires a guarded CDP client");
|
|
384
559
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -400,6 +575,8 @@ export async function runRecruitWorkflow({
|
|
|
400
575
|
});
|
|
401
576
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
402
577
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
578
|
+
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
579
|
+
const postActionEnabled = normalizedPostAction !== "none";
|
|
403
580
|
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
404
581
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
405
582
|
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
@@ -425,6 +602,7 @@ export async function runRecruitWorkflow({
|
|
|
425
602
|
}
|
|
426
603
|
const results = [];
|
|
427
604
|
const refreshAttempts = [];
|
|
605
|
+
let greetCount = 0;
|
|
428
606
|
let refreshRounds = 0;
|
|
429
607
|
let contextRecoveryAttempts = 0;
|
|
430
608
|
const candidateRecoveryCounts = new Map();
|
|
@@ -468,7 +646,7 @@ export async function runRecruitWorkflow({
|
|
|
468
646
|
}
|
|
469
647
|
|
|
470
648
|
function updateRecruitProgress(extra = {}) {
|
|
471
|
-
const counts = countRecruitResultStatuses(results);
|
|
649
|
+
const counts = countRecruitResultStatuses(results, { greetCount });
|
|
472
650
|
const listSnapshot = compactInfiniteListState(listState);
|
|
473
651
|
const humanRestState = humanRestController.getState();
|
|
474
652
|
runControl.updateProgress({
|
|
@@ -508,7 +686,7 @@ export async function runRecruitWorkflow({
|
|
|
508
686
|
key: candidateKey,
|
|
509
687
|
card_node_id: cardNodeId,
|
|
510
688
|
detail_step: detailStep || null,
|
|
511
|
-
counters: countRecruitResultStatuses(results),
|
|
689
|
+
counters: countRecruitResultStatuses(results, { greetCount }),
|
|
512
690
|
error: compactError(error, "RECRUIT_IN_PROGRESS_ERROR")
|
|
513
691
|
},
|
|
514
692
|
candidate_list: compactInfiniteListState(listState)
|
|
@@ -564,7 +742,7 @@ export async function runRecruitWorkflow({
|
|
|
564
742
|
reason,
|
|
565
743
|
trigger_error: compactError(error, "RECRUIT_CONTEXT_RECOVERY_TRIGGER"),
|
|
566
744
|
refresh: compactRefresh,
|
|
567
|
-
counters: countRecruitResultStatuses(results)
|
|
745
|
+
counters: countRecruitResultStatuses(results, { greetCount })
|
|
568
746
|
},
|
|
569
747
|
candidate_list: compactInfiniteListState(listState)
|
|
570
748
|
});
|
|
@@ -594,7 +772,7 @@ export async function runRecruitWorkflow({
|
|
|
594
772
|
metadata: {
|
|
595
773
|
card_count: cardNodeIds.length,
|
|
596
774
|
forced_recent_viewed: forceRecentViewed,
|
|
597
|
-
counters: countRecruitResultStatuses(results)
|
|
775
|
+
counters: countRecruitResultStatuses(results, { greetCount })
|
|
598
776
|
}
|
|
599
777
|
});
|
|
600
778
|
listEndReason = "";
|
|
@@ -780,6 +958,7 @@ export async function runRecruitWorkflow({
|
|
|
780
958
|
|
|
781
959
|
let screeningCandidate = cardCandidate;
|
|
782
960
|
let detailResult = null;
|
|
961
|
+
let detailActionRootNodeIds = [];
|
|
783
962
|
let recoverableDetailError = null;
|
|
784
963
|
let detailStep = "not_started";
|
|
785
964
|
if (index < detailCountLimit) {
|
|
@@ -813,6 +992,7 @@ export async function runRecruitWorkflow({
|
|
|
813
992
|
networkRecorder.clear();
|
|
814
993
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
815
994
|
const openedDetail = await openRecruitCardDetail(client, cardNodeId);
|
|
995
|
+
detailActionRootNodeIds = (openedDetail.detail_state?.roots || []).map((root) => root.nodeId).filter(Boolean);
|
|
816
996
|
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
817
997
|
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
818
998
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
@@ -955,7 +1135,7 @@ export async function runRecruitWorkflow({
|
|
|
955
1135
|
capture_target_wait: captureTargetWait
|
|
956
1136
|
};
|
|
957
1137
|
screeningCandidate = detailResult.candidate;
|
|
958
|
-
if (closeDetail) {
|
|
1138
|
+
if (closeDetail && !postActionEnabled) {
|
|
959
1139
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecruitDetail(client));
|
|
960
1140
|
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
961
1141
|
if (!detailResult.close_result?.closed) {
|
|
@@ -1022,6 +1202,12 @@ export async function runRecruitWorkflow({
|
|
|
1022
1202
|
imageDetail: llmImageDetail
|
|
1023
1203
|
}));
|
|
1024
1204
|
} catch (error) {
|
|
1205
|
+
if (isFatalLlmProviderError(error)) {
|
|
1206
|
+
throw createFatalLlmRunError(error, {
|
|
1207
|
+
domain: "recruit",
|
|
1208
|
+
candidate: screeningCandidate
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1025
1211
|
llmResult = createFailedLlmScreeningResult(error);
|
|
1026
1212
|
}
|
|
1027
1213
|
}
|
|
@@ -1037,6 +1223,70 @@ export async function runRecruitWorkflow({
|
|
|
1037
1223
|
: useLlmScreening
|
|
1038
1224
|
? llmResultToScreening(llmResult, screeningCandidate)
|
|
1039
1225
|
: screenCandidate(screeningCandidate, { criteria });
|
|
1226
|
+
let actionDiscovery = null;
|
|
1227
|
+
let postActionResult = null;
|
|
1228
|
+
let closeFailureError = null;
|
|
1229
|
+
let closeRecoveryFailure = null;
|
|
1230
|
+
if (postActionEnabled && detailResult) {
|
|
1231
|
+
const postActionStarted = Date.now();
|
|
1232
|
+
await runControl.waitIfPaused();
|
|
1233
|
+
runControl.throwIfCanceled();
|
|
1234
|
+
runControl.setPhase("recruit:post-action");
|
|
1235
|
+
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1236
|
+
actionDiscovery = await waitForRecruitDetailActionControls(client, {
|
|
1237
|
+
rootNodeIds: detailActionRootNodeIds,
|
|
1238
|
+
timeoutMs: actionTimeoutMs,
|
|
1239
|
+
intervalMs: actionIntervalMs,
|
|
1240
|
+
requireAny: true
|
|
1241
|
+
});
|
|
1242
|
+
postActionResult = await runRecruitPostAction({
|
|
1243
|
+
client,
|
|
1244
|
+
rootNodeIds: detailActionRootNodeIds,
|
|
1245
|
+
screening,
|
|
1246
|
+
actionDiscovery,
|
|
1247
|
+
postAction: normalizedPostAction,
|
|
1248
|
+
greetCount,
|
|
1249
|
+
maxGreetCount: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1250
|
+
executePostAction,
|
|
1251
|
+
afterClickDelayMs: actionAfterClickDelayMs
|
|
1252
|
+
});
|
|
1253
|
+
if (postActionResult.counted_as_greet && postActionResult.action_clicked) {
|
|
1254
|
+
greetCount += 1;
|
|
1255
|
+
}
|
|
1256
|
+
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1257
|
+
}
|
|
1258
|
+
if (postActionEnabled && detailResult && closeDetail) {
|
|
1259
|
+
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecruitDetail(client));
|
|
1260
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1261
|
+
if (!detailResult.close_result?.closed) {
|
|
1262
|
+
closeFailureError = createRecruitCloseFailureError(detailResult.close_result);
|
|
1263
|
+
try {
|
|
1264
|
+
const recovery = await recoverAndReapplyRecruitContext("detail_close_failed", closeFailureError, {
|
|
1265
|
+
forceRecentViewed: true
|
|
1266
|
+
});
|
|
1267
|
+
detailResult.cv_acquisition = {
|
|
1268
|
+
...(detailResult.cv_acquisition || {}),
|
|
1269
|
+
close_recovery: {
|
|
1270
|
+
ok: Boolean(recovery.ok),
|
|
1271
|
+
method: recovery.method || "",
|
|
1272
|
+
forced_recent_viewed: Boolean(recovery.forced_recent_viewed),
|
|
1273
|
+
card_count: recovery.card_count || 0
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
closeRecoveryFailure = error;
|
|
1278
|
+
detailResult.cv_acquisition = {
|
|
1279
|
+
...(detailResult.cv_acquisition || {}),
|
|
1280
|
+
close_recovery: {
|
|
1281
|
+
ok: false,
|
|
1282
|
+
reason: "context_recovery_failed",
|
|
1283
|
+
error: error?.message || String(error),
|
|
1284
|
+
forced_recent_viewed: true
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1040
1290
|
timings.total_ms = Date.now() - candidateStarted;
|
|
1041
1291
|
const compactResult = {
|
|
1042
1292
|
index,
|
|
@@ -1046,8 +1296,12 @@ export async function runRecruitWorkflow({
|
|
|
1046
1296
|
detail: compactDetail(detailResult),
|
|
1047
1297
|
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
1048
1298
|
screening: compactScreening(screening),
|
|
1299
|
+
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
1300
|
+
post_action: postActionResult,
|
|
1049
1301
|
error: recoverableDetailError
|
|
1050
1302
|
? compactRecoverableDetailError(recoverableDetailError)
|
|
1303
|
+
: closeRecoveryFailure
|
|
1304
|
+
? compactError(closeFailureError, "DETAIL_CLOSE_FAILED")
|
|
1051
1305
|
: detailResult?.image_evidence?.ok === false
|
|
1052
1306
|
? compactError({
|
|
1053
1307
|
code: detailResult.image_evidence.error_code,
|
|
@@ -1115,6 +1369,10 @@ export async function runRecruitWorkflow({
|
|
|
1115
1369
|
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1116
1370
|
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1117
1371
|
}
|
|
1372
|
+
if (postActionResult?.stop_run) {
|
|
1373
|
+
listEndReason = postActionResult.reason || "post_action_stop";
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1118
1376
|
}
|
|
1119
1377
|
|
|
1120
1378
|
runControl.setPhase("recruit:done");
|
|
@@ -1135,7 +1393,7 @@ export async function runRecruitWorkflow({
|
|
|
1135
1393
|
refresh_rounds: refreshRounds,
|
|
1136
1394
|
refresh_attempts: refreshAttempts,
|
|
1137
1395
|
context_recoveries: contextRecoveryAttempts,
|
|
1138
|
-
...countRecruitResultStatuses(results),
|
|
1396
|
+
...countRecruitResultStatuses(results, { greetCount }),
|
|
1139
1397
|
results
|
|
1140
1398
|
};
|
|
1141
1399
|
}
|
|
@@ -1181,11 +1439,18 @@ export function createRecruitRunService({
|
|
|
1181
1439
|
imageOutputDir = "",
|
|
1182
1440
|
humanRestEnabled = false,
|
|
1183
1441
|
humanBehavior = null,
|
|
1442
|
+
postAction = "none",
|
|
1443
|
+
maxGreetCount = null,
|
|
1444
|
+
executePostAction = true,
|
|
1445
|
+
actionTimeoutMs = 8000,
|
|
1446
|
+
actionIntervalMs = 400,
|
|
1447
|
+
actionAfterClickDelayMs = 900,
|
|
1184
1448
|
name = "recruit-domain-run"
|
|
1185
1449
|
} = {}) {
|
|
1186
1450
|
if (!client) throw new Error("startRecruitRun requires a guarded CDP client");
|
|
1187
1451
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
1188
1452
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1453
|
+
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
1189
1454
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1190
1455
|
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1191
1456
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -1227,7 +1492,13 @@ export function createRecruitRunService({
|
|
|
1227
1492
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1228
1493
|
human_behavior: effectiveHumanBehavior,
|
|
1229
1494
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1230
|
-
human_rest_enabled: effectiveHumanRestEnabled
|
|
1495
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1496
|
+
post_action: normalizedPostAction,
|
|
1497
|
+
max_greet_count: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1498
|
+
execute_post_action: Boolean(executePostAction),
|
|
1499
|
+
action_timeout_ms: actionTimeoutMs,
|
|
1500
|
+
action_interval_ms: actionIntervalMs,
|
|
1501
|
+
action_after_click_delay_ms: actionAfterClickDelayMs
|
|
1231
1502
|
},
|
|
1232
1503
|
progress: {
|
|
1233
1504
|
card_count: 0,
|
|
@@ -1247,6 +1518,8 @@ export function createRecruitRunService({
|
|
|
1247
1518
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1248
1519
|
human_rest_count: 0,
|
|
1249
1520
|
human_rest_ms: 0,
|
|
1521
|
+
greet_count: 0,
|
|
1522
|
+
post_action_clicked: 0,
|
|
1250
1523
|
last_human_event: null
|
|
1251
1524
|
},
|
|
1252
1525
|
checkpoint: {},
|
|
@@ -1281,7 +1554,13 @@ export function createRecruitRunService({
|
|
|
1281
1554
|
llmImageDetail,
|
|
1282
1555
|
imageOutputDir,
|
|
1283
1556
|
humanRestEnabled: effectiveHumanRestEnabled,
|
|
1284
|
-
humanBehavior: effectiveHumanBehavior
|
|
1557
|
+
humanBehavior: effectiveHumanBehavior,
|
|
1558
|
+
postAction: normalizedPostAction,
|
|
1559
|
+
maxGreetCount,
|
|
1560
|
+
executePostAction,
|
|
1561
|
+
actionTimeoutMs,
|
|
1562
|
+
actionIntervalMs,
|
|
1563
|
+
actionAfterClickDelayMs
|
|
1285
1564
|
}, runControl)
|
|
1286
1565
|
});
|
|
1287
1566
|
}
|