@reconcrap/boss-recommend-mcp 2.1.3 → 2.1.5
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 +15 -39
- package/package.json +1 -1
- package/skills/boss-recommend-pipeline/README.md +1 -1
- package/skills/boss-recommend-pipeline/SKILL.md +27 -26
- package/skills/boss-recruit-pipeline/SKILL.md +1 -4
- package/src/domains/recommend/actions.js +2 -3
- package/src/domains/recommend/run-service.js +32 -42
- package/src/index.js +21 -17
- package/src/parser.js +38 -87
- package/src/recommend-mcp.js +159 -69
package/README.md
CHANGED
|
@@ -116,9 +116,9 @@ MCP 工具:
|
|
|
116
116
|
boss-recommend-mcp list-jobs --slow-live --port 9222
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
返回的 `job_names` 可直接作为后续 `start_recommend_pipeline_run` 的 `confirmation.job_value`
|
|
119
|
+
返回的 `job_names` 可直接作为后续 `start_recommend_pipeline_run` 的 `overrides.job`;旧版 `confirmation.job_value` 仍兼容。
|
|
120
120
|
|
|
121
|
-
Cron / 一次性定时任务设置建议先在设置阶段完成 Chrome
|
|
121
|
+
Cron / 一次性定时任务设置建议先在设置阶段完成 Chrome/登录/岗位发现与一次总确认;确认文件推荐只包含 `{ "final_confirmed": true }`:
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
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
|
|
@@ -158,20 +158,18 @@ boss-recommend-mcp schedule-status --schedule-id <schedule_id>
|
|
|
158
158
|
- 学校标签支持多选语义:如“985、211”会同时勾选这两项
|
|
159
159
|
- 学校标签对“混合输入”按容错处理:如“985、211、qs100”会忽略无效项 `qs100`,保留并应用有效项;仅当全部无效或用户明确“不限”时才回落到“不限”
|
|
160
160
|
- 学历支持单选与多选语义:如“本科及以上”会展开为 `本科/硕士/博士`;如“大专、本科”只勾选这两项
|
|
161
|
-
-
|
|
162
|
-
- 页面就绪(已登录且在 recommend
|
|
163
|
-
- 在真正开始 search/screen
|
|
161
|
+
- 执行前会先补齐筛选值、岗位、后置动作和休息强度,然后只做一次总确认
|
|
162
|
+
- 页面就绪(已登录且在 recommend 页)后,会先提取岗位栏全部岗位,使用精确岗位名填入 run payload,再进入总确认
|
|
163
|
+
- 在真正开始 search/screen 或创建 cron 前,总确认需包含岗位、全部筛选参数、criteria、target_count、post_action、可选 max_greet_count、restLevel 和定时信息(如适用)
|
|
164
164
|
- npm 全局安装后会自动执行 install:生成 skill、导出 MCP 模板,并自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
|
|
165
165
|
- 2.x installer 会迁移已存在的 legacy Boss MCP 配置:把 `boss-recommend` 指向统一 `@reconcrap/boss-recommend-mcp`,并从同一个 `mcp.json` 中移除旧 `boss-recruit-mcp` / standalone `boss-chat` / 本地 legacy Boss 路径;写入前会生成 `.boss-mcp-migration-*.bak` 备份
|
|
166
166
|
- 2.x installer 会刷新外部 agent skills:`boss-recommend-pipeline`、`boss-recruit-pipeline`、`boss-chat` 都来自当前包,旧 recruit/chat skill 会被覆盖为统一 MCP 路由
|
|
167
167
|
- npm / npx 安装后会自动初始化 `screening-config.json` 模板(优先写入 workspace 的 `config/`,不可写时回退到用户目录)
|
|
168
168
|
- npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs`、`~/.boss-recommend-mcp/boss-chat` 及其 `logs/runs/profiles/reports/artifacts/state`
|
|
169
|
-
- `post_action`
|
|
170
|
-
- `
|
|
171
|
-
- 当 `post_action=greet` 时,必须在运行开始时确认 `max_greet_count`
|
|
172
|
-
- 若检测到 `max_greet_count` 可能由 agent 自动默认(例如与 `target_count` 相同且原始指令未明确),会强制再次向用户确认
|
|
169
|
+
- `post_action`、`target_count` 和可选 `max_greet_count` 通过同一次总确认锁定
|
|
170
|
+
- 新流程中 `confirmation.final_confirmed=true` 是总确认;旧版逐字段 `*_confirmed` JSON 仍兼容但不是推荐写法
|
|
173
171
|
- 一旦确认 `post_action`,本次运行内所有通过人选都统一按该动作执行
|
|
174
|
-
-
|
|
172
|
+
- 若达到可选 `max_greet_count` 但流程仍需继续,后续通过人选会继续筛选但不再执行打招呼动作
|
|
175
173
|
- 不会对每位候选人重复确认
|
|
176
174
|
- 推荐页详情处理完成后,会强制关闭详情页并确认已关闭
|
|
177
175
|
- 简历提取优先使用 Network 响应;没有可解析 Network CV 时,回退到完整滚动截图序列再交给多模态模型判断
|
|
@@ -492,27 +490,10 @@ Trae-CN / 长对话防循环建议:
|
|
|
492
490
|
{
|
|
493
491
|
"instruction": "推荐页筛选211女生,近14天没有,有 AI Agent 经验,符合标准的直接沟通",
|
|
494
492
|
"confirmation": {
|
|
495
|
-
"
|
|
496
|
-
"school_tag_confirmed": true,
|
|
497
|
-
"school_tag_value": ["985", "211"],
|
|
498
|
-
"degree_confirmed": true,
|
|
499
|
-
"degree_value": ["本科", "硕士", "博士"],
|
|
500
|
-
"gender_confirmed": true,
|
|
501
|
-
"gender_value": "女",
|
|
502
|
-
"recent_not_view_confirmed": true,
|
|
503
|
-
"recent_not_view_value": "近14天没有",
|
|
504
|
-
"criteria_confirmed": true,
|
|
505
|
-
"target_count_confirmed": true,
|
|
506
|
-
"target_count_value": 20,
|
|
507
|
-
"post_action_confirmed": true,
|
|
508
|
-
"post_action_value": "greet",
|
|
509
|
-
"final_confirmed": true,
|
|
510
|
-
"job_confirmed": true,
|
|
511
|
-
"job_value": "算法工程师(视频/图像模型方向) _ 杭州",
|
|
512
|
-
"max_greet_count_confirmed": true,
|
|
513
|
-
"max_greet_count_value": 10
|
|
493
|
+
"final_confirmed": true
|
|
514
494
|
},
|
|
515
495
|
"overrides": {
|
|
496
|
+
"page_scope": "recommend",
|
|
516
497
|
"school_tag": ["985", "211"],
|
|
517
498
|
"degree": ["本科", "硕士", "博士"],
|
|
518
499
|
"gender": "女",
|
|
@@ -520,21 +501,16 @@ Trae-CN / 长对话防循环建议:
|
|
|
520
501
|
"criteria": "候选人需要有 AI Agent 或 MCP 工具开发经验",
|
|
521
502
|
"job": "算法工程师(视频/图像模型方向) _ 杭州",
|
|
522
503
|
"target_count": 20,
|
|
523
|
-
"post_action": "greet"
|
|
524
|
-
"max_greet_count": 10
|
|
504
|
+
"post_action": "greet"
|
|
525
505
|
},
|
|
526
|
-
"
|
|
527
|
-
"
|
|
528
|
-
"criteria": "继续在聊天页处理有 AI Agent 或 MCP 项目经验的人选",
|
|
529
|
-
"start_from": "unread",
|
|
530
|
-
"target_count": 20,
|
|
531
|
-
"profile": "default",
|
|
532
|
-
"safe_pacing": true
|
|
533
|
-
}
|
|
506
|
+
"human_behavior": {
|
|
507
|
+
"restLevel": "medium"
|
|
534
508
|
}
|
|
535
509
|
}
|
|
536
510
|
```
|
|
537
511
|
|
|
512
|
+
`confirmation.final_confirmed=true` 表示用户已经看过并确认总览。旧版 `page_confirmed`、`school_tag_confirmed`、`job_confirmed` 等逐字段布尔值仍兼容,但新流程不需要主动生成它们。
|
|
513
|
+
|
|
538
514
|
## 测试
|
|
539
515
|
|
|
540
516
|
```bash
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- 先确认推荐页 filters
|
|
8
8
|
- 再确认筛选 criteria
|
|
9
|
-
- 再确认本次运行统一动作 `
|
|
9
|
+
- 再确认本次运行统一动作 `greet` 或 `none`
|
|
10
10
|
- 每次运行都要让用户明确选择休息强度 `low` / `medium` / `high`,并传入 `human_behavior.restLevel`
|
|
11
11
|
- 只确认一次 `post_action`,不要逐个候选人反复确认
|
|
12
12
|
- 运行中临时中断请使用 `pause_recommend_pipeline_run`(按 run_id),不要靠自然语言“暂停/继续”指令
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "boss-recommend-pipeline"
|
|
3
|
-
description: "Use when users want Boss recommend-page filtering/screening via boss-recommend-mcp.
|
|
3
|
+
description: "Use when users want Boss recommend-page filtering/screening via boss-recommend-mcp. Gather required params, show one consolidated review, then run or schedule through the recommend MCP tools."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Boss Recommend Pipeline Skill
|
|
7
7
|
|
|
8
8
|
## Goal
|
|
9
9
|
|
|
10
|
-
当用户要在 Boss 推荐页筛人时,必须走 `start_recommend_pipeline_run
|
|
10
|
+
当用户要在 Boss 推荐页筛人时,必须走 `start_recommend_pipeline_run`;若是稍后/cron 启动,必须走 `schedule_recommend_pipeline_run`。先补齐缺失值并读取岗位列表,然后展示一次包含岗位、筛选项、criteria、目标人数、后置动作、可选最大招呼数、休息强度和定时信息的总确认;用户确认后设置 `final_confirmed=true` 即可启动或创建定时任务。2.0 CDP-only 路径不再支持 legacy recommend -> chat 自动衔接;若用户要聊天页筛选或求简历,必须在推荐页任务完成后显式改用 `boss-chat` 工具。
|
|
11
11
|
|
|
12
12
|
## Hard Rules (Must Follow)
|
|
13
13
|
|
|
@@ -19,10 +19,11 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
19
19
|
|
|
20
20
|
- **确认不可代填(强制)**
|
|
21
21
|
- 禁止 agent 自行“设置合理参数”并代替用户确认。
|
|
22
|
-
-
|
|
22
|
+
- 禁止在用户未明确回复前,把 `final_confirmed=true`。
|
|
23
|
+
- 旧版 `*_confirmed` 字段仍兼容,但新流程不要逐项设置;把规范化后的值写入 `overrides`,总确认后只需要 `confirmation.final_confirmed=true`。
|
|
23
24
|
- 禁止在用户未明确回复前,自行填充 `page_scope/school_tag/degree/gender/recent_not_view/criteria/target_count/post_action/max_greet_count/job/rest_level`。
|
|
24
25
|
- 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
|
|
25
|
-
- 若工具返回 `pending_questions
|
|
26
|
+
- 若工具返回 `pending_questions`,只追问这些缺口;若只返回 `final_review`,不要再拆成逐字段确认。
|
|
26
27
|
|
|
27
28
|
- **岗位确认时机**
|
|
28
29
|
- 页面未就绪前,禁止询问 `job`。
|
|
@@ -30,7 +31,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
30
31
|
|
|
31
32
|
- **参数确认**
|
|
32
33
|
- `criteria` 必须是用户开放式自然语言;禁止“严格/宽松执行”等预设替代。
|
|
33
|
-
- `post_action=greet`
|
|
34
|
+
- `max_greet_count` 仅是可选的 `post_action=greet` 上限;禁止未告知用户就自动默认为 `target_count`。
|
|
34
35
|
- 正式执行前必须 `final_confirmed=true`。
|
|
35
36
|
- 真实筛选禁止传 `detail_limit: 0`;recommend 默认必须打开候选人详情/CV。只有用户明确要求“卡片-only 调试”时,才允许同时传 `detail_limit: 0` 和 `allow_card_only_screening: true`。
|
|
36
37
|
|
|
@@ -40,29 +41,29 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
40
41
|
- 最终执行前逐字回显将提交的 `instruction`;若与锁定值不一致,先修正再执行。
|
|
41
42
|
- 禁止在中途把用户意图拆成“recommend 已默认确认 + chat 单独执行”两条链路。
|
|
42
43
|
|
|
43
|
-
##
|
|
44
|
+
## Single Review Confirmation
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
必须确认:
|
|
46
|
+
先收集这些值:
|
|
48
47
|
|
|
49
48
|
- `page_scope`:`recommend|latest|featured`
|
|
50
|
-
- `school_tag
|
|
51
|
-
- `degree`(多选)
|
|
52
|
-
- `gender`
|
|
53
|
-
- `recent_not_view`
|
|
49
|
+
- `school_tag`、`degree`、`gender`、`recent_not_view`
|
|
54
50
|
- `criteria`(开放文本)
|
|
55
51
|
- `target_count`(可空)
|
|
56
|
-
- `post_action`:`
|
|
57
|
-
- `max_greet_count
|
|
52
|
+
- `post_action`:`greet|none`
|
|
53
|
+
- `max_greet_count`(可选,仅当 `post_action=greet`)
|
|
58
54
|
- `rest_level`:`low|medium|high`
|
|
55
|
+
- `job`(来自 `list_recommend_jobs` / `job_options` 的精确岗位名)
|
|
56
|
+
- cron/定时任务的启动时间(如适用)
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
然后只做一次总确认。用户回复“确认 / 全部确认 / 无需调整”后,下一次工具调用写入:
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"confirmation": { "final_confirmed": true }
|
|
63
|
+
}
|
|
64
|
+
```
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
- `final_review`(岗位 + 全参数总确认)
|
|
66
|
+
已确认值放在 `overrides` 和 `human_behavior.restLevel`。不要因为工具返回 `final_review` 就再问页面、学历、学校、性别、14天、criteria、目标人数、动作、最大招呼数等逐项确认。
|
|
66
67
|
|
|
67
68
|
## Chat Handoff
|
|
68
69
|
|
|
@@ -83,7 +84,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
83
84
|
- `degree`: `不限/初中及以下/中专/中技/高中/大专/本科/硕士/博士`
|
|
84
85
|
- `gender`: `不限/男/女`
|
|
85
86
|
- `recent_not_view`: `不限/近14天没有`
|
|
86
|
-
- `post_action`: `
|
|
87
|
+
- `post_action`: `greet/none`
|
|
87
88
|
- `rest_level`: `low/medium/high`
|
|
88
89
|
|
|
89
90
|
## Tool Usage
|
|
@@ -105,7 +106,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
105
106
|
- 主工具:`start_recommend_pipeline_run`
|
|
106
107
|
- 必填:`instruction`
|
|
107
108
|
- 关键输入:
|
|
108
|
-
- `confirmation
|
|
109
|
+
- `confirmation`:新流程只需要 `{ "final_confirmed": true }`;旧版 `page_confirmed/page_value/.../job_confirmed/job_value` 仍兼容但不要主动制造逐项确认。
|
|
109
110
|
- `overrides`:`page_scope/school_tag/degree/gender/recent_not_view/criteria/job/target_count/post_action/max_greet_count`
|
|
110
111
|
- `human_behavior`:必须包含本次用户确认的 `restLevel`(例如 `{ "restLevel": "medium" }`)
|
|
111
112
|
- 不要传 `follow_up.chat`;该路径属于 legacy-only 行为
|
|
@@ -122,9 +123,9 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
122
123
|
创建 cron 时必须在设置 cron 的当下完成全部交互:
|
|
123
124
|
|
|
124
125
|
1. 锁定用户原始 instruction,不改写、不摘要,criteria 放入 `overrides.criteria` 时必须逐字保留。
|
|
125
|
-
2.
|
|
126
|
+
2. 收集全部筛选项、`target_count`、`post_action`、可选 `max_greet_count` 和本次 `rest_level`。
|
|
126
127
|
3. 调用 `list_recommend_jobs`;若 Chrome 未打开,工具会尝试自动打开本机 9222 Chrome 并进入推荐页。若返回 `BOSS_LOGIN_REQUIRED` 或页面不可用,停止 cron 创建,让用户登录/修复后重试。
|
|
127
|
-
4. 用 `job_names`
|
|
128
|
+
4. 用 `job_names` 中的精确岗位名填入 `overrides.job`,展示包含启动时间的最终总确认;用户确认后写入 `final_confirmed=true`。
|
|
128
129
|
5. 调用 `prepare_recommend_pipeline_run` 传入将来要执行的完整 payload;只有 `READY + cron_ready=true` 才能继续。
|
|
129
130
|
6. 立即调用 `schedule_recommend_pipeline_run`,传入同一份完整 payload 和 `schedule_delay_minutes` / `schedule_run_at`。禁止让 OpenClaw 自己写 `/tmp/*.log` shell cron、自然语言提醒、或未来对话回调来代替此工具。
|
|
130
131
|
7. 只有拿到 `SCHEDULED + schedule_id` 后才告诉用户定时任务已创建。回复必须包含 `schedule_id`,而不是只说“10 分钟后会启动”。
|
|
@@ -192,7 +193,7 @@ npx -y @reconcrap/boss-recommend-mcp@latest schedule-status --schedule-id <sched
|
|
|
192
193
|
|
|
193
194
|
推荐做法:
|
|
194
195
|
|
|
195
|
-
1. 将锁定的用户原文写入 instruction 文件,将已确认参数写入 `overrides`
|
|
196
|
+
1. 将锁定的用户原文写入 instruction 文件,将已确认参数写入 `overrides` JSON;`confirmation` JSON 只需要 `{ "final_confirmed": true }`。
|
|
196
197
|
2. 先用 `prepare-run` 校验完整 payload 已 cron-ready;未返回 `READY + cron_ready=true` 不得创建定时任务或启动 run。
|
|
197
198
|
3. 若用户要“现在启动”,用 detached CLI 启动,让父命令返回启动证据,子进程继续持有 CDP 会话:
|
|
198
199
|
|
|
@@ -220,8 +221,8 @@ npx -y @reconcrap/boss-recommend-mcp@latest run --detached --instruction-file .\
|
|
|
220
221
|
## Response Style
|
|
221
222
|
|
|
222
223
|
- 用结构化中文。
|
|
223
|
-
-
|
|
224
|
-
- 仅在 `job_options`
|
|
224
|
+
- 未读取岗位列表前不要要求用户最终确认。
|
|
225
|
+
- 仅在 `job_options` 出现后选择精确岗位;最终确认卡片必须包含岗位和全部参数。
|
|
225
226
|
- 封闭式问题必须带完整标签选项;开放式问题(如 `criteria`)保持自由输入。
|
|
226
227
|
- 页面就绪失败提示必须包含 `debug_port`、recommend URL、以及登录 URL(若未登录):
|
|
227
228
|
- `https://www.zhipin.com/web/chat/recommend`
|
|
@@ -29,8 +29,7 @@ description: "Use when users want Boss search/recruit-page screening via the uni
|
|
|
29
29
|
- 只有工具返回 `BOSS_LOGIN_REQUIRED` / `requires_login=true` 时,才要求用户在自动打开的 Chrome 窗口人工登录 Boss 后重试;不要把“没开 9222 Chrome”当作缺参。
|
|
30
30
|
- 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
|
|
31
31
|
- 若用户未提供岗位,必须先询问岗位。搜索页岗位选择在关键词输入框旁边;不要猜测默认岗位。
|
|
32
|
-
-
|
|
33
|
-
- `post_action=greet` 时必须确认 `max_greet_count`;不要默认等于 `target_count`。
|
|
32
|
+
- 若用户提供城市、学历、学校、关键词、过滤已看、人选目标数、筛选条件等参数,必须逐项传入或确认。
|
|
34
33
|
- 搜索页和推荐页一样支持多选筛选条件;不要把多选降级成单选。
|
|
35
34
|
- 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
|
|
36
35
|
|
|
@@ -48,8 +47,6 @@ description: "Use when users want Boss search/recruit-page screening via the uni
|
|
|
48
47
|
- `degree`
|
|
49
48
|
- `school_tag`
|
|
50
49
|
- `recent_not_view`
|
|
51
|
-
- `post_action`
|
|
52
|
-
- `max_greet_count`
|
|
53
50
|
- `port`
|
|
54
51
|
|
|
55
52
|
启动工具时,把用户确认的休息强度写入 `human_behavior.restLevel`,例如:
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { waitForRecommendDetail } from "./detail.js";
|
|
23
23
|
import { getRecommendRoots } from "./roots.js";
|
|
24
24
|
|
|
25
|
-
const POST_ACTIONS = new Set(["none", "
|
|
25
|
+
const POST_ACTIONS = new Set(["none", "greet"]);
|
|
26
26
|
const GREET_EXACT_LABEL_PATTERN = /^(?:打招呼|聊一聊|立即沟通(?:[\((]\d+\s*[//]\s*\d+[\))])?|沟通)$/i;
|
|
27
27
|
export const RECOMMEND_DETAIL_ACTION_TEXT_SELECTORS = Object.freeze([
|
|
28
28
|
"button",
|
|
@@ -94,7 +94,6 @@ function bestControl(controls, exactLabelPattern) {
|
|
|
94
94
|
export function normalizeRecommendPostAction(value) {
|
|
95
95
|
const normalized = normalizeText(value).toLowerCase();
|
|
96
96
|
if (["", "none", "skip", "no", "不执行", "无"].includes(normalized)) return "none";
|
|
97
|
-
if (["favorite", "fav", "collect", "收藏", "感兴趣"].includes(normalized)) return "favorite";
|
|
98
97
|
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
99
98
|
return POST_ACTIONS.has(normalized) ? normalized : "";
|
|
100
99
|
}
|
|
@@ -110,7 +109,7 @@ export function resolveRecommendPostAction({
|
|
|
110
109
|
if (requested === "greet" && limit !== null && currentGreetCount >= limit) {
|
|
111
110
|
return {
|
|
112
111
|
requested,
|
|
113
|
-
effective: "
|
|
112
|
+
effective: "none",
|
|
114
113
|
reason: "greet_limit_reached",
|
|
115
114
|
greet_count: currentGreetCount,
|
|
116
115
|
max_greet_count: limit
|
|
@@ -283,22 +283,22 @@ async function runRecommendPostAction({
|
|
|
283
283
|
reason: ""
|
|
284
284
|
};
|
|
285
285
|
|
|
286
|
-
if (!screening?.passed) {
|
|
287
|
-
result.reason = "screening_not_passed";
|
|
288
|
-
return result;
|
|
289
|
-
}
|
|
290
|
-
if (plan.effective === "none") {
|
|
291
|
-
result.reason = "post_action_none";
|
|
292
|
-
return result;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const summary = actionDiscovery?.summary || {};
|
|
296
|
-
const control =
|
|
297
|
-
if (!control?.found) {
|
|
298
|
-
result.reason = `${plan.effective}_control_not_found`;
|
|
299
|
-
return result;
|
|
300
|
-
}
|
|
301
|
-
result.control = control;
|
|
286
|
+
if (!screening?.passed) {
|
|
287
|
+
result.reason = "screening_not_passed";
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
if (plan.effective === "none") {
|
|
291
|
+
result.reason = plan.reason === "greet_limit_reached" ? "greet_limit_reached" : "post_action_none";
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const summary = actionDiscovery?.summary || {};
|
|
296
|
+
const control = summary.greet;
|
|
297
|
+
if (!control?.found) {
|
|
298
|
+
result.reason = `${plan.effective}_control_not_found`;
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
result.control = control;
|
|
302
302
|
|
|
303
303
|
if (plan.effective === "greet" && control.continue_chat) {
|
|
304
304
|
result.reason = "already_connected_continue_chat";
|
|
@@ -315,15 +315,10 @@ async function runRecommendPostAction({
|
|
|
315
315
|
result.reason = "greet_control_not_available";
|
|
316
316
|
return result;
|
|
317
317
|
}
|
|
318
|
-
if (
|
|
319
|
-
result.reason =
|
|
320
|
-
result
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
if (control.disabled) {
|
|
324
|
-
result.reason = `${plan.effective}_control_disabled`;
|
|
325
|
-
return result;
|
|
326
|
-
}
|
|
318
|
+
if (control.disabled) {
|
|
319
|
+
result.reason = `${plan.effective}_control_disabled`;
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
327
322
|
if (!executePostAction) {
|
|
328
323
|
result.reason = "dry_run_post_action";
|
|
329
324
|
result.would_click = true;
|
|
@@ -358,23 +353,18 @@ async function runRecommendPostAction({
|
|
|
358
353
|
timeoutMs: 2500,
|
|
359
354
|
intervalMs: 300,
|
|
360
355
|
requireAny: false
|
|
361
|
-
});
|
|
362
|
-
const afterSummary = afterDiscovery?.summary || {};
|
|
363
|
-
const afterControl =
|
|
364
|
-
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
365
|
-
result.control_after = afterControl || null;
|
|
366
|
-
if (plan.effective === "greet") {
|
|
367
|
-
result.verified_after_click = Boolean(
|
|
368
|
-
afterControl?.continue_chat
|
|
369
|
-
|| String(afterControl?.label || "").includes("继续沟通")
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
afterControl?.active
|
|
374
|
-
|| String(afterControl?.label || "").includes("已")
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
} catch (error) {
|
|
356
|
+
});
|
|
357
|
+
const afterSummary = afterDiscovery?.summary || {};
|
|
358
|
+
const afterControl = afterSummary.greet;
|
|
359
|
+
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
360
|
+
result.control_after = afterControl || null;
|
|
361
|
+
if (plan.effective === "greet") {
|
|
362
|
+
result.verified_after_click = Boolean(
|
|
363
|
+
afterControl?.continue_chat
|
|
364
|
+
|| String(afterControl?.label || "").includes("继续沟通")
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
378
368
|
result.verify_error = {
|
|
379
369
|
message: error?.message || String(error)
|
|
380
370
|
};
|
package/src/index.js
CHANGED
|
@@ -520,6 +520,7 @@ function createRunInputSchema() {
|
|
|
520
520
|
},
|
|
521
521
|
confirmation: {
|
|
522
522
|
type: "object",
|
|
523
|
+
description: "推荐页确认状态。新流程推荐只在用户看过总览后传 final_confirmed=true;逐字段 *_confirmed 为兼容旧调用保留。",
|
|
523
524
|
properties: {
|
|
524
525
|
page_confirmed: { type: "boolean" },
|
|
525
526
|
page_value: {
|
|
@@ -580,11 +581,14 @@ function createRunInputSchema() {
|
|
|
580
581
|
minimum: 1
|
|
581
582
|
},
|
|
582
583
|
post_action_confirmed: { type: "boolean" },
|
|
583
|
-
post_action_value: {
|
|
584
|
-
type: "string",
|
|
585
|
-
enum: ["
|
|
586
|
-
},
|
|
587
|
-
final_confirmed: {
|
|
584
|
+
post_action_value: {
|
|
585
|
+
type: "string",
|
|
586
|
+
enum: ["greet", "none"]
|
|
587
|
+
},
|
|
588
|
+
final_confirmed: {
|
|
589
|
+
type: "boolean",
|
|
590
|
+
description: "用户已确认包含岗位、筛选项、criteria、目标、动作、可选最大招呼数和 restLevel 的总览。"
|
|
591
|
+
},
|
|
588
592
|
job_confirmed: { type: "boolean" },
|
|
589
593
|
job_value: { type: "string" },
|
|
590
594
|
max_greet_count_confirmed: { type: "boolean" },
|
|
@@ -648,10 +652,10 @@ function createRunInputSchema() {
|
|
|
648
652
|
job: { type: "string" },
|
|
649
653
|
target_count: { type: "integer", minimum: 1 },
|
|
650
654
|
max_greet_count: { type: "integer", minimum: 1 },
|
|
651
|
-
post_action: {
|
|
652
|
-
type: "string",
|
|
653
|
-
enum: ["
|
|
654
|
-
}
|
|
655
|
+
post_action: {
|
|
656
|
+
type: "string",
|
|
657
|
+
enum: ["greet", "none"]
|
|
658
|
+
}
|
|
655
659
|
},
|
|
656
660
|
additionalProperties: false
|
|
657
661
|
},
|
|
@@ -714,8 +718,8 @@ function createRunInputSchema() {
|
|
|
714
718
|
type: "boolean",
|
|
715
719
|
description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
|
|
716
720
|
},
|
|
717
|
-
human_behavior: createHumanBehaviorInputSchema("
|
|
718
|
-
humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
|
|
721
|
+
human_behavior: createHumanBehaviorInputSchema("recommend 运行必须显式包含本次用户确认的 restLevel: low|medium|high;其他节奏配置可选"),
|
|
722
|
+
humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior;recommend 运行同样必须显式包含 restLevel"),
|
|
719
723
|
human_behavior_enabled: {
|
|
720
724
|
type: "boolean",
|
|
721
725
|
description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
|
|
@@ -777,26 +781,26 @@ function createRunInputSchema() {
|
|
|
777
781
|
},
|
|
778
782
|
execute_post_action: {
|
|
779
783
|
type: "boolean",
|
|
780
|
-
description: "可选,是否实际执行通过后的 recommend 后置动作
|
|
784
|
+
description: "可选,是否实际执行通过后的 recommend 后置动作 greet;默认 true"
|
|
781
785
|
},
|
|
782
786
|
dry_run_post_action: {
|
|
783
787
|
type: "boolean",
|
|
784
|
-
description: "可选,只验证 recommend
|
|
788
|
+
description: "可选,只验证 recommend 打招呼动作发现/配额/可点击路径,不实际点击"
|
|
785
789
|
},
|
|
786
790
|
action_timeout_ms: {
|
|
787
791
|
type: "integer",
|
|
788
792
|
minimum: 1000,
|
|
789
|
-
description: "可选,等待详情页
|
|
793
|
+
description: "可选,等待详情页 greet 控件出现的超时时间"
|
|
790
794
|
},
|
|
791
795
|
action_interval_ms: {
|
|
792
796
|
type: "integer",
|
|
793
797
|
minimum: 100,
|
|
794
|
-
description: "可选,轮询详情页
|
|
798
|
+
description: "可选,轮询详情页 greet 控件的间隔"
|
|
795
799
|
},
|
|
796
800
|
action_after_click_delay_ms: {
|
|
797
801
|
type: "integer",
|
|
798
802
|
minimum: 0,
|
|
799
|
-
description: "可选,点击
|
|
803
|
+
description: "可选,点击 greet 后等待页面状态稳定的时间"
|
|
800
804
|
},
|
|
801
805
|
no_filter: {
|
|
802
806
|
type: "boolean",
|
|
@@ -2433,7 +2437,7 @@ async function handleRunFeaturedCalibrationTool({ workspaceRoot, args }) {
|
|
|
2433
2437
|
calibration_resolution: getFeaturedCalibrationResolution(workspaceRoot),
|
|
2434
2438
|
guidance: {
|
|
2435
2439
|
current_workaround: "Use an existing favorite-calibration.json if present; get_featured_calibration_status reports whether it is usable.",
|
|
2436
|
-
next_development_task: "Implement CDP-only featured calibration with explicit user approval for any
|
|
2440
|
+
next_development_task: "Implement CDP-only featured calibration with explicit user approval for any calibration click."
|
|
2437
2441
|
}
|
|
2438
2442
|
};
|
|
2439
2443
|
}
|
package/src/parser.js
CHANGED
|
@@ -32,9 +32,8 @@ const FILTER_CONFIRM_OPTIONS = [
|
|
|
32
32
|
{ label: "筛选项无误,继续", value: "confirm" },
|
|
33
33
|
{ label: "筛选项需要调整", value: "revise" }
|
|
34
34
|
];
|
|
35
|
-
const POST_ACTION_OPTIONS = ["
|
|
35
|
+
const POST_ACTION_OPTIONS = ["greet", "none"];
|
|
36
36
|
const POST_ACTION_LABELS = {
|
|
37
|
-
favorite: "收藏",
|
|
38
37
|
greet: "直接沟通",
|
|
39
38
|
none: "什么也不做"
|
|
40
39
|
};
|
|
@@ -306,7 +305,6 @@ function normalizeRecentNotView(value) {
|
|
|
306
305
|
function normalizePostAction(value) {
|
|
307
306
|
const normalized = normalizeText(value).toLowerCase();
|
|
308
307
|
if (!normalized) return null;
|
|
309
|
-
if (["favorite", "fav", "收藏"].includes(normalized)) return "favorite";
|
|
310
308
|
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
311
309
|
if (["none", "noop", "no-op", "什么也不做", "不做任何操作", "不操作", "仅筛选", "只筛选"].includes(normalized)) {
|
|
312
310
|
return "none";
|
|
@@ -546,51 +544,43 @@ function buildCriteria({ instruction, rawInstruction, overrideCriteria }) {
|
|
|
546
544
|
};
|
|
547
545
|
}
|
|
548
546
|
|
|
549
|
-
function resolvePostAction({ instruction, confirmation, overrides }) {
|
|
547
|
+
function resolvePostAction({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
550
548
|
const confirmed = confirmation?.post_action_confirmed === true;
|
|
551
549
|
const confirmationValue = normalizePostAction(confirmation?.post_action_value);
|
|
552
550
|
const overrideValue = normalizePostAction(overrides?.post_action);
|
|
553
551
|
const instructionValue =
|
|
554
|
-
|
|
555
|
-
? "favorite"
|
|
556
|
-
: /打招呼|直接沟通|沟通/.test(instruction)
|
|
552
|
+
/打招呼|直接沟通|沟通/.test(instruction)
|
|
557
553
|
? "greet"
|
|
558
554
|
: /什么也不做|不做任何操作|不操作|仅筛选|只筛选/.test(instruction)
|
|
559
555
|
? "none"
|
|
560
|
-
|
|
556
|
+
: null;
|
|
561
557
|
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
562
558
|
|
|
563
|
-
if (confirmed && confirmationValue) {
|
|
564
|
-
return {
|
|
565
|
-
post_action: confirmationValue,
|
|
566
|
-
proposed_post_action: confirmationValue,
|
|
567
|
-
needs_post_action_confirmation: false
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
559
|
return {
|
|
572
|
-
post_action: confirmed
|
|
560
|
+
post_action: (confirmed || finalConfirmed) && proposed ? proposed : null,
|
|
573
561
|
proposed_post_action: proposed,
|
|
574
|
-
needs_post_action_confirmation:
|
|
562
|
+
needs_post_action_confirmation: !proposed
|
|
575
563
|
};
|
|
576
564
|
}
|
|
577
565
|
|
|
578
|
-
function resolveTargetCount({ instruction, confirmation, overrides }) {
|
|
566
|
+
function resolveTargetCount({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
579
567
|
const confirmed = confirmation?.target_count_confirmed === true;
|
|
580
568
|
const overrideValue = parsePositiveIntegerValue(overrides?.target_count);
|
|
581
569
|
const confirmationValue = parsePositiveIntegerValue(confirmation?.target_count_value);
|
|
582
570
|
const instructionValue = extractTargetCount(instruction);
|
|
583
571
|
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
584
|
-
const resolved =
|
|
572
|
+
const resolved = (confirmed || finalConfirmed)
|
|
573
|
+
? (overrideValue || confirmationValue || instructionValue || null)
|
|
574
|
+
: null;
|
|
585
575
|
|
|
586
576
|
return {
|
|
587
577
|
target_count: resolved,
|
|
588
578
|
proposed_target_count: proposed,
|
|
589
|
-
needs_target_count_confirmation:
|
|
579
|
+
needs_target_count_confirmation: false
|
|
590
580
|
};
|
|
591
581
|
}
|
|
592
582
|
|
|
593
|
-
function resolveMaxGreetCount({ instruction, confirmation, overrides, postActionResolution }) {
|
|
583
|
+
function resolveMaxGreetCount({ instruction, confirmation, overrides, postActionResolution, finalConfirmed = false }) {
|
|
594
584
|
const actionHint = postActionResolution.post_action || postActionResolution.proposed_post_action;
|
|
595
585
|
if (actionHint !== "greet") {
|
|
596
586
|
return {
|
|
@@ -602,52 +592,33 @@ function resolveMaxGreetCount({ instruction, confirmation, overrides, postAction
|
|
|
602
592
|
}
|
|
603
593
|
|
|
604
594
|
const overrideValue = parsePositiveIntegerValue(overrides?.max_greet_count);
|
|
605
|
-
const confirmed = confirmation?.max_greet_count_confirmed === true;
|
|
606
595
|
const confirmationValue = parsePositiveIntegerValue(confirmation?.max_greet_count_value);
|
|
607
596
|
const instructionValue = extractMaxGreetCount(instruction);
|
|
608
|
-
const targetCountHint = (
|
|
609
|
-
parsePositiveIntegerValue(confirmation?.target_count_value)
|
|
610
|
-
|| parsePositiveIntegerValue(overrides?.target_count)
|
|
611
|
-
|| extractTargetCount(instruction)
|
|
612
|
-
|| null
|
|
613
|
-
);
|
|
614
597
|
const proposed = confirmationValue || overrideValue || instructionValue || null;
|
|
615
|
-
const resolved =
|
|
616
|
-
const suspiciousAutoFill = Boolean(
|
|
617
|
-
!confirmed
|
|
618
|
-
&& Number.isInteger(proposed)
|
|
619
|
-
&& proposed > 0
|
|
620
|
-
&& !Number.isInteger(instructionValue)
|
|
621
|
-
&& Number.isInteger(targetCountHint)
|
|
622
|
-
&& targetCountHint > 0
|
|
623
|
-
&& proposed === targetCountHint
|
|
624
|
-
);
|
|
625
|
-
const needsConfirmation = (
|
|
626
|
-
!(Number.isInteger(resolved) && resolved > 0)
|
|
627
|
-
);
|
|
598
|
+
const resolved = confirmationValue || overrideValue || instructionValue || null;
|
|
628
599
|
|
|
629
600
|
return {
|
|
630
|
-
max_greet_count:
|
|
601
|
+
max_greet_count: resolved,
|
|
631
602
|
proposed_max_greet_count: proposed,
|
|
632
|
-
needs_max_greet_count_confirmation:
|
|
633
|
-
suspicious_auto_fill:
|
|
603
|
+
needs_max_greet_count_confirmation: false,
|
|
604
|
+
suspicious_auto_fill: false
|
|
634
605
|
};
|
|
635
606
|
}
|
|
636
607
|
|
|
637
|
-
function resolvePageScope({ instruction, confirmation, overrides }) {
|
|
608
|
+
function resolvePageScope({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
638
609
|
const confirmed = confirmation?.page_confirmed === true;
|
|
639
610
|
const confirmationValue = normalizePageScope(confirmation?.page_value);
|
|
640
611
|
const overrideValue = normalizePageScope(overrides?.page_scope);
|
|
641
612
|
const instructionValue = extractPageScope(instruction);
|
|
642
613
|
const proposed = overrideValue || confirmationValue || instructionValue || "recommend";
|
|
643
614
|
return {
|
|
644
|
-
page_scope: confirmed && confirmationValue ?
|
|
615
|
+
page_scope: (confirmed && confirmationValue) || finalConfirmed ? proposed : null,
|
|
645
616
|
proposed_page_scope: proposed,
|
|
646
|
-
needs_page_confirmation: !
|
|
617
|
+
needs_page_confirmation: !proposed
|
|
647
618
|
};
|
|
648
619
|
}
|
|
649
620
|
|
|
650
|
-
function collectSuspiciousFields({ invalidOverrideSchoolTags
|
|
621
|
+
function collectSuspiciousFields({ invalidOverrideSchoolTags }) {
|
|
651
622
|
const suspicious = [];
|
|
652
623
|
if (Array.isArray(invalidOverrideSchoolTags) && invalidOverrideSchoolTags.length > 0) {
|
|
653
624
|
suspicious.push({
|
|
@@ -656,19 +627,13 @@ function collectSuspiciousFields({ invalidOverrideSchoolTags, maxGreetCountResol
|
|
|
656
627
|
reason: `已忽略无效学校标签:${invalidOverrideSchoolTags.join(" / ")};仅保留可识别选项。`
|
|
657
628
|
});
|
|
658
629
|
}
|
|
659
|
-
if (maxGreetCountResolution?.suspicious_auto_fill) {
|
|
660
|
-
suspicious.push({
|
|
661
|
-
field: "max_greet_count",
|
|
662
|
-
value: maxGreetCountResolution.proposed_max_greet_count,
|
|
663
|
-
reason: "检测到 max_greet_count 与 target_count 相同且原始指令未明确该值,可能是自动填充,需用户再次确认。"
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
630
|
return suspicious;
|
|
667
631
|
}
|
|
668
632
|
|
|
669
633
|
export function parseRecommendInstruction({ instruction, confirmation, overrides }) {
|
|
670
634
|
const rawInstruction = String(instruction || "");
|
|
671
635
|
const text = normalizeText(rawInstruction);
|
|
636
|
+
const finalConfirmed = confirmation?.final_confirmed === true;
|
|
672
637
|
const detectedSchoolTags = extractSchoolTags(text);
|
|
673
638
|
const detectedDegrees = extractDegrees(text);
|
|
674
639
|
const schoolTagAudit = auditSchoolTagSelections(overrides?.school_tag);
|
|
@@ -692,7 +657,7 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
692
657
|
|| extractJobSelectionHint(rawInstruction)
|
|
693
658
|
|| ""
|
|
694
659
|
);
|
|
695
|
-
const pageScopeResolution = resolvePageScope({ instruction: text, confirmation, overrides });
|
|
660
|
+
const pageScopeResolution = resolvePageScope({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
696
661
|
|
|
697
662
|
const inferredSchoolTag = detectedSchoolTags.length > 0
|
|
698
663
|
? sortSchoolTagSelections(detectedSchoolTags)
|
|
@@ -717,15 +682,16 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
717
682
|
post_action: null,
|
|
718
683
|
max_greet_count: null
|
|
719
684
|
};
|
|
720
|
-
const targetCountResolution = resolveTargetCount({ instruction: text, confirmation, overrides });
|
|
685
|
+
const targetCountResolution = resolveTargetCount({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
721
686
|
screenParams.target_count = targetCountResolution.target_count;
|
|
722
|
-
const postActionResolution = resolvePostAction({ instruction: text, confirmation, overrides });
|
|
687
|
+
const postActionResolution = resolvePostAction({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
723
688
|
screenParams.post_action = postActionResolution.post_action;
|
|
724
689
|
const maxGreetCountResolution = resolveMaxGreetCount({
|
|
725
690
|
instruction: text,
|
|
726
691
|
confirmation,
|
|
727
692
|
overrides,
|
|
728
|
-
postActionResolution
|
|
693
|
+
postActionResolution,
|
|
694
|
+
finalConfirmed
|
|
729
695
|
});
|
|
730
696
|
screenParams.max_greet_count = maxGreetCountResolution.max_greet_count;
|
|
731
697
|
|
|
@@ -735,37 +701,23 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
735
701
|
}
|
|
736
702
|
|
|
737
703
|
const suspicious_fields = collectSuspiciousFields({
|
|
738
|
-
invalidOverrideSchoolTags: schoolTagAudit.invalid
|
|
739
|
-
maxGreetCountResolution
|
|
704
|
+
invalidOverrideSchoolTags: schoolTagAudit.invalid
|
|
740
705
|
});
|
|
741
|
-
const
|
|
742
|
-
const
|
|
743
|
-
const
|
|
744
|
-
const
|
|
745
|
-
const needs_school_tag_confirmation =
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const needs_degree_confirmation = (
|
|
750
|
-
confirmation?.degree_confirmed !== true
|
|
751
|
-
|| !hasConfirmedDegreeValue
|
|
752
|
-
);
|
|
753
|
-
const needs_gender_confirmation = (
|
|
754
|
-
confirmation?.gender_confirmed !== true
|
|
755
|
-
|| !hasConfirmedGenderValue
|
|
756
|
-
);
|
|
757
|
-
const needs_recent_not_view_confirmation = (
|
|
758
|
-
confirmation?.recent_not_view_confirmed !== true
|
|
759
|
-
|| !hasConfirmedRecentNotViewValue
|
|
760
|
-
);
|
|
706
|
+
const hasResolvedSchoolTagValue = Array.isArray(searchParams.school_tag) && searchParams.school_tag.length > 0;
|
|
707
|
+
const hasResolvedDegreeValue = Array.isArray(searchParams.degree) && searchParams.degree.length > 0;
|
|
708
|
+
const hasResolvedGenderValue = Boolean(searchParams.gender);
|
|
709
|
+
const hasResolvedRecentNotViewValue = Boolean(searchParams.recent_not_view);
|
|
710
|
+
const needs_school_tag_confirmation = !hasResolvedSchoolTagValue;
|
|
711
|
+
const needs_degree_confirmation = !hasResolvedDegreeValue;
|
|
712
|
+
const needs_gender_confirmation = !hasResolvedGenderValue;
|
|
713
|
+
const needs_recent_not_view_confirmation = !hasResolvedRecentNotViewValue;
|
|
761
714
|
const needs_filters_confirmation = (
|
|
762
|
-
|
|
763
|
-
|| needs_school_tag_confirmation
|
|
715
|
+
needs_school_tag_confirmation
|
|
764
716
|
|| needs_degree_confirmation
|
|
765
717
|
|| needs_gender_confirmation
|
|
766
718
|
|| needs_recent_not_view_confirmation
|
|
767
719
|
);
|
|
768
|
-
const needs_criteria_confirmation =
|
|
720
|
+
const needs_criteria_confirmation = !screenParams.criteria;
|
|
769
721
|
const needs_target_count_confirmation = targetCountResolution.needs_target_count_confirmation;
|
|
770
722
|
const needs_post_action_confirmation = postActionResolution.needs_post_action_confirmation;
|
|
771
723
|
const needs_max_greet_count_confirmation = maxGreetCountResolution.needs_max_greet_count_confirmation;
|
|
@@ -861,7 +813,6 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
861
813
|
question: "请确认本次运行对通过人选统一执行的动作。",
|
|
862
814
|
value: postActionResolution.proposed_post_action,
|
|
863
815
|
options: [
|
|
864
|
-
{ label: POST_ACTION_LABELS.favorite, value: "favorite" },
|
|
865
816
|
{ label: POST_ACTION_LABELS.greet, value: "greet" },
|
|
866
817
|
{ label: POST_ACTION_LABELS.none, value: "none" }
|
|
867
818
|
]
|
|
@@ -873,7 +824,7 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
873
824
|
field: "max_greet_count",
|
|
874
825
|
question: maxGreetCountResolution.suspicious_auto_fill
|
|
875
826
|
? "检测到最大打招呼人数可能是自动默认值,请明确确认本次最多打招呼多少位候选人(必须为正整数)。"
|
|
876
|
-
: "
|
|
827
|
+
: "本次选择直接沟通时,最多打招呼多少位候选人?可留空表示不单独限制打招呼人数。",
|
|
877
828
|
value: maxGreetCountResolution.proposed_max_greet_count
|
|
878
829
|
});
|
|
879
830
|
}
|
package/src/recommend-mcp.js
CHANGED
|
@@ -52,10 +52,12 @@ import {
|
|
|
52
52
|
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
53
53
|
|
|
54
54
|
const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
|
|
55
|
-
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
56
|
-
const DEFAULT_RECOMMEND_POLL_AFTER_SEC = 10;
|
|
57
|
-
const TARGET_COUNT_SEMANTICS = "target_count means candidates that pass screening; scan continues until that many candidates pass or the list ends";
|
|
58
|
-
const RUN_MODE_ASYNC = "async";
|
|
55
|
+
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
56
|
+
const DEFAULT_RECOMMEND_POLL_AFTER_SEC = 10;
|
|
57
|
+
const TARGET_COUNT_SEMANTICS = "target_count means candidates that pass screening; scan continues until that many candidates pass or the list ends";
|
|
58
|
+
const RUN_MODE_ASYNC = "async";
|
|
59
|
+
const REST_LEVEL_OPTIONS = ["low", "medium", "high"];
|
|
60
|
+
const REST_LEVEL_SET = new Set(REST_LEVEL_OPTIONS);
|
|
59
61
|
|
|
60
62
|
const TERMINAL_STATUSES = new Set([
|
|
61
63
|
RUN_STATUS_COMPLETED,
|
|
@@ -1019,63 +1021,145 @@ async function connectRecommendChromeSession({
|
|
|
1019
1021
|
};
|
|
1020
1022
|
}
|
|
1021
1023
|
|
|
1022
|
-
function parseRecommendPipelineRequest(args = {}) {
|
|
1023
|
-
return parseRecommendInstruction({
|
|
1024
|
-
instruction: args.instruction,
|
|
1024
|
+
function parseRecommendPipelineRequest(args = {}) {
|
|
1025
|
+
return parseRecommendInstruction({
|
|
1026
|
+
instruction: args.instruction,
|
|
1025
1027
|
confirmation: args.confirmation,
|
|
1026
1028
|
overrides: args.overrides
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
function
|
|
1031
|
-
|
|
1032
|
-
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function readOwn(source, keys = []) {
|
|
1033
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return undefined;
|
|
1034
|
+
for (const key of keys) {
|
|
1035
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) return source[key];
|
|
1036
|
+
}
|
|
1037
|
+
return undefined;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function getExplicitRestLevel(args = {}) {
|
|
1041
|
+
const behavior = readOwn(args, ["human_behavior", "humanBehavior"]);
|
|
1042
|
+
const raw = readOwn(behavior, ["restLevel", "rest_level"]);
|
|
1043
|
+
const normalized = normalizeText(raw).toLowerCase();
|
|
1044
|
+
return {
|
|
1045
|
+
raw: raw ?? null,
|
|
1046
|
+
restLevel: REST_LEVEL_SET.has(normalized) ? normalized : null,
|
|
1047
|
+
valid: REST_LEVEL_SET.has(normalized),
|
|
1048
|
+
missing: raw === undefined || raw === null || normalizeText(raw) === ""
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function buildReviewScreenParams(parsed) {
|
|
1053
|
+
return {
|
|
1054
|
+
...(parsed.screenParams || {}),
|
|
1055
|
+
criteria: parsed.screenParams?.criteria || null,
|
|
1056
|
+
criteria_normalized: parsed.criteria_normalized || null,
|
|
1057
|
+
target_count: parsed.screenParams?.target_count ?? parsed.proposed_target_count ?? null,
|
|
1058
|
+
post_action: parsed.screenParams?.post_action || parsed.proposed_post_action || null,
|
|
1059
|
+
max_greet_count: parsed.screenParams?.max_greet_count ?? parsed.proposed_max_greet_count ?? null
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function buildReviewPageScope(parsed) {
|
|
1064
|
+
return parsed.page_scope || parsed.proposed_page_scope || "recommend";
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function buildReviewJob(args = {}) {
|
|
1068
|
+
return normalizeText(args.confirmation?.job_value || args.overrides?.job || "") || null;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function buildScheduleReview(args = {}) {
|
|
1072
|
+
const scheduleRunAt = normalizeText(args.schedule_run_at || args.scheduleRunAt || args.run_at || args.runAt);
|
|
1073
|
+
const scheduleDelayMinutes = args.schedule_delay_minutes ?? args.scheduleDelayMinutes;
|
|
1074
|
+
const scheduleDelaySeconds = args.schedule_delay_seconds ?? args.scheduleDelaySeconds;
|
|
1075
|
+
if (!scheduleRunAt && scheduleDelayMinutes === undefined && scheduleDelaySeconds === undefined) return null;
|
|
1076
|
+
return {
|
|
1077
|
+
schedule_run_at: scheduleRunAt || null,
|
|
1078
|
+
schedule_delay_minutes: scheduleDelayMinutes ?? null,
|
|
1079
|
+
schedule_delay_seconds: scheduleDelaySeconds ?? null
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function buildRequiredConfirmations(parsed, args = {}) {
|
|
1084
|
+
const required = [];
|
|
1085
|
+
if (parsed.needs_page_confirmation) required.push("page_scope");
|
|
1033
1086
|
if (parsed.needs_filters_confirmation) required.push("filters");
|
|
1034
1087
|
if (parsed.needs_school_tag_confirmation) required.push("school_tag");
|
|
1035
1088
|
if (parsed.needs_degree_confirmation) required.push("degree");
|
|
1036
1089
|
if (parsed.needs_gender_confirmation) required.push("gender");
|
|
1037
1090
|
if (parsed.needs_recent_not_view_confirmation) required.push("recent_not_view");
|
|
1038
|
-
if (parsed.needs_criteria_confirmation) required.push("criteria");
|
|
1039
|
-
if (parsed.needs_target_count_confirmation) required.push("target_count");
|
|
1040
|
-
if (parsed.needs_post_action_confirmation) required.push("post_action");
|
|
1041
|
-
if (parsed.
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
if (
|
|
1048
|
-
|
|
1049
|
-
|
|
1091
|
+
if (parsed.needs_criteria_confirmation) required.push("criteria");
|
|
1092
|
+
if (parsed.needs_target_count_confirmation) required.push("target_count");
|
|
1093
|
+
if (parsed.needs_post_action_confirmation) required.push("post_action");
|
|
1094
|
+
if ((parsed.suspicious_fields || []).length) required.push("suspicious_fields");
|
|
1095
|
+
|
|
1096
|
+
const confirmation = args.confirmation || {};
|
|
1097
|
+
const jobValue = normalizeText(confirmation.job_value || args.overrides?.job || "");
|
|
1098
|
+
if (!jobValue) required.push("job");
|
|
1099
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1100
|
+
if (!restLevel.valid) required.push("rest_level");
|
|
1101
|
+
const blocksFinalReview = required.some((field) => field !== "rest_level");
|
|
1102
|
+
if (confirmation.final_confirmed !== true && !blocksFinalReview) required.push("final_review");
|
|
1103
|
+
return Array.from(new Set(required));
|
|
1104
|
+
}
|
|
1050
1105
|
|
|
1051
|
-
function buildJobPendingQuestion(args = {}) {
|
|
1052
|
-
const value = normalizeText(args.confirmation?.job_value || args.overrides?.job || "");
|
|
1106
|
+
function buildJobPendingQuestion(args = {}) {
|
|
1107
|
+
const value = normalizeText(args.confirmation?.job_value || args.overrides?.job || "");
|
|
1053
1108
|
return {
|
|
1054
1109
|
field: "job",
|
|
1055
1110
|
question: "请确认推荐页岗位。CDP-only rewrite 会先切换到该岗位,再按所选页面范围执行筛选。",
|
|
1056
1111
|
value: value || null
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function buildRestLevelPendingQuestion(args = {}) {
|
|
1116
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1117
|
+
return {
|
|
1118
|
+
field: "rest_level",
|
|
1119
|
+
question: restLevel.missing
|
|
1120
|
+
? "请确认本次运行休息强度 rest_level。"
|
|
1121
|
+
: "rest_level 只能是 low / medium / high,请重新确认本次运行休息强度。",
|
|
1122
|
+
value: restLevel.restLevel || restLevel.raw || null,
|
|
1123
|
+
options: REST_LEVEL_OPTIONS.map((value) => ({
|
|
1124
|
+
label: value,
|
|
1125
|
+
value
|
|
1126
|
+
}))
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function buildSuspiciousFieldsQuestion(parsed) {
|
|
1131
|
+
return {
|
|
1132
|
+
field: "suspicious_fields",
|
|
1133
|
+
question: "检测到需要修正或明确确认的异常字段,请先修正后再启动。",
|
|
1134
|
+
value: parsed.suspicious_fields || []
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function buildFinalReviewQuestion(parsed, args = {}) {
|
|
1139
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1140
|
+
return {
|
|
1141
|
+
field: "final_review",
|
|
1142
|
+
question: "请最终确认本次推荐页筛选参数无误;确认后设置 final_confirmed=true 即可启动或创建定时任务。",
|
|
1143
|
+
value: {
|
|
1144
|
+
page_scope: buildReviewPageScope(parsed),
|
|
1145
|
+
job: buildReviewJob(args),
|
|
1146
|
+
search_params: parsed.searchParams,
|
|
1147
|
+
screen_params: buildReviewScreenParams(parsed),
|
|
1148
|
+
human_behavior: {
|
|
1149
|
+
restLevel: restLevel.restLevel || null
|
|
1150
|
+
},
|
|
1151
|
+
schedule: buildScheduleReview(args)
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function buildNeedInputResponse(parsed, args = {}) {
|
|
1157
|
+
return {
|
|
1158
|
+
status: "NEED_INPUT",
|
|
1159
|
+
missing_fields: parsed.missing_fields,
|
|
1160
|
+
required_confirmations: buildRequiredConfirmations(parsed, args),
|
|
1161
|
+
search_params: parsed.searchParams,
|
|
1162
|
+
screen_params: parsed.screenParams,
|
|
1079
1163
|
pending_questions: parsed.pending_questions,
|
|
1080
1164
|
review: parsed.review,
|
|
1081
1165
|
error: {
|
|
@@ -1086,30 +1170,36 @@ function buildNeedInputResponse(parsed) {
|
|
|
1086
1170
|
};
|
|
1087
1171
|
}
|
|
1088
1172
|
|
|
1089
|
-
function buildNeedConfirmationResponse(parsed, args, requiredConfirmations) {
|
|
1090
|
-
const pending = [...(parsed.pending_questions || [])];
|
|
1091
|
-
if (requiredConfirmations.includes("
|
|
1092
|
-
pending.push(
|
|
1093
|
-
}
|
|
1094
|
-
if (requiredConfirmations.includes("
|
|
1095
|
-
pending.push(
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1173
|
+
function buildNeedConfirmationResponse(parsed, args, requiredConfirmations) {
|
|
1174
|
+
const pending = [...(parsed.pending_questions || [])];
|
|
1175
|
+
if (requiredConfirmations.includes("suspicious_fields") && !pending.some((item) => item.field === "suspicious_fields")) {
|
|
1176
|
+
pending.push(buildSuspiciousFieldsQuestion(parsed));
|
|
1177
|
+
}
|
|
1178
|
+
if (requiredConfirmations.includes("job") && !pending.some((item) => item.field === "job")) {
|
|
1179
|
+
pending.push(buildJobPendingQuestion(args));
|
|
1180
|
+
}
|
|
1181
|
+
if (requiredConfirmations.includes("rest_level") && !pending.some((item) => item.field === "rest_level")) {
|
|
1182
|
+
pending.push(buildRestLevelPendingQuestion(args));
|
|
1183
|
+
}
|
|
1184
|
+
if (requiredConfirmations.includes("final_review") && !pending.some((item) => item.field === "final_review")) {
|
|
1185
|
+
pending.push(buildFinalReviewQuestion(parsed, args));
|
|
1186
|
+
}
|
|
1187
|
+
return {
|
|
1188
|
+
status: "NEED_CONFIRMATION",
|
|
1189
|
+
required_confirmations: requiredConfirmations,
|
|
1190
|
+
page_scope: buildReviewPageScope(parsed),
|
|
1191
|
+
search_params: parsed.searchParams,
|
|
1192
|
+
screen_params: buildReviewScreenParams(parsed),
|
|
1193
|
+
pending_questions: pending,
|
|
1194
|
+
review: {
|
|
1195
|
+
...(parsed.review || {}),
|
|
1106
1196
|
required_confirmations: requiredConfirmations
|
|
1107
1197
|
}
|
|
1108
1198
|
};
|
|
1109
1199
|
}
|
|
1110
|
-
|
|
1111
|
-
function evaluateRecommendPipelineGate(parsed, args = {}) {
|
|
1112
|
-
if (parsed.missing_fields?.length) return buildNeedInputResponse(parsed);
|
|
1200
|
+
|
|
1201
|
+
function evaluateRecommendPipelineGate(parsed, args = {}) {
|
|
1202
|
+
if (parsed.missing_fields?.length) return buildNeedInputResponse(parsed, args);
|
|
1113
1203
|
const requiredConfirmations = buildRequiredConfirmations(parsed, args);
|
|
1114
1204
|
if (requiredConfirmations.length) {
|
|
1115
1205
|
return buildNeedConfirmationResponse(parsed, args, requiredConfirmations);
|