@reconcrap/boss-recommend-mcp 2.0.57 → 2.1.1
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 +16 -2
- package/config/screening-config.example.json +1 -0
- package/package.json +1 -1
- package/skills/boss-chat/README.md +2 -1
- package/skills/boss-chat/SKILL.md +9 -1
- package/skills/boss-recommend-pipeline/README.md +1 -0
- package/skills/boss-recommend-pipeline/SKILL.md +33 -5
- package/skills/boss-recruit-pipeline/README.md +2 -0
- package/skills/boss-recruit-pipeline/SKILL.md +8 -0
- package/src/cli.js +185 -92
- package/src/core/browser/index.js +161 -1
- package/src/domains/chat/run-service.js +35 -29
- package/src/domains/recommend/run-service.js +6 -1
- package/src/domains/recruit/run-service.js +6 -1
- package/src/index.js +22 -4
- package/src/recommend-mcp.js +15 -9
- package/src/recruit-mcp.js +11 -1
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Boss 推荐 / 搜索 / 聊天筛选 MCP(stdio)服务。
|
|
|
20
20
|
MCP 工具:
|
|
21
21
|
|
|
22
22
|
- `list_recommend_jobs`(只读读取推荐页岗位下拉框,返回可直接用于 cron/一次性任务的 `job_names`)
|
|
23
|
+
- `prepare_recommend_pipeline_run`(只校验完整 payload 是否已可用于 cron/一次性任务;不启动筛选,返回 `READY + cron_ready=true` 后才应创建 cron)
|
|
23
24
|
- `start_recommend_pipeline_run`(异步启动;同样先经过前置门禁,通过后返回 run_id)
|
|
24
25
|
- `get_recommend_pipeline_run`(轮询 run_id 状态)
|
|
25
26
|
- `cancel_recommend_pipeline_run`(取消运行中任务)
|
|
@@ -53,10 +54,19 @@ boss-recommend-mcp list-jobs --slow-live --port 9222
|
|
|
53
54
|
|
|
54
55
|
返回的 `job_names` 可直接作为后续 `start_recommend_pipeline_run` 的 `confirmation.job_value` / `overrides.job`。
|
|
55
56
|
|
|
57
|
+
Cron / 一次性定时任务设置建议先在设置阶段完成 Chrome/登录/岗位发现与全部确认:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
boss-recommend-mcp prepare-run --instruction-file boss-recommend-instruction.txt --overrides-file boss-recommend-overrides.json --confirmation-file boss-recommend-confirmation.json --slow-live --port 9222
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
只有输出 `status: "READY"` 且 `cron_ready: true` 后,才把同一份 payload 写入 cron 并在到点时调用 `start_recommend_pipeline_run` / `boss-recommend-mcp run --detached`。如果设置阶段返回 `BOSS_LOGIN_REQUIRED`、`NEED_INPUT` 或 `NEED_CONFIRMATION`,先让用户登录或补齐确认,不要创建 cron。
|
|
64
|
+
|
|
56
65
|
状态机:
|
|
57
66
|
|
|
58
67
|
- `NEED_INPUT`
|
|
59
68
|
- `NEED_CONFIRMATION`
|
|
69
|
+
- `READY`(仅准备工具)
|
|
60
70
|
- `COMPLETED`
|
|
61
71
|
- `FAILED`
|
|
62
72
|
|
|
@@ -259,13 +269,16 @@ config/screening-config.example.json
|
|
|
259
269
|
- `debugPort`:未显式传 `port` 时,recommend / search / chat CDP-only MCP run 和健康检查默认连接这个 Chrome 调试端口。
|
|
260
270
|
- `outputDir`:recommend / search / chat 完成后的最终 CSV 与 report JSON 会写入这里;run state / checkpoint 仍保留在各自状态目录,方便 pause/resume/cancel。
|
|
261
271
|
- `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 的可靠性实验,支持:
|
|
272
|
+
- `humanBehavior`:默认 `{ "enabled": true, "profile": "paced_with_rests", "restLevel": "low" }`。用于 recommend / search / chat 的可靠性实验,支持:
|
|
263
273
|
- `profile: "baseline"`:关闭人类节奏,保持确定性行为。
|
|
264
274
|
- `profile: "paced"`:启用 CDP-only Bezier 鼠标移动、较大按钮的安全 inset 点击点、分块 `Input.insertText`、列表 wheel/settle jitter,以及小的动作前后读秒。
|
|
265
275
|
- `profile: "paced_with_rests"`:在 `paced` 基础上启用候选人短休和批次休息。
|
|
276
|
+
- `restLevel: "low"`:保持旧版休息策略不变,候选人短休 8% 概率暂停 3-7 秒,批次休息约每 25-32 人暂停 15-30 秒。
|
|
277
|
+
- `restLevel: "medium"`:随机分散短/长休息,平均目标约每 5 小时或 700 位候选人累计休息 30 分钟。
|
|
278
|
+
- `restLevel: "high"`:随机分散短/长休息,平均目标约每 5 小时或 700 位候选人累计休息 1 小时。
|
|
266
279
|
- `humanRestEnabled`:兼容旧配置。设为 `true` 时等价于 `humanBehavior.profile="paced_with_rests"`;设为 `false` 时不会关闭当前默认节奏。如需关闭,请显式设置 `humanBehavior.enabled=false` 或 `humanBehavior.profile="baseline"`。
|
|
267
280
|
- 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` 与 `
|
|
281
|
+
- chat/recommend/search run 也兼容显式参数 `safe_pacing`、`batch_rest_enabled` 与 `human_behavior.restLevel`:run 参数优先于配置文件。AI harness/skill 启动每次 run 前必须让用户明确选择 `low/medium/high`,再把选择写入 `human_behavior.restLevel`。
|
|
269
282
|
|
|
270
283
|
## 常用命令
|
|
271
284
|
|
|
@@ -371,6 +384,7 @@ Trae-CN / 长对话防循环建议:
|
|
|
371
384
|
说明:
|
|
372
385
|
|
|
373
386
|
- `start_recommend_pipeline_run` 为异步入口,但不会跳过同步确认流程。
|
|
387
|
+
- `prepare_recommend_pipeline_run` / `boss-recommend-mcp prepare-run` 用于 cron 设置阶段;它不启动筛选,只确认 payload 已经不需要到点后再问用户。
|
|
374
388
|
- 定时心跳默认 120 秒一次;`updated_at` 仍会在阶段或进度变化时刷新。
|
|
375
389
|
- 每个 run 会持久化到 `~/.boss-recommend-mcp/runs/<run_id>.json`(可通过 `BOSS_RECOMMEND_HOME` 覆盖)。
|
|
376
390
|
- screen 阶段会持久化 checkpoint:`~/.boss-recommend-mcp/runs/<run_id>.checkpoint.json`。
|
package/package.json
CHANGED
|
@@ -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,18 +84,24 @@ 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
|
|
|
88
91
|
- 岗位发现工具:`list_recommend_jobs`
|
|
89
|
-
- 用途:当用户需要为 cron /
|
|
92
|
+
- 用途:当用户需要为 cron / 一次性自动任务提前填写完整参数时,先用它读取推荐页岗位下拉框的全部可用岗位名;默认会复用/自动打开本机 9222 Chrome 并导航到推荐页。
|
|
90
93
|
- 输出:优先把 `job_names` 里的值作为后续 `overrides.job` / `confirmation.job_value`。
|
|
91
|
-
-
|
|
94
|
+
- 限制:只读岗位列表,不启动筛选任务;若返回 `BOSS_LOGIN_REQUIRED`,必须让用户在自动打开的 Chrome 完成登录后重试,本次 cron 不得创建。
|
|
95
|
+
- Cron 准备工具:`prepare_recommend_pipeline_run`
|
|
96
|
+
- 用途:只校验参数是否已可用于 cron / 一次性任务,不启动筛选任务。
|
|
97
|
+
- 要求:只有返回 `status=READY` 且 `cron_ready=true` 后,才允许创建 cron。
|
|
98
|
+
- 若返回 `NEED_INPUT` / `NEED_CONFIRMATION` / `FAILED`:继续补齐 `pending_questions` 或修复登录/页面/config;不得先创建 cron。
|
|
92
99
|
- 主工具:`start_recommend_pipeline_run`
|
|
93
100
|
- 必填:`instruction`
|
|
94
101
|
- 关键输入:
|
|
95
102
|
- `confirmation`:`page_confirmed/page_value/filters_confirmed/school_tag_confirmed.../job_confirmed/job_value/final_confirmed`
|
|
96
103
|
- `overrides`:`page_scope/school_tag/degree/gender/recent_not_view/criteria/job/target_count/post_action/max_greet_count`
|
|
104
|
+
- `human_behavior`:必须包含本次用户确认的 `restLevel`(例如 `{ "restLevel": "medium" }`)
|
|
97
105
|
- 不要传 `follow_up.chat`;该路径属于 legacy-only 行为
|
|
98
106
|
|
|
99
107
|
最小策略:
|
|
@@ -103,6 +111,25 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
103
111
|
- 拿到 `ACCEPTED + run_id` 后默认停止本轮,不自动轮询。
|
|
104
112
|
- 在拿到 `ACCEPTED + run_id` 之前,禁止以“我已帮你确认参数”为由越过必填确认阶段。
|
|
105
113
|
|
|
114
|
+
## Cron / Scheduled Run Policy
|
|
115
|
+
|
|
116
|
+
创建 cron 时必须在设置 cron 的当下完成全部交互:
|
|
117
|
+
|
|
118
|
+
1. 锁定用户原始 instruction,不改写、不摘要,criteria 放入 `overrides.criteria` 时必须逐字保留。
|
|
119
|
+
2. 收集 Stage A 全部筛选项、`target_count`、`post_action`、`max_greet_count`(如需要)和本次 `rest_level`。
|
|
120
|
+
3. 调用 `list_recommend_jobs`;若 Chrome 未打开,工具会尝试自动打开本机 9222 Chrome 并进入推荐页。若返回 `BOSS_LOGIN_REQUIRED` 或页面不可用,停止 cron 创建,让用户登录/修复后重试。
|
|
121
|
+
4. 用 `job_names` 中的精确岗位名完成岗位确认,并完成最终总确认,写入 `job_confirmed=true` 与 `final_confirmed=true`。
|
|
122
|
+
5. 调用 `prepare_recommend_pipeline_run` 传入将来要执行的完整 payload;只有 `READY + cron_ready=true` 才能创建 cron。
|
|
123
|
+
6. cron 到点时只调用 `start_recommend_pipeline_run`,并传入准备阶段验证过的同一份 payload。到点后若 Chrome/登录异常,应作为运行失败处理,不得再向用户追问参数。
|
|
124
|
+
|
|
125
|
+
Shell-only OpenClaw/QClaw cron 设置同样先运行:
|
|
126
|
+
|
|
127
|
+
```powershell
|
|
128
|
+
npx -y @reconcrap/boss-recommend-mcp@latest prepare-run --instruction-file .\boss-recommend-instruction.txt --overrides-file .\boss-recommend-overrides.json --confirmation-file .\boss-recommend-confirmation.json --slow-live --port 9222
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
仅当上述命令输出 `READY` 且 `cron_ready=true` 后,才把对应的 detached `run` 命令写入 cron。
|
|
132
|
+
|
|
106
133
|
## Async Run Policy
|
|
107
134
|
|
|
108
135
|
- 用户未明确要求“持续跟进”时,不自动 `sleep + get_recommend_pipeline_run`。
|
|
@@ -146,13 +173,14 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
146
173
|
推荐做法:
|
|
147
174
|
|
|
148
175
|
1. 将锁定的用户原文写入 instruction 文件,将已确认参数写入 `overrides` 与 `confirmation` JSON 文件。
|
|
149
|
-
2.
|
|
176
|
+
2. 先用 `prepare-run` 校验完整 payload 已 cron-ready;未返回 `READY + cron_ready=true` 不得创建 cron 或启动 run。
|
|
177
|
+
3. 用 detached CLI 启动,让父命令返回启动证据,子进程继续持有 CDP 会话:
|
|
150
178
|
|
|
151
179
|
```powershell
|
|
152
180
|
npx -y @reconcrap/boss-recommend-mcp@latest run --detached --instruction-file .\boss-recommend-instruction.txt --overrides-file .\boss-recommend-overrides.json --confirmation-file .\boss-recommend-confirmation.json --slow-live --port 9222
|
|
153
181
|
```
|
|
154
182
|
|
|
155
|
-
|
|
183
|
+
4. 若返回 `ACCEPTED + run_id`,即任务已启动;记录 `run_id/stdout_path/stderr_path`。若返回 `NEED_INPUT` 或 `NEED_CONFIRMATION`,说明 cron 设置阶段漏掉了准备门禁,应回到 `prepare-run` 补齐,不能在到点后继续问用户确认。
|
|
156
184
|
|
|
157
185
|
兼容路径:
|
|
158
186
|
|
|
@@ -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
|
@@ -23,10 +23,11 @@ import {
|
|
|
23
23
|
prepareBossChatRunTool,
|
|
24
24
|
resumeBossChatRunTool
|
|
25
25
|
} from "./chat-mcp.js";
|
|
26
|
-
import {
|
|
27
|
-
listRecommendJobsTool,
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
import {
|
|
27
|
+
listRecommendJobsTool,
|
|
28
|
+
prepareRecommendPipelineRunTool,
|
|
29
|
+
startRecommendPipelineRunTool
|
|
30
|
+
} from "./recommend-mcp.js";
|
|
30
31
|
import {
|
|
31
32
|
getBossScreenConfigResolution,
|
|
32
33
|
resolveBossChatRuntimeLayout as resolveCdpBossChatRuntimeLayout,
|
|
@@ -53,7 +54,7 @@ const defaultMcpServerName = "boss-recommend";
|
|
|
53
54
|
const defaultMcpCommand = "npx";
|
|
54
55
|
const recommendMcpPackageName = "@reconcrap/boss-recommend-mcp";
|
|
55
56
|
const recommendMcpBinaryName = "boss-recommend-mcp";
|
|
56
|
-
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
|
|
57
|
+
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
|
|
57
58
|
const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
|
|
58
59
|
const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
|
|
59
60
|
const installConfigDefaults = Object.freeze({
|
|
@@ -63,12 +64,13 @@ const installConfigDefaults = Object.freeze({
|
|
|
63
64
|
llmMaxRetries: 3,
|
|
64
65
|
llmImageLimit: 8,
|
|
65
66
|
llmImageDetail: "low",
|
|
66
|
-
humanRestEnabled: true,
|
|
67
|
-
humanBehavior: {
|
|
68
|
-
enabled: true,
|
|
69
|
-
profile: "paced_with_rests"
|
|
70
|
-
|
|
71
|
-
}
|
|
67
|
+
humanRestEnabled: true,
|
|
68
|
+
humanBehavior: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
profile: "paced_with_rests",
|
|
71
|
+
restLevel: "low"
|
|
72
|
+
}
|
|
73
|
+
});
|
|
72
74
|
const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
|
|
73
75
|
const bossChatCliUnsupportedStartCode = "CHAT_CLI_ASYNC_UNSUPPORTED_CDP_ONLY";
|
|
74
76
|
const calibrateUnsupportedCode = "CALIBRATE_UNSUPPORTED_CDP_ONLY";
|
|
@@ -613,14 +615,20 @@ function parseBossChatTargetCountOption(raw) {
|
|
|
613
615
|
return parsed ?? text;
|
|
614
616
|
}
|
|
615
617
|
|
|
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
|
-
}
|
|
618
|
+
function parseBooleanOption(raw, fallback = undefined) {
|
|
619
|
+
if (raw === undefined || raw === null || raw === "") return fallback;
|
|
620
|
+
if (raw === true) return true;
|
|
621
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
622
|
+
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
|
|
623
|
+
if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
|
|
624
|
+
return fallback;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function parseRestLevelOption(raw) {
|
|
628
|
+
if (raw === undefined || raw === null || raw === "") return undefined;
|
|
629
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
630
|
+
return ["low", "medium", "high"].includes(normalized) ? normalized : undefined;
|
|
631
|
+
}
|
|
624
632
|
|
|
625
633
|
function normalizePageScope(value) {
|
|
626
634
|
const normalized = String(value || "").trim().toLowerCase();
|
|
@@ -1271,20 +1279,33 @@ async function resolveCliConfigTarget(options = {}) {
|
|
|
1271
1279
|
};
|
|
1272
1280
|
}
|
|
1273
1281
|
|
|
1274
|
-
function applyMissingInstallConfigDefaults(config = {}) {
|
|
1275
|
-
const nextConfig = { ...config };
|
|
1276
|
-
const patchedKeys = [];
|
|
1277
|
-
for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
|
|
1282
|
+
function applyMissingInstallConfigDefaults(config = {}) {
|
|
1283
|
+
const nextConfig = { ...config };
|
|
1284
|
+
const patchedKeys = [];
|
|
1285
|
+
for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
|
|
1278
1286
|
if (!Object.prototype.hasOwnProperty.call(nextConfig, key)) {
|
|
1279
1287
|
nextConfig[key] = defaultValue;
|
|
1280
|
-
patchedKeys.push(key);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
nextConfig
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
+
patchedKeys.push(key);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (
|
|
1292
|
+
nextConfig.humanBehavior
|
|
1293
|
+
&& typeof nextConfig.humanBehavior === "object"
|
|
1294
|
+
&& !Array.isArray(nextConfig.humanBehavior)
|
|
1295
|
+
&& !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "restLevel")
|
|
1296
|
+
&& !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "rest_level")
|
|
1297
|
+
) {
|
|
1298
|
+
nextConfig.humanBehavior = {
|
|
1299
|
+
...nextConfig.humanBehavior,
|
|
1300
|
+
restLevel: "low"
|
|
1301
|
+
};
|
|
1302
|
+
patchedKeys.push("humanBehavior.restLevel");
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
nextConfig,
|
|
1306
|
+
patchedKeys
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1288
1309
|
|
|
1289
1310
|
async function ensureUserConfig(options = {}) {
|
|
1290
1311
|
const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
@@ -2601,10 +2622,11 @@ function printHelp() {
|
|
|
2601
2622
|
console.log("boss-recommend-mcp");
|
|
2602
2623
|
console.log("");
|
|
2603
2624
|
console.log("Usage:");
|
|
2604
|
-
console.log(" boss-recommend-mcp Start the MCP server");
|
|
2605
|
-
console.log(" boss-recommend-mcp start Start the MCP server");
|
|
2606
|
-
console.log(" boss-recommend-mcp run
|
|
2607
|
-
console.log(" boss-recommend-mcp
|
|
2625
|
+
console.log(" boss-recommend-mcp Start the MCP server");
|
|
2626
|
+
console.log(" boss-recommend-mcp start Start the MCP server");
|
|
2627
|
+
console.log(" boss-recommend-mcp prepare-run Validate a cron-ready recommend run payload without starting screening");
|
|
2628
|
+
console.log(" boss-recommend-mcp run Start a CDP-only recommend run through the shared run service");
|
|
2629
|
+
console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
|
|
2608
2630
|
console.log(" boss-recommend-mcp chat <subcommand> Run CDP-only boss-chat health/prepare/status commands");
|
|
2609
2631
|
console.log(" boss-recommend-mcp install Install/migrate skills and MCP configs; replaces legacy Boss MCP routes (supports --agent trae-cn/openclaw/qclaw/...)");
|
|
2610
2632
|
console.log(" boss-recommend-mcp install-skill Install bundled Codex skills (recommend/recruit/chat)");
|
|
@@ -2616,10 +2638,11 @@ function printHelp() {
|
|
|
2616
2638
|
console.log(" boss-recommend-mcp calibrate Disabled until CDP-only featured calibration is live-verified");
|
|
2617
2639
|
console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
|
|
2618
2640
|
console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
|
|
2619
|
-
console.log("");
|
|
2620
|
-
console.log("Run command:");
|
|
2621
|
-
console.log(" boss-recommend-mcp run --instruction \"
|
|
2622
|
-
console.log(" boss-recommend-mcp run --
|
|
2641
|
+
console.log("");
|
|
2642
|
+
console.log("Run command:");
|
|
2643
|
+
console.log(" boss-recommend-mcp prepare-run --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json");
|
|
2644
|
+
console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" --overrides-file overrides.json --confirmation-file confirmation.json");
|
|
2645
|
+
console.log(" boss-recommend-mcp run --detached --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json");
|
|
2623
2646
|
console.log(" boss-recommend-mcp list-jobs --slow-live --port 9222");
|
|
2624
2647
|
console.log(" boss-recommend-mcp chat prepare-run --slow-live --port 9222 # CDP-only preflight; start runs through MCP start_boss_chat_run");
|
|
2625
2648
|
console.log(" boss-recommend-mcp config set --base-url <url> --api-key <key> --model <model> [--thinking-level off|low|medium|high|current] [--greeting-message <text>] [--openai-organization <id>] [--openai-project <id>]");
|
|
@@ -2713,27 +2736,37 @@ async function installAll(options = {}) {
|
|
|
2713
2736
|
}
|
|
2714
2737
|
}
|
|
2715
2738
|
|
|
2716
|
-
|
|
2717
|
-
const instruction = getRunInstruction(options);
|
|
2718
|
-
const confirmation = getRunConfirmation(options);
|
|
2719
|
-
const overrides = getRunOverrides(options);
|
|
2720
|
-
const followUp = getRunFollowUp(options);
|
|
2721
|
-
const
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
follow_up: followUp ?? undefined,
|
|
2739
|
+
function buildRecommendRunCliInput(options = {}) {
|
|
2740
|
+
const instruction = getRunInstruction(options);
|
|
2741
|
+
const confirmation = getRunConfirmation(options);
|
|
2742
|
+
const overrides = getRunOverrides(options);
|
|
2743
|
+
const followUp = getRunFollowUp(options);
|
|
2744
|
+
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
2745
|
+
|
|
2746
|
+
const args = {
|
|
2747
|
+
instruction,
|
|
2748
|
+
confirmation: confirmation ?? undefined,
|
|
2749
|
+
overrides: overrides ?? undefined,
|
|
2750
|
+
follow_up: followUp ?? undefined,
|
|
2729
2751
|
host: typeof options.host === "string" && options.host.trim() ? options.host.trim() : undefined,
|
|
2730
2752
|
port,
|
|
2731
2753
|
target_url_includes: typeof options["target-url-includes"] === "string" && options["target-url-includes"].trim()
|
|
2732
2754
|
? options["target-url-includes"].trim()
|
|
2733
2755
|
: undefined,
|
|
2734
2756
|
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
|
-
};
|
|
2757
|
+
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
|
|
2758
|
+
};
|
|
2759
|
+
const restLevel = parseRestLevelOption(
|
|
2760
|
+
options["rest-level"]
|
|
2761
|
+
?? options.rest_level
|
|
2762
|
+
?? options["human-behavior-rest-level"]
|
|
2763
|
+
?? options.human_behavior_rest_level
|
|
2764
|
+
);
|
|
2765
|
+
if (restLevel) {
|
|
2766
|
+
args.human_behavior = {
|
|
2767
|
+
restLevel
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2737
2770
|
|
|
2738
2771
|
const optionalPassthrough = [
|
|
2739
2772
|
"detail_limit",
|
|
@@ -2766,15 +2799,46 @@ async function runPipelineOnce(options = {}) {
|
|
|
2766
2799
|
"llm_image_limit",
|
|
2767
2800
|
"llm_image_detail"
|
|
2768
2801
|
];
|
|
2769
|
-
for (const key of optionalPassthrough) {
|
|
2770
|
-
const kebab = key.replace(/_/g, "-");
|
|
2771
|
-
if (options[key] !== undefined) args[key] = options[key];
|
|
2772
|
-
else if (options[kebab] !== undefined) args[key] = options[kebab];
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2802
|
+
for (const key of optionalPassthrough) {
|
|
2803
|
+
const kebab = key.replace(/_/g, "-");
|
|
2804
|
+
if (options[key] !== undefined) args[key] = options[key];
|
|
2805
|
+
else if (options[kebab] !== undefined) args[key] = options[kebab];
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
return {
|
|
2809
|
+
args,
|
|
2810
|
+
port
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
async function preparePipelineOnce(options = {}) {
|
|
2815
|
+
const workspaceRoot = getWorkspaceRoot(options);
|
|
2816
|
+
const { args, port } = buildRecommendRunCliInput(options);
|
|
2817
|
+
const result = prepareRecommendPipelineRunTool({
|
|
2818
|
+
workspaceRoot,
|
|
2819
|
+
args
|
|
2820
|
+
});
|
|
2821
|
+
printJson({
|
|
2822
|
+
...result,
|
|
2823
|
+
cli: {
|
|
2824
|
+
command: "prepare-run",
|
|
2825
|
+
cdp_only: true,
|
|
2826
|
+
shared_run_service: true,
|
|
2827
|
+
workspace_root: workspaceRoot,
|
|
2828
|
+
port
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
if (result.status !== "READY" || result.cron_ready !== true) {
|
|
2832
|
+
process.exitCode = 1;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
async function runPipelineOnce(options = {}) {
|
|
2837
|
+
const workspaceRoot = getWorkspaceRoot(options);
|
|
2838
|
+
const { args, port } = buildRecommendRunCliInput(options);
|
|
2839
|
+
const result = await startRecommendPipelineRunTool({
|
|
2840
|
+
workspaceRoot,
|
|
2841
|
+
args
|
|
2778
2842
|
});
|
|
2779
2843
|
printJson({
|
|
2780
2844
|
...result,
|
|
@@ -2810,16 +2874,22 @@ async function listRecommendJobsCli(options = {}) {
|
|
|
2810
2874
|
}));
|
|
2811
2875
|
}
|
|
2812
2876
|
|
|
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
|
-
|
|
2877
|
+
function buildBossChatCliInput(options = {}) {
|
|
2878
|
+
const greetingTextRaw =
|
|
2879
|
+
options["greeting-text"]
|
|
2880
|
+
?? options.greeting_text
|
|
2881
|
+
?? options.greetingText
|
|
2882
|
+
?? options.greeting;
|
|
2883
|
+
const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
|
|
2884
|
+
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2885
|
+
const host = String(options.host || "").trim();
|
|
2886
|
+
const restLevel = parseRestLevelOption(
|
|
2887
|
+
options["rest-level"]
|
|
2888
|
+
?? options.rest_level
|
|
2889
|
+
?? options["human-behavior-rest-level"]
|
|
2890
|
+
?? options.human_behavior_rest_level
|
|
2891
|
+
);
|
|
2892
|
+
return {
|
|
2823
2893
|
profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
|
|
2824
2894
|
job: typeof options.job === "string" ? options.job.trim() : undefined,
|
|
2825
2895
|
start_from: String(options["start-from"] || options.start_from || "").trim().toLowerCase() || undefined,
|
|
@@ -2837,13 +2907,18 @@ function buildBossChatCliInput(options = {}) {
|
|
|
2837
2907
|
dry_run: options["dry-run"] === true || options.dryRun === true,
|
|
2838
2908
|
no_state: options["no-state"] === true || options.noState === true,
|
|
2839
2909
|
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
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
}
|
|
2910
|
+
human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
|
|
2911
|
+
? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
|
|
2912
|
+
: undefined,
|
|
2913
|
+
human_behavior: restLevel
|
|
2914
|
+
? {
|
|
2915
|
+
restLevel
|
|
2916
|
+
}
|
|
2917
|
+
: undefined,
|
|
2918
|
+
safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
|
|
2919
|
+
batch_rest_enabled: parseBooleanOption(options["batch-rest"] ?? options.batch_rest_enabled)
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2847
2922
|
|
|
2848
2923
|
function getBossChatCliRunTarget(options = {}) {
|
|
2849
2924
|
return {
|
|
@@ -2932,11 +3007,11 @@ export async function runCli(argv = process.argv) {
|
|
|
2932
3007
|
case "mcp":
|
|
2933
3008
|
startServer();
|
|
2934
3009
|
break;
|
|
2935
|
-
case "run":
|
|
2936
|
-
try {
|
|
2937
|
-
if (
|
|
2938
|
-
(options.detached === true || options.background === true)
|
|
2939
|
-
&& process.env[detachedRecommendRunChildEnv] !== "1"
|
|
3010
|
+
case "run":
|
|
3011
|
+
try {
|
|
3012
|
+
if (
|
|
3013
|
+
(options.detached === true || options.background === true)
|
|
3014
|
+
&& process.env[detachedRecommendRunChildEnv] !== "1"
|
|
2940
3015
|
) {
|
|
2941
3016
|
await runPipelineDetached(argv.slice(3), options);
|
|
2942
3017
|
} else {
|
|
@@ -2951,9 +3026,26 @@ export async function runCli(argv = process.argv) {
|
|
|
2951
3026
|
retryable: false
|
|
2952
3027
|
}
|
|
2953
3028
|
});
|
|
2954
|
-
process.exitCode = 1;
|
|
2955
|
-
}
|
|
2956
|
-
break;
|
|
3029
|
+
process.exitCode = 1;
|
|
3030
|
+
}
|
|
3031
|
+
break;
|
|
3032
|
+
case "prepare-run":
|
|
3033
|
+
case "prepare":
|
|
3034
|
+
try {
|
|
3035
|
+
await preparePipelineOnce(options);
|
|
3036
|
+
} catch (error) {
|
|
3037
|
+
printJson({
|
|
3038
|
+
status: "FAILED",
|
|
3039
|
+
cron_ready: false,
|
|
3040
|
+
error: {
|
|
3041
|
+
code: "INVALID_CLI_INPUT",
|
|
3042
|
+
message: error.message || "Invalid CLI input",
|
|
3043
|
+
retryable: false
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
3046
|
+
process.exitCode = 1;
|
|
3047
|
+
}
|
|
3048
|
+
break;
|
|
2957
3049
|
case "list-jobs":
|
|
2958
3050
|
case "jobs":
|
|
2959
3051
|
case "recommend-jobs":
|
|
@@ -3111,10 +3203,11 @@ export const __testables = {
|
|
|
3111
3203
|
installSkill,
|
|
3112
3204
|
isInstalledPackageRoot,
|
|
3113
3205
|
mergeMcpServerConfigFile,
|
|
3114
|
-
resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
|
|
3115
|
-
runBossChatCliCommand,
|
|
3116
|
-
|
|
3117
|
-
|
|
3206
|
+
resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
|
|
3207
|
+
runBossChatCliCommand,
|
|
3208
|
+
preparePipelineOnce,
|
|
3209
|
+
runPipelineOnce
|
|
3210
|
+
};
|
|
3118
3211
|
|
|
3119
3212
|
if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
|
|
3120
3213
|
await runCli(process.argv);
|
|
@@ -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
|
-
|
|
973
|
-
|
|
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
|
-
|
|
1778
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -87,8 +87,9 @@ import {
|
|
|
87
87
|
} from "./run-state.js";
|
|
88
88
|
|
|
89
89
|
const require = createRequire(import.meta.url);
|
|
90
|
-
const { version: SERVER_VERSION } = require("../package.json");
|
|
91
|
-
|
|
90
|
+
const { version: SERVER_VERSION } = require("../package.json");
|
|
91
|
+
|
|
92
|
+
const TOOL_PREPARE_RUN = "prepare_recommend_pipeline_run";
|
|
92
93
|
const TOOL_START_RUN = "start_recommend_pipeline_run";
|
|
93
94
|
const TOOL_GET_RUN = "get_recommend_pipeline_run";
|
|
94
95
|
const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
|
|
@@ -308,7 +309,17 @@ function createHumanBehaviorInputSchema(description = "可选,启用可靠性
|
|
|
308
309
|
listScrollJitter: { type: "boolean" },
|
|
309
310
|
shortRest: { type: "boolean" },
|
|
310
311
|
batchRest: { type: "boolean" },
|
|
311
|
-
actionCooldown: { type: "boolean" }
|
|
312
|
+
actionCooldown: { type: "boolean" },
|
|
313
|
+
restLevel: {
|
|
314
|
+
type: "string",
|
|
315
|
+
enum: ["low", "medium", "high"],
|
|
316
|
+
description: "本次 run 的休息强度:low 保持旧策略;medium 约 5 小时/700 人累计休息 30 分钟;high 约 5 小时/700 人累计休息 1 小时"
|
|
317
|
+
},
|
|
318
|
+
rest_level: {
|
|
319
|
+
type: "string",
|
|
320
|
+
enum: ["low", "medium", "high"],
|
|
321
|
+
description: "兼容字段;优先使用 restLevel"
|
|
322
|
+
}
|
|
312
323
|
},
|
|
313
324
|
additionalProperties: false,
|
|
314
325
|
description
|
|
@@ -1079,10 +1090,15 @@ function createToolsSchema() {
|
|
|
1079
1090
|
description: "CDP-only 读取 Boss 推荐页岗位下拉框,返回所有可用岗位完整名称,方便 cron/一次性任务提前填写 job 参数。不会启动筛选任务。",
|
|
1080
1091
|
inputSchema: createListRecommendJobsInputSchema()
|
|
1081
1092
|
},
|
|
1093
|
+
{
|
|
1094
|
+
name: TOOL_PREPARE_RUN,
|
|
1095
|
+
description: "只校验 Boss 推荐页流水线参数是否已可用于 cron/一次性任务;不会启动筛选任务。只有返回 READY/cron_ready=true 后才应创建定时任务。",
|
|
1096
|
+
inputSchema: createRunInputSchema()
|
|
1097
|
+
},
|
|
1082
1098
|
{
|
|
1083
1099
|
name: TOOL_START_RUN,
|
|
1084
1100
|
description: "异步启动 Boss 推荐页流水线(含同步门禁预检);只有在前置确认与页面就绪通过后才返回 run_id。",
|
|
1085
|
-
inputSchema: createRunInputSchema()
|
|
1101
|
+
inputSchema: createRunInputSchema()
|
|
1086
1102
|
},
|
|
1087
1103
|
{
|
|
1088
1104
|
name: TOOL_GET_RUN,
|
|
@@ -2608,6 +2624,8 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2608
2624
|
let payload;
|
|
2609
2625
|
if (toolName === TOOL_LIST_RECOMMEND_JOBS) {
|
|
2610
2626
|
payload = await listRecommendJobsTool({ workspaceRoot, args });
|
|
2627
|
+
} else if (toolName === TOOL_PREPARE_RUN) {
|
|
2628
|
+
payload = prepareRecommendPipelineRunTool({ workspaceRoot, args });
|
|
2611
2629
|
} else if (toolName === TOOL_START_RUN) {
|
|
2612
2630
|
payload = await handleStartRunTool({ workspaceRoot, args });
|
|
2613
2631
|
} else if (toolName === TOOL_GET_RUN) {
|
package/src/recommend-mcp.js
CHANGED
|
@@ -1464,15 +1464,21 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1464
1464
|
};
|
|
1465
1465
|
}
|
|
1466
1466
|
|
|
1467
|
-
export function prepareRecommendPipelineRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1468
|
-
const prepared = prepareRecommendPipelineStart(args, { workspaceRoot });
|
|
1469
|
-
if (prepared.response)
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1467
|
+
export function prepareRecommendPipelineRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1468
|
+
const prepared = prepareRecommendPipelineStart(args, { workspaceRoot });
|
|
1469
|
+
if (prepared.response) {
|
|
1470
|
+
return {
|
|
1471
|
+
...prepared.response,
|
|
1472
|
+
cron_ready: false
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
const { parsed, normalized } = prepared;
|
|
1476
|
+
return {
|
|
1477
|
+
status: "READY",
|
|
1478
|
+
cron_ready: true,
|
|
1479
|
+
review: parsed.review,
|
|
1480
|
+
post_action: {
|
|
1481
|
+
requested: normalized.postAction,
|
|
1476
1482
|
execute_post_action: args.dry_run_post_action === true ? false : args.execute_post_action !== false,
|
|
1477
1483
|
max_greet_count: normalized.maxGreetCount
|
|
1478
1484
|
},
|
package/src/recruit-mcp.js
CHANGED
|
@@ -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
|