@reconcrap/boss-recommend-mcp 2.1.13 → 2.1.15
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 +17 -6
- package/config/screening-config.example.json +5 -0
- package/package.json +1 -1
- package/skills/boss-chat/README.md +2 -2
- package/skills/boss-chat/SKILL.md +7 -7
- package/skills/boss-recruit-pipeline/SKILL.md +23 -1
- package/src/chat-mcp.js +70 -73
- package/src/chat-runtime-config.js +26 -0
- package/src/core/reporting/legacy-csv.js +9 -1
- package/src/core/screening/index.js +351 -20
- package/src/domains/chat/detail.js +79 -47
- package/src/domains/chat/run-service.js +456 -185
- package/src/domains/recommend/run-service.js +11 -3
- package/src/domains/recruit/constants.js +65 -0
- package/src/domains/recruit/instruction-parser.js +362 -86
- package/src/domains/recruit/run-service.js +289 -10
- package/src/domains/recruit/search.js +2076 -298
- package/src/index.js +18 -12
- package/src/recommend-mcp.js +77 -8
- package/src/recruit-mcp.js +228 -3
package/README.md
CHANGED
|
@@ -176,7 +176,7 @@ boss-recommend-mcp schedule-status --schedule-id <schedule_id>
|
|
|
176
176
|
- 不会对每位候选人重复确认
|
|
177
177
|
- 推荐页详情处理完成后,会强制关闭详情页并确认已关闭
|
|
178
178
|
- 简历提取优先使用 Network 响应;没有可解析 Network CV 时,回退到完整滚动截图序列再交给多模态模型判断
|
|
179
|
-
- recommend / search / chat 正式运行默认全部使用 `screening-config.json` 配置的 LLM 筛选;deterministic/local scorer 只保留给明确测试场景,必须显式传 `debug_test_mode=true` 且 `screening_mode=deterministic` 或 `use_llm=false`。
|
|
179
|
+
- recommend / search / 带 `criteria` 的 chat 正式运行默认全部使用 `screening-config.json` 配置的 LLM 筛选;chat 的 `criteria` 留空时进入收集简历模式,不需要 LLM 配置。deterministic/local scorer 只保留给明确测试场景,必须显式传 `debug_test_mode=true` 且 `screening_mode=deterministic` 或 `use_llm=false`。
|
|
180
180
|
- `detail_limit=0`、`no_filter`、`filter_enabled=false`、后置动作 dry-run、chat 求简历 dry-run 等调试路径不会在正式 live run 默认启用;需要测试时必须显式传 `debug_test_mode=true`。
|
|
181
181
|
- 提供显式运维自愈工具:只在手动调用 `run_recommend_self_heal` 时运行,不会接入正常 run / doctor / preflight 自动链路
|
|
182
182
|
- 运行前会自动做依赖体检(Node.js、Python、Pillow、`chrome-remote-interface`、`ws`),缺失时会在 `doctor` 与流水线失败诊断中明确提示
|
|
@@ -354,7 +354,12 @@ config/screening-config.example.json
|
|
|
354
354
|
}
|
|
355
355
|
],
|
|
356
356
|
"greetingMessage": "Hi同学,能麻烦发下简历吗?",
|
|
357
|
+
"llmScreeningStrategy": "single_pass",
|
|
357
358
|
"llmThinkingLevel": "low",
|
|
359
|
+
"llmFastThinkingLevel": "current",
|
|
360
|
+
"llmVerifyThinkingLevel": "low",
|
|
361
|
+
"llmFastMaxTokens": 384,
|
|
362
|
+
"llmVerifyMaxTokens": null,
|
|
358
363
|
"llmMaxRetries": 1
|
|
359
364
|
}
|
|
360
365
|
```
|
|
@@ -366,9 +371,15 @@ config/screening-config.example.json
|
|
|
366
371
|
- `greetingMessage`:chat 求简历流程发送的招呼语。兼容 `greetingText` / `greeting_text`;本次 run 显式传入的 `greeting_text` 优先级最高。
|
|
367
372
|
- `debugPort`:未显式传 `port` 时,recommend / search / chat CDP-only MCP run 和健康检查默认连接这个 Chrome 调试端口。
|
|
368
373
|
- `outputDir`:recommend / search / chat 完成后的最终 CSV 与 report JSON 会写入这里;run state / checkpoint 仍保留在各自状态目录,方便 pause/resume/cancel。
|
|
369
|
-
- `
|
|
374
|
+
- `llmScreeningStrategy`:默认 `single_pass`,保持旧行为并使用 `llmThinkingLevel`。设为 `fast_first_verified` 时,recommend / search / chat 会先用 `llmFastThinkingLevel` 快速判断,再只对快速通过、快速判断要求复核、或快速输出无效/缺字段的候选人用 `llmVerifyThinkingLevel` 复核;明确 `passed=false` 且 `review_required=false` 的快速淘汰不会复核。复核发生时,复核结果作为最终结果。
|
|
375
|
+
- `llmFastThinkingLevel` / `llmVerifyThinkingLevel`:仅在 `llmScreeningStrategy="fast_first_verified"` 时生效;默认分别为 `current` 和 `low`,可设为 `off/minimal/low/medium/high/auto/current`。此策略会忽略 `llmThinkingLevel` 的通过选择。
|
|
376
|
+
- `llmFastMaxTokens` / `llmVerifyMaxTokens`:仅在 `llmScreeningStrategy="fast_first_verified"` 时生效。快速首轮默认最多输出 `384` tokens,即使全局 `llmMaxTokens` 更大,也会使用这个快速轮上限,避免 `current` 模式输出冗长推理;可用 `llmFastMaxTokens` 覆盖。复核轮默认沿用全局 `llmMaxTokens` / `llmMaxCompletionTokens`,也可用 `llmVerifyMaxTokens` 单独覆盖。
|
|
377
|
+
- `fast_first_verified` 的快速轮只有在硬性不通过证据直接、明确、且没有可见反向证据时才应返回 `review_required=false`。如果简历里存在任一可能合格的学校/学历、产品或行业、职责或指标、海外/语言证据、或需要精确计算的任期/年限证据,即使快速轮倾向 `passed=false`,也应返回 `review_required=true` 交给复核轮。
|
|
378
|
+
- LLM provider 返回预算、额度、账单或鉴权类 fatal 错误(例如 `budget_exceeded`、quota exceeded、401/403)时,单次 LLM 调用最多重试 2 次;若仍失败,recommend / search / chat 会直接让本次 run 失败,不再把后续候选人记录为筛选不通过。
|
|
379
|
+
- `llmThinkingLevel`:默认 `low`。可设为 `off/minimal/low/medium/high/auto/current`,用于控制 OpenAI-compatible LLM 的 thinking/reasoning 强度。recommend / search / chat 共用同一套 LLM 筛选提示词:模型会按筛选标准顺序检查硬性淘汰项,若某项已可确定不满足(包括标准要求“证据不足即不通过”的缺失证据),会直接返回不通过;只有当前淘汰项存在截图不清、信息冲突或可能被后续简历内容澄清时才继续核实。
|
|
370
380
|
- `current`:不显式发送 thinking/reasoning 参数,并要求 LLM 返回 `{"passed": true/false, "summary": "少于100个中文词的筛选总结"}`;CSV 的 `判断依据(CoT)` 写入该 summary。
|
|
371
381
|
- 其他值:只要求 LLM 返回 `{"passed": true/false}`;若 provider 返回 reasoning / CoT 字段,CSV 的 `判断依据(CoT)` 写入该内容。
|
|
382
|
+
- 在 `fast_first_verified` 下,快速首轮始终要求 `{"passed": true/false, "summary": "少于100个中文词的筛选总结", "review_required": true/false}`。若最终来自快速轮,CSV 的 `判断依据(CoT)` 写入快速 summary;若最终来自复核轮,CSV 优先写入复核 CoT/reasoning,复核轮为 `current` 时写入复核 summary。
|
|
372
383
|
- `humanBehavior`:默认 `{ "enabled": true, "profile": "paced_with_rests", "restLevel": "low" }`。用于 recommend / search / chat 的可靠性实验,支持:
|
|
373
384
|
- `profile: "baseline"`:关闭人类节奏,保持确定性行为。
|
|
374
385
|
- `profile: "paced"`:启用 CDP-only Bezier 鼠标移动、较大按钮的安全 inset 点击点、分块 `Input.insertText`、列表 wheel/settle jitter,以及小的动作前后读秒。
|
|
@@ -422,7 +433,7 @@ node src/cli.js chat prepare-run --slow-live --port 9222
|
|
|
422
433
|
|
|
423
434
|
说明:
|
|
424
435
|
|
|
425
|
-
- `
|
|
436
|
+
- `start_from` / `target_count` 为必填;`criteria` 可选,留空时 chat run 进入收集简历模式:不触发 LLM 筛选,针对没有在线/附件简历的人选发送求简历消息并点击求简历按钮
|
|
426
437
|
- `greeting_text` 可选(兼容 `greetingText`)
|
|
427
438
|
- `profile` 可选,默认 `default`
|
|
428
439
|
- `job` 与 `port` 继承 recommend run 已选岗位和调试端口
|
|
@@ -457,15 +468,15 @@ node src/cli.js chat prepare-run --slow-live --port 9222
|
|
|
457
468
|
chat-only 交互建议:
|
|
458
469
|
|
|
459
470
|
- 先调用一次 `list_boss_chat_jobs` 或 `prepare_boss_chat_run`(可不带参数),服务会先导航到 `https://www.zhipin.com/web/chat/index` 并返回 `NEED_INPUT`,其中包含岗位 `job_options` 与待补字段。
|
|
460
|
-
- 然后基于 `job_options` 让用户选择 `job`,并补齐 `start_from`、`target_count
|
|
471
|
+
- 然后基于 `job_options` 让用户选择 `job`,并补齐 `start_from`、`target_count` 后调用 `start_boss_chat_run` 启动任务;若要筛选再求简历,提供 `criteria`,若只想收集缺失简历则留空。
|
|
461
472
|
- `greeting_text` 可选;未传时使用 `screening-config.json.greetingMessage`,若未配置则使用默认招呼语(`Hi同学,能麻烦发下简历吗?`)。
|
|
462
473
|
- `target_count` 支持正整数、`all`、`-1`;若用户给出 `全部候选人` / `所有候选人`,会自动按不限(扫到底)处理。
|
|
463
474
|
|
|
464
475
|
Trae-CN / 长对话防循环建议:
|
|
465
476
|
|
|
466
|
-
- 固定流程:`boss_chat_health_check` -> `list_boss_chat_jobs(空参可)` / `prepare_boss_chat_run(空参可)` -> 一次性补齐 `job/start_from/target_count
|
|
477
|
+
- 固定流程:`boss_chat_health_check` -> `list_boss_chat_jobs(空参可)` / `prepare_boss_chat_run(空参可)` -> 一次性补齐 `job/start_from/target_count`,筛选任务再带 `criteria` -> `start_boss_chat_run`。
|
|
467
478
|
- chat-only 场景严禁调用 `list_recommend_jobs`、`run_recommend` 或 `start_recommend_pipeline_run`。
|
|
468
|
-
- `start_boss_chat_run` 的工具 schema 已把 `job/start_from/target_count
|
|
479
|
+
- `start_boss_chat_run` 的工具 schema 已把 `job/start_from/target_count` 标记为必填;`criteria` 留空会进入收集简历模式。不要用它获取岗位列表。
|
|
469
480
|
- 若 `pending_questions` / UI 选项里出现“扫到底(必须传 `target_count="all"`)”,下一次工具调用请直接照抄 `"target_count": "all"`,不要只保留“扫到底”这层自然语言语义。
|
|
470
481
|
- `start_boss_chat_run` 返回 `ACCEPTED` 后直接结束当前回合,不要自动轮询。
|
|
471
482
|
- 缺参或校验失败时,一次性列出全部缺失/错误项,避免重复同一句提示触发宿主“陷入循环”保护。
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
"model": "gpt-4.1-mini",
|
|
5
5
|
"llmModels": [],
|
|
6
6
|
"greetingMessage": "Hi同学,能麻烦发下简历吗?",
|
|
7
|
+
"llmScreeningStrategy": "single_pass",
|
|
7
8
|
"llmThinkingLevel": "low",
|
|
9
|
+
"llmFastThinkingLevel": "current",
|
|
10
|
+
"llmVerifyThinkingLevel": "low",
|
|
11
|
+
"llmFastMaxTokens": 384,
|
|
12
|
+
"llmVerifyMaxTokens": null,
|
|
8
13
|
"llmTimeoutMs": 60000,
|
|
9
14
|
"llmMaxTokens": 512,
|
|
10
15
|
"llmMaxRetries": 3,
|
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 list_boss_chat_jobs once (empty params allowed), or prepare_boss_chat_run once, to fetch job_options and missing fields.
|
|
20
|
-
3) Ask for these required fields in one shot: job, start_from (unread/all), target_count,
|
|
20
|
+
3) Ask for these required fields in one shot: job, start_from (unread/all), target_count, rest_level (low/medium/high). Ask for criteria only when the user wants screening; leave it blank for collect-CV runs.
|
|
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
|
|
|
@@ -26,7 +26,7 @@ Anti-loop rules:
|
|
|
26
26
|
- On validation errors, list all missing/invalid fields once.
|
|
27
27
|
- Do not call list_recommend_jobs for chat-only tasks; it is recommend-page only and will switch the browser to /web/chat/recommend.
|
|
28
28
|
- Do not call run_recommend or start_recommend_pipeline_run for chat-only tasks; use start_boss_chat_run.
|
|
29
|
-
- 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/
|
|
29
|
+
- 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. Include criteria for screening; leave criteria blank to collect CVs from candidates without an online/attachment CV.
|
|
30
30
|
- Do not call start_boss_chat_run repeatedly in one turn.
|
|
31
31
|
- Do not call get_boss_chat_run unless user explicitly asks for progress.
|
|
32
32
|
- Do not choose rest_level for the user. Pass the user's choice as `human_behavior.restLevel`.
|
|
@@ -29,11 +29,11 @@ Trae/Trae-CN split-server config exposes these under the `boss-chat` MCP server.
|
|
|
29
29
|
- `job`
|
|
30
30
|
- `start_from`: `unread|all`
|
|
31
31
|
- `target_count`
|
|
32
|
-
- `criteria`
|
|
33
32
|
- `rest_level`: `low|medium|high`
|
|
34
33
|
|
|
35
34
|
可选:
|
|
36
35
|
|
|
36
|
+
- `criteria`(留空时进入收集简历模式:不触发 LLM 筛选,只向没有在线/附件简历的人选求简历)
|
|
37
37
|
- `profile`(默认 `default`)
|
|
38
38
|
- `greeting_text`(兼容 `greetingText`,可选自定义首条打招呼消息)
|
|
39
39
|
- `port`
|
|
@@ -62,7 +62,7 @@ Trae/Trae-CN split-server config exposes these under the `boss-chat` MCP server.
|
|
|
62
62
|
|
|
63
63
|
## Hard Rules
|
|
64
64
|
|
|
65
|
-
- LLM 配置必须复用 `boss-recommend-mcp` 的 `screening-config.json`;不要再向用户单独要 `baseUrl/apiKey/model
|
|
65
|
+
- 有筛选 `criteria` 时,LLM 配置必须复用 `boss-recommend-mcp` 的 `screening-config.json`;不要再向用户单独要 `baseUrl/apiKey/model`。`criteria` 留空的收集简历模式不需要 LLM 配置。
|
|
66
66
|
- 路由护栏(强制):
|
|
67
67
|
- 只在用户明确是 chat-only 任务时使用本 skill。
|
|
68
68
|
- 只要用户提到推荐页、先找人后沟通、或需要推荐筛选阶段,禁止直接调用 `start_boss_chat_run`;必须先交给 `boss-recommend-pipeline` 完成推荐页任务。
|
|
@@ -70,7 +70,7 @@ Trae/Trae-CN split-server config exposes these under the `boss-chat` MCP server.
|
|
|
70
70
|
- 启动或准备 chat run 时,若本机默认 `127.0.0.1:9222` Chrome DevTools 端口不可连,工具会自动打开 Chrome 并导航到 `https://www.zhipin.com/web/chat/index`。
|
|
71
71
|
- 只有工具返回 `BOSS_LOGIN_REQUIRED` / `requires_login=true` 时,才要求用户在自动打开的 Chrome 窗口人工登录 Boss 后重试;不要把“没开 9222 Chrome”当作缺参。
|
|
72
72
|
- 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
|
|
73
|
-
- `job` / `start_from`
|
|
73
|
+
- `job` / `start_from` 缺一不可;`criteria` 可留空,留空时表示收集缺失简历而不是筛选。缺参时只补缺口。
|
|
74
74
|
- `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
|
|
75
75
|
- chat-only 岗位列表只能通过 `list_boss_chat_jobs` 或 `prepare_boss_chat_run` 获取;严禁调用 `list_recommend_jobs`,因为它会切到推荐页。
|
|
76
76
|
- chat-only 启动只能调用 `start_boss_chat_run`;严禁调用 `run_recommend` 或 `start_recommend_pipeline_run`。
|
|
@@ -80,10 +80,10 @@ Trae/Trae-CN split-server config exposes these under the `boss-chat` MCP server.
|
|
|
80
80
|
- 当用户选择“扫到底/全部候选人/所有候选人”时,调用参数优先写:`"target_count": "all"`;`-1` 只作为兼容输入和内部 CLI 表示。
|
|
81
81
|
- `greeting_text` 是可选项,不能因为缺少它阻塞启动或追加必填追问。
|
|
82
82
|
- 若工具或提问选项里出现“扫到底(必须传 `target_count=\"all\"`)”之类字样,下一次工具调用时必须直接照抄这个字面量,不要只保留“扫到底”语义。
|
|
83
|
-
- 禁止 agent 自行补全 `job/start_from
|
|
83
|
+
- 禁止 agent 自行补全 `job/start_from` 并直接执行,必须由用户明确给出或确认。`criteria` 只有在用户要筛选时才必须明确给出;用户要收集简历时可留空。
|
|
84
84
|
- chat-only 启动流程必须先进入聊天页并拉取岗位列表,再让用户从列表中选择 `job`。
|
|
85
85
|
- 必须先用空参调用 `list_boss_chat_jobs` 或 `prepare_boss_chat_run` 获取 `job_options`;不要用 `start_boss_chat_run` 做预备调用。
|
|
86
|
-
- `start_boss_chat_run` 只能用于真正启动,必须一次性传齐 `job` / `start_from` / `target_count`
|
|
86
|
+
- `start_boss_chat_run` 只能用于真正启动,必须一次性传齐 `job` / `start_from` / `target_count`;`criteria` 留空会进入收集简历模式。
|
|
87
87
|
- 若 `start_boss_chat_run` 返回 `NEED_INPUT` 且 `missing_fields` 包含 `target_count`,说明你没有把用户选择写入工具参数;下一次调用必须照 `next_call_example` 原样补上 `"target_count": "all"` 或正整数,不要重复空调用。
|
|
88
88
|
- 默认不自动轮询;只有用户要求查进度时才调用 `get_boss_chat_run`。
|
|
89
89
|
- `start_boss_chat_run` 返回 `ACCEPTED` 后,默认立即结束当前回合,不得主动连续调用 `get_boss_chat_run`。
|
|
@@ -95,12 +95,12 @@ Trae/Trae-CN split-server config exposes these under the `boss-chat` MCP server.
|
|
|
95
95
|
|
|
96
96
|
- 若用户先运行了 recommend 流水线,并在手动状态检查时确认 recommend 已完成,且用户目标是“立即进入聊天沟通”:
|
|
97
97
|
- 先调用 `prepare_boss_chat_run` 获取聊天页岗位列表与缺参。
|
|
98
|
-
- 显式确认 `job/start_from/target_count
|
|
98
|
+
- 显式确认 `job/start_from/target_count` 后再调用 `start_boss_chat_run`;若要继续筛选再求简历,还要确认 `criteria`。
|
|
99
99
|
- 不要查找或依赖 `follow_up.chat`;该自动衔接路径属于 legacy-only 行为。
|
|
100
100
|
|
|
101
101
|
## Response Style
|
|
102
102
|
|
|
103
103
|
- 用结构化中文。
|
|
104
104
|
- 首轮建议先调用一次 `list_boss_chat_jobs`(可空参)或 `prepare_boss_chat_run`(可空参)获取 `job_options` 与 `pending_questions`。
|
|
105
|
-
- 缺参时必须逐项确认:`job`(来自岗位列表)、`start_from`(`unread|all`)、`target_count`、`criteria
|
|
105
|
+
- 缺参时必须逐项确认:`job`(来自岗位列表)、`start_from`(`unread|all`)、`target_count`、`rest_level`;只有用户要筛选时才追问 `criteria`。
|
|
106
106
|
- 若健康检查失败,明确提示共享配置文件 `screening-config.json` 不可用。
|
|
@@ -20,6 +20,8 @@ Trae/Trae-CN split-server config exposes these under the `boss-recruit` MCP serv
|
|
|
20
20
|
- 继续:`resume_recruit_pipeline_run`
|
|
21
21
|
- 取消:`cancel_recruit_pipeline_run`
|
|
22
22
|
|
|
23
|
+
If the visible tool surface only offers `boss-recommend/*` for a search/recruit task, stop and report a tool-surface/config error. Do not call `boss-recommend/list_recommend_jobs`, `boss-recommend/run_recommend`, or `boss-recommend/start_recommend_pipeline_run` as a fallback for search.
|
|
24
|
+
|
|
23
25
|
## Hard Rules
|
|
24
26
|
|
|
25
27
|
- 只在用户明确说搜索页、search、recruit、招聘搜索、`/web/chat/search` 时使用本 skill。
|
|
@@ -31,9 +33,14 @@ Trae/Trae-CN split-server config exposes these under the `boss-recruit` MCP serv
|
|
|
31
33
|
- 只有工具返回 `BOSS_LOGIN_REQUIRED` / `requires_login=true` 时,才要求用户在自动打开的 Chrome 窗口人工登录 Boss 后重试;不要把“没开 9222 Chrome”当作缺参。
|
|
32
34
|
- 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
|
|
33
35
|
- 若用户未提供岗位,必须先询问岗位。搜索页岗位选择在关键词输入框旁边;不要猜测默认岗位。
|
|
36
|
+
- 搜索页任务不要调用 `list_recommend_jobs` 获取岗位;推荐页岗位列表和搜索页岗位选择不是同一个工具面。用户已经给出岗位时直接传 `overrides.job`。
|
|
34
37
|
- 若用户提供城市、学历、学校、关键词、过滤已看、人选目标数、筛选条件等参数,必须逐项传入或确认。
|
|
35
38
|
- 搜索页和推荐页一样支持多选筛选条件;不要把多选降级成单选。
|
|
36
39
|
- 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
|
|
40
|
+
- 启动前展示一次包含岗位、关键词、城市、学历、学校标签、是否过滤已看、criteria、目标人数、后置动作和休息强度的总确认;用户确认后,`confirmation` 只需要 `{ "final_confirmed": true }`。
|
|
41
|
+
- 不要让工具重写用户的 `criteria`。用户给出 `筛选条件` / `筛选标准` / `硬条件` 时,逐字写入 `overrides.criteria`;不要传系统简化版。
|
|
42
|
+
- 用户说学校类型“不限”时,在 `overrides.school_tag` 显式传 `"不限"` 或在 `overrides.schools` 传 `[]`;不要因为 criteria 里出现 `985/211/双一流` 就把它们当作搜索页学校过滤器。
|
|
43
|
+
- 用户说只看未查看“不限”时,在 `overrides.recent_not_view` 显式传 `"不限"` 或在 `overrides.filter_recent_viewed` 传 `false`。
|
|
37
44
|
|
|
38
45
|
## Required Inputs
|
|
39
46
|
|
|
@@ -51,10 +58,25 @@ Trae/Trae-CN split-server config exposes these under the `boss-recruit` MCP serv
|
|
|
51
58
|
- `recent_not_view`
|
|
52
59
|
- `port`
|
|
53
60
|
|
|
61
|
+
## Confirmation Flow
|
|
62
|
+
|
|
63
|
+
1. 先从用户原始消息里提取参数;缺什么只问什么。不要先调用 MCP 让工具猜缺参。
|
|
64
|
+
2. `criteria` 必须来自用户明确提供的筛选条件,或来自你向用户追问后得到的完整自然语言标准。禁止把岗位、关键词、学历等字段拼成 criteria。
|
|
65
|
+
3. 如果用户一开始已经给齐 `job`、`keyword`、`criteria`、`target_count`、`rest_level`,并且可选筛选项也已明确或确认不限,直接展示一次总确认。
|
|
66
|
+
4. 用户确认后调用 `boss-recruit/start_recruit_pipeline_run` 或 `boss-recruit/run_recruit_pipeline`,传:
|
|
67
|
+
- `confirmation: { "final_confirmed": true }`
|
|
68
|
+
- 所有规范化字段放在 `overrides`
|
|
69
|
+
- 完整 criteria 放在 `overrides.criteria`
|
|
70
|
+
- 本次休息强度放在 `human_behavior.restLevel`
|
|
71
|
+
5. 工具返回 `NEED_INPUT` 时,按 `pending_questions` 只补具体缺口;不要接受或转述工具生成的简化 criteria。
|
|
72
|
+
|
|
54
73
|
启动工具时,把用户确认的休息强度写入 `human_behavior.restLevel`,例如:
|
|
55
74
|
|
|
56
75
|
```json
|
|
57
|
-
{
|
|
76
|
+
{
|
|
77
|
+
"confirmation": { "final_confirmed": true },
|
|
78
|
+
"human_behavior": { "restLevel": "medium" }
|
|
79
|
+
}
|
|
58
80
|
```
|
|
59
81
|
|
|
60
82
|
## Response Style
|
package/src/chat-mcp.js
CHANGED
|
@@ -66,12 +66,11 @@ const RUN_MODE_ASYNC = "async";
|
|
|
66
66
|
const DETACHED_WORKER_SCRIPT = fileURLToPath(new URL("./detached-worker.js", import.meta.url));
|
|
67
67
|
const DETACHED_WORKER_POLL_MS = 1000;
|
|
68
68
|
|
|
69
|
-
const CHAT_REQUIRED_FIELDS = Object.freeze([
|
|
70
|
-
"job",
|
|
71
|
-
"start_from",
|
|
72
|
-
"target_count"
|
|
73
|
-
|
|
74
|
-
]);
|
|
69
|
+
const CHAT_REQUIRED_FIELDS = Object.freeze([
|
|
70
|
+
"job",
|
|
71
|
+
"start_from",
|
|
72
|
+
"target_count"
|
|
73
|
+
]);
|
|
75
74
|
|
|
76
75
|
const TERMINAL_STATUSES = new Set([
|
|
77
76
|
RUN_STATUS_COMPLETED,
|
|
@@ -1097,14 +1096,13 @@ function buildChatNextCallExample(args, missingFields, normalized) {
|
|
|
1097
1096
|
return Object.keys(example).length ? example : null;
|
|
1098
1097
|
}
|
|
1099
1098
|
|
|
1100
|
-
function getMissingChatStartFields(args = {}, normalized = normalizeChatStartInput(args)) {
|
|
1101
|
-
const missing = [];
|
|
1102
|
-
if (!normalized.job) missing.push("job");
|
|
1103
|
-
if (!["unread", "all"].includes(normalized.startFrom)) missing.push("start_from");
|
|
1104
|
-
if (!normalized.target.provided || normalized.target.parseError) missing.push("target_count");
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1099
|
+
function getMissingChatStartFields(args = {}, normalized = normalizeChatStartInput(args)) {
|
|
1100
|
+
const missing = [];
|
|
1101
|
+
if (!normalized.job) missing.push("job");
|
|
1102
|
+
if (!["unread", "all"].includes(normalized.startFrom)) missing.push("start_from");
|
|
1103
|
+
if (!normalized.target.provided || normalized.target.parseError) missing.push("target_count");
|
|
1104
|
+
return missing;
|
|
1105
|
+
}
|
|
1108
1106
|
|
|
1109
1107
|
function buildTargetCountDiagnostics(args, missingFields, normalized) {
|
|
1110
1108
|
if (!missingFields.includes("target_count")) return {};
|
|
@@ -1168,17 +1166,10 @@ function buildPendingChatQuestions({ args, missingFields, normalized, jobOptions
|
|
|
1168
1166
|
parse_error: normalized.target.parseError || null
|
|
1169
1167
|
};
|
|
1170
1168
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
value: normalized.criteria || null
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
return {
|
|
1179
|
-
field,
|
|
1180
|
-
question: `请提供 ${field}。`,
|
|
1181
|
-
value: null
|
|
1169
|
+
return {
|
|
1170
|
+
field,
|
|
1171
|
+
question: `请提供 ${field}。`,
|
|
1172
|
+
value: null
|
|
1182
1173
|
};
|
|
1183
1174
|
});
|
|
1184
1175
|
}
|
|
@@ -1186,19 +1177,21 @@ function buildPendingChatQuestions({ args, missingFields, normalized, jobOptions
|
|
|
1186
1177
|
async function buildNeedInputResponse({ args, missingFields, normalized }) {
|
|
1187
1178
|
const diagnostics = buildTargetCountDiagnostics(args, missingFields, normalized);
|
|
1188
1179
|
return {
|
|
1189
|
-
status: "NEED_INPUT",
|
|
1190
|
-
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
1191
|
-
missing_fields: missingFields,
|
|
1192
|
-
|
|
1180
|
+
status: "NEED_INPUT",
|
|
1181
|
+
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
1182
|
+
missing_fields: missingFields,
|
|
1183
|
+
criteria_optional: true,
|
|
1184
|
+
empty_criteria_mode: "collect_cv",
|
|
1185
|
+
...diagnostics,
|
|
1193
1186
|
pending_questions: buildPendingChatQuestions({ args, missingFields, normalized }),
|
|
1194
1187
|
job_options: [],
|
|
1195
|
-
error: {
|
|
1196
|
-
code: "MISSING_REQUIRED_FIELDS",
|
|
1197
|
-
message: "缺少必要字段。请补齐 job、start_from、target_count
|
|
1198
|
-
retryable: true
|
|
1199
|
-
}
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1188
|
+
error: {
|
|
1189
|
+
code: "MISSING_REQUIRED_FIELDS",
|
|
1190
|
+
message: "缺少必要字段。请补齐 job、start_from、target_count 后再启动 Boss chat CDP-only run。criteria 可留空;留空时会进入收集简历模式。",
|
|
1191
|
+
retryable: true
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1202
1195
|
|
|
1203
1196
|
function shouldRequestChatResume(args = {}, context = {}) {
|
|
1204
1197
|
const action = normalizeText(args.post_action || args.action).toLowerCase();
|
|
@@ -1232,38 +1225,40 @@ function isDebugTestMode(args = {}) {
|
|
|
1232
1225
|
return args.debug_test_mode === true || args.allow_debug_test_mode === true;
|
|
1233
1226
|
}
|
|
1234
1227
|
|
|
1235
|
-
function normalizeScreeningModeArg(args = {}) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (
|
|
1247
|
-
if (args.
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1228
|
+
function normalizeScreeningModeArg(args = {}, normalized = normalizeChatStartInput(args)) {
|
|
1229
|
+
if (!normalized.criteria) return "collect_cv";
|
|
1230
|
+
const raw = normalizeText(args.screening_mode || args.screeningMode || "");
|
|
1231
|
+
if (args.use_llm === false) return "deterministic";
|
|
1232
|
+
return ["deterministic", "local", "local_scorer"].includes(raw.toLowerCase())
|
|
1233
|
+
? "deterministic"
|
|
1234
|
+
: "llm";
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function collectChatDebugTestOptions(args = {}) {
|
|
1238
|
+
const reasons = [];
|
|
1239
|
+
if (normalizeScreeningModeArg(args) === "deterministic") reasons.push("deterministic_screening");
|
|
1240
|
+
if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
|
|
1241
|
+
if (args.dry_run === true || args.dry_run_request_cv === true) reasons.push("dry_run_request_cv");
|
|
1242
|
+
return reasons;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function shouldUseChatLlm(args = {}, normalized = normalizeChatStartInput(args)) {
|
|
1246
|
+
return normalizeScreeningModeArg(args, normalized) === "llm";
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function getRunOptions(args, normalized, session, { workspaceRoot = "", configResolution = null } = {}) {
|
|
1256
1250
|
const slowLive = args.slow_live === true;
|
|
1257
1251
|
const isAllTarget = normalized.publicTargetCount === "all";
|
|
1258
1252
|
const processedLimit = parsePositiveInteger(
|
|
1259
1253
|
args.max_candidates,
|
|
1260
1254
|
isAllTarget ? CHAT_ALL_MAX_CANDIDATES : CHAT_ALL_MAX_CANDIDATES
|
|
1261
|
-
);
|
|
1262
|
-
const shouldRequestResume = shouldRequestChatResume(args);
|
|
1263
|
-
const
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1255
|
+
);
|
|
1256
|
+
const shouldRequestResume = shouldRequestChatResume(args);
|
|
1257
|
+
const screeningMode = normalizeScreeningModeArg(args, normalized);
|
|
1258
|
+
const useLlm = shouldUseChatLlm(args, normalized);
|
|
1259
|
+
const resolvedConfig = configResolution || (useLlm ? resolveBossScreeningConfig(workspaceRoot) : { ok: false });
|
|
1260
|
+
const humanBehavior = resolveHumanBehaviorForRun(args, resolvedConfig?.config || {});
|
|
1261
|
+
return {
|
|
1267
1262
|
client: session.client,
|
|
1268
1263
|
targetUrl: CHAT_TARGET_URL,
|
|
1269
1264
|
job: normalized.job,
|
|
@@ -1302,7 +1297,7 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "", configRe
|
|
|
1302
1297
|
llmImageDetail: normalizeText(
|
|
1303
1298
|
args.llm_image_detail || resolvedConfig.config?.llmImageDetail || resolvedConfig.config?.imageDetail
|
|
1304
1299
|
) || "low",
|
|
1305
|
-
screeningMode
|
|
1300
|
+
screeningMode,
|
|
1306
1301
|
listMaxScrolls: parsePositiveInteger(args.list_max_scrolls, 200),
|
|
1307
1302
|
listStableSignatureLimit: parsePositiveInteger(args.list_stable_signature_limit, 2),
|
|
1308
1303
|
listWheelDeltaY: parsePositiveInteger(args.list_wheel_delta_y, 850),
|
|
@@ -1373,7 +1368,7 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "", runId =
|
|
|
1373
1368
|
}
|
|
1374
1369
|
|
|
1375
1370
|
const shouldRequestResume = shouldRequestChatResume(args);
|
|
1376
|
-
const useLlm = shouldUseChatLlm(args);
|
|
1371
|
+
const useLlm = shouldUseChatLlm(args, normalized);
|
|
1377
1372
|
const debugTestOptions = collectChatDebugTestOptions(args);
|
|
1378
1373
|
if (debugTestOptions.length && !isDebugTestMode(args)) {
|
|
1379
1374
|
return {
|
|
@@ -1538,9 +1533,11 @@ export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } =
|
|
|
1538
1533
|
status: missingFields.length ? "NEED_INPUT" : "READY",
|
|
1539
1534
|
stage: "chat_run_setup",
|
|
1540
1535
|
page_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
1541
|
-
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
1542
|
-
missing_fields: missingFields,
|
|
1543
|
-
|
|
1536
|
+
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
1537
|
+
missing_fields: missingFields,
|
|
1538
|
+
criteria_optional: true,
|
|
1539
|
+
empty_criteria_mode: "collect_cv",
|
|
1540
|
+
job_options: jobOptions,
|
|
1544
1541
|
selected_job: selectedJob,
|
|
1545
1542
|
selected_job_label: jobs?.selected_label || selectedJob?.label || "",
|
|
1546
1543
|
job_options_source: jobs?.source || "",
|
|
@@ -1552,10 +1549,10 @@ export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } =
|
|
|
1552
1549
|
jobOptions
|
|
1553
1550
|
}),
|
|
1554
1551
|
...diagnostics,
|
|
1555
|
-
...(nextCallExample ? { next_call_example: nextCallExample } : {}),
|
|
1556
|
-
message: missingFields.length
|
|
1557
|
-
? "已通过 CDP-only 读取 Boss 聊天页岗位列表,请补齐 job / start_from / target_count
|
|
1558
|
-
: "Boss chat CDP-only preflight is ready. Use start_boss_chat_run to start screening.",
|
|
1552
|
+
...(nextCallExample ? { next_call_example: nextCallExample } : {}),
|
|
1553
|
+
message: missingFields.length
|
|
1554
|
+
? "已通过 CDP-only 读取 Boss 聊天页岗位列表,请补齐 job / start_from / target_count。criteria 可留空;留空会进入收集简历模式。"
|
|
1555
|
+
: "Boss chat CDP-only preflight is ready. Use start_boss_chat_run to start screening or collect CVs.",
|
|
1559
1556
|
runtime_evaluate_used: false,
|
|
1560
1557
|
method_summary: methodSummary(session.methodLog || []),
|
|
1561
1558
|
method_log: session.methodLog || [],
|
|
@@ -1723,7 +1720,7 @@ export async function startBossChatDetachedRunTool({ workspaceRoot = "", args =
|
|
|
1723
1720
|
});
|
|
1724
1721
|
}
|
|
1725
1722
|
|
|
1726
|
-
const useLlm = shouldUseChatLlm(args);
|
|
1723
|
+
const useLlm = shouldUseChatLlm(args, normalized);
|
|
1727
1724
|
const debugTestOptions = collectChatDebugTestOptions(args);
|
|
1728
1725
|
if (debugTestOptions.length && !isDebugTestMode(args)) {
|
|
1729
1726
|
return {
|
|
@@ -12,6 +12,7 @@ const SCREEN_CONFIG_TEMPLATE_DEFAULTS = Object.freeze({
|
|
|
12
12
|
model: "gpt-4.1-mini"
|
|
13
13
|
});
|
|
14
14
|
const LLM_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "auto", "current"]);
|
|
15
|
+
const LLM_SCREENING_STRATEGIES = new Set(["single_pass", "fast_first_verified"]);
|
|
15
16
|
|
|
16
17
|
export const TARGET_COUNT_CANONICAL_ALL = "all";
|
|
17
18
|
export const TARGET_COUNT_ACCEPTED_EXAMPLES = [TARGET_COUNT_CANONICAL_ALL, -1, 20, "全部候选人"];
|
|
@@ -322,6 +323,11 @@ function normalizeLlmThinkingLevel(raw, fallback = "low") {
|
|
|
322
323
|
return LLM_THINKING_LEVELS.has(normalized) ? normalized : fallback;
|
|
323
324
|
}
|
|
324
325
|
|
|
326
|
+
function normalizeLlmScreeningStrategy(raw, fallback = "single_pass") {
|
|
327
|
+
const normalized = normalizeText(raw).toLowerCase();
|
|
328
|
+
return LLM_SCREENING_STRATEGIES.has(normalized) ? normalized : fallback;
|
|
329
|
+
}
|
|
330
|
+
|
|
325
331
|
function firstConfiguredValue(...values) {
|
|
326
332
|
for (const value of values) {
|
|
327
333
|
if (value === undefined || value === null) continue;
|
|
@@ -352,6 +358,26 @@ function normalizeScreeningLlmModel(config = {}, rawEntry = {}, index = 0) {
|
|
|
352
358
|
firstConfiguredValue(entry.llmThinkingLevel, entry.thinkingLevel, entry.reasoningEffort, config.llmThinkingLevel, config.thinkingLevel, config.reasoningEffort),
|
|
353
359
|
"low"
|
|
354
360
|
),
|
|
361
|
+
llmScreeningStrategy: normalizeLlmScreeningStrategy(
|
|
362
|
+
firstConfiguredValue(entry.llmScreeningStrategy, entry.screeningStrategy, entry.screening_strategy, config.llmScreeningStrategy, config.screeningStrategy, config.screening_strategy),
|
|
363
|
+
"single_pass"
|
|
364
|
+
),
|
|
365
|
+
llmFastThinkingLevel: normalizeLlmThinkingLevel(
|
|
366
|
+
firstConfiguredValue(entry.llmFastThinkingLevel, entry.fastThinkingLevel, entry.fast_thinking_level, config.llmFastThinkingLevel, config.fastThinkingLevel, config.fast_thinking_level),
|
|
367
|
+
"current"
|
|
368
|
+
),
|
|
369
|
+
llmVerifyThinkingLevel: normalizeLlmThinkingLevel(
|
|
370
|
+
firstConfiguredValue(entry.llmVerifyThinkingLevel, entry.verifyThinkingLevel, entry.verify_thinking_level, config.llmVerifyThinkingLevel, config.verifyThinkingLevel, config.verify_thinking_level),
|
|
371
|
+
"low"
|
|
372
|
+
),
|
|
373
|
+
llmFastMaxTokens: parsePositiveInteger(
|
|
374
|
+
firstConfiguredValue(entry.llmFastMaxTokens, entry.fastMaxTokens, entry.fast_max_tokens, config.llmFastMaxTokens, config.fastMaxTokens, config.fast_max_tokens),
|
|
375
|
+
null
|
|
376
|
+
),
|
|
377
|
+
llmVerifyMaxTokens: parsePositiveInteger(
|
|
378
|
+
firstConfiguredValue(entry.llmVerifyMaxTokens, entry.verifyMaxTokens, entry.verify_max_tokens, config.llmVerifyMaxTokens, config.verifyMaxTokens, config.verify_max_tokens),
|
|
379
|
+
null
|
|
380
|
+
),
|
|
355
381
|
llmTimeoutMs: parsePositiveInteger(firstConfiguredValue(entry.llmTimeoutMs, entry.timeoutMs, config.llmTimeoutMs, config.timeoutMs), null),
|
|
356
382
|
llmMaxRetries: parsePositiveInteger(firstConfiguredValue(entry.llmMaxRetries, entry.maxRetries, config.llmMaxRetries, config.maxRetries), null),
|
|
357
383
|
llmMaxTokens: parsePositiveInteger(firstConfiguredValue(entry.llmMaxTokens, entry.maxTokens, config.llmMaxTokens, config.maxTokens), null),
|
|
@@ -17,6 +17,10 @@ export const LEGACY_RESULT_HEADER = [
|
|
|
17
17
|
"原始判定通过",
|
|
18
18
|
"最终判定通过",
|
|
19
19
|
"LLM thinking_level",
|
|
20
|
+
"LLM screening_strategy",
|
|
21
|
+
"LLM decision_source",
|
|
22
|
+
"LLM verified",
|
|
23
|
+
"LLM verification_reason",
|
|
20
24
|
"错误码",
|
|
21
25
|
"错误信息",
|
|
22
26
|
"候选人ID",
|
|
@@ -282,7 +286,11 @@ export function legacyScreenResultRow(row = {}) {
|
|
|
282
286
|
rawPassed,
|
|
283
287
|
finalPassed,
|
|
284
288
|
firstText(llm.provider?.thinking_level),
|
|
285
|
-
|
|
289
|
+
firstText(llm.screening_strategy),
|
|
290
|
+
firstText(llm.decision_source),
|
|
291
|
+
typeof llm.verified === "boolean" ? llm.verified : "",
|
|
292
|
+
firstText(llm.verification_reason),
|
|
293
|
+
row.error_code || error.code || error.name || llm.error_code || (llm.error ? "LLM_SCREENING_ERROR" : ""),
|
|
286
294
|
row.error_message || error.message || llm.error || "",
|
|
287
295
|
candidate.id || row.candidate_id || "",
|
|
288
296
|
timingValue(row, "total_ms"),
|