@reconcrap/boss-recommend-mcp 1.3.38 → 2.0.0

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.
Files changed (85) hide show
  1. package/README.md +53 -33
  2. package/package.json +61 -9
  3. package/skills/boss-recommend-pipeline/SKILL.md +4 -0
  4. package/src/chat-mcp.js +1333 -0
  5. package/src/chat-runtime-config.js +559 -0
  6. package/src/cli.js +1095 -196
  7. package/src/core/browser/index.js +378 -0
  8. package/src/core/capture/index.js +298 -0
  9. package/src/core/cv-acquisition/index.js +219 -0
  10. package/src/core/greet-quota/index.js +54 -0
  11. package/src/core/infinite-list/index.js +459 -0
  12. package/src/core/reporting/legacy-csv.js +332 -0
  13. package/src/core/run/index.js +286 -0
  14. package/src/core/screening/index.js +1166 -0
  15. package/src/core/self-heal/index.js +848 -0
  16. package/src/domains/chat/cards.js +129 -0
  17. package/src/domains/chat/constants.js +183 -0
  18. package/src/domains/chat/detail.js +1369 -0
  19. package/src/domains/chat/index.js +7 -0
  20. package/src/domains/chat/jobs.js +334 -0
  21. package/src/domains/chat/page-guard.js +88 -0
  22. package/src/domains/chat/roots.js +56 -0
  23. package/src/domains/chat/run-service.js +1101 -0
  24. package/src/domains/recommend/actions.js +457 -0
  25. package/src/domains/recommend/cards.js +228 -0
  26. package/src/domains/recommend/constants.js +141 -0
  27. package/src/domains/recommend/detail.js +341 -0
  28. package/src/domains/recommend/filters.js +581 -0
  29. package/src/domains/recommend/index.js +10 -0
  30. package/src/domains/recommend/jobs.js +232 -0
  31. package/src/domains/recommend/refresh.js +204 -0
  32. package/src/domains/recommend/roots.js +78 -0
  33. package/src/domains/recommend/run-service.js +903 -0
  34. package/src/domains/recommend/scopes.js +245 -0
  35. package/src/domains/recruit/actions.js +277 -0
  36. package/src/domains/recruit/cards.js +67 -0
  37. package/src/domains/recruit/constants.js +130 -0
  38. package/src/domains/recruit/detail.js +414 -0
  39. package/src/domains/recruit/index.js +9 -0
  40. package/src/domains/recruit/instruction-parser.js +451 -0
  41. package/src/domains/recruit/refresh.js +40 -0
  42. package/src/domains/recruit/roots.js +68 -0
  43. package/src/domains/recruit/run-service.js +580 -0
  44. package/src/domains/recruit/search.js +1149 -0
  45. package/src/index.js +578 -419
  46. package/src/recommend-mcp.js +1257 -0
  47. package/src/recruit-mcp.js +1035 -0
  48. package/src/adapters.js +0 -3079
  49. package/src/boss-chat.js +0 -1037
  50. package/src/pipeline.js +0 -2249
  51. package/src/recommend-healing-config.js +0 -131
  52. package/src/recommend-healing-rules.json +0 -261
  53. package/src/self-heal.js +0 -2237
  54. package/src/test-adapters-runtime.js +0 -628
  55. package/src/test-boss-chat.js +0 -3196
  56. package/src/test-index-async.js +0 -498
  57. package/src/test-parser.js +0 -742
  58. package/src/test-pipeline.js +0 -2703
  59. package/src/test-run-state.js +0 -152
  60. package/src/test-self-heal.js +0 -224
  61. package/vendor/boss-chat-cli/README.md +0 -134
  62. package/vendor/boss-chat-cli/package.json +0 -53
  63. package/vendor/boss-chat-cli/src/app.js +0 -1501
  64. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  65. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  66. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  67. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  68. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  69. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  70. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  71. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  72. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  73. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  74. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  75. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  76. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  77. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  78. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  79. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  80. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -6927
  81. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  82. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  83. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2294
  84. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  85. package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
@@ -0,0 +1,580 @@
1
+ import { createRunLifecycleManager } from "../../core/run/index.js";
2
+ import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
3
+ import {
4
+ compactCvAcquisitionState,
5
+ countParsedNetworkProfiles,
6
+ createCvAcquisitionState,
7
+ getCvNetworkWaitPlan,
8
+ recordCvImageFallback,
9
+ recordCvNetworkHit,
10
+ recordCvNetworkMiss,
11
+ summarizeImageEvidence,
12
+ waitForCvNetworkEvents
13
+ } from "../../core/cv-acquisition/index.js";
14
+ import {
15
+ compactInfiniteListState,
16
+ createInfiniteListState,
17
+ getNextInfiniteListCandidate,
18
+ markInfiniteListCandidateProcessed,
19
+ resetInfiniteListForRefreshRound
20
+ } from "../../core/infinite-list/index.js";
21
+ import { screenCandidate } from "../../core/screening/index.js";
22
+ import {
23
+ closeRecruitDetail,
24
+ createRecruitDetailNetworkRecorder,
25
+ extractRecruitDetailCandidate,
26
+ openRecruitCardDetail,
27
+ waitForRecruitDetailNetworkEvents
28
+ } from "./detail.js";
29
+ import {
30
+ readRecruitCardCandidate,
31
+ waitForRecruitCardNodeIds
32
+ } from "./cards.js";
33
+ import {
34
+ applyRecruitSearchParams,
35
+ hasRecruitSearchParams,
36
+ normalizeRecruitSearchParams
37
+ } from "./search.js";
38
+ import { refreshRecruitSearchAtEnd } from "./refresh.js";
39
+ import { getRecruitRoots } from "./roots.js";
40
+
41
+ function compactScreening(screening) {
42
+ return {
43
+ status: screening.status,
44
+ passed: screening.passed,
45
+ score: screening.score,
46
+ reasons: screening.reasons,
47
+ candidate: {
48
+ domain: screening.candidate?.domain || "recruit",
49
+ source: screening.candidate?.source || "",
50
+ id: screening.candidate?.id || null,
51
+ identity: screening.candidate?.identity || {}
52
+ }
53
+ };
54
+ }
55
+
56
+ function compactCandidate(candidate) {
57
+ return {
58
+ id: candidate?.id || null,
59
+ identity: candidate?.identity || {},
60
+ text_length: candidate?.text?.raw?.length || 0,
61
+ tag_count: candidate?.tags?.length || 0
62
+ };
63
+ }
64
+
65
+ function compactDetail(detailResult) {
66
+ if (!detailResult) return null;
67
+ return {
68
+ popup_text_length: detailResult.detail?.popup_text?.length || 0,
69
+ resume_text_length: detailResult.detail?.resume_text?.length || 0,
70
+ network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
71
+ parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
72
+ cv_acquisition: detailResult.cv_acquisition || null,
73
+ image_evidence: summarizeImageEvidence(detailResult.image_evidence),
74
+ close_result: detailResult.close_result
75
+ };
76
+ }
77
+
78
+ function normalizeSearchParams(searchParams = {}) {
79
+ return normalizeRecruitSearchParams(searchParams);
80
+ }
81
+
82
+ function compactRefreshAttempt(refreshAttempt) {
83
+ if (!refreshAttempt) return null;
84
+ return {
85
+ ok: Boolean(refreshAttempt.ok),
86
+ method: refreshAttempt.method || "",
87
+ forced_recent_viewed: Boolean(refreshAttempt.forced_recent_viewed),
88
+ card_count: refreshAttempt.card_count || 0,
89
+ search_params: refreshAttempt.search_params || null,
90
+ application: refreshAttempt.application
91
+ ? {
92
+ applied: Boolean(refreshAttempt.application.applied),
93
+ post_search_state: refreshAttempt.application.post_search_state,
94
+ steps: (refreshAttempt.application.steps || []).map((step) => ({
95
+ step: step.step,
96
+ applied: step.result?.applied,
97
+ clicked: step.result?.clicked,
98
+ searched: step.result?.searched,
99
+ reason: step.result?.reason || null
100
+ }))
101
+ }
102
+ : null
103
+ };
104
+ }
105
+
106
+ export async function runRecruitWorkflow({
107
+ client,
108
+ targetUrl = "",
109
+ criteria = "",
110
+ searchParams = {},
111
+ maxCandidates = 5,
112
+ detailLimit = 1,
113
+ closeDetail = true,
114
+ delayMs = 0,
115
+ cardTimeoutMs = 90000,
116
+ resetBeforeSearch = true,
117
+ resetTimeoutMs = 180000,
118
+ cityOptionTimeoutMs = 30000,
119
+ maxImagePages = 8,
120
+ imageWheelDeltaY = 650,
121
+ cvAcquisitionMode = "unknown",
122
+ listMaxScrolls = 20,
123
+ listStableSignatureLimit = 2,
124
+ listWheelDeltaY = 850,
125
+ listSettleMs = 1200,
126
+ listFallbackPoint = null,
127
+ refreshOnEnd = true,
128
+ maxRefreshRounds = 2,
129
+ refreshResetSettleMs = 5000
130
+ } = {}, runControl) {
131
+ if (!client) throw new Error("runRecruitWorkflow requires a guarded CDP client");
132
+ const normalizedSearchParams = normalizeSearchParams(searchParams);
133
+ const limit = Math.max(1, Number(maxCandidates) || 1);
134
+ const detailCountLimit = Math.max(0, Number(detailLimit) || 0);
135
+ const networkRecorder = detailCountLimit > 0
136
+ ? createRecruitDetailNetworkRecorder(client)
137
+ : null;
138
+ const cvAcquisitionState = createCvAcquisitionState({ mode: cvAcquisitionMode });
139
+ const listState = createInfiniteListState({
140
+ domain: "recruit",
141
+ listName: "search-results"
142
+ });
143
+ const results = [];
144
+ const refreshAttempts = [];
145
+ let refreshRounds = 0;
146
+ let cardNodeIds = [];
147
+ let listEndReason = "";
148
+
149
+ runControl.setPhase("recruit:cleanup");
150
+ await closeRecruitDetail(client, { attemptsLimit: 2 });
151
+
152
+ await runControl.waitIfPaused();
153
+ runControl.throwIfCanceled();
154
+ runControl.setPhase("recruit:roots");
155
+ let rootState = await getRecruitRoots(client);
156
+ runControl.checkpoint({
157
+ iframe_selector: rootState.iframe.selector,
158
+ iframe_document_node_id: rootState.iframe.documentNodeId,
159
+ search_params: normalizedSearchParams
160
+ });
161
+
162
+ if (hasRecruitSearchParams(normalizedSearchParams)) {
163
+ await runControl.waitIfPaused();
164
+ runControl.throwIfCanceled();
165
+ runControl.setPhase("recruit:search");
166
+ const searchResult = await applyRecruitSearchParams(client, {
167
+ searchParams: normalizedSearchParams,
168
+ requireCards: true,
169
+ resetBeforeApply: resetBeforeSearch,
170
+ searchTimeoutMs: cardTimeoutMs,
171
+ resetTimeoutMs,
172
+ cityOptionTimeoutMs
173
+ });
174
+ runControl.checkpoint({
175
+ search: {
176
+ search_params: searchResult.search_params,
177
+ before_counts: searchResult.before_counts,
178
+ post_search_state: searchResult.post_search_state,
179
+ steps: searchResult.steps.map((step) => ({
180
+ step: step.step,
181
+ applied: step.result?.applied,
182
+ clicked: step.result?.clicked,
183
+ searched: step.result?.searched,
184
+ reason: step.result?.reason || null
185
+ }))
186
+ }
187
+ });
188
+ rootState = await getRecruitRoots(client);
189
+ }
190
+
191
+ await runControl.waitIfPaused();
192
+ runControl.throwIfCanceled();
193
+ runControl.setPhase("recruit:cards");
194
+ cardNodeIds = await waitForRecruitCardNodeIds(client, rootState.iframe.documentNodeId, {
195
+ timeoutMs: cardTimeoutMs,
196
+ intervalMs: 300
197
+ });
198
+ if (!cardNodeIds.length) {
199
+ throw new Error("No recruit/search candidate cards found for run service");
200
+ }
201
+
202
+ runControl.updateProgress({
203
+ card_count: cardNodeIds.length,
204
+ target_count: limit,
205
+ processed: 0,
206
+ screened: 0,
207
+ detail_opened: 0,
208
+ passed: 0,
209
+ unique_seen: compactInfiniteListState(listState).seen_count,
210
+ scroll_count: 0,
211
+ refresh_rounds: 0,
212
+ refresh_attempts: 0
213
+ });
214
+
215
+ while (results.length < limit) {
216
+ await runControl.waitIfPaused();
217
+ runControl.throwIfCanceled();
218
+ runControl.setPhase("recruit:candidate");
219
+
220
+ const nextCandidateResult = await getNextInfiniteListCandidate({
221
+ client,
222
+ state: listState,
223
+ maxScrolls: listMaxScrolls,
224
+ stableSignatureLimit: listStableSignatureLimit,
225
+ wheelDeltaY: listWheelDeltaY,
226
+ settleMs: listSettleMs,
227
+ fallbackPoint: listFallbackPoint,
228
+ findNodeIds: async () => {
229
+ const currentRootState = await getRecruitRoots(client);
230
+ const currentCardNodeIds = await waitForRecruitCardNodeIds(client, currentRootState.iframe.documentNodeId, {
231
+ timeoutMs: Math.min(cardTimeoutMs, 5000),
232
+ intervalMs: 300
233
+ });
234
+ cardNodeIds = currentCardNodeIds;
235
+ return currentCardNodeIds;
236
+ },
237
+ readCandidate: async (nodeId, { visibleIndex }) => readRecruitCardCandidate(client, nodeId, {
238
+ targetUrl,
239
+ source: "recruit-run-card",
240
+ metadata: {
241
+ run_candidate_index: results.length,
242
+ visible_index: visibleIndex,
243
+ search_params: normalizedSearchParams
244
+ }
245
+ })
246
+ });
247
+ if (!nextCandidateResult.ok) {
248
+ listEndReason = nextCandidateResult.reason || "list_exhausted";
249
+ if (
250
+ nextCandidateResult.end_reached
251
+ && refreshOnEnd
252
+ && results.length < limit
253
+ && refreshRounds < Math.max(0, Number(maxRefreshRounds) || 0)
254
+ ) {
255
+ await runControl.waitIfPaused();
256
+ runControl.throwIfCanceled();
257
+ runControl.setPhase("recruit:refresh");
258
+ refreshRounds += 1;
259
+ const refreshResult = await refreshRecruitSearchAtEnd(client, {
260
+ searchParams: normalizedSearchParams,
261
+ requireCards: true,
262
+ searchTimeoutMs: cardTimeoutMs,
263
+ resetTimeoutMs,
264
+ resetSettleMs: refreshResetSettleMs,
265
+ cityOptionTimeoutMs
266
+ });
267
+ const compactRefresh = compactRefreshAttempt(refreshResult);
268
+ refreshAttempts.push(compactRefresh);
269
+ runControl.checkpoint({
270
+ refresh_round: refreshRounds,
271
+ refresh: compactRefresh
272
+ });
273
+ runControl.updateProgress({
274
+ card_count: refreshResult.card_count || cardNodeIds.length,
275
+ target_count: limit,
276
+ processed: results.length,
277
+ screened: results.length,
278
+ detail_opened: results.filter((item) => item.detail).length,
279
+ passed: results.filter((item) => item.screening.passed).length,
280
+ unique_seen: compactInfiniteListState(listState).seen_count,
281
+ scroll_count: compactInfiniteListState(listState).scroll_count,
282
+ refresh_rounds: refreshRounds,
283
+ refresh_attempts: refreshAttempts.length,
284
+ refresh_method: refreshResult.method || null,
285
+ refresh_forced_recent_viewed: true,
286
+ list_end_reason: listEndReason
287
+ });
288
+ if (refreshResult.ok) {
289
+ rootState = await getRecruitRoots(client);
290
+ cardNodeIds = await waitForRecruitCardNodeIds(client, rootState.iframe.documentNodeId, {
291
+ timeoutMs: cardTimeoutMs,
292
+ intervalMs: 300
293
+ });
294
+ resetInfiniteListForRefreshRound(listState, {
295
+ reason: listEndReason,
296
+ round: refreshRounds,
297
+ method: refreshResult.method,
298
+ metadata: {
299
+ card_count: cardNodeIds.length,
300
+ forced_recent_viewed: true
301
+ }
302
+ });
303
+ listEndReason = "";
304
+ continue;
305
+ }
306
+ }
307
+ break;
308
+ }
309
+
310
+ const index = results.length;
311
+ const cardNodeId = nextCandidateResult.item.node_id;
312
+ const candidateKey = nextCandidateResult.item.key;
313
+ const cardCandidate = nextCandidateResult.item.candidate;
314
+
315
+ let screeningCandidate = cardCandidate;
316
+ let detailResult = null;
317
+ if (index < detailCountLimit) {
318
+ await runControl.waitIfPaused();
319
+ runControl.throwIfCanceled();
320
+ runControl.setPhase("recruit:detail");
321
+ networkRecorder.clear();
322
+ const openedDetail = await openRecruitCardDetail(client, cardNodeId);
323
+ const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
324
+ const networkWait = await waitForCvNetworkEvents(
325
+ waitForRecruitDetailNetworkEvents,
326
+ networkRecorder,
327
+ {
328
+ waitPlan,
329
+ minCount: 1,
330
+ requireLoaded: true,
331
+ intervalMs: 120
332
+ }
333
+ );
334
+ detailResult = await extractRecruitDetailCandidate(client, {
335
+ cardCandidate,
336
+ cardNodeId,
337
+ detailState: openedDetail.detail_state,
338
+ networkEvents: networkRecorder.events,
339
+ targetUrl,
340
+ closeDetail: false
341
+ });
342
+ const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
343
+ let source = "network";
344
+ let imageEvidence = null;
345
+ if (parsedNetworkProfileCount > 0) {
346
+ recordCvNetworkHit(cvAcquisitionState, {
347
+ parsedNetworkProfileCount,
348
+ waitResult: networkWait
349
+ });
350
+ } else {
351
+ const captureNodeId = openedDetail.detail_state?.popup?.node_id
352
+ || openedDetail.detail_state?.resumeIframe?.node_id
353
+ || null;
354
+ if (captureNodeId) {
355
+ imageEvidence = await captureScrolledNodeScreenshots(client, captureNodeId, {
356
+ padding: 4,
357
+ maxScreenshots: maxImagePages,
358
+ wheelDeltaY: imageWheelDeltaY,
359
+ settleMs: 1200,
360
+ metadata: {
361
+ domain: "recruit",
362
+ capture_mode: "scroll_sequence",
363
+ acquisition_reason: "network_miss_image_fallback",
364
+ run_candidate_index: index,
365
+ candidate_key: candidateKey
366
+ }
367
+ });
368
+ source = "image";
369
+ recordCvImageFallback(cvAcquisitionState, {
370
+ parsedNetworkProfileCount,
371
+ waitResult: networkWait,
372
+ imageEvidence
373
+ });
374
+ } else {
375
+ source = "missing_capture_node";
376
+ recordCvNetworkMiss(cvAcquisitionState, {
377
+ reason: "network_miss_no_capture_node",
378
+ parsedNetworkProfileCount,
379
+ waitResult: networkWait
380
+ });
381
+ }
382
+ }
383
+
384
+ let closeResult = null;
385
+ if (closeDetail) {
386
+ closeResult = await closeRecruitDetail(client);
387
+ }
388
+ detailResult.close_result = closeResult;
389
+ detailResult.image_evidence = imageEvidence;
390
+ detailResult.cv_acquisition = {
391
+ source,
392
+ mode_after: compactCvAcquisitionState(cvAcquisitionState).mode,
393
+ wait_plan: waitPlan,
394
+ network_wait: networkWait,
395
+ parsed_network_profile_count: parsedNetworkProfileCount,
396
+ image_evidence: summarizeImageEvidence(imageEvidence)
397
+ };
398
+ screeningCandidate = detailResult.candidate;
399
+ }
400
+
401
+ await runControl.waitIfPaused();
402
+ runControl.throwIfCanceled();
403
+ runControl.setPhase("recruit:screening");
404
+ const screening = screenCandidate(screeningCandidate, { criteria });
405
+ const compactResult = {
406
+ index,
407
+ candidate_key: candidateKey,
408
+ card_node_id: cardNodeId,
409
+ candidate: compactCandidate(screeningCandidate),
410
+ detail: compactDetail(detailResult),
411
+ screening: compactScreening(screening)
412
+ };
413
+ results.push(compactResult);
414
+ markInfiniteListCandidateProcessed(listState, candidateKey, {
415
+ metadata: {
416
+ result_index: index,
417
+ candidate_id: screeningCandidate.id || null
418
+ }
419
+ });
420
+
421
+ runControl.updateProgress({
422
+ card_count: cardNodeIds.length,
423
+ target_count: limit,
424
+ processed: results.length,
425
+ screened: results.length,
426
+ detail_opened: results.filter((item) => item.detail).length,
427
+ passed: results.filter((item) => item.screening.passed).length,
428
+ unique_seen: compactInfiniteListState(listState).seen_count,
429
+ scroll_count: compactInfiniteListState(listState).scroll_count,
430
+ refresh_rounds: refreshRounds,
431
+ refresh_attempts: refreshAttempts.length,
432
+ list_end_reason: listEndReason || null,
433
+ last_candidate_id: screeningCandidate.id || null,
434
+ last_candidate_key: candidateKey,
435
+ last_score: screening.score
436
+ });
437
+ runControl.checkpoint({
438
+ last_candidate: {
439
+ id: screeningCandidate.id || null,
440
+ key: candidateKey,
441
+ identity: screeningCandidate.identity || {},
442
+ screening: {
443
+ status: screening.status,
444
+ passed: screening.passed,
445
+ score: screening.score
446
+ }
447
+ }
448
+ });
449
+
450
+ if (delayMs > 0) {
451
+ await runControl.sleep(delayMs);
452
+ }
453
+ }
454
+
455
+ runControl.setPhase("recruit:done");
456
+ return {
457
+ domain: "recruit",
458
+ target_url: targetUrl,
459
+ search_params: normalizedSearchParams,
460
+ card_count: cardNodeIds.length,
461
+ candidate_list: compactInfiniteListState(listState),
462
+ list_end_reason: listEndReason || null,
463
+ refresh_rounds: refreshRounds,
464
+ refresh_attempts: refreshAttempts,
465
+ processed: results.length,
466
+ screened: results.length,
467
+ detail_opened: results.filter((item) => item.detail).length,
468
+ passed: results.filter((item) => item.screening.passed).length,
469
+ results
470
+ };
471
+ }
472
+
473
+ export function createRecruitRunService({
474
+ lifecycle,
475
+ idPrefix = "recruit",
476
+ workflow = runRecruitWorkflow
477
+ } = {}) {
478
+ const manager = lifecycle || createRunLifecycleManager({ idPrefix });
479
+
480
+ function startRecruitRun({
481
+ client,
482
+ targetUrl = "",
483
+ criteria = "",
484
+ searchParams = {},
485
+ maxCandidates = 5,
486
+ detailLimit = 1,
487
+ closeDetail = true,
488
+ delayMs = 0,
489
+ cardTimeoutMs = 90000,
490
+ resetBeforeSearch = true,
491
+ resetTimeoutMs = 180000,
492
+ cityOptionTimeoutMs = 30000,
493
+ maxImagePages = 8,
494
+ imageWheelDeltaY = 650,
495
+ cvAcquisitionMode = "unknown",
496
+ listMaxScrolls = 20,
497
+ listStableSignatureLimit = 2,
498
+ listWheelDeltaY = 850,
499
+ listSettleMs = 1200,
500
+ listFallbackPoint = null,
501
+ refreshOnEnd = true,
502
+ maxRefreshRounds = 2,
503
+ refreshResetSettleMs = 5000,
504
+ name = "recruit-domain-run"
505
+ } = {}) {
506
+ if (!client) throw new Error("startRecruitRun requires a guarded CDP client");
507
+ const normalizedSearchParams = normalizeSearchParams(searchParams);
508
+ return manager.startRun({
509
+ name,
510
+ context: {
511
+ domain: "recruit",
512
+ target_url: targetUrl,
513
+ criteria_present: Boolean(criteria),
514
+ search_params: normalizedSearchParams,
515
+ max_candidates: maxCandidates,
516
+ detail_limit: detailLimit,
517
+ close_detail: closeDetail,
518
+ reset_before_search: resetBeforeSearch,
519
+ reset_timeout_ms: resetTimeoutMs,
520
+ city_option_timeout_ms: cityOptionTimeoutMs,
521
+ cv_acquisition_mode: cvAcquisitionMode,
522
+ max_image_pages: maxImagePages,
523
+ image_wheel_delta_y: imageWheelDeltaY,
524
+ list_max_scrolls: listMaxScrolls,
525
+ list_stable_signature_limit: listStableSignatureLimit,
526
+ list_wheel_delta_y: listWheelDeltaY,
527
+ list_settle_ms: listSettleMs,
528
+ list_fallback_point: listFallbackPoint,
529
+ refresh_on_end: refreshOnEnd,
530
+ max_refresh_rounds: maxRefreshRounds,
531
+ refresh_reset_settle_ms: refreshResetSettleMs
532
+ },
533
+ progress: {
534
+ card_count: 0,
535
+ target_count: Math.max(1, Number(maxCandidates) || 1),
536
+ processed: 0,
537
+ screened: 0,
538
+ detail_opened: 0,
539
+ passed: 0
540
+ },
541
+ checkpoint: {},
542
+ task: (runControl) => workflow({
543
+ client,
544
+ targetUrl,
545
+ criteria,
546
+ searchParams: normalizedSearchParams,
547
+ maxCandidates,
548
+ detailLimit,
549
+ closeDetail,
550
+ delayMs,
551
+ cardTimeoutMs,
552
+ resetBeforeSearch,
553
+ resetTimeoutMs,
554
+ cityOptionTimeoutMs,
555
+ maxImagePages,
556
+ imageWheelDeltaY,
557
+ cvAcquisitionMode,
558
+ listMaxScrolls,
559
+ listStableSignatureLimit,
560
+ listWheelDeltaY,
561
+ listSettleMs,
562
+ listFallbackPoint,
563
+ refreshOnEnd,
564
+ maxRefreshRounds,
565
+ refreshResetSettleMs
566
+ }, runControl)
567
+ });
568
+ }
569
+
570
+ return {
571
+ startRecruitRun,
572
+ getRecruitRun: manager.getRun,
573
+ pauseRecruitRun: manager.pauseRun,
574
+ resumeRecruitRun: manager.resumeRun,
575
+ cancelRecruitRun: manager.cancelRun,
576
+ waitForRecruitRun: manager.waitForRun,
577
+ listRecruitRuns: manager.listRuns,
578
+ manager
579
+ };
580
+ }