@reconcrap/boss-recommend-mcp 2.1.14 → 2.1.16
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 +34 -5
- package/package.json +8 -7
- 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 +127 -88
- package/src/core/greet-quota/index.js +17 -0
- package/src/core/reporting/legacy-csv.js +5 -1
- package/src/domains/chat/detail.js +79 -47
- package/src/domains/chat/run-service.js +400 -158
- package/src/domains/recommend/colleague-contact.js +333 -0
- package/src/domains/recommend/index.js +1 -0
- package/src/domains/recommend/run-service.js +166 -77
- package/src/domains/recruit/constants.js +69 -0
- package/src/domains/recruit/instruction-parser.js +403 -86
- package/src/domains/recruit/run-service.js +320 -11
- package/src/domains/recruit/search.js +2118 -306
- package/src/index.js +38 -23
- package/src/parser.js +45 -2
- package/src/recommend-mcp.js +92 -18
- package/src/recruit-mcp.js +236 -3
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
configureHumanInteraction,
|
|
13
13
|
createHumanRestController,
|
|
14
14
|
humanDelay,
|
|
15
|
-
normalizeHumanBehaviorOptions
|
|
15
|
+
normalizeHumanBehaviorOptions,
|
|
16
|
+
sleep
|
|
16
17
|
} from "../../core/browser/index.js";
|
|
17
18
|
import {
|
|
18
19
|
compactCvAcquisitionState,
|
|
@@ -53,6 +54,10 @@ import {
|
|
|
53
54
|
openRecruitCardDetail,
|
|
54
55
|
waitForRecruitDetailNetworkEvents
|
|
55
56
|
} from "./detail.js";
|
|
57
|
+
import {
|
|
58
|
+
clickRecruitActionControl,
|
|
59
|
+
waitForRecruitDetailActionControls
|
|
60
|
+
} from "./actions.js";
|
|
56
61
|
import {
|
|
57
62
|
readRecruitCardCandidate,
|
|
58
63
|
waitForRecruitCardNodeIds
|
|
@@ -70,6 +75,10 @@ import {
|
|
|
70
75
|
RECRUIT_CARD_SELECTOR,
|
|
71
76
|
RECRUIT_LIST_CONTAINER_SELECTORS
|
|
72
77
|
} from "./constants.js";
|
|
78
|
+
import {
|
|
79
|
+
describeGreetQuotaAfterSpend,
|
|
80
|
+
GREET_CREDITS_EXHAUSTED_CODE
|
|
81
|
+
} from "../../core/greet-quota/index.js";
|
|
73
82
|
|
|
74
83
|
function compactScreening(screening) {
|
|
75
84
|
return {
|
|
@@ -120,6 +129,175 @@ function createMissingLlmConfigResult() {
|
|
|
120
129
|
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production search runs"));
|
|
121
130
|
}
|
|
122
131
|
|
|
132
|
+
function normalizeRecruitPostAction(value) {
|
|
133
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
134
|
+
if (["", "none", "skip", "no", "不执行", "无", "什么也不做"].includes(normalized)) return "none";
|
|
135
|
+
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
136
|
+
return "none";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveRecruitPostAction({
|
|
140
|
+
postAction = "none",
|
|
141
|
+
greetCount = 0,
|
|
142
|
+
maxGreetCount = null
|
|
143
|
+
} = {}) {
|
|
144
|
+
const requested = normalizeRecruitPostAction(postAction);
|
|
145
|
+
const currentGreetCount = Number.isInteger(greetCount) && greetCount >= 0 ? greetCount : 0;
|
|
146
|
+
const limit = Number.isInteger(maxGreetCount) && maxGreetCount > 0 ? maxGreetCount : null;
|
|
147
|
+
if (requested === "greet" && limit !== null && currentGreetCount >= limit) {
|
|
148
|
+
return {
|
|
149
|
+
requested,
|
|
150
|
+
effective: "none",
|
|
151
|
+
reason: "greet_limit_reached",
|
|
152
|
+
greet_count: currentGreetCount,
|
|
153
|
+
max_greet_count: limit
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
requested,
|
|
158
|
+
effective: requested,
|
|
159
|
+
reason: "requested_action",
|
|
160
|
+
greet_count: currentGreetCount,
|
|
161
|
+
max_greet_count: limit
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function compactActionDiscovery(discovery) {
|
|
166
|
+
if (!discovery) return null;
|
|
167
|
+
return {
|
|
168
|
+
ok: Boolean(discovery.ok),
|
|
169
|
+
elapsed_ms: discovery.elapsed_ms,
|
|
170
|
+
summary: discovery.summary || null
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function runRecruitPostAction({
|
|
175
|
+
client,
|
|
176
|
+
rootNodeIds = [],
|
|
177
|
+
screening,
|
|
178
|
+
actionDiscovery,
|
|
179
|
+
postAction = "none",
|
|
180
|
+
greetCount = 0,
|
|
181
|
+
maxGreetCount = null,
|
|
182
|
+
executePostAction = true,
|
|
183
|
+
afterClickDelayMs = 900,
|
|
184
|
+
lastGreetQuotaAfterSpend = null
|
|
185
|
+
} = {}) {
|
|
186
|
+
const plan = resolveRecruitPostAction({
|
|
187
|
+
postAction,
|
|
188
|
+
greetCount,
|
|
189
|
+
maxGreetCount
|
|
190
|
+
});
|
|
191
|
+
const result = {
|
|
192
|
+
requested: postAction,
|
|
193
|
+
execute_post_action: Boolean(executePostAction),
|
|
194
|
+
plan,
|
|
195
|
+
eligible: Boolean(screening?.passed),
|
|
196
|
+
action_attempted: false,
|
|
197
|
+
action_clicked: false,
|
|
198
|
+
counted_as_greet: false,
|
|
199
|
+
reason: ""
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (!screening?.passed) {
|
|
203
|
+
result.reason = "screening_not_passed";
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
if (plan.effective === "none") {
|
|
207
|
+
result.reason = plan.reason === "greet_limit_reached" ? "greet_limit_reached" : "post_action_none";
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const summary = actionDiscovery?.summary || {};
|
|
212
|
+
const control = summary.greet?.control || summary.greet;
|
|
213
|
+
if (!control?.found && !control?.node_id) {
|
|
214
|
+
if (plan.effective === "greet" && lastGreetQuotaAfterSpend?.exhausted_after_spend) {
|
|
215
|
+
result.reason = "greet_credits_exhausted";
|
|
216
|
+
result.out_of_greet_credits = true;
|
|
217
|
+
result.stop_run = true;
|
|
218
|
+
result.greet_quota_after_last_click = lastGreetQuotaAfterSpend;
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
result.reason = `${plan.effective}_control_not_found`;
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
result.control = control;
|
|
225
|
+
|
|
226
|
+
if (plan.effective === "greet" && control.continue_chat) {
|
|
227
|
+
result.reason = "already_connected_continue_chat";
|
|
228
|
+
result.already_connected = true;
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
if (plan.effective === "greet" && control.greet_quota?.exhausted) {
|
|
232
|
+
result.reason = "greet_credits_exhausted";
|
|
233
|
+
result.out_of_greet_credits = true;
|
|
234
|
+
result.stop_run = true;
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
if (plan.effective === "greet" && control.available === false) {
|
|
238
|
+
result.reason = "greet_control_not_available";
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
if (control.disabled) {
|
|
242
|
+
result.reason = `${plan.effective}_control_disabled`;
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
if (!executePostAction) {
|
|
246
|
+
result.reason = "dry_run_post_action";
|
|
247
|
+
result.would_click = true;
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
result.action_attempted = true;
|
|
252
|
+
result.control_before = control;
|
|
253
|
+
let clickResult;
|
|
254
|
+
try {
|
|
255
|
+
clickResult = await clickRecruitActionControl(client, {
|
|
256
|
+
...control,
|
|
257
|
+
kind: plan.effective
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error?.code === GREET_CREDITS_EXHAUSTED_CODE) {
|
|
261
|
+
result.reason = "greet_credits_exhausted";
|
|
262
|
+
result.out_of_greet_credits = true;
|
|
263
|
+
result.stop_run = true;
|
|
264
|
+
result.greet_quota = error.greet_quota || control.greet_quota || null;
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
result.click_result = clickResult;
|
|
270
|
+
result.action_clicked = true;
|
|
271
|
+
result.greet_quota_after_click = describeGreetQuotaAfterSpend(
|
|
272
|
+
clickResult.greet_quota?.found ? clickResult.greet_quota : control.greet_quota || control.label || ""
|
|
273
|
+
);
|
|
274
|
+
result.counted_as_greet = plan.effective === "greet";
|
|
275
|
+
result.reason = "clicked";
|
|
276
|
+
if (afterClickDelayMs > 0) await sleep(afterClickDelayMs);
|
|
277
|
+
try {
|
|
278
|
+
const afterDiscovery = await waitForRecruitDetailActionControls(client, {
|
|
279
|
+
rootNodeIds,
|
|
280
|
+
timeoutMs: 2500,
|
|
281
|
+
intervalMs: 300,
|
|
282
|
+
requireAny: false
|
|
283
|
+
});
|
|
284
|
+
const afterControl = afterDiscovery?.summary?.greet?.control || afterDiscovery?.summary?.greet || null;
|
|
285
|
+
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
286
|
+
result.control_after = afterControl;
|
|
287
|
+
if (plan.effective === "greet") {
|
|
288
|
+
result.verified_after_click = Boolean(
|
|
289
|
+
afterControl?.continue_chat
|
|
290
|
+
|| String(afterControl?.label || "").includes("继续沟通")
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
result.verify_error = {
|
|
295
|
+
message: error?.message || String(error)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
123
301
|
function normalizeSearchParams(searchParams = {}) {
|
|
124
302
|
return normalizeRecruitSearchParams(searchParams);
|
|
125
303
|
}
|
|
@@ -328,13 +506,17 @@ function createRecoverableDetailFailureScreening(candidate, error) {
|
|
|
328
506
|
};
|
|
329
507
|
}
|
|
330
508
|
|
|
331
|
-
export function countRecruitResultStatuses(results = []
|
|
509
|
+
export function countRecruitResultStatuses(results = [], {
|
|
510
|
+
greetCount = 0
|
|
511
|
+
} = {}) {
|
|
332
512
|
return {
|
|
333
513
|
processed: results.length,
|
|
334
514
|
screened: results.length,
|
|
335
515
|
detail_opened: results.filter((item) => item.detail).length,
|
|
336
516
|
passed: results.filter((item) => item.screening?.passed).length,
|
|
337
517
|
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
518
|
+
greet_count: greetCount,
|
|
519
|
+
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
338
520
|
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
339
521
|
detail_open_failed: results.filter((item) => (
|
|
340
522
|
item.error?.code === "DETAIL_STALE_NODE"
|
|
@@ -380,7 +562,13 @@ export async function runRecruitWorkflow({
|
|
|
380
562
|
llmImageDetail = "high",
|
|
381
563
|
imageOutputDir = "",
|
|
382
564
|
humanRestEnabled = false,
|
|
383
|
-
humanBehavior = null
|
|
565
|
+
humanBehavior = null,
|
|
566
|
+
postAction = "none",
|
|
567
|
+
maxGreetCount = null,
|
|
568
|
+
executePostAction = true,
|
|
569
|
+
actionTimeoutMs = 8000,
|
|
570
|
+
actionIntervalMs = 400,
|
|
571
|
+
actionAfterClickDelayMs = 900
|
|
384
572
|
} = {}, runControl) {
|
|
385
573
|
if (!client) throw new Error("runRecruitWorkflow requires a guarded CDP client");
|
|
386
574
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -402,7 +590,11 @@ export async function runRecruitWorkflow({
|
|
|
402
590
|
});
|
|
403
591
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
404
592
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
593
|
+
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
594
|
+
const postActionEnabled = normalizedPostAction !== "none";
|
|
405
595
|
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
596
|
+
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted !== false;
|
|
597
|
+
let searchExchangeResumeFilterApplied = false;
|
|
406
598
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
407
599
|
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
408
600
|
const networkRecorder = detailCountLimit > 0
|
|
@@ -427,6 +619,8 @@ export async function runRecruitWorkflow({
|
|
|
427
619
|
}
|
|
428
620
|
const results = [];
|
|
429
621
|
const refreshAttempts = [];
|
|
622
|
+
let greetCount = 0;
|
|
623
|
+
let lastGreetQuotaAfterSpend = null;
|
|
430
624
|
let refreshRounds = 0;
|
|
431
625
|
let contextRecoveryAttempts = 0;
|
|
432
626
|
const candidateRecoveryCounts = new Map();
|
|
@@ -470,7 +664,7 @@ export async function runRecruitWorkflow({
|
|
|
470
664
|
}
|
|
471
665
|
|
|
472
666
|
function updateRecruitProgress(extra = {}) {
|
|
473
|
-
const counts = countRecruitResultStatuses(results);
|
|
667
|
+
const counts = countRecruitResultStatuses(results, { greetCount });
|
|
474
668
|
const listSnapshot = compactInfiniteListState(listState);
|
|
475
669
|
const humanRestState = humanRestController.getState();
|
|
476
670
|
runControl.updateProgress({
|
|
@@ -492,6 +686,8 @@ export async function runRecruitWorkflow({
|
|
|
492
686
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
493
687
|
human_rest_count: humanRestState.rest_count,
|
|
494
688
|
human_rest_ms: humanRestState.total_rest_ms,
|
|
689
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
690
|
+
search_exchange_resume_filter_applied: searchExchangeResumeFilterApplied ? 1 : 0,
|
|
495
691
|
last_human_event: lastHumanEvent,
|
|
496
692
|
...extra
|
|
497
693
|
});
|
|
@@ -510,7 +706,7 @@ export async function runRecruitWorkflow({
|
|
|
510
706
|
key: candidateKey,
|
|
511
707
|
card_node_id: cardNodeId,
|
|
512
708
|
detail_step: detailStep || null,
|
|
513
|
-
counters: countRecruitResultStatuses(results),
|
|
709
|
+
counters: countRecruitResultStatuses(results, { greetCount }),
|
|
514
710
|
error: compactError(error, "RECRUIT_IN_PROGRESS_ERROR")
|
|
515
711
|
},
|
|
516
712
|
candidate_list: compactInfiniteListState(listState)
|
|
@@ -566,7 +762,7 @@ export async function runRecruitWorkflow({
|
|
|
566
762
|
reason,
|
|
567
763
|
trigger_error: compactError(error, "RECRUIT_CONTEXT_RECOVERY_TRIGGER"),
|
|
568
764
|
refresh: compactRefresh,
|
|
569
|
-
counters: countRecruitResultStatuses(results)
|
|
765
|
+
counters: countRecruitResultStatuses(results, { greetCount })
|
|
570
766
|
},
|
|
571
767
|
candidate_list: compactInfiniteListState(listState)
|
|
572
768
|
});
|
|
@@ -596,7 +792,7 @@ export async function runRecruitWorkflow({
|
|
|
596
792
|
metadata: {
|
|
597
793
|
card_count: cardNodeIds.length,
|
|
598
794
|
forced_recent_viewed: forceRecentViewed,
|
|
599
|
-
counters: countRecruitResultStatuses(results)
|
|
795
|
+
counters: countRecruitResultStatuses(results, { greetCount })
|
|
600
796
|
}
|
|
601
797
|
});
|
|
602
798
|
listEndReason = "";
|
|
@@ -636,6 +832,12 @@ export async function runRecruitWorkflow({
|
|
|
636
832
|
resetTimeoutMs,
|
|
637
833
|
cityOptionTimeoutMs
|
|
638
834
|
});
|
|
835
|
+
const exchangeResumeStep = searchResult.steps.find((step) => step.step === "exchange_resume");
|
|
836
|
+
searchExchangeResumeFilterApplied = Boolean(
|
|
837
|
+
searchExchangeResumeFilterRequested
|
|
838
|
+
&& exchangeResumeStep?.result?.applied
|
|
839
|
+
&& exchangeResumeStep?.result?.requested === true
|
|
840
|
+
);
|
|
639
841
|
runControl.checkpoint({
|
|
640
842
|
search: {
|
|
641
843
|
search_params: searchResult.search_params,
|
|
@@ -782,6 +984,7 @@ export async function runRecruitWorkflow({
|
|
|
782
984
|
|
|
783
985
|
let screeningCandidate = cardCandidate;
|
|
784
986
|
let detailResult = null;
|
|
987
|
+
let detailActionRootNodeIds = [];
|
|
785
988
|
let recoverableDetailError = null;
|
|
786
989
|
let detailStep = "not_started";
|
|
787
990
|
if (index < detailCountLimit) {
|
|
@@ -815,6 +1018,7 @@ export async function runRecruitWorkflow({
|
|
|
815
1018
|
networkRecorder.clear();
|
|
816
1019
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
817
1020
|
const openedDetail = await openRecruitCardDetail(client, cardNodeId);
|
|
1021
|
+
detailActionRootNodeIds = (openedDetail.detail_state?.roots || []).map((root) => root.nodeId).filter(Boolean);
|
|
818
1022
|
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
819
1023
|
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
820
1024
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
@@ -957,7 +1161,7 @@ export async function runRecruitWorkflow({
|
|
|
957
1161
|
capture_target_wait: captureTargetWait
|
|
958
1162
|
};
|
|
959
1163
|
screeningCandidate = detailResult.candidate;
|
|
960
|
-
if (closeDetail) {
|
|
1164
|
+
if (closeDetail && !postActionEnabled) {
|
|
961
1165
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecruitDetail(client));
|
|
962
1166
|
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
963
1167
|
if (!detailResult.close_result?.closed) {
|
|
@@ -1045,6 +1249,74 @@ export async function runRecruitWorkflow({
|
|
|
1045
1249
|
: useLlmScreening
|
|
1046
1250
|
? llmResultToScreening(llmResult, screeningCandidate)
|
|
1047
1251
|
: screenCandidate(screeningCandidate, { criteria });
|
|
1252
|
+
let actionDiscovery = null;
|
|
1253
|
+
let postActionResult = null;
|
|
1254
|
+
let closeFailureError = null;
|
|
1255
|
+
let closeRecoveryFailure = null;
|
|
1256
|
+
if (postActionEnabled && detailResult) {
|
|
1257
|
+
const postActionStarted = Date.now();
|
|
1258
|
+
await runControl.waitIfPaused();
|
|
1259
|
+
runControl.throwIfCanceled();
|
|
1260
|
+
runControl.setPhase("recruit:post-action");
|
|
1261
|
+
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1262
|
+
actionDiscovery = await waitForRecruitDetailActionControls(client, {
|
|
1263
|
+
rootNodeIds: detailActionRootNodeIds,
|
|
1264
|
+
timeoutMs: actionTimeoutMs,
|
|
1265
|
+
intervalMs: actionIntervalMs,
|
|
1266
|
+
requireAny: true
|
|
1267
|
+
});
|
|
1268
|
+
postActionResult = await runRecruitPostAction({
|
|
1269
|
+
client,
|
|
1270
|
+
rootNodeIds: detailActionRootNodeIds,
|
|
1271
|
+
screening,
|
|
1272
|
+
actionDiscovery,
|
|
1273
|
+
postAction: normalizedPostAction,
|
|
1274
|
+
greetCount,
|
|
1275
|
+
maxGreetCount: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1276
|
+
executePostAction,
|
|
1277
|
+
afterClickDelayMs: actionAfterClickDelayMs,
|
|
1278
|
+
lastGreetQuotaAfterSpend
|
|
1279
|
+
});
|
|
1280
|
+
if (postActionResult.counted_as_greet && postActionResult.action_clicked) {
|
|
1281
|
+
greetCount += 1;
|
|
1282
|
+
}
|
|
1283
|
+
if (postActionResult.greet_quota_after_click?.found) {
|
|
1284
|
+
lastGreetQuotaAfterSpend = postActionResult.greet_quota_after_click;
|
|
1285
|
+
}
|
|
1286
|
+
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1287
|
+
}
|
|
1288
|
+
if (postActionEnabled && detailResult && closeDetail) {
|
|
1289
|
+
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecruitDetail(client));
|
|
1290
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1291
|
+
if (!detailResult.close_result?.closed) {
|
|
1292
|
+
closeFailureError = createRecruitCloseFailureError(detailResult.close_result);
|
|
1293
|
+
try {
|
|
1294
|
+
const recovery = await recoverAndReapplyRecruitContext("detail_close_failed", closeFailureError, {
|
|
1295
|
+
forceRecentViewed: true
|
|
1296
|
+
});
|
|
1297
|
+
detailResult.cv_acquisition = {
|
|
1298
|
+
...(detailResult.cv_acquisition || {}),
|
|
1299
|
+
close_recovery: {
|
|
1300
|
+
ok: Boolean(recovery.ok),
|
|
1301
|
+
method: recovery.method || "",
|
|
1302
|
+
forced_recent_viewed: Boolean(recovery.forced_recent_viewed),
|
|
1303
|
+
card_count: recovery.card_count || 0
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
closeRecoveryFailure = error;
|
|
1308
|
+
detailResult.cv_acquisition = {
|
|
1309
|
+
...(detailResult.cv_acquisition || {}),
|
|
1310
|
+
close_recovery: {
|
|
1311
|
+
ok: false,
|
|
1312
|
+
reason: "context_recovery_failed",
|
|
1313
|
+
error: error?.message || String(error),
|
|
1314
|
+
forced_recent_viewed: true
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1048
1320
|
timings.total_ms = Date.now() - candidateStarted;
|
|
1049
1321
|
const compactResult = {
|
|
1050
1322
|
index,
|
|
@@ -1054,8 +1326,12 @@ export async function runRecruitWorkflow({
|
|
|
1054
1326
|
detail: compactDetail(detailResult),
|
|
1055
1327
|
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
1056
1328
|
screening: compactScreening(screening),
|
|
1329
|
+
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
1330
|
+
post_action: postActionResult,
|
|
1057
1331
|
error: recoverableDetailError
|
|
1058
1332
|
? compactRecoverableDetailError(recoverableDetailError)
|
|
1333
|
+
: closeRecoveryFailure
|
|
1334
|
+
? compactError(closeFailureError, "DETAIL_CLOSE_FAILED")
|
|
1059
1335
|
: detailResult?.image_evidence?.ok === false
|
|
1060
1336
|
? compactError({
|
|
1061
1337
|
code: detailResult.image_evidence.error_code,
|
|
@@ -1123,6 +1399,10 @@ export async function runRecruitWorkflow({
|
|
|
1123
1399
|
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1124
1400
|
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1125
1401
|
}
|
|
1402
|
+
if (postActionResult?.stop_run) {
|
|
1403
|
+
listEndReason = postActionResult.reason || "post_action_stop";
|
|
1404
|
+
break;
|
|
1405
|
+
}
|
|
1126
1406
|
}
|
|
1127
1407
|
|
|
1128
1408
|
runControl.setPhase("recruit:done");
|
|
@@ -1140,10 +1420,13 @@ export async function runRecruitWorkflow({
|
|
|
1140
1420
|
human_rest: humanRestController.getState(),
|
|
1141
1421
|
last_human_event: lastHumanEvent,
|
|
1142
1422
|
list_end_reason: listEndReason || null,
|
|
1423
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
1424
|
+
search_exchange_resume_filter_applied: searchExchangeResumeFilterApplied ? 1 : 0,
|
|
1425
|
+
last_greet_quota_after_spend: lastGreetQuotaAfterSpend,
|
|
1143
1426
|
refresh_rounds: refreshRounds,
|
|
1144
1427
|
refresh_attempts: refreshAttempts,
|
|
1145
1428
|
context_recoveries: contextRecoveryAttempts,
|
|
1146
|
-
...countRecruitResultStatuses(results),
|
|
1429
|
+
...countRecruitResultStatuses(results, { greetCount }),
|
|
1147
1430
|
results
|
|
1148
1431
|
};
|
|
1149
1432
|
}
|
|
@@ -1189,11 +1472,19 @@ export function createRecruitRunService({
|
|
|
1189
1472
|
imageOutputDir = "",
|
|
1190
1473
|
humanRestEnabled = false,
|
|
1191
1474
|
humanBehavior = null,
|
|
1475
|
+
postAction = "none",
|
|
1476
|
+
maxGreetCount = null,
|
|
1477
|
+
executePostAction = true,
|
|
1478
|
+
actionTimeoutMs = 8000,
|
|
1479
|
+
actionIntervalMs = 400,
|
|
1480
|
+
actionAfterClickDelayMs = 900,
|
|
1192
1481
|
name = "recruit-domain-run"
|
|
1193
1482
|
} = {}) {
|
|
1194
1483
|
if (!client) throw new Error("startRecruitRun requires a guarded CDP client");
|
|
1195
1484
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
1196
1485
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1486
|
+
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
1487
|
+
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted !== false;
|
|
1197
1488
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1198
1489
|
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1199
1490
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -1235,7 +1526,15 @@ export function createRecruitRunService({
|
|
|
1235
1526
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1236
1527
|
human_behavior: effectiveHumanBehavior,
|
|
1237
1528
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1238
|
-
human_rest_enabled: effectiveHumanRestEnabled
|
|
1529
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1530
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
1531
|
+
search_exchange_resume_filter_applied: 0,
|
|
1532
|
+
post_action: normalizedPostAction,
|
|
1533
|
+
max_greet_count: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1534
|
+
execute_post_action: Boolean(executePostAction),
|
|
1535
|
+
action_timeout_ms: actionTimeoutMs,
|
|
1536
|
+
action_interval_ms: actionIntervalMs,
|
|
1537
|
+
action_after_click_delay_ms: actionAfterClickDelayMs
|
|
1239
1538
|
},
|
|
1240
1539
|
progress: {
|
|
1241
1540
|
card_count: 0,
|
|
@@ -1255,6 +1554,10 @@ export function createRecruitRunService({
|
|
|
1255
1554
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1256
1555
|
human_rest_count: 0,
|
|
1257
1556
|
human_rest_ms: 0,
|
|
1557
|
+
greet_count: 0,
|
|
1558
|
+
post_action_clicked: 0,
|
|
1559
|
+
search_exchange_resume_filter_requested: searchExchangeResumeFilterRequested ? 1 : 0,
|
|
1560
|
+
search_exchange_resume_filter_applied: 0,
|
|
1258
1561
|
last_human_event: null
|
|
1259
1562
|
},
|
|
1260
1563
|
checkpoint: {},
|
|
@@ -1289,7 +1592,13 @@ export function createRecruitRunService({
|
|
|
1289
1592
|
llmImageDetail,
|
|
1290
1593
|
imageOutputDir,
|
|
1291
1594
|
humanRestEnabled: effectiveHumanRestEnabled,
|
|
1292
|
-
humanBehavior: effectiveHumanBehavior
|
|
1595
|
+
humanBehavior: effectiveHumanBehavior,
|
|
1596
|
+
postAction: normalizedPostAction,
|
|
1597
|
+
maxGreetCount,
|
|
1598
|
+
executePostAction,
|
|
1599
|
+
actionTimeoutMs,
|
|
1600
|
+
actionIntervalMs,
|
|
1601
|
+
actionAfterClickDelayMs
|
|
1293
1602
|
}, runControl)
|
|
1294
1603
|
});
|
|
1295
1604
|
}
|