@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.
@@ -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
  }