@reconcrap/boss-recommend-mcp 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,6 +85,8 @@ boss-recommend-mcp list-jobs --slow-live --port 9222
85
85
  - 页面就绪(已登录且在 recommend 页)后,会先提取岗位栏全部岗位并要求用户确认本次岗位;确认后先点击岗位,再执行 search/screen
86
86
  - 在真正开始 search/screen 前,会进行最后一轮全参数总确认(岗位 + 全部筛选参数 + criteria + target_count + post_action + max_greet_count)
87
87
  - npm 全局安装后会自动执行 install:生成 skill、导出 MCP 模板,并自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
88
+ - 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` 备份
89
+ - 2.x installer 会刷新外部 agent skills:`boss-recommend-pipeline`、`boss-recruit-pipeline`、`boss-chat` 都来自当前包,旧 recruit/chat skill 会被覆盖为统一 MCP 路由
88
90
  - npm / npx 安装后会自动初始化 `screening-config.json` 模板(优先写入 workspace 的 `config/`,不可写时回退到用户目录)
89
91
  - npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs`、`~/.boss-recommend-mcp/boss-chat` 及其 `logs/runs/profiles/reports/artifacts/state`
90
92
  - `post_action` 必须在每次完整运行开始时确认一次
@@ -126,6 +128,37 @@ npm install
126
128
  node src/cli.js start
127
129
  ```
128
130
 
131
+ ### 迁移 legacy MCP / skills
132
+
133
+ 全局 npm 安装会自动运行 `boss-recommend-mcp install`。该安装器会在 Windows 和 macOS 上自动检测 Trae / Trae CN / OpenClaw 的常见配置目录:
134
+
135
+ - Windows: `%APPDATA%\Trae*\User\mcp.json`、`%USERPROFILE%\.trae*\mcp.json`、`%USERPROFILE%\.openclaw\mcp.json`、`%APPDATA%\OpenClaw\User\mcp.json`
136
+ - macOS: `~/Library/Application Support/Trae*/User/mcp.json`、`~/.trae*/mcp.json`、`~/.openclaw/mcp.json`、`~/Library/Application Support/OpenClaw/User/mcp.json`
137
+
138
+ 如果检测到 legacy Boss server entries,installer 会:
139
+
140
+ - 保留非 Boss MCP server。
141
+ - 写入统一 server:`boss-recommend -> npx -y @reconcrap/boss-recommend-mcp@<installed-version> start`
142
+ - 从同一个 `mcp.json` 删除旧 `boss-recruit-mcp`、standalone `boss-chat`、旧本地 Boss repo 路径,避免 agent 继续调用 legacy 包。
143
+ - 在原文件旁生成 `mcp.json.boss-mcp-migration-*.bak`。
144
+ - 同步外部 skills 目录里的 `boss-recommend-pipeline`、`boss-recruit-pipeline`、`boss-chat`。
145
+
146
+ 手动指定 agent:
147
+
148
+ ```bash
149
+ boss-recommend-mcp install --agent trae-cn
150
+ boss-recommend-mcp install --agent openclaw
151
+ boss-recommend-mcp doctor --agent trae-cn
152
+ boss-recommend-mcp doctor --agent openclaw
153
+ ```
154
+
155
+ 自定义路径:
156
+
157
+ ```bash
158
+ BOSS_RECOMMEND_MCP_CONFIG_TARGETS="/path/to/mcp.json" boss-recommend-mcp install
159
+ BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS="/path/to/skills" boss-recommend-mcp install
160
+ ```
161
+
129
162
  可选环境变量(用于跨 agent 自动配置):
130
163
 
131
164
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -31,6 +31,7 @@
31
31
  "test:core-run": "node src/test-core-run.js",
32
32
  "test:core-screening": "node src/test-core-screening.js",
33
33
  "test:core-self-heal": "node src/test-core-self-heal.js",
34
+ "test:installer-migration": "node src/test-installer-migration.js",
34
35
  "test:recommend-actions": "node src/test-recommend-actions.js",
35
36
  "test:recommend-domain": "node src/test-recommend-domain.js",
36
37
  "test:recommend-run-service": "node src/test-recommend-run-service.js",
@@ -9,7 +9,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
9
9
 
10
10
  当用户要在 Boss 聊天页单独跑筛选/沟通任务时,必须走内置的 chat 工具,而不是要求用户单独安装 `boss-chat`。
11
11
 
12
- 适用范围是“chat-only 会话”。若用户意图包含推荐页找人(尤其是“先推荐再沟通”),必须让 `boss-recommend-pipeline` 接管,并通过 `follow_up.chat` 完成联动。
12
+ 适用范围是“chat-only 会话”。若用户意图包含推荐页找人(尤其是“先推荐再沟通”),必须先让 `boss-recommend-pipeline` 完成推荐页任务;推荐完成后如用户仍要聊天页处理,再显式走本 skill。
13
13
 
14
14
  ## Tool Routing
15
15
 
@@ -55,7 +55,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
55
55
  - LLM 配置必须复用 `boss-recommend-mcp` 的 `screening-config.json`;不要再向用户单独要 `baseUrl/apiKey/model`。
56
56
  - 路由护栏(强制):
57
57
  - 只在用户明确是 chat-only 任务时使用本 skill。
58
- - 只要用户提到推荐页、先找人后沟通、或需要推荐筛选阶段,禁止调用 `start_boss_chat_run`;必须交给 `boss-recommend-pipeline` 并走 `follow_up.chat`。
58
+ - 只要用户提到推荐页、先找人后沟通、或需要推荐筛选阶段,禁止直接调用 `start_boss_chat_run`;必须先交给 `boss-recommend-pipeline` 完成推荐页任务。
59
59
  - 不得在 recommend 任务尚未完成时并行启动独立 chat run。
60
60
  - `job` / `start_from` / `criteria` 缺一不可;缺参时只补缺口。
61
61
  - `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
@@ -78,8 +78,9 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
78
78
  ## Handoff Rule (Recommend -> Chat)
79
79
 
80
80
  - 若用户先运行了 recommend 流水线,并在手动状态检查时确认 recommend 已完成,且用户目标是“立即进入聊天沟通”:
81
- - 若该 recommend run **未配置** `follow_up.chat`:应立即调用 `start_boss_chat_run` 启动 chat(不要等下一次 30 分钟轮询)。
82
- - 若该 recommend run **已配置** `follow_up.chat`:不要再重复新开 chat run,改为查询同一父 run / run 状态。
81
+ - 先调用 `prepare_boss_chat_run` 获取聊天页岗位列表与缺参。
82
+ - 显式确认 `job/start_from/target_count/criteria` 后再调用 `start_boss_chat_run`。
83
+ - 不要查找或依赖 `follow_up.chat`;该自动衔接路径属于 legacy-only 行为。
83
84
 
84
85
  ## Response Style
85
86
 
@@ -7,20 +7,20 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
7
7
 
8
8
  ## Goal
9
9
 
10
- 当用户要在 Boss 推荐页筛人时,必须走 `start_recommend_pipeline_run`,并按“两阶段确认 -> 页面就绪 -> 岗位确认 -> 最终确认 -> 执行”的顺序完成。若用户还要求筛完后自动进入聊天页 follow-up,则必须把 chat 要求放进同一个 recommend run 的 `follow_up.chat`,不要要求单独再安装 `boss-chat`。
10
+ 当用户要在 Boss 推荐页筛人时,必须走 `start_recommend_pipeline_run`,并按“两阶段确认 -> 页面就绪 -> 岗位确认 -> 最终确认 -> 执行”的顺序完成。2.0 CDP-only 路径不再支持 legacy recommend -> chat 自动衔接;若用户要聊天页筛选或求简历,必须在推荐页任务完成后显式改用 `boss-chat` 工具。
11
11
 
12
12
  ## Hard Rules (Must Follow)
13
13
 
14
14
  - **路由**
15
15
  - 语义是推荐页(`recommend/推荐页/recommend page//web/chat/recommend`)时,只能走本 skill。
16
- - 语义是“推荐页找人 + 结束后沟通/聊天”时,仍然只能走本 skill;必须通过 `follow_up.chat` 挂到同一个 recommend run。
16
+ - 语义是“推荐页找人 + 结束后沟通/聊天”时,先完成 recommend run;不得配置 `follow_up.chat`,后续聊天任务必须显式交给 `boss-chat`。
17
17
  - 只有用户**明确**说搜索页(`search/搜索页//web/chat/search`)时,才可转 `boss-recruit-pipeline`。
18
18
  - recommend 失败时(如 `JOB_TRIGGER_NOT_FOUND/NO_RECOMMEND_IFRAME/BOSS_LOGIN_REQUIRED`)禁止降级到 recruit;先修 recommend 页面就绪/登录态。
19
19
 
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/follow_up.chat.*`。
23
+ - 禁止在用户未明确回复前,自行填充 `page_scope/school_tag/degree/gender/recent_not_view/criteria/target_count/post_action/max_greet_count/job`。
24
24
  - 若工具返回 `pending_questions`,必须逐项向用户提问并等待用户回复;不得跳过提问直接执行。
25
25
 
26
26
  - **岗位确认时机**
@@ -31,6 +31,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
31
31
  - `criteria` 必须是用户开放式自然语言;禁止“严格/宽松执行”等预设替代。
32
32
  - `post_action=greet` 时,必须确认 `max_greet_count`;禁止自动默认为 `target_count`。
33
33
  - 正式执行前必须 `final_confirmed=true`。
34
+ - 真实筛选禁止传 `detail_limit: 0`;recommend 默认必须打开候选人详情/CV。只有用户明确要求“卡片-only 调试”时,才允许同时传 `detail_limit: 0` 和 `allow_card_only_screening: true`。
34
35
 
35
36
  - **Instruction 原文锁定**
36
37
  - 首次用户需求原文锁定为 `locked_instruction_raw`。
@@ -59,27 +60,16 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
59
60
  必须确认:
60
61
 
61
62
  - `job`(来自 `job_options`,必须全量展示)
62
- - `final_review`(岗位 + 全参数总确认;若有 `follow_up.chat`,必须把 chat summary 一并展示)
63
-
64
- ## Follow-up Chat
65
-
66
- 当用户要求“推荐页跑完后自动开始聊天页任务”时:
67
-
68
- - 仍然只走 `start_recommend_pipeline_run`
69
- - chat 配置放入顶层 `follow_up.chat`
70
- - `follow_up.chat` 必填:
71
- - `criteria`
72
- - `start_from`: `unread|all`
73
- - `target_count`
74
- - `follow_up.chat` 可选:
75
- - `greeting_text`(兼容 `greetingText`,自定义首条打招呼消息)
76
- - `profile`(默认 `default`)
77
- - `dry_run/no_state/safe_pacing/batch_rest_enabled`
78
- - `greeting_text` 未传时,boss-chat 会自动按默认优先级回退:本次显式值 > profile 历史值 > 内置默认招呼语
79
- - `job` / `port` 继承 recommend run 上下文;不要单独向用户再要一份
80
- - LLM 配置固定复用 recommend 的 `screening-config.json`;不要单独向用户再要 `baseUrl/apiKey/model`
81
- - 缺少 `follow_up.chat` 必填项时,按 `pending_questions` 补缺口;不要额外发起一轮独立的 chat 确认流程
82
- - 不允许提前调用 `start_boss_chat_run`;必须等 recommend run 完成并由 `follow_up.chat` 自动衔接
63
+ - `final_review`(岗位 + 全参数总确认)
64
+
65
+ ## Chat Handoff
66
+
67
+ 当用户要求“推荐页跑完后继续聊天页任务”时:
68
+
69
+ - 本次 recommend run 只提交 recommend 参数,不要写 `follow_up.chat`。
70
+ - recommend 完成并由用户确认要继续后,切换到 `boss-chat` skill。
71
+ - `boss-chat` 会重新调用 `prepare_boss_chat_run` 获取聊天页岗位列表,并显式确认 `job/start_from/target_count/criteria`。
72
+ - 不得在 recommend run 尚未完成时并行启动 `start_boss_chat_run`。
83
73
 
84
74
  ## Closed vs Open Questions
85
75
 
@@ -104,7 +94,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
104
94
  - 关键输入:
105
95
  - `confirmation`:`page_confirmed/page_value/filters_confirmed/school_tag_confirmed.../job_confirmed/job_value/final_confirmed`
106
96
  - `overrides`:`page_scope/school_tag/degree/gender/recent_not_view/criteria/job/target_count/post_action/max_greet_count`
107
- - `follow_up.chat`:仅在“recommend 完成后自动进入 boss-chat”场景传入
97
+ - 不要传 `follow_up.chat`;该路径属于 legacy-only 行为
108
98
 
109
99
  最小策略:
110
100
 
@@ -116,16 +106,13 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
116
106
  ## Async Run Policy
117
107
 
118
108
  - 用户未明确要求“持续跟进”时,不自动 `sleep + get_recommend_pipeline_run`。
119
- - 若 run 已进入 `chat_followup`,默认也不得自动轮询子 chat 状态;除非用户明确指定轮询频率/间隔。
120
109
  - 用户要求查进度时,再用 `get_recommend_pipeline_run`。
121
110
  - **长任务轮询节奏(强制)**:
122
- - 推荐+聊天联动任务可能运行数小时,禁止高频轮询。
111
+ - 推荐任务可能运行数小时,禁止高频轮询。
123
112
  - 默认最小轮询间隔为 **30 分钟**(除非用户明确要求更频繁)。
124
113
  - 若刚启动 run(拿到 `ACCEPTED + run_id`),不得立即进入连续轮询。
125
114
  - `pause/resume/cancel` 必须复用同一 `run_id`,不要重复 `start`。
126
- - **完成后衔接(强制)**:
127
- - 若本次 run 已带 `follow_up.chat`:chat 由同一 run 自动衔接,禁止再单独重复启动 chat run。
128
- - 若用户手动触发 `get_recommend_pipeline_run` 且发现 recommend 已完成、而当前会话目标是“继续聊天沟通”且尚未启动 chat:应立即启动 chat(无需等待下一轮 30 分钟轮询)。
115
+ - **完成后衔接(强制)**:若用户手动触发 `get_recommend_pipeline_run` 且发现 recommend 已完成、而当前会话目标是“继续聊天沟通”且尚未启动 chat:切换到 `boss-chat` 并重新走 chat-only 参数确认。
129
116
 
130
117
  ## Preflight and Recovery
131
118
 
@@ -150,7 +137,7 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
150
137
 
151
138
  MCP 不可用时:
152
139
 
153
- `npx -y @reconcrap/boss-recommend-mcp@latest run --instruction "..." [--confirmation-json '{...}'] [--overrides-json '{...}'] [--follow-up-json '{...}']`
140
+ `npx -y @reconcrap/boss-recommend-mcp@latest run --instruction "..." [--confirmation-json '{...}'] [--overrides-json '{...}']`
154
141
 
155
142
  禁止错误回退:
156
143
 
@@ -0,0 +1,17 @@
1
+ # boss-recruit-pipeline
2
+
3
+ Bundled search/recruit-page automation skill shipped with `boss-recommend-mcp` 2.x.
4
+
5
+ Package: `@reconcrap/boss-recommend-mcp` (npm)
6
+ Source: `https://github.com/reconcrap-cpu/boss-recommend-mcp`
7
+
8
+ This skill intentionally replaces legacy `boss-recruit-mcp` skill installs. It routes Boss search/recruit tasks to the unified CDP-only MCP tools:
9
+
10
+ - `run_recruit_pipeline`
11
+ - `start_recruit_pipeline_run`
12
+ - `get_recruit_pipeline_run`
13
+ - `pause_recruit_pipeline_run`
14
+ - `resume_recruit_pipeline_run`
15
+ - `cancel_recruit_pipeline_run`
16
+
17
+ Do not call the old `@reconcrap/boss-recruit-mcp` package from this skill.
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: "boss-recruit-pipeline"
3
+ description: "Use when users want Boss search/recruit-page screening via the unified boss-recommend-mcp package. Replaces the legacy boss-recruit-mcp skill."
4
+ ---
5
+
6
+ # Boss Recruit Pipeline Skill
7
+
8
+ ## Goal
9
+
10
+ 当用户要在 Boss 搜索页 / 招聘搜索页筛人时,必须走 `@reconcrap/boss-recommend-mcp` 2.x 内置的 recruit/search MCP 工具,不要安装或调用旧的 `@reconcrap/boss-recruit-mcp`。
11
+
12
+ ## Tool Routing
13
+
14
+ - 同步启动:`run_recruit_pipeline`
15
+ - 异步启动:`start_recruit_pipeline_run`
16
+ - 查询进度:`get_recruit_pipeline_run`
17
+ - 暂停:`pause_recruit_pipeline_run`
18
+ - 继续:`resume_recruit_pipeline_run`
19
+ - 取消:`cancel_recruit_pipeline_run`
20
+
21
+ ## Hard Rules
22
+
23
+ - 只在用户明确说搜索页、search、recruit、招聘搜索、`/web/chat/search` 时使用本 skill。
24
+ - 如果用户说推荐页、recommend、`/web/chat/recommend`,必须交给 `boss-recommend-pipeline`。
25
+ - 如果用户说聊天页、未读、全部聊天、求简历,必须交给 `boss-chat`。
26
+ - 禁止调用旧包:`@reconcrap/boss-recruit-mcp`、`boss-recruit-mcp`、旧本地 recruit repo、旧 vendor 脚本。
27
+ - 浏览器自动化必须走 CDP-only 2.x MCP 工具;不得要求用户启用 legacy page-JS 或 `Runtime.evaluate` 路径。
28
+ - 若用户未提供岗位,必须先询问岗位。搜索页岗位选择在关键词输入框旁边;不要猜测默认岗位。
29
+ - 若用户提供城市、学历、学校、关键词、过滤已看、人选目标数、筛选条件、post action、max greet 等参数,必须逐项传入或确认。
30
+ - `post_action=greet` 时必须确认 `max_greet_count`;不要默认等于 `target_count`。
31
+ - 搜索页和推荐页一样支持多选筛选条件;不要把多选降级成单选。
32
+
33
+ ## Required Inputs
34
+
35
+ - `job`
36
+ - `keyword` 或用户明确的搜索意图
37
+ - `criteria`
38
+ - `target_count`
39
+
40
+ 常用可选项:
41
+
42
+ - `city`
43
+ - `degree`
44
+ - `school_tag`
45
+ - `recent_not_view`
46
+ - `post_action`
47
+ - `max_greet_count`
48
+ - `port`
49
+
50
+ ## Response Style
51
+
52
+ - 用结构化中文确认参数。
53
+ - 缺参时只补缺口,不要改写用户的筛选条件。
54
+ - 拿到 `ACCEPTED + run_id` 后默认停止本轮,不主动高频轮询。
55
+ - 查询、暂停、恢复、取消时必须复用同一个 `run_id`。
package/src/cli.js CHANGED
@@ -35,7 +35,9 @@ const currentFilePath = fileURLToPath(import.meta.url);
35
35
  const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
36
36
  const packageJsonPath = path.join(packageRoot, "package.json");
37
37
  const skillName = "boss-recommend-pipeline";
38
- const bundledSkillNames = [skillName, "boss-chat"];
38
+ const recruitSkillName = "boss-recruit-pipeline";
39
+ const chatSkillName = "boss-chat";
40
+ const bundledSkillNames = [skillName, recruitSkillName, chatSkillName];
39
41
  const exampleConfigPath = path.join(packageRoot, "config", "screening-config.example.json");
40
42
  const bossUrl = "https://www.zhipin.com/web/chat/recommend";
41
43
  const bossLoginUrl = "https://www.zhipin.com/web/user/?ka=bticket";
@@ -724,7 +726,7 @@ function buildMcpLaunchConfig(options = {}) {
724
726
  ? args
725
727
  : command === "boss-recommend-mcp"
726
728
  ? ["start"]
727
- : buildDefaultMcpArgs();
729
+ : buildDefaultMcpArgs(options);
728
730
  const launchConfig = { command, args: launchArgs };
729
731
  if (env && typeof env === "object" && !Array.isArray(env) && Object.keys(env).length > 0) {
730
732
  launchConfig.env = env;
@@ -793,24 +795,56 @@ function parseAgentTargets(rawValue) {
793
795
  return unique;
794
796
  }
795
797
 
798
+ function getExternalAppSupportBaseDirs() {
799
+ const home = os.homedir();
800
+ if (process.platform === "win32") {
801
+ return dedupePaths([
802
+ process.env.APPDATA || "",
803
+ path.join(home, "AppData", "Roaming")
804
+ ]);
805
+ }
806
+ if (process.platform === "darwin") {
807
+ return dedupePaths([
808
+ path.join(home, "Library", "Application Support")
809
+ ]);
810
+ }
811
+ return dedupePaths([
812
+ process.env.XDG_CONFIG_HOME || "",
813
+ path.join(home, ".config")
814
+ ]);
815
+ }
816
+
817
+ function buildAppUserPaths({ dirNames = [], tail = [] } = {}) {
818
+ const paths = [];
819
+ for (const baseDir of getExternalAppSupportBaseDirs()) {
820
+ const discovered = discoverAppDataDirsByPattern(baseDir, /^trae(?:[\s\-_]?cn)?$/i);
821
+ const names = dedupeLower([...dirNames, ...discovered]);
822
+ for (const dirName of names) {
823
+ paths.push(path.join(baseDir, dirName, "User", ...tail));
824
+ }
825
+ }
826
+ return dedupePaths(paths);
827
+ }
828
+
796
829
  function getKnownExternalMcpConfigPathsByAgent() {
797
830
  const home = os.homedir();
798
- const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
799
- const traeDirNames = dedupeLower([
831
+ const appBases = getExternalAppSupportBaseDirs();
832
+ const traeDirNames = [
800
833
  "Trae",
801
834
  "Trae CN",
802
835
  "TraeCN",
803
836
  "trae-cn",
804
- "trae_cn",
805
- ...discoverAppDataDirsByPattern(appData, /^trae(?:[\s\-_]?cn)?$/i)
806
- ]);
807
- const traeConfigPaths = traeDirNames.map((dir) => path.join(appData, dir, "User", "mcp.json"));
837
+ "trae_cn"
838
+ ];
839
+ const traeConfigPaths = buildAppUserPaths({ dirNames: traeDirNames, tail: ["mcp.json"] });
840
+ const cursorConfigPaths = appBases.map((baseDir) => path.join(baseDir, "Cursor", "User", "mcp.json"));
841
+ const openClawConfigPaths = appBases.map((baseDir) => path.join(baseDir, "OpenClaw", "User", "mcp.json"));
808
842
  return {
809
- cursor: [path.join(appData, "Cursor", "User", "mcp.json"), path.join(home, ".cursor", "mcp.json")],
843
+ cursor: [...cursorConfigPaths, path.join(home, ".cursor", "mcp.json")],
810
844
  trae: [...traeConfigPaths, path.join(home, ".trae", "mcp.json"), path.join(home, ".trae-cn", "mcp.json")],
811
845
  "trae-cn": [...traeConfigPaths, path.join(home, ".trae-cn", "mcp.json"), path.join(home, ".trae", "mcp.json")],
812
846
  claude: [path.join(home, ".claude", "mcp.json")],
813
- openclaw: [path.join(home, ".openclaw", "mcp.json")]
847
+ openclaw: [path.join(home, ".openclaw", "mcp.json"), ...openClawConfigPaths]
814
848
  };
815
849
  }
816
850
 
@@ -837,21 +871,40 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
837
871
  ? current.mcpServers
838
872
  : {};
839
873
  const existingEntry = existingServers[serverName];
874
+ const retainedServers = {};
875
+ const migratedLegacyServers = [];
876
+ for (const [name, config] of Object.entries(existingServers)) {
877
+ if (name === serverName) continue;
878
+ if (isBossMcpServerEntry(name, config)) {
879
+ migratedLegacyServers.push(name);
880
+ continue;
881
+ }
882
+ retainedServers[name] = config;
883
+ }
840
884
  const merged = {
841
885
  ...current,
842
886
  mcpServers: {
843
- ...existingServers,
887
+ ...retainedServers,
844
888
  [serverName]: launchConfig
845
889
  }
846
890
  };
847
891
 
848
892
  ensureDir(path.dirname(filePath));
893
+ const before = pathExists(filePath) ? fs.readFileSync(filePath, "utf8") : "";
894
+ const next = JSON.stringify(merged, null, 2);
895
+ let backupFile = null;
896
+ if (before && before.trim() !== next.trim()) {
897
+ backupFile = `${filePath}.boss-mcp-migration-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`;
898
+ fs.writeFileSync(backupFile, before, "utf8");
899
+ }
849
900
  fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
850
- const updated = JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
901
+ const updated = before.trim() !== next.trim() || JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
851
902
  return {
852
903
  file: filePath,
853
904
  server: serverName,
854
- updated
905
+ updated,
906
+ migrated_legacy_servers: migratedLegacyServers,
907
+ backup_file: backupFile
855
908
  };
856
909
  }
857
910
 
@@ -867,7 +920,9 @@ function installExternalMcpConfigs(options = {}) {
867
920
  file: target,
868
921
  server: merged.server,
869
922
  created: !existed,
870
- updated: merged.updated
923
+ updated: merged.updated,
924
+ migrated_legacy_servers: merged.migrated_legacy_servers,
925
+ backup_file: merged.backup_file
871
926
  });
872
927
  } catch (error) {
873
928
  skipped.push({
@@ -881,25 +936,30 @@ function installExternalMcpConfigs(options = {}) {
881
936
 
882
937
  function getKnownExternalSkillBaseDirsByAgent() {
883
938
  const home = os.homedir();
884
- const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
885
- const traeDirNames = dedupeLower([
939
+ const appBases = getExternalAppSupportBaseDirs();
940
+ const traeDirNames = [
886
941
  "Trae",
887
942
  "Trae CN",
888
943
  "TraeCN",
889
944
  "trae-cn",
890
- "trae_cn",
891
- ...discoverAppDataDirsByPattern(appData, /^trae(?:[\s\-_]?cn)?$/i)
892
- ]);
893
- const traeSkillDirs = traeDirNames.map((dir) => path.join(appData, dir, "User", "skills"));
945
+ "trae_cn"
946
+ ];
947
+ const traeSkillDirs = buildAppUserPaths({ dirNames: traeDirNames, tail: ["skills"] });
948
+ const cursorSkillDirs = appBases.map((baseDir) => path.join(baseDir, "Cursor", "User", "skills"));
949
+ const openClawSkillDirs = appBases.map((baseDir) => path.join(baseDir, "OpenClaw", "User", "skills"));
894
950
  return {
895
- cursor: [path.join(home, ".cursor", "skills"), path.join(appData, "Cursor", "User", "skills")],
951
+ cursor: [path.join(home, ".cursor", "skills"), ...cursorSkillDirs],
896
952
  trae: [path.join(home, ".trae", "skills"), path.join(home, ".trae-cn", "skills"), ...traeSkillDirs],
897
953
  "trae-cn": [path.join(home, ".trae-cn", "skills"), path.join(home, ".trae", "skills"), ...traeSkillDirs],
898
954
  claude: [path.join(home, ".claude", "skills")],
899
- openclaw: [path.join(home, ".openclaw", "skills"), path.join(appData, "OpenClaw", "User", "skills")]
955
+ openclaw: [path.join(home, ".openclaw", "skills"), ...openClawSkillDirs]
900
956
  };
901
957
  }
902
958
 
959
+ function serializeMcpLaunchConfig(launchConfig) {
960
+ return JSON.stringify(launchConfig || {}).toLowerCase().replace(/\\/g, "/");
961
+ }
962
+
903
963
  function isRecommendMcpLaunchConfig(launchConfig) {
904
964
  if (!launchConfig || typeof launchConfig !== "object") return false;
905
965
  const command = String(launchConfig.command || "").toLowerCase();
@@ -913,14 +973,33 @@ function isRecommendMcpLaunchConfig(launchConfig) {
913
973
  );
914
974
  }
915
975
 
976
+ function isBossMcpServerEntry(name, launchConfig) {
977
+ const lowerName = String(name || "").toLowerCase();
978
+ const serialized = serializeMcpLaunchConfig(launchConfig);
979
+ return (
980
+ /boss[-_\s]?(recommend|recruit|chat)/i.test(lowerName)
981
+ || serialized.includes(recommendMcpPackageName.toLowerCase())
982
+ || serialized.includes("@reconcrap/boss-recruit-mcp")
983
+ || serialized.includes("@reconcrap/boss-chat")
984
+ || serialized.includes("boss-recommend-mcp")
985
+ || serialized.includes("boss-recruit-mcp")
986
+ || serialized.includes("boss-chat")
987
+ || serialized.includes("boss recommend pipeline")
988
+ || serialized.includes("boss recruit pipeline")
989
+ );
990
+ }
991
+
916
992
  function inspectMcpServerEntries(filePath) {
917
993
  if (!pathExists(filePath)) {
918
994
  return {
919
995
  exists: false,
920
996
  has_boss_recommend: false,
921
997
  has_boss_recruit: false,
998
+ has_boss_chat: false,
922
999
  recommend_server_names: [],
923
- recruit_server_names: []
1000
+ recruit_server_names: [],
1001
+ chat_server_names: [],
1002
+ boss_server_names: []
924
1003
  };
925
1004
  }
926
1005
  const parsed = readJsonObjectFileSafe(filePath);
@@ -929,8 +1008,13 @@ function inspectMcpServerEntries(filePath) {
929
1008
  : {};
930
1009
  const recommendNames = [];
931
1010
  const recruitNames = [];
1011
+ const chatNames = [];
1012
+ const bossNames = [];
932
1013
  for (const [name, config] of Object.entries(servers)) {
933
1014
  const lowerName = String(name || "").toLowerCase();
1015
+ if (isBossMcpServerEntry(name, config)) {
1016
+ bossNames.push(name);
1017
+ }
934
1018
  if (isRecommendMcpLaunchConfig(config) || lowerName.includes("boss-recommend")) {
935
1019
  recommendNames.push(name);
936
1020
  }
@@ -942,13 +1026,23 @@ function inspectMcpServerEntries(filePath) {
942
1026
  ) {
943
1027
  recruitNames.push(name);
944
1028
  }
1029
+ if (
1030
+ lowerName.includes("boss-chat")
1031
+ || serialized.includes("@reconcrap/boss-chat")
1032
+ || serialized.includes("boss-chat")
1033
+ ) {
1034
+ chatNames.push(name);
1035
+ }
945
1036
  }
946
1037
  return {
947
1038
  exists: true,
948
1039
  has_boss_recommend: recommendNames.length > 0,
949
1040
  has_boss_recruit: recruitNames.length > 0,
1041
+ has_boss_chat: chatNames.length > 0,
950
1042
  recommend_server_names: recommendNames,
951
- recruit_server_names: recruitNames
1043
+ recruit_server_names: recruitNames,
1044
+ chat_server_names: chatNames,
1045
+ boss_server_names: bossNames
952
1046
  };
953
1047
  }
954
1048
 
@@ -972,9 +1066,16 @@ function mirrorSkillToExternalDirs(options = {}) {
972
1066
  for (const bundledSkillName of bundledSkillNames) {
973
1067
  try {
974
1068
  const targetDir = path.join(baseDir, bundledSkillName);
1069
+ const legacyBeforeCopy = isLegacyBossSkillDir(targetDir);
975
1070
  ensureDir(path.dirname(targetDir));
976
1071
  fs.cpSync(getSkillSourceDir(bundledSkillName), targetDir, { recursive: true, force: true });
977
- mirrored.push({ base_dir: baseDir, target_dir: targetDir, skill: bundledSkillName });
1072
+ fs.writeFileSync(path.join(targetDir, ".installed-version"), `${packageVersion}\n`, "utf8");
1073
+ mirrored.push({
1074
+ base_dir: baseDir,
1075
+ target_dir: targetDir,
1076
+ skill: bundledSkillName,
1077
+ replaced_legacy: legacyBeforeCopy
1078
+ });
978
1079
  } catch (error) {
979
1080
  skipped.push({ base_dir: baseDir, skill: bundledSkillName, reason: error.message });
980
1081
  }
@@ -983,6 +1084,23 @@ function mirrorSkillToExternalDirs(options = {}) {
983
1084
  return { baseDirs, mirrored, skipped };
984
1085
  }
985
1086
 
1087
+ function isLegacyBossSkillDir(targetDir) {
1088
+ const skillFile = path.join(targetDir, "SKILL.md");
1089
+ if (!pathExists(skillFile)) return false;
1090
+ try {
1091
+ const content = fs.readFileSync(skillFile, "utf8").toLowerCase();
1092
+ return (
1093
+ content.includes("@reconcrap/boss-recruit-mcp")
1094
+ || content.includes("@reconcrap/boss-chat")
1095
+ || content.includes("boss-screen-cli")
1096
+ || content.includes(`runtime.${"evaluate"}`)
1097
+ || content.includes("page js")
1098
+ );
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+ }
1103
+
986
1104
  function syncSkillAssets(options = {}) {
987
1105
  const force = options.force === true;
988
1106
  const results = [];
@@ -2180,8 +2298,8 @@ function printHelp() {
2180
2298
  console.log(" boss-recommend-mcp run Disabled until the one-shot CLI has a CDP-only async replacement");
2181
2299
  console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
2182
2300
  console.log(" boss-recommend-mcp chat <subcommand> Run CDP-only boss-chat health/prepare/status commands");
2183
- console.log(" boss-recommend-mcp install Install skill/MCP templates and auto-init screening-config.json (supports --agent trae-cn/cursor/...)");
2184
- console.log(" boss-recommend-mcp install-skill Install bundled Codex skills");
2301
+ console.log(" boss-recommend-mcp install Install/migrate skills and MCP configs; replaces legacy Boss MCP routes (supports --agent trae-cn/openclaw/...)");
2302
+ console.log(" boss-recommend-mcp install-skill Install bundled Codex skills (recommend/recruit/chat)");
2185
2303
  console.log(" boss-recommend-mcp init-config Create screening-config.json if missing (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
2186
2304
  console.log(" boss-recommend-mcp config set Write baseUrl/apiKey/model (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
2187
2305
  console.log(" boss-recommend-mcp set-port Persist preferred Chrome debug port to screening-config.json");
@@ -2257,7 +2375,14 @@ async function installAll(options = {}) {
2257
2375
  console.log(`Auto-configured external MCP files: ${externalMcpResult.applied.length}`);
2258
2376
  for (const item of externalMcpResult.applied) {
2259
2377
  const action = item.created ? "created" : item.updated ? "updated" : "unchanged";
2260
- console.log(`- ${item.file} (${action})`);
2378
+ const migrated = Array.isArray(item.migrated_legacy_servers) && item.migrated_legacy_servers.length > 0
2379
+ ? `; migrated legacy servers: ${item.migrated_legacy_servers.join(", ")}`
2380
+ : "";
2381
+ const backup = item.backup_file ? `; backup: ${item.backup_file}` : "";
2382
+ console.log(`- ${item.file} (${action}${migrated}${backup})`);
2383
+ }
2384
+ for (const item of externalMcpResult.skipped) {
2385
+ console.warn(`External MCP warning: ${item.file} -> ${item.reason}`);
2261
2386
  }
2262
2387
  } else {
2263
2388
  console.log("No external MCP config target detected. Set BOSS_RECOMMEND_MCP_CONFIG_TARGETS to auto-configure custom agents.");
@@ -2265,7 +2390,10 @@ async function installAll(options = {}) {
2265
2390
  if (externalSkillResult.baseDirs.length > 0) {
2266
2391
  console.log(`Mirrored skill to external dirs: ${externalSkillResult.mirrored.length}`);
2267
2392
  for (const item of externalSkillResult.mirrored) {
2268
- console.log(`- ${item.target_dir}`);
2393
+ console.log(`- ${item.target_dir}${item.replaced_legacy ? " (replaced legacy skill)" : ""}`);
2394
+ }
2395
+ for (const item of externalSkillResult.skipped) {
2396
+ console.warn(`External skill warning: ${item.base_dir} / ${item.skill} -> ${item.reason}`);
2269
2397
  }
2270
2398
  } else {
2271
2399
  console.log("No external skill dir detected. Set BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS to mirror skill for non-Codex agents.");
@@ -2635,8 +2763,10 @@ export const __testables = {
2635
2763
  getBossChatCliRunTarget,
2636
2764
  getDefaultMcpPackageSpecifier,
2637
2765
  getRunFollowUp,
2766
+ inspectMcpServerEntries,
2638
2767
  installSkill,
2639
2768
  isInstalledPackageRoot,
2769
+ mergeMcpServerConfigFile,
2640
2770
  resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
2641
2771
  runBossChatCliCommand,
2642
2772
  runPipelineOnce
@@ -332,7 +332,7 @@ export async function runRecommendWorkflow({
332
332
  fallbackPageScope = "recommend",
333
333
  filter = {},
334
334
  maxCandidates = 5,
335
- detailLimit = 0,
335
+ detailLimit,
336
336
  closeDetail = true,
337
337
  delayMs = 0,
338
338
  cardTimeoutMs = 10000,
@@ -362,7 +362,7 @@ export async function runRecommendWorkflow({
362
362
  const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
363
363
  const postActionEnabled = normalizedPostAction !== "none";
364
364
  const limit = Math.max(1, Number(maxCandidates) || 1);
365
- const detailCountLimit = Math.max(0, Number(detailLimit) || 0);
365
+ const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
366
366
  const effectiveDetailLimit = postActionEnabled ? limit : detailCountLimit;
367
367
  const networkRecorder = effectiveDetailLimit > 0
368
368
  ? createRecommendDetailNetworkRecorder(client)
@@ -785,7 +785,7 @@ export function createRecommendRunService({
785
785
  fallbackPageScope = "recommend",
786
786
  filter = {},
787
787
  maxCandidates = 5,
788
- detailLimit = 0,
788
+ detailLimit,
789
789
  closeDetail = true,
790
790
  delayMs = 0,
791
791
  cardTimeoutMs = 10000,
@@ -814,6 +814,8 @@ export function createRecommendRunService({
814
814
  const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
815
815
  const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
816
816
  const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
817
+ const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
818
+ const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
817
819
  return manager.startRun({
818
820
  name,
819
821
  context: {
@@ -825,7 +827,7 @@ export function createRecommendRunService({
825
827
  fallback_page_scope: normalizedFallbackPageScope,
826
828
  filter: normalizedFilter,
827
829
  max_candidates: maxCandidates,
828
- detail_limit: detailLimit,
830
+ detail_limit: normalizedDetailLimit,
829
831
  close_detail: closeDetail,
830
832
  cv_acquisition_mode: cvAcquisitionMode,
831
833
  max_image_pages: maxImagePages,
@@ -846,7 +848,7 @@ export function createRecommendRunService({
846
848
  },
847
849
  progress: {
848
850
  card_count: 0,
849
- target_count: Math.max(1, Number(maxCandidates) || 1),
851
+ target_count: candidateLimit,
850
852
  processed: 0,
851
853
  screened: 0,
852
854
  detail_opened: 0,
@@ -864,7 +866,7 @@ export function createRecommendRunService({
864
866
  fallbackPageScope: normalizedFallbackPageScope,
865
867
  filter: normalizedFilter,
866
868
  maxCandidates,
867
- detailLimit,
869
+ detailLimit: normalizedDetailLimit,
868
870
  closeDetail,
869
871
  delayMs,
870
872
  cardTimeoutMs,
@@ -64,4 +64,3 @@ export async function readFirstRecruitCardCandidate(client, frameNodeId, options
64
64
  candidate
65
65
  };
66
66
  }
67
-
@@ -65,4 +65,3 @@ export async function queryFirstAcrossRoots(client, roots, selectors) {
65
65
  }
66
66
  return null;
67
67
  }
68
-
package/src/index.js CHANGED
@@ -540,7 +540,11 @@ function createRunInputSchema() {
540
540
  detail_limit: {
541
541
  type: "integer",
542
542
  minimum: 0,
543
- description: "打开详情的人数上限;默认 00 表示只用卡片信息"
543
+ description: "打开详情/CV 的人数上限;默认跟随 target_count/max_candidates。生产筛选不应传 0;只有 allow_card_only_screening=true 时才会接受 0 作为调试卡片-only 模式"
544
+ },
545
+ allow_card_only_screening: {
546
+ type: "boolean",
547
+ description: "高级调试开关;默认 false。只有显式为 true 时,recommend 才会尊重 detail_limit=0 并只用卡片信息筛选"
544
548
  },
545
549
  delay_ms: {
546
550
  type: "integer",
@@ -75,6 +75,15 @@ function parseNonNegativeInteger(raw, fallback) {
75
75
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
76
76
  }
77
77
 
78
+ function resolveRecommendDetailLimit(args = {}, normalized = {}) {
79
+ const fallback = parsePositiveInteger(normalized.targetCount, 5);
80
+ const requested = parseNonNegativeInteger(args.detail_limit, fallback);
81
+ if (requested === 0 && args.allow_card_only_screening !== true) {
82
+ return fallback;
83
+ }
84
+ return requested;
85
+ }
86
+
78
87
  function methodSummary(methodLog = []) {
79
88
  const summary = {};
80
89
  for (const entry of methodLog || []) {
@@ -867,7 +876,7 @@ function getRunOptions(args, parsed, normalized, session) {
867
876
  fallbackPageScope: "recommend",
868
877
  filter: normalized.filter,
869
878
  maxCandidates: normalized.targetCount,
870
- detailLimit: parseNonNegativeInteger(args.detail_limit, 0),
879
+ detailLimit: resolveRecommendDetailLimit(args, normalized),
871
880
  closeDetail: true,
872
881
  delayMs: parseNonNegativeInteger(args.delay_ms, 0),
873
882
  cardTimeoutMs: slowLive ? 180000 : 90000,