@reconcrap/boss-recommend-mcp 2.0.57 → 2.1.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.
package/README.md CHANGED
@@ -259,13 +259,16 @@ config/screening-config.example.json
259
259
  - `debugPort`:未显式传 `port` 时,recommend / search / chat CDP-only MCP run 和健康检查默认连接这个 Chrome 调试端口。
260
260
  - `outputDir`:recommend / search / chat 完成后的最终 CSV 与 report JSON 会写入这里;run state / checkpoint 仍保留在各自状态目录,方便 pause/resume/cancel。
261
261
  - `llmThinkingLevel`:默认 `low`。可设为 `off/minimal/low/medium/high/auto/current`,用于控制 OpenAI-compatible LLM 的 thinking/reasoning 强度。
262
- - `humanBehavior`:默认 `{ "enabled": true, "profile": "paced_with_rests" }`。用于 recommend / search / chat 的可靠性实验,支持:
262
+ - `humanBehavior`:默认 `{ "enabled": true, "profile": "paced_with_rests", "restLevel": "low" }`。用于 recommend / search / chat 的可靠性实验,支持:
263
263
  - `profile: "baseline"`:关闭人类节奏,保持确定性行为。
264
264
  - `profile: "paced"`:启用 CDP-only Bezier 鼠标移动、较大按钮的安全 inset 点击点、分块 `Input.insertText`、列表 wheel/settle jitter,以及小的动作前后读秒。
265
265
  - `profile: "paced_with_rests"`:在 `paced` 基础上启用候选人短休和批次休息。
266
+ - `restLevel: "low"`:保持旧版休息策略不变,候选人短休 8% 概率暂停 3-7 秒,批次休息约每 25-32 人暂停 15-30 秒。
267
+ - `restLevel: "medium"`:随机分散短/长休息,平均目标约每 5 小时或 700 位候选人累计休息 30 分钟。
268
+ - `restLevel: "high"`:随机分散短/长休息,平均目标约每 5 小时或 700 位候选人累计休息 1 小时。
266
269
  - `humanRestEnabled`:兼容旧配置。设为 `true` 时等价于 `humanBehavior.profile="paced_with_rests"`;设为 `false` 时不会关闭当前默认节奏。如需关闭,请显式设置 `humanBehavior.enabled=false` 或 `humanBehavior.profile="baseline"`。
267
270
  - recommend / search / chat 图片简历 fallback 与主列表滚动都会在启用 `listScrollJitter` 时使用 coverage-safe scroll jitter:每次 wheel delta 在安全范围内变化,并保留截图重叠、重复检测、bottom-marker / stop-boundary 逻辑,实际 delta 和 settle 时间会写入 artifact metadata。
268
- - chat/recommend/search run 也兼容显式参数 `safe_pacing` 与 `batch_rest_enabled`:run 参数优先于配置文件。
271
+ - chat/recommend/search run 也兼容显式参数 `safe_pacing`、`batch_rest_enabled` 与 `human_behavior.restLevel`:run 参数优先于配置文件。AI harness/skill 启动每次 run 前必须让用户明确选择 `low/medium/high`,再把选择写入 `human_behavior.restLevel`。
269
272
 
270
273
  ## 常用命令
271
274
 
@@ -16,6 +16,7 @@
16
16
  "humanBehavior": {
17
17
  "enabled": true,
18
18
  "profile": "paced_with_rests",
19
+ "restLevel": "low",
19
20
  "clickMovement": true,
20
21
  "textEntry": true,
21
22
  "listScrollJitter": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.57",
3
+ "version": "2.1.0",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -17,7 +17,7 @@ Please run a Boss chat-only task (do not switch to recommend flow).
17
17
  Execution order:
18
18
  1) Call boss_chat_health_check.
19
19
  2) Call prepare_boss_chat_run once (empty params allowed) to fetch job_options and missing fields.
20
- 3) Ask for these required fields in one shot: job, start_from (unread/all), target_count, criteria.
20
+ 3) Ask for these required fields in one shot: job, start_from (unread/all), target_count, criteria, rest_level (low/medium/high).
21
21
  4) After user reply, call start_boss_chat_run exactly once to start the run.
22
22
  5) If ACCEPTED, reply only with run_id and "task started"; no auto polling.
23
23
 
@@ -27,6 +27,7 @@ Anti-loop rules:
27
27
  - Do not use start_boss_chat_run for preflight. It is only for the final start call and must include job/start_from/target_count/criteria.
28
28
  - Do not call start_boss_chat_run repeatedly in one turn.
29
29
  - Do not call get_boss_chat_run unless user explicitly asks for progress.
30
+ - Do not choose rest_level for the user. Pass the user's choice as `human_behavior.restLevel`.
30
31
 
31
32
  target_count mapping:
32
33
  - Positive integer means explicit cap (for example 20).
@@ -27,6 +27,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
27
27
  - `start_from`: `unread|all`
28
28
  - `target_count`
29
29
  - `criteria`
30
+ - `rest_level`: `low|medium|high`
30
31
 
31
32
  可选:
32
33
 
@@ -38,6 +39,12 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
38
39
  - `safe_pacing`
39
40
  - `batch_rest_enabled`
40
41
 
42
+ 启动工具时,把用户确认的休息强度写入 `human_behavior.restLevel`,例如:
43
+
44
+ ```json
45
+ { "human_behavior": { "restLevel": "medium" } }
46
+ ```
47
+
41
48
  `greeting_text` 默认规则:
42
49
 
43
50
  - 本次显式传入 `greeting_text`:使用本次值
@@ -62,6 +69,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
62
69
  - 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
63
70
  - `job` / `start_from` / `criteria` 缺一不可;缺参时只补缺口。
64
71
  - `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
72
+ - 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
65
73
  - 当用户说“全部候选人/所有候选人”时,必须按“扫到底(unlimited)”处理,不要再追问正整数。
66
74
  - 参数名必须写 `target_count`(不要写“目标数量”等中文键名)。
67
75
  - 当用户选择“扫到底/全部候选人/所有候选人”时,调用参数优先写:`"target_count": "all"`;`-1` 只作为兼容输入和内部 CLI 表示。
@@ -89,5 +97,5 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
89
97
 
90
98
  - 用结构化中文。
91
99
  - 首轮建议先调用一次 `prepare_boss_chat_run`(可空参)获取 `job_options` 与 `pending_questions`。
92
- - 缺参时必须逐项确认:`job`(来自岗位列表)、`start_from`(`unread|all`)、`target_count`、`criteria`。
100
+ - 缺参时必须逐项确认:`job`(来自岗位列表)、`start_from`(`unread|all`)、`target_count`、`criteria`、`rest_level`。
93
101
  - 若健康检查失败,明确提示共享配置文件 `screening-config.json` 不可用。
@@ -7,6 +7,7 @@
7
7
  - 先确认推荐页 filters
8
8
  - 再确认筛选 criteria
9
9
  - 再确认本次运行统一动作 `favorite` 或 `greet`
10
+ - 每次运行都要让用户明确选择休息强度 `low` / `medium` / `high`,并传入 `human_behavior.restLevel`
10
11
  - 只确认一次 `post_action`,不要逐个候选人反复确认
11
12
  - 运行中临时中断请使用 `pause_recommend_pipeline_run`(按 run_id),不要靠自然语言“暂停/继续”指令
12
13
  - 继续执行请使用 `resume_recommend_pipeline_run`;状态查询默认按用户指令触发,不自动轮询
@@ -20,7 +20,8 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
20
20
  - **确认不可代填(强制)**
21
21
  - 禁止 agent 自行“设置合理参数”并代替用户确认。
22
22
  - 禁止在用户未明确回复前,把任意 `*_confirmed` 字段设为 `true`。
23
- - 禁止在用户未明确回复前,自行填充 `page_scope/school_tag/degree/gender/recent_not_view/criteria/target_count/post_action/max_greet_count/job`。
23
+ - 禁止在用户未明确回复前,自行填充 `page_scope/school_tag/degree/gender/recent_not_view/criteria/target_count/post_action/max_greet_count/job/rest_level`。
24
+ - 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
24
25
  - 若工具返回 `pending_questions`,必须逐项向用户提问并等待用户回复;不得跳过提问直接执行。
25
26
 
26
27
  - **岗位确认时机**
@@ -54,6 +55,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
54
55
  - `target_count`(可空)
55
56
  - `post_action`:`favorite|greet|none`
56
57
  - `max_greet_count`(仅当 `post_action=greet`)
58
+ - `rest_level`:`low|medium|high`
57
59
 
58
60
  ### Stage B (页面就绪后)
59
61
 
@@ -82,6 +84,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
82
84
  - `gender`: `不限/男/女`
83
85
  - `recent_not_view`: `不限/近14天没有`
84
86
  - `post_action`: `favorite/greet/none`
87
+ - `rest_level`: `low/medium/high`
85
88
 
86
89
  ## Tool Usage
87
90
 
@@ -94,6 +97,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
94
97
  - 关键输入:
95
98
  - `confirmation`:`page_confirmed/page_value/filters_confirmed/school_tag_confirmed.../job_confirmed/job_value/final_confirmed`
96
99
  - `overrides`:`page_scope/school_tag/degree/gender/recent_not_view/criteria/job/target_count/post_action/max_greet_count`
100
+ - `human_behavior`:必须包含本次用户确认的 `restLevel`(例如 `{ "restLevel": "medium" }`)
97
101
  - 不要传 `follow_up.chat`;该路径属于 legacy-only 行为
98
102
 
99
103
  最小策略:
@@ -15,3 +15,5 @@ This skill intentionally replaces legacy `boss-recruit-mcp` skill installs. It r
15
15
  - `cancel_recruit_pipeline_run`
16
16
 
17
17
  Do not call the old `@reconcrap/boss-recruit-mcp` package from this skill.
18
+
19
+ Each run must ask the user to choose `rest_level` (`low` / `medium` / `high`) and pass the answer as `human_behavior.restLevel`; do not pick a default for the user.
@@ -32,6 +32,7 @@ description: "Use when users want Boss search/recruit-page screening via the uni
32
32
  - 若用户提供城市、学历、学校、关键词、过滤已看、人选目标数、筛选条件、post action、max greet 等参数,必须逐项传入或确认。
33
33
  - `post_action=greet` 时必须确认 `max_greet_count`;不要默认等于 `target_count`。
34
34
  - 搜索页和推荐页一样支持多选筛选条件;不要把多选降级成单选。
35
+ - 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
35
36
 
36
37
  ## Required Inputs
37
38
 
@@ -39,6 +40,7 @@ description: "Use when users want Boss search/recruit-page screening via the uni
39
40
  - `keyword` 或用户明确的搜索意图
40
41
  - `criteria`
41
42
  - `target_count`
43
+ - `rest_level`: `low|medium|high`
42
44
 
43
45
  常用可选项:
44
46
 
@@ -50,6 +52,12 @@ description: "Use when users want Boss search/recruit-page screening via the uni
50
52
  - `max_greet_count`
51
53
  - `port`
52
54
 
55
+ 启动工具时,把用户确认的休息强度写入 `human_behavior.restLevel`,例如:
56
+
57
+ ```json
58
+ { "human_behavior": { "restLevel": "medium" } }
59
+ ```
60
+
53
61
  ## Response Style
54
62
 
55
63
  - 用结构化中文确认参数。
package/src/cli.js CHANGED
@@ -63,12 +63,13 @@ const installConfigDefaults = Object.freeze({
63
63
  llmMaxRetries: 3,
64
64
  llmImageLimit: 8,
65
65
  llmImageDetail: "low",
66
- humanRestEnabled: true,
67
- humanBehavior: {
68
- enabled: true,
69
- profile: "paced_with_rests"
70
- }
71
- });
66
+ humanRestEnabled: true,
67
+ humanBehavior: {
68
+ enabled: true,
69
+ profile: "paced_with_rests",
70
+ restLevel: "low"
71
+ }
72
+ });
72
73
  const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
73
74
  const bossChatCliUnsupportedStartCode = "CHAT_CLI_ASYNC_UNSUPPORTED_CDP_ONLY";
74
75
  const calibrateUnsupportedCode = "CALIBRATE_UNSUPPORTED_CDP_ONLY";
@@ -613,14 +614,20 @@ function parseBossChatTargetCountOption(raw) {
613
614
  return parsed ?? text;
614
615
  }
615
616
 
616
- function parseBooleanOption(raw, fallback = undefined) {
617
- if (raw === undefined || raw === null || raw === "") return fallback;
618
- if (raw === true) return true;
619
- const normalized = String(raw).trim().toLowerCase();
620
- if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
621
- if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
622
- return fallback;
623
- }
617
+ function parseBooleanOption(raw, fallback = undefined) {
618
+ if (raw === undefined || raw === null || raw === "") return fallback;
619
+ if (raw === true) return true;
620
+ const normalized = String(raw).trim().toLowerCase();
621
+ if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
622
+ if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
623
+ return fallback;
624
+ }
625
+
626
+ function parseRestLevelOption(raw) {
627
+ if (raw === undefined || raw === null || raw === "") return undefined;
628
+ const normalized = String(raw).trim().toLowerCase();
629
+ return ["low", "medium", "high"].includes(normalized) ? normalized : undefined;
630
+ }
624
631
 
625
632
  function normalizePageScope(value) {
626
633
  const normalized = String(value || "").trim().toLowerCase();
@@ -1271,20 +1278,33 @@ async function resolveCliConfigTarget(options = {}) {
1271
1278
  };
1272
1279
  }
1273
1280
 
1274
- function applyMissingInstallConfigDefaults(config = {}) {
1275
- const nextConfig = { ...config };
1276
- const patchedKeys = [];
1277
- for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
1281
+ function applyMissingInstallConfigDefaults(config = {}) {
1282
+ const nextConfig = { ...config };
1283
+ const patchedKeys = [];
1284
+ for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
1278
1285
  if (!Object.prototype.hasOwnProperty.call(nextConfig, key)) {
1279
1286
  nextConfig[key] = defaultValue;
1280
- patchedKeys.push(key);
1281
- }
1282
- }
1283
- return {
1284
- nextConfig,
1285
- patchedKeys
1286
- };
1287
- }
1287
+ patchedKeys.push(key);
1288
+ }
1289
+ }
1290
+ if (
1291
+ nextConfig.humanBehavior
1292
+ && typeof nextConfig.humanBehavior === "object"
1293
+ && !Array.isArray(nextConfig.humanBehavior)
1294
+ && !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "restLevel")
1295
+ && !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "rest_level")
1296
+ ) {
1297
+ nextConfig.humanBehavior = {
1298
+ ...nextConfig.humanBehavior,
1299
+ restLevel: "low"
1300
+ };
1301
+ patchedKeys.push("humanBehavior.restLevel");
1302
+ }
1303
+ return {
1304
+ nextConfig,
1305
+ patchedKeys
1306
+ };
1307
+ }
1288
1308
 
1289
1309
  async function ensureUserConfig(options = {}) {
1290
1310
  const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
@@ -2721,19 +2741,30 @@ async function runPipelineOnce(options = {}) {
2721
2741
  const workspaceRoot = getWorkspaceRoot(options);
2722
2742
  const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
2723
2743
 
2724
- const args = {
2725
- instruction,
2726
- confirmation: confirmation ?? undefined,
2727
- overrides: overrides ?? undefined,
2728
- follow_up: followUp ?? undefined,
2744
+ const args = {
2745
+ instruction,
2746
+ confirmation: confirmation ?? undefined,
2747
+ overrides: overrides ?? undefined,
2748
+ follow_up: followUp ?? undefined,
2729
2749
  host: typeof options.host === "string" && options.host.trim() ? options.host.trim() : undefined,
2730
2750
  port,
2731
2751
  target_url_includes: typeof options["target-url-includes"] === "string" && options["target-url-includes"].trim()
2732
2752
  ? options["target-url-includes"].trim()
2733
2753
  : undefined,
2734
2754
  allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
2735
- slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
2736
- };
2755
+ slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
2756
+ };
2757
+ const restLevel = parseRestLevelOption(
2758
+ options["rest-level"]
2759
+ ?? options.rest_level
2760
+ ?? options["human-behavior-rest-level"]
2761
+ ?? options.human_behavior_rest_level
2762
+ );
2763
+ if (restLevel) {
2764
+ args.human_behavior = {
2765
+ restLevel
2766
+ };
2767
+ }
2737
2768
 
2738
2769
  const optionalPassthrough = [
2739
2770
  "detail_limit",
@@ -2810,16 +2841,22 @@ async function listRecommendJobsCli(options = {}) {
2810
2841
  }));
2811
2842
  }
2812
2843
 
2813
- function buildBossChatCliInput(options = {}) {
2814
- const greetingTextRaw =
2815
- options["greeting-text"]
2816
- ?? options.greeting_text
2817
- ?? options.greetingText
2818
- ?? options.greeting;
2819
- const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
2820
- const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
2821
- const host = String(options.host || "").trim();
2822
- return {
2844
+ function buildBossChatCliInput(options = {}) {
2845
+ const greetingTextRaw =
2846
+ options["greeting-text"]
2847
+ ?? options.greeting_text
2848
+ ?? options.greetingText
2849
+ ?? options.greeting;
2850
+ const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
2851
+ const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
2852
+ const host = String(options.host || "").trim();
2853
+ const restLevel = parseRestLevelOption(
2854
+ options["rest-level"]
2855
+ ?? options.rest_level
2856
+ ?? options["human-behavior-rest-level"]
2857
+ ?? options.human_behavior_rest_level
2858
+ );
2859
+ return {
2823
2860
  profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
2824
2861
  job: typeof options.job === "string" ? options.job.trim() : undefined,
2825
2862
  start_from: String(options["start-from"] || options.start_from || "").trim().toLowerCase() || undefined,
@@ -2837,13 +2874,18 @@ function buildBossChatCliInput(options = {}) {
2837
2874
  dry_run: options["dry-run"] === true || options.dryRun === true,
2838
2875
  no_state: options["no-state"] === true || options.noState === true,
2839
2876
  human_behavior_enabled: parseBooleanOption(options["human-behavior-enabled"] ?? options.human_behavior_enabled),
2840
- human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
2841
- ? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
2842
- : undefined,
2843
- safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
2844
- batch_rest_enabled: parseBooleanOption(options["batch-rest"] ?? options.batch_rest_enabled)
2845
- };
2846
- }
2877
+ human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
2878
+ ? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
2879
+ : undefined,
2880
+ human_behavior: restLevel
2881
+ ? {
2882
+ restLevel
2883
+ }
2884
+ : undefined,
2885
+ safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
2886
+ batch_rest_enabled: parseBooleanOption(options["batch-rest"] ?? options.batch_rest_enabled)
2887
+ };
2888
+ }
2847
2889
 
2848
2890
  function getBossChatCliRunTarget(options = {}) {
2849
2891
  return {
@@ -86,6 +86,46 @@ const HUMAN_BEHAVIOR_PROFILE_ALIASES = Object.freeze({
86
86
  rests: "paced_with_rests",
87
87
  rest: "paced_with_rests"
88
88
  });
89
+ const DEFAULT_HUMAN_REST_LEVEL = "low";
90
+ const HUMAN_REST_LEVEL_ALIASES = Object.freeze({
91
+ default: "low",
92
+ light: "low",
93
+ normal: "medium",
94
+ med: "medium",
95
+ heavy: "high"
96
+ });
97
+ const HUMAN_REST_LEVEL_PROFILES = Object.freeze({
98
+ medium: Object.freeze({
99
+ targetRestMs: 30 * 60 * 1000,
100
+ targetCandidateCount: 700,
101
+ targetWindowMs: 5 * 60 * 60 * 1000,
102
+ intervalMin: 4,
103
+ intervalMax: 16,
104
+ longRestProbability: 0.22,
105
+ shortRestMinMs: 8000,
106
+ shortRestMaxMs: 45000,
107
+ longRestMinMs: 60000,
108
+ longRestMaxMs: 180000,
109
+ minDebtToRestMs: 8000,
110
+ forceDebtMs: 90000,
111
+ maxOverspendMs: 15000
112
+ }),
113
+ high: Object.freeze({
114
+ targetRestMs: 60 * 60 * 1000,
115
+ targetCandidateCount: 700,
116
+ targetWindowMs: 5 * 60 * 60 * 1000,
117
+ intervalMin: 3,
118
+ intervalMax: 12,
119
+ longRestProbability: 0.28,
120
+ shortRestMinMs: 12000,
121
+ shortRestMaxMs: 75000,
122
+ longRestMinMs: 90000,
123
+ longRestMaxMs: 300000,
124
+ minDebtToRestMs: 12000,
125
+ forceDebtMs: 150000,
126
+ maxOverspendMs: 25000
127
+ })
128
+ });
89
129
 
90
130
  function clampNumber(value, min, max) {
91
131
  const number = Number(value);
@@ -152,6 +192,14 @@ export function normalizeHumanBehaviorProfile(raw, fallback = "baseline") {
152
192
  : fallback;
153
193
  }
154
194
 
195
+ export function normalizeHumanRestLevel(raw, fallback = DEFAULT_HUMAN_REST_LEVEL) {
196
+ const normalized = String(raw || "").trim().toLowerCase().replace(/[\s-]+/g, "_");
197
+ const level = HUMAN_REST_LEVEL_ALIASES[normalized] || normalized;
198
+ return level === "low" || level === "medium" || level === "high"
199
+ ? level
200
+ : fallback;
201
+ }
202
+
155
203
  export function normalizeHumanBehaviorOptions(raw = null, {
156
204
  legacyEnabled = false,
157
205
  safePacing = null,
@@ -231,6 +279,10 @@ export function normalizeHumanBehaviorOptions(raw = null, {
231
279
  readFirstOption(rawObject, ["batchRest", "batch_rest", "batchRestEnabled", "batch_rest_enabled"]),
232
280
  profileDefaults.batchRest
233
281
  );
282
+ const restLevel = normalizeHumanRestLevel(
283
+ readFirstOption(rawObject, ["restLevel", "rest_level"]),
284
+ DEFAULT_HUMAN_REST_LEVEL
285
+ );
234
286
  if (batchRestFlag !== null) {
235
287
  batchRest = batchRestFlag;
236
288
  if (batchRestFlag === true && readFirstOption(rawObject, ["shortRest", "short_rest", "randomRest", "random_rest"]) === undefined) {
@@ -248,6 +300,7 @@ export function normalizeHumanBehaviorOptions(raw = null, {
248
300
  shortRest: enabled && shortRest === true,
249
301
  batchRest: enabled && batchRest === true,
250
302
  actionCooldown: enabled && actionCooldown === true,
303
+ restLevel,
251
304
  restEnabled: enabled && (shortRest === true || batchRest === true)
252
305
  };
253
306
  }
@@ -404,6 +457,8 @@ export function createHumanRestController({
404
457
  shortRestEnabled = true,
405
458
  batchRestEnabled = true,
406
459
  random = Math.random,
460
+ nowFn = Date.now,
461
+ restLevel = DEFAULT_HUMAN_REST_LEVEL,
407
462
  shortRestProbability = 0.08,
408
463
  shortRestMinMs = 3000,
409
464
  shortRestMaxMs = 7000,
@@ -413,12 +468,26 @@ export function createHumanRestController({
413
468
  batchRestMaxMs = 30000
414
469
  } = {}) {
415
470
  const nextRandom = normalizeRandom(random);
471
+ const readNow = typeof nowFn === "function" ? nowFn : Date.now;
472
+ const normalizedRestLevel = normalizeHumanRestLevel(restLevel);
473
+ const budgetProfile = (shortRestEnabled !== false || batchRestEnabled !== false)
474
+ ? HUMAN_REST_LEVEL_PROFILES[normalizedRestLevel] || null
475
+ : null;
476
+ const nextBudgetRestInterval = () => budgetProfile
477
+ ? randomIntegerBetween(nextRandom, budgetProfile.intervalMin, budgetProfile.intervalMax)
478
+ : 0;
416
479
  const state = {
417
480
  enabled: enabled === true,
481
+ rest_level: normalizedRestLevel,
418
482
  short_rest_enabled: enabled === true && shortRestEnabled !== false,
419
483
  batch_rest_enabled: enabled === true && batchRestEnabled !== false,
420
484
  rest_counter: 0,
421
485
  rest_threshold: Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1))),
486
+ processed_count: 0,
487
+ candidates_since_last_rest: 0,
488
+ candidates_until_next_rest: nextBudgetRestInterval(),
489
+ active_elapsed_ms: 0,
490
+ last_active_at_ms: Number(readNow()) || 0,
422
491
  rest_count: 0,
423
492
  total_rest_ms: 0
424
493
  };
@@ -427,6 +496,67 @@ export function createHumanRestController({
427
496
  state.rest_threshold = Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1)));
428
497
  }
429
498
 
499
+ function updateActiveElapsed() {
500
+ const now = Number(readNow()) || 0;
501
+ if (state.last_active_at_ms >= 0 && now >= state.last_active_at_ms) {
502
+ state.active_elapsed_ms += now - state.last_active_at_ms;
503
+ }
504
+ state.last_active_at_ms = now;
505
+ return now;
506
+ }
507
+
508
+ function getBudgetTargetMs() {
509
+ if (!budgetProfile) return 0;
510
+ const candidateTarget = state.processed_count * (budgetProfile.targetRestMs / budgetProfile.targetCandidateCount);
511
+ const elapsedTarget = state.active_elapsed_ms * (budgetProfile.targetRestMs / budgetProfile.targetWindowMs);
512
+ return Math.max(candidateTarget, elapsedTarget);
513
+ }
514
+
515
+ function chooseBudgetRestPause(debtMs) {
516
+ const longRest = nextRandom() < budgetProfile.longRestProbability;
517
+ const minMs = longRest ? budgetProfile.longRestMinMs : budgetProfile.shortRestMinMs;
518
+ const maxMs = longRest ? budgetProfile.longRestMaxMs : budgetProfile.shortRestMaxMs;
519
+ const scaleMin = longRest ? 0.75 : 0.38;
520
+ const scaleMax = longRest ? 1.1 : 0.78;
521
+ const desiredMs = debtMs * randomBetween(nextRandom, scaleMin, scaleMax);
522
+ const randomizedMs = randomBetween(nextRandom, minMs, maxMs);
523
+ const blendedMs = Math.max(minMs, Math.min(maxMs, (desiredMs + randomizedMs) / 2));
524
+ const maxAllowedMs = Math.max(minMs, debtMs + budgetProfile.maxOverspendMs);
525
+ return {
526
+ pauseMs: Math.round(Math.min(blendedMs, maxAllowedMs)),
527
+ restSize: longRest ? "long" : "short"
528
+ };
529
+ }
530
+
531
+ async function takeBudgetBreakIfNeeded(sleeper) {
532
+ state.processed_count += 1;
533
+ state.candidates_since_last_rest += 1;
534
+ state.candidates_until_next_rest -= 1;
535
+ const debtMs = getBudgetTargetMs() - state.total_rest_ms;
536
+ const intervalDue = state.candidates_until_next_rest <= 0;
537
+ const forceDue = debtMs >= budgetProfile.forceDebtMs;
538
+ if (!intervalDue && !forceDue) {
539
+ return null;
540
+ }
541
+ if (debtMs < budgetProfile.minDebtToRestMs) {
542
+ if (intervalDue) state.candidates_until_next_rest = nextBudgetRestInterval();
543
+ return null;
544
+ }
545
+ const { pauseMs, restSize } = chooseBudgetRestPause(debtMs);
546
+ await sleeper(pauseMs);
547
+ const event = {
548
+ kind: "random_rest",
549
+ rest_level: normalizedRestLevel,
550
+ rest_size: restSize,
551
+ pause_ms: pauseMs,
552
+ processed_since_last_rest: state.candidates_since_last_rest,
553
+ rest_budget_debt_ms: Math.round(Math.max(0, debtMs))
554
+ };
555
+ state.candidates_since_last_rest = 0;
556
+ state.candidates_until_next_rest = nextBudgetRestInterval();
557
+ return event;
558
+ }
559
+
430
560
  async function takeBreakIfNeeded({ sleepFn = sleep } = {}) {
431
561
  if (!state.enabled) {
432
562
  return {
@@ -438,18 +568,44 @@ export function createHumanRestController({
438
568
  };
439
569
  }
440
570
  const sleeper = typeof sleepFn === "function" ? sleepFn : sleep;
571
+ updateActiveElapsed();
572
+ if (budgetProfile) {
573
+ const budgetEvent = await takeBudgetBreakIfNeeded(sleeper);
574
+ const pauseMs = budgetEvent?.pause_ms || 0;
575
+ if (pauseMs > 0) {
576
+ state.rest_count += 1;
577
+ state.total_rest_ms += pauseMs;
578
+ state.last_active_at_ms = Number(readNow()) || state.last_active_at_ms;
579
+ }
580
+ return {
581
+ enabled: true,
582
+ rested: Boolean(budgetEvent),
583
+ pause_ms: pauseMs,
584
+ rest_level: normalizedRestLevel,
585
+ rest_counter: state.rest_counter,
586
+ rest_threshold: state.rest_threshold,
587
+ processed_count: state.processed_count,
588
+ candidates_until_next_rest: state.candidates_until_next_rest,
589
+ active_elapsed_ms: state.active_elapsed_ms,
590
+ rest_count: state.rest_count,
591
+ total_rest_ms: state.total_rest_ms,
592
+ events: budgetEvent ? [budgetEvent] : []
593
+ };
594
+ }
441
595
  state.rest_counter += 1;
596
+ state.processed_count += 1;
442
597
  const events = [];
443
598
  if (state.short_rest_enabled && nextRandom() < Math.max(0, Number(shortRestProbability) || 0)) {
444
599
  const pauseMs = Math.round(randomBetween(nextRandom, shortRestMinMs, shortRestMaxMs));
445
600
  await sleeper(pauseMs);
446
- events.push({ kind: "random_rest", pause_ms: pauseMs });
601
+ events.push({ kind: "random_rest", rest_level: normalizedRestLevel, pause_ms: pauseMs });
447
602
  }
448
603
  if (state.batch_rest_enabled && state.rest_counter >= state.rest_threshold) {
449
604
  const pauseMs = Math.round(randomBetween(nextRandom, batchRestMinMs, batchRestMaxMs));
450
605
  await sleeper(pauseMs);
451
606
  events.push({
452
607
  kind: "batch_rest",
608
+ rest_level: normalizedRestLevel,
453
609
  pause_ms: pauseMs,
454
610
  processed_since_last_batch_rest: state.rest_counter
455
611
  });
@@ -460,13 +616,17 @@ export function createHumanRestController({
460
616
  if (pauseMs > 0) {
461
617
  state.rest_count += events.length;
462
618
  state.total_rest_ms += pauseMs;
619
+ state.last_active_at_ms = Number(readNow()) || state.last_active_at_ms;
463
620
  }
464
621
  return {
465
622
  enabled: true,
466
623
  rested: events.length > 0,
467
624
  pause_ms: pauseMs,
625
+ rest_level: normalizedRestLevel,
468
626
  rest_counter: state.rest_counter,
469
627
  rest_threshold: state.rest_threshold,
628
+ processed_count: state.processed_count,
629
+ active_elapsed_ms: state.active_elapsed_ms,
470
630
  rest_count: state.rest_count,
471
631
  total_rest_ms: state.total_rest_ms,
472
632
  events
@@ -743,11 +743,12 @@ export async function runChatWorkflow({
743
743
  safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
744
744
  actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
745
745
  });
746
- const humanRestController = createHumanRestController({
747
- enabled: effectiveHumanRestEnabled,
748
- shortRestEnabled: effectiveHumanBehavior.shortRest,
749
- batchRestEnabled: effectiveHumanBehavior.batchRest
750
- });
746
+ const humanRestController = createHumanRestController({
747
+ enabled: effectiveHumanRestEnabled,
748
+ shortRestEnabled: effectiveHumanBehavior.shortRest,
749
+ batchRestEnabled: effectiveHumanBehavior.batchRest,
750
+ restLevel: effectiveHumanBehavior.restLevel
751
+ });
751
752
  const normalizedDetailSource = normalizeDetailSource(detailSource);
752
753
  const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
753
754
  const useLlmScreening = normalizedScreeningMode !== "deterministic";
@@ -966,11 +967,12 @@ export async function runChatWorkflow({
966
967
  context_recoveries: contextRecoveryAttempts,
967
968
  list_end_reason: listEndReason,
968
969
  viewport_checks: viewportGuard.getStats().checks,
969
- viewport_recoveries: viewportGuard.getStats().recoveries,
970
- human_behavior_enabled: effectiveHumanBehavior.enabled,
971
- human_behavior_profile: effectiveHumanBehavior.profile,
972
- human_rest_enabled: effectiveHumanRestEnabled,
973
- human_rest_count: humanRestController.getState().rest_count,
970
+ viewport_recoveries: viewportGuard.getStats().recoveries,
971
+ human_behavior_enabled: effectiveHumanBehavior.enabled,
972
+ human_behavior_profile: effectiveHumanBehavior.profile,
973
+ human_rest_level: effectiveHumanBehavior.restLevel,
974
+ human_rest_enabled: effectiveHumanRestEnabled,
975
+ human_rest_count: humanRestController.getState().rest_count,
974
976
  human_rest_ms: humanRestController.getState().total_rest_ms,
975
977
  last_human_event: lastHumanEvent
976
978
  });
@@ -1771,11 +1773,12 @@ export async function runChatWorkflow({
1771
1773
  context_recoveries: contextRecoveryAttempts,
1772
1774
  list_end_reason: listEndReason || null,
1773
1775
  viewport_checks: viewportGuard.getStats().checks,
1774
- viewport_recoveries: viewportGuard.getStats().recoveries,
1775
- human_behavior_enabled: effectiveHumanBehavior.enabled,
1776
- human_behavior_profile: effectiveHumanBehavior.profile,
1777
- human_rest_enabled: effectiveHumanRestEnabled,
1778
- human_rest_count: humanRestController.getState().rest_count,
1776
+ viewport_recoveries: viewportGuard.getStats().recoveries,
1777
+ human_behavior_enabled: effectiveHumanBehavior.enabled,
1778
+ human_behavior_profile: effectiveHumanBehavior.profile,
1779
+ human_rest_level: effectiveHumanBehavior.restLevel,
1780
+ human_rest_enabled: effectiveHumanRestEnabled,
1781
+ human_rest_count: humanRestController.getState().rest_count,
1779
1782
  human_rest_ms: humanRestController.getState().total_rest_ms,
1780
1783
  last_human_event: lastHumanEvent,
1781
1784
  last_candidate_id: screeningCandidate.id || null,
@@ -1815,10 +1818,11 @@ export async function runChatWorkflow({
1815
1818
  compactResult.human_rest = restResult;
1816
1819
  addTiming(compactResult.timings, "human_rest_ms", restElapsed);
1817
1820
  compactResult.timings.total_ms = Date.now() - candidateStarted;
1818
- runControl.updateProgress({
1819
- human_rest_enabled: effectiveHumanRestEnabled,
1820
- human_rest_count: humanRestController.getState().rest_count,
1821
- human_rest_ms: humanRestController.getState().total_rest_ms,
1821
+ runControl.updateProgress({
1822
+ human_rest_enabled: effectiveHumanRestEnabled,
1823
+ human_rest_level: effectiveHumanBehavior.restLevel,
1824
+ human_rest_count: humanRestController.getState().rest_count,
1825
+ human_rest_ms: humanRestController.getState().total_rest_ms,
1822
1826
  human_rest_last: restResult,
1823
1827
  context_recoveries: contextRecoveryAttempts,
1824
1828
  last_human_event: lastHumanEvent
@@ -1958,12 +1962,13 @@ export function createChatRunService({
1958
1962
  list_settle_ms: listSettleMs,
1959
1963
  list_fallback_point: listFallbackPoint,
1960
1964
  online_resume_button_timeout_ms: onlineResumeButtonTimeoutMs,
1961
- image_output_dir: imageOutputDir || "",
1962
- human_behavior_enabled: effectiveHumanBehavior.enabled,
1963
- human_behavior_profile: effectiveHumanBehavior.profile,
1964
- human_behavior: effectiveHumanBehavior,
1965
- human_rest_enabled: effectiveHumanRestEnabled
1966
- },
1965
+ image_output_dir: imageOutputDir || "",
1966
+ human_behavior_enabled: effectiveHumanBehavior.enabled,
1967
+ human_behavior_profile: effectiveHumanBehavior.profile,
1968
+ human_behavior: effectiveHumanBehavior,
1969
+ human_rest_level: effectiveHumanBehavior.restLevel,
1970
+ human_rest_enabled: effectiveHumanRestEnabled
1971
+ },
1967
1972
  progress: {
1968
1973
  card_count: 0,
1969
1974
  target_count: targetPassCount || (processUntilListEnd ? "all" : processedLimit),
@@ -1978,10 +1983,11 @@ export function createChatRunService({
1978
1983
  requested: 0,
1979
1984
  request_satisfied: 0,
1980
1985
  request_skipped: 0,
1981
- context_recoveries: 0,
1982
- human_behavior_enabled: effectiveHumanBehavior.enabled,
1983
- human_behavior_profile: effectiveHumanBehavior.profile,
1984
- human_rest_enabled: effectiveHumanRestEnabled,
1986
+ context_recoveries: 0,
1987
+ human_behavior_enabled: effectiveHumanBehavior.enabled,
1988
+ human_behavior_profile: effectiveHumanBehavior.profile,
1989
+ human_rest_level: effectiveHumanBehavior.restLevel,
1990
+ human_rest_enabled: effectiveHumanRestEnabled,
1985
1991
  human_rest_count: 0,
1986
1992
  human_rest_ms: 0,
1987
1993
  last_human_event: null
@@ -690,7 +690,8 @@ export async function runRecommendWorkflow({
690
690
  const humanRestController = createHumanRestController({
691
691
  enabled: effectiveHumanRestEnabled,
692
692
  shortRestEnabled: effectiveHumanBehavior.shortRest,
693
- batchRestEnabled: effectiveHumanBehavior.batchRest
693
+ batchRestEnabled: effectiveHumanBehavior.batchRest,
694
+ restLevel: effectiveHumanBehavior.restLevel
694
695
  });
695
696
  const normalizedFilter = normalizeFilter(filter);
696
697
  const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
@@ -790,6 +791,7 @@ export async function runRecommendWorkflow({
790
791
  viewport_recoveries: viewportGuard.getStats().recoveries,
791
792
  human_behavior_enabled: effectiveHumanBehavior.enabled,
792
793
  human_behavior_profile: effectiveHumanBehavior.profile,
794
+ human_rest_level: effectiveHumanBehavior.restLevel,
793
795
  human_rest_enabled: effectiveHumanRestEnabled,
794
796
  human_rest_count: humanRestState.rest_count,
795
797
  human_rest_ms: humanRestState.total_rest_ms,
@@ -1509,6 +1511,7 @@ export async function runRecommendWorkflow({
1509
1511
  addTiming(compactResult.timings, "human_rest_ms", restElapsed);
1510
1512
  compactResult.timings.total_ms = Date.now() - candidateStarted;
1511
1513
  updateRecommendProgress({
1514
+ human_rest_level: effectiveHumanBehavior.restLevel,
1512
1515
  human_rest_last: restResult
1513
1516
  });
1514
1517
  }
@@ -1651,6 +1654,7 @@ export function createRecommendRunService({
1651
1654
  human_behavior_enabled: effectiveHumanBehavior.enabled,
1652
1655
  human_behavior_profile: effectiveHumanBehavior.profile,
1653
1656
  human_behavior: effectiveHumanBehavior,
1657
+ human_rest_level: effectiveHumanBehavior.restLevel,
1654
1658
  human_rest_enabled: effectiveHumanRestEnabled
1655
1659
  },
1656
1660
  progress: {
@@ -1670,6 +1674,7 @@ export function createRecommendRunService({
1670
1674
  context_recoveries: 0,
1671
1675
  human_behavior_enabled: effectiveHumanBehavior.enabled,
1672
1676
  human_behavior_profile: effectiveHumanBehavior.profile,
1677
+ human_rest_level: effectiveHumanBehavior.restLevel,
1673
1678
  human_rest_enabled: effectiveHumanRestEnabled,
1674
1679
  human_rest_count: 0,
1675
1680
  human_rest_ms: 0,
@@ -395,7 +395,8 @@ export async function runRecruitWorkflow({
395
395
  const humanRestController = createHumanRestController({
396
396
  enabled: effectiveHumanRestEnabled,
397
397
  shortRestEnabled: effectiveHumanBehavior.shortRest,
398
- batchRestEnabled: effectiveHumanBehavior.batchRest
398
+ batchRestEnabled: effectiveHumanBehavior.batchRest,
399
+ restLevel: effectiveHumanBehavior.restLevel
399
400
  });
400
401
  const normalizedSearchParams = normalizeSearchParams(searchParams);
401
402
  const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
@@ -485,6 +486,7 @@ export async function runRecruitWorkflow({
485
486
  viewport_recoveries: viewportGuard.getStats().recoveries,
486
487
  human_behavior_enabled: effectiveHumanBehavior.enabled,
487
488
  human_behavior_profile: effectiveHumanBehavior.profile,
489
+ human_rest_level: effectiveHumanBehavior.restLevel,
488
490
  human_rest_enabled: effectiveHumanRestEnabled,
489
491
  human_rest_count: humanRestState.rest_count,
490
492
  human_rest_ms: humanRestState.total_rest_ms,
@@ -1101,6 +1103,7 @@ export async function runRecruitWorkflow({
1101
1103
  addTiming(compactResult.timings, "human_rest_ms", restElapsed);
1102
1104
  compactResult.timings.total_ms = Date.now() - candidateStarted;
1103
1105
  updateRecruitProgress({
1106
+ human_rest_level: effectiveHumanBehavior.restLevel,
1104
1107
  human_rest_last: restResult
1105
1108
  });
1106
1109
  }
@@ -1223,6 +1226,7 @@ export function createRecruitRunService({
1223
1226
  human_behavior_enabled: effectiveHumanBehavior.enabled,
1224
1227
  human_behavior_profile: effectiveHumanBehavior.profile,
1225
1228
  human_behavior: effectiveHumanBehavior,
1229
+ human_rest_level: effectiveHumanBehavior.restLevel,
1226
1230
  human_rest_enabled: effectiveHumanRestEnabled
1227
1231
  },
1228
1232
  progress: {
@@ -1239,6 +1243,7 @@ export function createRecruitRunService({
1239
1243
  context_recoveries: 0,
1240
1244
  human_behavior_enabled: effectiveHumanBehavior.enabled,
1241
1245
  human_behavior_profile: effectiveHumanBehavior.profile,
1246
+ human_rest_level: effectiveHumanBehavior.restLevel,
1242
1247
  human_rest_enabled: effectiveHumanRestEnabled,
1243
1248
  human_rest_count: 0,
1244
1249
  human_rest_ms: 0,
package/src/index.js CHANGED
@@ -308,7 +308,17 @@ function createHumanBehaviorInputSchema(description = "可选,启用可靠性
308
308
  listScrollJitter: { type: "boolean" },
309
309
  shortRest: { type: "boolean" },
310
310
  batchRest: { type: "boolean" },
311
- actionCooldown: { type: "boolean" }
311
+ actionCooldown: { type: "boolean" },
312
+ restLevel: {
313
+ type: "string",
314
+ enum: ["low", "medium", "high"],
315
+ description: "本次 run 的休息强度:low 保持旧策略;medium 约 5 小时/700 人累计休息 30 分钟;high 约 5 小时/700 人累计休息 1 小时"
316
+ },
317
+ rest_level: {
318
+ type: "string",
319
+ enum: ["low", "medium", "high"],
320
+ description: "兼容字段;优先使用 restLevel"
321
+ }
312
322
  },
313
323
  additionalProperties: false,
314
324
  description
@@ -665,7 +665,17 @@ function createHumanBehaviorInputSchema(description = "可选,search/recruit
665
665
  listScrollJitter: { type: "boolean" },
666
666
  shortRest: { type: "boolean" },
667
667
  batchRest: { type: "boolean" },
668
- actionCooldown: { type: "boolean" }
668
+ actionCooldown: { type: "boolean" },
669
+ restLevel: {
670
+ type: "string",
671
+ enum: ["low", "medium", "high"],
672
+ description: "本次 run 的休息强度:low 保持旧策略;medium 约 5 小时/700 人累计休息 30 分钟;high 约 5 小时/700 人累计休息 1 小时"
673
+ },
674
+ rest_level: {
675
+ type: "string",
676
+ enum: ["low", "medium", "high"],
677
+ description: "兼容字段;优先使用 restLevel"
678
+ }
669
679
  },
670
680
  additionalProperties: false,
671
681
  description