@reconcrap/boss-recommend-mcp 2.1.10 → 2.1.12
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 +10 -5
- package/package.json +1 -1
- package/skills/boss-chat/README.md +3 -1
- package/skills/boss-chat/SKILL.md +7 -2
- package/skills/boss-recommend-pipeline/SKILL.md +2 -0
- package/skills/boss-recruit-pipeline/SKILL.md +2 -0
- package/src/cli.js +129 -85
- package/src/index.js +341 -113
- package/src/recommend-mcp.js +128 -12
package/README.md
CHANGED
|
@@ -100,6 +100,7 @@ MCP 工具:
|
|
|
100
100
|
- `resume_recruit_pipeline_run`
|
|
101
101
|
- `cancel_recruit_pipeline_run`
|
|
102
102
|
- `boss_chat_health_check`
|
|
103
|
+
- `list_boss_chat_jobs`(只读读取聊天页岗位列表;chat-only 获取 `job_options` 的首选别名,不会启动任务)
|
|
103
104
|
- `prepare_boss_chat_run`
|
|
104
105
|
- `start_boss_chat_run`
|
|
105
106
|
- `get_boss_chat_run`
|
|
@@ -224,8 +225,9 @@ node src/cli.js start
|
|
|
224
225
|
如果检测到 legacy Boss server entries,installer 会:
|
|
225
226
|
|
|
226
227
|
- 保留非 Boss MCP server。
|
|
227
|
-
-
|
|
228
|
-
-
|
|
228
|
+
- Trae/Trae-CN 默认写入三个小 toolset server:`boss-recommend`(`BOSS_RECOMMEND_MCP_TOOLSET=recommend`)、`boss-chat`(`chat`)、`boss-recruit`(`recruit`)。这样 recommend/chat/search 的 tool list 不会互相挤占 agent 可见工具预算。
|
|
229
|
+
- 其它宿主默认仍写入兼容统一 server:`boss-recommend -> npx -y @reconcrap/boss-recommend-mcp@<installed-version> start`。
|
|
230
|
+
- 如果传入 `--mcp-launch global-wrapper`,Trae/Trae-CN 同样会写入三个 toolset server,但 command 指向升级稳定 wrapper。该 wrapper 会加载 `~/.nvm/nvm.sh` 并执行当前全局 `boss-recommend-mcp start`,适合 macOS 上通过 `npm -g i @reconcrap/boss-recommend-mcp@latest` 持续升级。
|
|
229
231
|
- 从同一个 `mcp.json` 删除旧 `boss-recruit-mcp`、standalone `boss-chat`、旧本地 Boss repo 路径,避免 agent 继续调用 legacy 包。
|
|
230
232
|
- 在原文件旁生成 `mcp.json.boss-mcp-migration-*.bak`。
|
|
231
233
|
- 同步外部 skills 目录里的 `boss-recommend-pipeline`、`boss-recruit-pipeline`、`boss-chat`。
|
|
@@ -262,6 +264,7 @@ BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS="/path/to/skills" boss-recommend-mcp install
|
|
|
262
264
|
BOSS_RECOMMEND_HOME # 统一状态目录,默认 ~/.boss-recommend-mcp
|
|
263
265
|
BOSS_CHAT_HOME # 覆盖 boss-chat 运行态目录;默认 ~/.boss-recommend-mcp/boss-chat
|
|
264
266
|
BOSS_RECOMMEND_SCREEN_CONFIG # 显式指定 screening-config.json 路径(最高优先级)
|
|
267
|
+
BOSS_RECOMMEND_MCP_TOOLSET # 可选收窄 MCP 工具:all|recommend|chat|recruit;Trae/Trae-CN installer 会自动设置
|
|
265
268
|
BOSS_RECOMMEND_MCP_CONFIG_TARGETS # JSON 数组或系统 path 分隔路径列表,指定额外 mcp.json 目标文件
|
|
266
269
|
BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS # JSON 数组或系统 path 分隔路径列表,指定额外 skills 根目录
|
|
267
270
|
```
|
|
@@ -424,7 +427,7 @@ node src/cli.js chat prepare-run --slow-live --port 9222
|
|
|
424
427
|
- `baseUrl` / `apiKey` / `model` 不再单独传入,固定复用 recommend 的 `screening-config.json`
|
|
425
428
|
- `greeting_text` 默认优先级:本次显式值 > `screening-config.json.greetingMessage` > 内置默认招呼语(`Hi同学,能麻烦发下简历吗?`)
|
|
426
429
|
- 若缺少 `follow_up.chat` 必填项,pipeline 会返回 `NEED_INPUT`
|
|
427
|
-
- 如需聊天页筛选,请调用 `prepare_boss_chat_run` 获取岗位列表,再调用 `start_boss_chat_run`。
|
|
430
|
+
- 如需聊天页筛选,请调用 `list_boss_chat_jobs` 或 `prepare_boss_chat_run` 获取岗位列表,再调用 `start_boss_chat_run`。chat-only、未读、全部聊天、求简历任务不要调用 `list_recommend_jobs` / `run_recommend` / `start_recommend_pipeline_run`;这些 recommend 工具会对明确的 chat/search 误路由 fail closed。
|
|
428
431
|
- `boss-chat` 状态统一写入 `~/.boss-recommend-mcp/boss-chat`(或 `BOSS_CHAT_HOME` 指定目录),不再依赖工作区 `cwd`
|
|
429
432
|
|
|
430
433
|
## Chat-only
|
|
@@ -438,6 +441,7 @@ node src/cli.js chat prepare-run --slow-live --port 9222
|
|
|
438
441
|
- `boss-recommend-mcp chat get-run|pause-run|resume-run|cancel-run`
|
|
439
442
|
- MCP:
|
|
440
443
|
- `boss_chat_health_check`
|
|
444
|
+
- `list_boss_chat_jobs`
|
|
441
445
|
- `prepare_boss_chat_run`
|
|
442
446
|
- `start_boss_chat_run`
|
|
443
447
|
- `get_boss_chat_run`
|
|
@@ -450,14 +454,15 @@ node src/cli.js chat prepare-run --slow-live --port 9222
|
|
|
450
454
|
|
|
451
455
|
chat-only 交互建议:
|
|
452
456
|
|
|
453
|
-
- 先调用一次 `prepare_boss_chat_run`(可不带参数),服务会先导航到 `https://www.zhipin.com/web/chat/index` 并返回 `NEED_INPUT`,其中包含岗位 `job_options` 与待补字段。
|
|
457
|
+
- 先调用一次 `list_boss_chat_jobs` 或 `prepare_boss_chat_run`(可不带参数),服务会先导航到 `https://www.zhipin.com/web/chat/index` 并返回 `NEED_INPUT`,其中包含岗位 `job_options` 与待补字段。
|
|
454
458
|
- 然后基于 `job_options` 让用户选择 `job`,并补齐 `start_from`、`target_count`、`criteria` 后调用 `start_boss_chat_run` 启动任务。
|
|
455
459
|
- `greeting_text` 可选;未传时使用 `screening-config.json.greetingMessage`,若未配置则使用默认招呼语(`Hi同学,能麻烦发下简历吗?`)。
|
|
456
460
|
- `target_count` 支持正整数、`all`、`-1`;若用户给出 `全部候选人` / `所有候选人`,会自动按不限(扫到底)处理。
|
|
457
461
|
|
|
458
462
|
Trae-CN / 长对话防循环建议:
|
|
459
463
|
|
|
460
|
-
- 固定流程:`boss_chat_health_check` -> `prepare_boss_chat_run(空参可)` -> 一次性补齐 `job/start_from/target_count/criteria` -> `start_boss_chat_run`。
|
|
464
|
+
- 固定流程:`boss_chat_health_check` -> `list_boss_chat_jobs(空参可)` / `prepare_boss_chat_run(空参可)` -> 一次性补齐 `job/start_from/target_count/criteria` -> `start_boss_chat_run`。
|
|
465
|
+
- chat-only 场景严禁调用 `list_recommend_jobs`、`run_recommend` 或 `start_recommend_pipeline_run`。
|
|
461
466
|
- `start_boss_chat_run` 的工具 schema 已把 `job/start_from/target_count/criteria` 标记为必填;不要用它获取岗位列表。
|
|
462
467
|
- 若 `pending_questions` / UI 选项里出现“扫到底(必须传 `target_count="all"`)”,下一次工具调用请直接照抄 `"target_count": "all"`,不要只保留“扫到底”这层自然语言语义。
|
|
463
468
|
- `start_boss_chat_run` 返回 `ACCEPTED` 后直接结束当前回合,不要自动轮询。
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ Please run a Boss chat-only task (do not switch to recommend flow).
|
|
|
16
16
|
|
|
17
17
|
Execution order:
|
|
18
18
|
1) Call boss_chat_health_check.
|
|
19
|
-
2) Call
|
|
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
20
|
3) Ask for these required fields in one shot: job, start_from (unread/all), target_count, criteria, rest_level (low/medium/high).
|
|
21
21
|
4) After user reply, call start_boss_chat_run exactly once to start the run.
|
|
22
22
|
5) If ACCEPTED, reply only with run_id and "task started"; no auto polling.
|
|
@@ -24,6 +24,8 @@ Execution order:
|
|
|
24
24
|
Anti-loop rules:
|
|
25
25
|
- Do not repeat the same sentence across turns.
|
|
26
26
|
- On validation errors, list all missing/invalid fields once.
|
|
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
|
+
- Do not call run_recommend or start_recommend_pipeline_run for chat-only tasks; use start_boss_chat_run.
|
|
27
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/criteria.
|
|
28
30
|
- Do not call start_boss_chat_run repeatedly in one turn.
|
|
29
31
|
- Do not call get_boss_chat_run unless user explicitly asks for progress.
|
|
@@ -13,7 +13,10 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
|
|
|
13
13
|
|
|
14
14
|
## Tool Routing
|
|
15
15
|
|
|
16
|
+
Trae/Trae-CN split-server config exposes these under the `boss-chat` MCP server. Chat-only tasks should call `boss-chat/<tool>` when the host shows server-qualified tool names.
|
|
17
|
+
|
|
16
18
|
- 健康检查:`boss_chat_health_check`
|
|
19
|
+
- 只读获取聊天页岗位列表:`list_boss_chat_jobs`
|
|
17
20
|
- 预备并获取岗位列表:`prepare_boss_chat_run`
|
|
18
21
|
- 启动异步任务:`start_boss_chat_run`
|
|
19
22
|
- 查询进度:`get_boss_chat_run`
|
|
@@ -69,6 +72,8 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
|
|
|
69
72
|
- 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
|
|
70
73
|
- `job` / `start_from` / `criteria` 缺一不可;缺参时只补缺口。
|
|
71
74
|
- `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
|
|
75
|
+
- chat-only 岗位列表只能通过 `list_boss_chat_jobs` 或 `prepare_boss_chat_run` 获取;严禁调用 `list_recommend_jobs`,因为它会切到推荐页。
|
|
76
|
+
- chat-only 启动只能调用 `start_boss_chat_run`;严禁调用 `run_recommend` 或 `start_recommend_pipeline_run`。
|
|
72
77
|
- 每次 run 必须明确询问用户本次休息强度 `rest_level`:`low`(旧策略)/ `medium`(约 5 小时或 700 人累计休息 30 分钟)/ `high`(约 5 小时或 700 人累计休息 1 小时);不得默认使用配置文件里的值替用户决定。
|
|
73
78
|
- 当用户说“全部候选人/所有候选人”时,必须按“扫到底(unlimited)”处理,不要再追问正整数。
|
|
74
79
|
- 参数名必须写 `target_count`(不要写“目标数量”等中文键名)。
|
|
@@ -77,7 +82,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
|
|
|
77
82
|
- 若工具或提问选项里出现“扫到底(必须传 `target_count=\"all\"`)”之类字样,下一次工具调用时必须直接照抄这个字面量,不要只保留“扫到底”语义。
|
|
78
83
|
- 禁止 agent 自行补全 `job/start_from/criteria` 并直接执行,必须由用户明确给出或确认。
|
|
79
84
|
- chat-only 启动流程必须先进入聊天页并拉取岗位列表,再让用户从列表中选择 `job`。
|
|
80
|
-
- 必须先用空参调用 `prepare_boss_chat_run` 获取 `job_options`;不要用 `start_boss_chat_run` 做预备调用。
|
|
85
|
+
- 必须先用空参调用 `list_boss_chat_jobs` 或 `prepare_boss_chat_run` 获取 `job_options`;不要用 `start_boss_chat_run` 做预备调用。
|
|
81
86
|
- `start_boss_chat_run` 只能用于真正启动,必须一次性传齐 `job` / `start_from` / `target_count` / `criteria`。
|
|
82
87
|
- 若 `start_boss_chat_run` 返回 `NEED_INPUT` 且 `missing_fields` 包含 `target_count`,说明你没有把用户选择写入工具参数;下一次调用必须照 `next_call_example` 原样补上 `"target_count": "all"` 或正整数,不要重复空调用。
|
|
83
88
|
- 默认不自动轮询;只有用户要求查进度时才调用 `get_boss_chat_run`。
|
|
@@ -96,6 +101,6 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
|
|
|
96
101
|
## Response Style
|
|
97
102
|
|
|
98
103
|
- 用结构化中文。
|
|
99
|
-
- 首轮建议先调用一次 `prepare_boss_chat_run`(可空参)获取 `job_options` 与 `pending_questions`。
|
|
104
|
+
- 首轮建议先调用一次 `list_boss_chat_jobs`(可空参)或 `prepare_boss_chat_run`(可空参)获取 `job_options` 与 `pending_questions`。
|
|
100
105
|
- 缺参时必须逐项确认:`job`(来自岗位列表)、`start_from`(`unread|all`)、`target_count`、`criteria`、`rest_level`。
|
|
101
106
|
- 若健康检查失败,明确提示共享配置文件 `screening-config.json` 不可用。
|
|
@@ -97,6 +97,8 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
|
|
|
97
97
|
|
|
98
98
|
## Tool Usage
|
|
99
99
|
|
|
100
|
+
Trae/Trae-CN split-server config exposes these under the `boss-recommend` MCP server. 推荐页任务应调用 `boss-recommend/<tool>`;不要切到 `boss-chat` 或 `boss-recruit`。
|
|
101
|
+
|
|
100
102
|
- 岗位发现工具:`list_recommend_jobs`
|
|
101
103
|
- 用途:当用户需要为 cron / 一次性自动任务提前填写完整参数时,先用它读取推荐页岗位下拉框的全部可用岗位名;默认会复用/自动打开本机 9222 Chrome 并导航到推荐页。
|
|
102
104
|
- 输出:优先把 `job_names` 里的值作为后续 `overrides.job` / `confirmation.job_value`。
|
|
@@ -11,6 +11,8 @@ description: "Use when users want Boss search/recruit-page screening via the uni
|
|
|
11
11
|
|
|
12
12
|
## Tool Routing
|
|
13
13
|
|
|
14
|
+
Trae/Trae-CN split-server config exposes these under the `boss-recruit` MCP server. Search/recruit tasks should call `boss-recruit/<tool>` when the host shows server-qualified tool names.
|
|
15
|
+
|
|
14
16
|
- 同步启动:`run_recruit_pipeline`
|
|
15
17
|
- 异步启动:`start_recruit_pipeline_run`
|
|
16
18
|
- 查询进度:`get_recruit_pipeline_run`
|
package/src/cli.js
CHANGED
|
@@ -57,12 +57,15 @@ const bossLoginUrl = "https://www.zhipin.com/web/user/?ka=bticket";
|
|
|
57
57
|
const chromeOnboardingUrlPattern = /^chrome:\/\/(welcome|intro|newtab|signin|history-sync|settings\/syncSetup)/i;
|
|
58
58
|
const bossLoginUrlPattern = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com)/i;
|
|
59
59
|
const bossLoginTitlePattern = /登录|signin|扫码登录|BOSS直聘登录/i;
|
|
60
|
-
const supportedMcpClients = ["generic", "cursor", "trae", "claudecode", "openclaw", "qclaw"];
|
|
60
|
+
const supportedMcpClients = ["generic", "cursor", "trae", "claudecode", "openclaw", "qclaw"];
|
|
61
61
|
const defaultMcpServerName = "boss-recommend";
|
|
62
|
+
const bossChatMcpServerName = "boss-chat";
|
|
63
|
+
const bossRecruitMcpServerName = "boss-recruit";
|
|
62
64
|
const defaultMcpCommand = "npx";
|
|
63
65
|
const recommendMcpPackageName = "@reconcrap/boss-recommend-mcp";
|
|
64
66
|
const recommendMcpBinaryName = "boss-recommend-mcp";
|
|
65
67
|
const globalMcpWrapperFileName = "boss-recommend-mcp-mcp-server";
|
|
68
|
+
const mcpToolsetEnv = "BOSS_RECOMMEND_MCP_TOOLSET";
|
|
66
69
|
const supportedMcpLaunchModes = ["npx", "global-wrapper"];
|
|
67
70
|
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
|
|
68
71
|
const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
|
|
@@ -807,12 +810,35 @@ function shouldDefaultRecommendDetachedMcpEnv(options = {}) {
|
|
|
807
810
|
return client === "openclaw"
|
|
808
811
|
|| client === "qclaw"
|
|
809
812
|
|| agent === "openclaw"
|
|
810
|
-
|| agent === "qclaw";
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
function
|
|
814
|
-
|
|
815
|
-
|
|
813
|
+
|| agent === "qclaw";
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function normalizeMcpToolsetOption(value) {
|
|
817
|
+
const raw = String(value || "").trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
818
|
+
if (!raw || raw === "all") return "";
|
|
819
|
+
if (raw === "boss-recommend" || raw === "recommend-page") return "recommend";
|
|
820
|
+
if (raw === "boss-chat" || raw === "chat-only" || raw === "chat-page") return "chat";
|
|
821
|
+
if (raw === "boss-recruit" || raw === "search" || raw === "search-page" || raw === "recruit-page") return "recruit";
|
|
822
|
+
if (["recommend", "chat", "recruit"].includes(raw)) return raw;
|
|
823
|
+
throw new Error(`Unsupported --toolset value: ${raw}. Supported: recommend, chat, recruit, all`);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function getMcpToolsetEnv(options = {}) {
|
|
827
|
+
const toolset = normalizeMcpToolsetOption(options.toolset || options["toolset"]);
|
|
828
|
+
return toolset ? { [mcpToolsetEnv]: toolset } : {};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function shouldUseSplitBossMcpServers(options = {}) {
|
|
832
|
+
if (options["server-name"] || options.serverName || options.toolset || options["toolset"]) return false;
|
|
833
|
+
if (options.split === true || options["split-tools"] === true || options.splitTools === true) return true;
|
|
834
|
+
const client = normalizeMcpClientName(options.client);
|
|
835
|
+
const agent = normalizeAgentName(options.agent);
|
|
836
|
+
return client === "trae" || agent === "trae" || agent === "trae-cn";
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function getDefaultMcpEnv(options = {}) {
|
|
840
|
+
return shouldDefaultRecommendDetachedMcpEnv(options)
|
|
841
|
+
? { ...detachedRecommendMcpEnv }
|
|
816
842
|
: {};
|
|
817
843
|
}
|
|
818
844
|
|
|
@@ -834,6 +860,7 @@ function buildMcpLaunchConfig(options = {}) {
|
|
|
834
860
|
};
|
|
835
861
|
const mergedEnv = {
|
|
836
862
|
...getDefaultMcpEnv(options),
|
|
863
|
+
...getMcpToolsetEnv(options),
|
|
837
864
|
...(isPlainObject(env) ? env : {})
|
|
838
865
|
};
|
|
839
866
|
if (Object.keys(mergedEnv).length > 0) {
|
|
@@ -853,19 +880,36 @@ function buildMcpLaunchConfig(options = {}) {
|
|
|
853
880
|
? ["start"]
|
|
854
881
|
: buildDefaultMcpArgs(options);
|
|
855
882
|
const launchConfig = { command, args: launchArgs };
|
|
856
|
-
const mergedEnv = {
|
|
857
|
-
...getDefaultMcpEnv(options),
|
|
858
|
-
...(
|
|
859
|
-
|
|
883
|
+
const mergedEnv = {
|
|
884
|
+
...getDefaultMcpEnv(options),
|
|
885
|
+
...getMcpToolsetEnv(options),
|
|
886
|
+
...(isPlainObject(env) ? env : {})
|
|
887
|
+
};
|
|
860
888
|
if (Object.keys(mergedEnv).length > 0) {
|
|
861
889
|
launchConfig.env = mergedEnv;
|
|
862
890
|
}
|
|
863
891
|
return launchConfig;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
function
|
|
867
|
-
if (
|
|
868
|
-
return
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function buildBossMcpServerEntries(options = {}) {
|
|
895
|
+
if (shouldUseSplitBossMcpServers(options)) {
|
|
896
|
+
return {
|
|
897
|
+
[defaultMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "recommend" }),
|
|
898
|
+
[bossChatMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "chat" }),
|
|
899
|
+
[bossRecruitMcpServerName]: buildMcpLaunchConfig({ ...options, toolset: "recruit" })
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
const serverName = typeof options["server-name"] === "string" && options["server-name"].trim()
|
|
903
|
+
? options["server-name"].trim()
|
|
904
|
+
: defaultMcpServerName;
|
|
905
|
+
return {
|
|
906
|
+
[serverName]: buildMcpLaunchConfig(options)
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function mergeExistingMcpEntryEnv(existingEntry, launchConfig) {
|
|
911
|
+
if (!isPlainObject(existingEntry?.env) || !isPlainObject(launchConfig)) {
|
|
912
|
+
return launchConfig;
|
|
869
913
|
}
|
|
870
914
|
return {
|
|
871
915
|
...launchConfig,
|
|
@@ -874,28 +918,21 @@ function mergeExistingMcpEntryEnv(existingEntry, launchConfig) {
|
|
|
874
918
|
...(isPlainObject(launchConfig.env) ? launchConfig.env : {})
|
|
875
919
|
}
|
|
876
920
|
};
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
function buildMcpConfigFileContent(options = {}) {
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
mcpServers: {
|
|
895
|
-
[serverName]: launchConfig
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function buildMcpConfigFileContent(options = {}) {
|
|
924
|
+
const servers = buildBossMcpServerEntries(options);
|
|
925
|
+
if (normalizeMcpClientName(options.client) === "qclaw") {
|
|
926
|
+
return {
|
|
927
|
+
mcp: {
|
|
928
|
+
servers
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
mcpServers: servers
|
|
934
|
+
};
|
|
935
|
+
}
|
|
899
936
|
|
|
900
937
|
function writeMcpConfigFiles(options = {}) {
|
|
901
938
|
const clients = parseMcpClientTargets(options.client);
|
|
@@ -1039,25 +1076,28 @@ function getMcpServersFromConfig(config = {}, useQClawShape = false) {
|
|
|
1039
1076
|
return {};
|
|
1040
1077
|
}
|
|
1041
1078
|
|
|
1042
|
-
function mergeMcpServerConfigFile(filePath, options = {}) {
|
|
1043
|
-
const current = readJsonObjectFileSafe(filePath);
|
|
1044
|
-
const useQClawShape = isQClawMcpConfigTarget(filePath, options, current);
|
|
1045
|
-
const nextConfig = buildMcpConfigFileContent({ ...options, client: useQClawShape ? "qclaw" : options.client });
|
|
1046
|
-
const nextServers = useQClawShape ? nextConfig.mcp?.servers : nextConfig.mcpServers;
|
|
1047
|
-
const
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1079
|
+
function mergeMcpServerConfigFile(filePath, options = {}) {
|
|
1080
|
+
const current = readJsonObjectFileSafe(filePath);
|
|
1081
|
+
const useQClawShape = isQClawMcpConfigTarget(filePath, options, current);
|
|
1082
|
+
const nextConfig = buildMcpConfigFileContent({ ...options, client: useQClawShape ? "qclaw" : options.client });
|
|
1083
|
+
const nextServers = useQClawShape ? nextConfig.mcp?.servers : nextConfig.mcpServers;
|
|
1084
|
+
const serverNames = Object.keys(nextServers || {});
|
|
1085
|
+
const nextBossServerNames = new Set(serverNames);
|
|
1086
|
+
const existingServers = getMcpServersFromConfig(current, useQClawShape);
|
|
1087
|
+
const mergedBossServers = {};
|
|
1088
|
+
for (const serverName of serverNames) {
|
|
1089
|
+
mergedBossServers[serverName] = mergeExistingMcpEntryEnv(
|
|
1090
|
+
existingServers[serverName],
|
|
1091
|
+
nextServers?.[serverName] || buildMcpLaunchConfig(options)
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
const retainedServers = {};
|
|
1095
|
+
const migratedLegacyServers = [];
|
|
1096
|
+
for (const [name, config] of Object.entries(existingServers)) {
|
|
1097
|
+
if (nextBossServerNames.has(name)) continue;
|
|
1098
|
+
if (isBossMcpServerEntry(name, config)) {
|
|
1099
|
+
migratedLegacyServers.push(name);
|
|
1100
|
+
continue;
|
|
1061
1101
|
}
|
|
1062
1102
|
retainedServers[name] = config;
|
|
1063
1103
|
}
|
|
@@ -1065,20 +1105,20 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
|
|
|
1065
1105
|
? {
|
|
1066
1106
|
...current,
|
|
1067
1107
|
mcp: {
|
|
1068
|
-
...(current?.mcp && typeof current.mcp === "object" && !Array.isArray(current.mcp) ? current.mcp : {}),
|
|
1069
|
-
servers: {
|
|
1070
|
-
...retainedServers,
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
: {
|
|
1076
|
-
...current,
|
|
1077
|
-
mcpServers: {
|
|
1078
|
-
...retainedServers,
|
|
1079
|
-
|
|
1080
|
-
}
|
|
1081
|
-
};
|
|
1108
|
+
...(current?.mcp && typeof current.mcp === "object" && !Array.isArray(current.mcp) ? current.mcp : {}),
|
|
1109
|
+
servers: {
|
|
1110
|
+
...retainedServers,
|
|
1111
|
+
...mergedBossServers
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
: {
|
|
1116
|
+
...current,
|
|
1117
|
+
mcpServers: {
|
|
1118
|
+
...retainedServers,
|
|
1119
|
+
...mergedBossServers
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1082
1122
|
|
|
1083
1123
|
ensureDir(path.dirname(filePath));
|
|
1084
1124
|
const before = pathExists(filePath) ? fs.readFileSync(filePath, "utf8") : "";
|
|
@@ -1088,14 +1128,15 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
|
|
|
1088
1128
|
backupFile = `${filePath}.boss-mcp-migration-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
1089
1129
|
fs.writeFileSync(backupFile, before, "utf8");
|
|
1090
1130
|
}
|
|
1091
|
-
fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
|
|
1092
|
-
const updated = before.trim() !== next.trim()
|
|
1093
|
-
return {
|
|
1094
|
-
file: filePath,
|
|
1095
|
-
server:
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1131
|
+
fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
|
|
1132
|
+
const updated = before.trim() !== next.trim();
|
|
1133
|
+
return {
|
|
1134
|
+
file: filePath,
|
|
1135
|
+
server: serverNames[0] || defaultMcpServerName,
|
|
1136
|
+
servers: serverNames,
|
|
1137
|
+
config_shape: useQClawShape ? "qclaw" : "mcpServers",
|
|
1138
|
+
updated,
|
|
1139
|
+
migrated_legacy_servers: migratedLegacyServers,
|
|
1099
1140
|
backup_file: backupFile
|
|
1100
1141
|
};
|
|
1101
1142
|
}
|
|
@@ -1108,12 +1149,13 @@ function installExternalMcpConfigs(options = {}) {
|
|
|
1108
1149
|
try {
|
|
1109
1150
|
const existed = pathExists(target);
|
|
1110
1151
|
const merged = mergeMcpServerConfigFile(target, options);
|
|
1111
|
-
applied.push({
|
|
1112
|
-
file: target,
|
|
1113
|
-
server: merged.server,
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1152
|
+
applied.push({
|
|
1153
|
+
file: target,
|
|
1154
|
+
server: merged.server,
|
|
1155
|
+
servers: merged.servers,
|
|
1156
|
+
created: !existed,
|
|
1157
|
+
updated: merged.updated,
|
|
1158
|
+
migrated_legacy_servers: merged.migrated_legacy_servers,
|
|
1117
1159
|
backup_file: merged.backup_file
|
|
1118
1160
|
});
|
|
1119
1161
|
} catch (error) {
|
|
@@ -2718,6 +2760,7 @@ function printHelp() {
|
|
|
2718
2760
|
console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
|
|
2719
2761
|
console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
|
|
2720
2762
|
console.log(" boss-recommend-mcp install --mcp-launch global-wrapper Use ~/.boss-recommend-mcp/bin wrapper so npm global upgrades affect MCP hosts");
|
|
2763
|
+
console.log(" boss-recommend-mcp start --toolset recommend Start a narrowed MCP server (all|recommend|chat|recruit)");
|
|
2721
2764
|
console.log("");
|
|
2722
2765
|
console.log("Run command:");
|
|
2723
2766
|
console.log(" boss-recommend-mcp prepare-run --instruction \"...\" --overrides-file overrides.json --confirmation-file confirmation.json --rest-level medium");
|
|
@@ -3397,6 +3440,7 @@ export const __testables = {
|
|
|
3397
3440
|
buildBossChatCliInput,
|
|
3398
3441
|
buildDefaultMcpArgs,
|
|
3399
3442
|
buildMcpLaunchConfig,
|
|
3443
|
+
buildMcpConfigFileContent,
|
|
3400
3444
|
ensureGlobalMcpWrapper,
|
|
3401
3445
|
getGlobalMcpWrapperPath,
|
|
3402
3446
|
collectRuntimeDirectories,
|
package/src/index.js
CHANGED
|
@@ -110,6 +110,7 @@ const TOOL_RUN_FEATURED_CALIBRATION = "run_featured_calibration";
|
|
|
110
110
|
const TOOL_GET_FEATURED_CALIBRATION_STATUS = "get_featured_calibration_status";
|
|
111
111
|
const TOOL_RUN_RECOMMEND_SELF_HEAL = "run_recommend_self_heal";
|
|
112
112
|
const TOOL_BOSS_CHAT_HEALTH_CHECK = "boss_chat_health_check";
|
|
113
|
+
const TOOL_BOSS_CHAT_LIST_JOBS = "list_boss_chat_jobs";
|
|
113
114
|
const TOOL_BOSS_CHAT_PREPARE_RUN = "prepare_boss_chat_run";
|
|
114
115
|
const TOOL_BOSS_CHAT_START_RUN = "start_boss_chat_run";
|
|
115
116
|
const TOOL_BOSS_CHAT_GET_RUN = "get_boss_chat_run";
|
|
@@ -122,8 +123,50 @@ const TOOL_GET_RECRUIT_PIPELINE_RUN = "get_recruit_pipeline_run";
|
|
|
122
123
|
const TOOL_CANCEL_RECRUIT_PIPELINE_RUN = "cancel_recruit_pipeline_run";
|
|
123
124
|
const TOOL_PAUSE_RECRUIT_PIPELINE_RUN = "pause_recruit_pipeline_run";
|
|
124
125
|
const TOOL_RESUME_RECRUIT_PIPELINE_RUN = "resume_recruit_pipeline_run";
|
|
125
|
-
|
|
126
|
-
const SERVER_NAME = "boss-recommend-mcp";
|
|
126
|
+
|
|
127
|
+
const SERVER_NAME = "boss-recommend-mcp";
|
|
128
|
+
const MCP_TOOLSET_ENV = "BOSS_RECOMMEND_MCP_TOOLSET";
|
|
129
|
+
const MCP_TOOLSET_ALL = "all";
|
|
130
|
+
const MCP_TOOLSET_RECOMMEND = "recommend";
|
|
131
|
+
const MCP_TOOLSET_CHAT = "chat";
|
|
132
|
+
const MCP_TOOLSET_RECRUIT = "recruit";
|
|
133
|
+
const VALID_MCP_TOOLSETS = new Set([
|
|
134
|
+
MCP_TOOLSET_ALL,
|
|
135
|
+
MCP_TOOLSET_RECOMMEND,
|
|
136
|
+
MCP_TOOLSET_CHAT,
|
|
137
|
+
MCP_TOOLSET_RECRUIT
|
|
138
|
+
]);
|
|
139
|
+
const RECOMMEND_TOOL_NAMES = new Set([
|
|
140
|
+
TOOL_LIST_RECOMMEND_JOBS,
|
|
141
|
+
TOOL_RUN_RECOMMEND,
|
|
142
|
+
TOOL_START_RUN,
|
|
143
|
+
TOOL_PREPARE_RUN,
|
|
144
|
+
TOOL_SCHEDULE_RUN,
|
|
145
|
+
TOOL_GET_SCHEDULED_RUN,
|
|
146
|
+
TOOL_GET_RUN,
|
|
147
|
+
TOOL_LIST_RUNS,
|
|
148
|
+
TOOL_CANCEL_RUN,
|
|
149
|
+
TOOL_PAUSE_RUN,
|
|
150
|
+
TOOL_RESUME_RUN
|
|
151
|
+
]);
|
|
152
|
+
const BOSS_CHAT_TOOL_NAMES = new Set([
|
|
153
|
+
TOOL_BOSS_CHAT_HEALTH_CHECK,
|
|
154
|
+
TOOL_BOSS_CHAT_LIST_JOBS,
|
|
155
|
+
TOOL_BOSS_CHAT_PREPARE_RUN,
|
|
156
|
+
TOOL_BOSS_CHAT_START_RUN,
|
|
157
|
+
TOOL_BOSS_CHAT_GET_RUN,
|
|
158
|
+
TOOL_BOSS_CHAT_PAUSE_RUN,
|
|
159
|
+
TOOL_BOSS_CHAT_RESUME_RUN,
|
|
160
|
+
TOOL_BOSS_CHAT_CANCEL_RUN
|
|
161
|
+
]);
|
|
162
|
+
const RECRUIT_TOOL_NAMES = new Set([
|
|
163
|
+
TOOL_RUN_RECRUIT_PIPELINE,
|
|
164
|
+
TOOL_START_RECRUIT_PIPELINE_RUN,
|
|
165
|
+
TOOL_GET_RECRUIT_PIPELINE_RUN,
|
|
166
|
+
TOOL_CANCEL_RECRUIT_PIPELINE_RUN,
|
|
167
|
+
TOOL_PAUSE_RECRUIT_PIPELINE_RUN,
|
|
168
|
+
TOOL_RESUME_RECRUIT_PIPELINE_RUN
|
|
169
|
+
]);
|
|
127
170
|
const FRAMING_UNKNOWN = "unknown";
|
|
128
171
|
const FRAMING_HEADER = "header";
|
|
129
172
|
const FRAMING_LINE = "line";
|
|
@@ -453,6 +496,26 @@ function readRawRunState(runId) {
|
|
|
453
496
|
}
|
|
454
497
|
}
|
|
455
498
|
|
|
499
|
+
function compactProgressForList(progress = {}) {
|
|
500
|
+
const compact = {};
|
|
501
|
+
for (const key of [
|
|
502
|
+
"processed",
|
|
503
|
+
"screened",
|
|
504
|
+
"passed",
|
|
505
|
+
"skipped",
|
|
506
|
+
"target_count",
|
|
507
|
+
"card_count",
|
|
508
|
+
"detail_opened",
|
|
509
|
+
"greet_count",
|
|
510
|
+
"post_action_clicked"
|
|
511
|
+
]) {
|
|
512
|
+
if (Number.isFinite(progress?.[key])) {
|
|
513
|
+
compact[key] = progress[key];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return compact;
|
|
517
|
+
}
|
|
518
|
+
|
|
456
519
|
function getRunSortTime(run = {}, fallbackMs = 0) {
|
|
457
520
|
for (const key of ["updated_at", "heartbeat_at", "completed_at", "started_at", "updatedAt", "completedAt", "startedAt"]) {
|
|
458
521
|
const ms = Date.parse(run?.[key] || "");
|
|
@@ -476,7 +539,7 @@ function compactRunForList(run = {}) {
|
|
|
476
539
|
heartbeat_at: run.heartbeat_at || null,
|
|
477
540
|
completed_at: run.completed_at || run.completedAt || null,
|
|
478
541
|
pid: Number.isInteger(run.pid) && run.pid > 0 ? run.pid : null,
|
|
479
|
-
progress: run.progress
|
|
542
|
+
progress: compactProgressForList(run.progress),
|
|
480
543
|
last_message: normalizeText(run.last_message || error?.message || ""),
|
|
481
544
|
control: {
|
|
482
545
|
pause_requested: run.control?.pause_requested === true,
|
|
@@ -493,11 +556,10 @@ function compactRunForList(run = {}) {
|
|
|
493
556
|
report_json: normalizeText(result.report_json || result.result?.report_json || ""),
|
|
494
557
|
checkpoint_path: normalizeText(result.checkpoint_path || result.result?.checkpoint_path || "")
|
|
495
558
|
} : null,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
worker_stderr_path: normalizeText(run.resume?.worker_stderr_path || "")
|
|
559
|
+
artifacts: {
|
|
560
|
+
output_csv: normalizeText(result?.output_csv || result?.result?.output_csv || ""),
|
|
561
|
+
report_json: normalizeText(result?.report_json || result?.result?.report_json || ""),
|
|
562
|
+
checkpoint_path: normalizeText(result?.checkpoint_path || result?.result?.checkpoint_path || "")
|
|
501
563
|
}
|
|
502
564
|
};
|
|
503
565
|
}
|
|
@@ -1293,32 +1355,276 @@ function createScheduleRunInputSchema() {
|
|
|
1293
1355
|
};
|
|
1294
1356
|
}
|
|
1295
1357
|
|
|
1296
|
-
function
|
|
1297
|
-
|
|
1358
|
+
function createCompactRunInputSchema() {
|
|
1359
|
+
const targetCountSchema = {
|
|
1360
|
+
anyOf: [
|
|
1361
|
+
{ type: "integer", minimum: 0 },
|
|
1362
|
+
{ type: "string" }
|
|
1363
|
+
],
|
|
1364
|
+
description: "目标通过人数;扫到底/不限可传 all"
|
|
1365
|
+
};
|
|
1366
|
+
return {
|
|
1367
|
+
type: "object",
|
|
1368
|
+
properties: {
|
|
1369
|
+
instruction: {
|
|
1370
|
+
type: "string",
|
|
1371
|
+
description: "用户原始筛选标准/任务说明;正式启动时逐字复用"
|
|
1372
|
+
},
|
|
1373
|
+
confirmation: {
|
|
1374
|
+
type: "object",
|
|
1375
|
+
properties: {
|
|
1376
|
+
final_confirmed: {
|
|
1377
|
+
type: "boolean",
|
|
1378
|
+
description: "用户完成总确认后传 true"
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
additionalProperties: true
|
|
1382
|
+
},
|
|
1383
|
+
overrides: {
|
|
1384
|
+
type: "object",
|
|
1385
|
+
properties: {
|
|
1386
|
+
page_scope: { type: "string", enum: ["recommend", "latest", "featured"] },
|
|
1387
|
+
school_tag: {
|
|
1388
|
+
anyOf: [
|
|
1389
|
+
{ type: "string" },
|
|
1390
|
+
{ type: "array", items: { type: "string" } }
|
|
1391
|
+
]
|
|
1392
|
+
},
|
|
1393
|
+
degree: {
|
|
1394
|
+
anyOf: [
|
|
1395
|
+
{ type: "string" },
|
|
1396
|
+
{ type: "array", items: { type: "string" } }
|
|
1397
|
+
]
|
|
1398
|
+
},
|
|
1399
|
+
gender: { type: "string" },
|
|
1400
|
+
recent_not_view: { type: "string" },
|
|
1401
|
+
criteria: { type: "string" },
|
|
1402
|
+
target_count: targetCountSchema,
|
|
1403
|
+
post_action: { type: "string", enum: ["greet", "none"] },
|
|
1404
|
+
max_greet_count: targetCountSchema,
|
|
1405
|
+
job: { type: "string" }
|
|
1406
|
+
},
|
|
1407
|
+
additionalProperties: true
|
|
1408
|
+
},
|
|
1409
|
+
human_behavior: {
|
|
1410
|
+
type: "object",
|
|
1411
|
+
properties: {
|
|
1412
|
+
restLevel: { type: "string", enum: ["low", "medium", "high"] },
|
|
1413
|
+
rest_level: { type: "string", enum: ["low", "medium", "high"] }
|
|
1414
|
+
},
|
|
1415
|
+
additionalProperties: true
|
|
1416
|
+
},
|
|
1417
|
+
host: { type: "string" },
|
|
1418
|
+
port: { type: "integer", minimum: 1 },
|
|
1419
|
+
slow_live: { type: "boolean" },
|
|
1420
|
+
delay_ms: { type: "integer", minimum: 0 },
|
|
1421
|
+
detail_limit: { type: "integer", minimum: 0 },
|
|
1422
|
+
execute_post_action: { type: "boolean" },
|
|
1423
|
+
no_filter: { type: "boolean" },
|
|
1424
|
+
dry_run: { type: "boolean" }
|
|
1425
|
+
},
|
|
1426
|
+
required: ["instruction"],
|
|
1427
|
+
additionalProperties: true
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function createCompactScheduleRunInputSchema() {
|
|
1432
|
+
const base = createCompactRunInputSchema();
|
|
1433
|
+
return {
|
|
1434
|
+
...base,
|
|
1435
|
+
properties: {
|
|
1436
|
+
...base.properties,
|
|
1437
|
+
schedule_id: {
|
|
1438
|
+
type: "string",
|
|
1439
|
+
description: "可选,自定义定时任务 id;默认自动生成"
|
|
1440
|
+
},
|
|
1441
|
+
schedule_run_at: {
|
|
1442
|
+
type: "string",
|
|
1443
|
+
description: "ISO 时间字符串"
|
|
1444
|
+
},
|
|
1445
|
+
schedule_delay_minutes: {
|
|
1446
|
+
type: "number",
|
|
1447
|
+
minimum: 0
|
|
1448
|
+
},
|
|
1449
|
+
schedule_delay_seconds: {
|
|
1450
|
+
type: "number",
|
|
1451
|
+
minimum: 0
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
function normalizeMcpToolset(value) {
|
|
1458
|
+
const raw = String(value || "").trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
1459
|
+
if (!raw) return MCP_TOOLSET_ALL;
|
|
1460
|
+
if (raw === "boss-recommend" || raw === "recommend-page" || raw === "recommended") return MCP_TOOLSET_RECOMMEND;
|
|
1461
|
+
if (raw === "boss-chat" || raw === "chat-only" || raw === "chat-page") return MCP_TOOLSET_CHAT;
|
|
1462
|
+
if (raw === "boss-recruit" || raw === "search" || raw === "search-page" || raw === "recruit-page") return MCP_TOOLSET_RECRUIT;
|
|
1463
|
+
return VALID_MCP_TOOLSETS.has(raw) ? raw : MCP_TOOLSET_ALL;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function getConfiguredMcpToolset(argv = process.argv, env = process.env) {
|
|
1467
|
+
const args = Array.isArray(argv) ? argv.slice(2) : [];
|
|
1468
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1469
|
+
const arg = String(args[index] || "");
|
|
1470
|
+
if (arg === "--toolset" || arg === "--tools") {
|
|
1471
|
+
return normalizeMcpToolset(args[index + 1]);
|
|
1472
|
+
}
|
|
1473
|
+
if (arg.startsWith("--toolset=")) {
|
|
1474
|
+
return normalizeMcpToolset(arg.slice("--toolset=".length));
|
|
1475
|
+
}
|
|
1476
|
+
if (arg.startsWith("--tools=")) {
|
|
1477
|
+
return normalizeMcpToolset(arg.slice("--tools=".length));
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return normalizeMcpToolset(env?.[MCP_TOOLSET_ENV]);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function toolNamesForMcpToolset(toolset) {
|
|
1484
|
+
const normalized = normalizeMcpToolset(toolset);
|
|
1485
|
+
if (normalized === MCP_TOOLSET_RECOMMEND) return RECOMMEND_TOOL_NAMES;
|
|
1486
|
+
if (normalized === MCP_TOOLSET_CHAT) return BOSS_CHAT_TOOL_NAMES;
|
|
1487
|
+
if (normalized === MCP_TOOLSET_RECRUIT) return RECRUIT_TOOL_NAMES;
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function filterToolsForMcpToolset(tools, toolset = getConfiguredMcpToolset()) {
|
|
1492
|
+
const names = toolNamesForMcpToolset(toolset);
|
|
1493
|
+
if (!names) return tools;
|
|
1494
|
+
return tools.filter((tool) => names.has(tool.name));
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
function createToolsSchema(toolset = getConfiguredMcpToolset()) {
|
|
1498
|
+
const normalizedToolset = normalizeMcpToolset(toolset);
|
|
1499
|
+
const runInputSchema = normalizedToolset === MCP_TOOLSET_RECOMMEND
|
|
1500
|
+
? createCompactRunInputSchema()
|
|
1501
|
+
: createRunInputSchema();
|
|
1502
|
+
const scheduleRunInputSchema = normalizedToolset === MCP_TOOLSET_RECOMMEND
|
|
1503
|
+
? createCompactScheduleRunInputSchema()
|
|
1504
|
+
: createScheduleRunInputSchema();
|
|
1505
|
+
const tools = [
|
|
1506
|
+
{
|
|
1507
|
+
name: TOOL_BOSS_CHAT_HEALTH_CHECK,
|
|
1508
|
+
description: "Boss 聊天页/chat-only 健康检查。chat-only、未读、全部聊天、求简历等任务必须先走 boss-chat 工具,不要调用 list_recommend_jobs 或 start_recommend_pipeline_run。",
|
|
1509
|
+
inputSchema: {
|
|
1510
|
+
type: "object",
|
|
1511
|
+
properties: {
|
|
1512
|
+
host: {
|
|
1513
|
+
type: "string",
|
|
1514
|
+
description: "可选,Chrome 调试 host;默认 127.0.0.1"
|
|
1515
|
+
},
|
|
1516
|
+
port: {
|
|
1517
|
+
type: "integer",
|
|
1518
|
+
minimum: 1,
|
|
1519
|
+
description: "可选,Chrome 调试端口;默认读取 screening-config.json.debugPort 或 9222"
|
|
1520
|
+
},
|
|
1521
|
+
target_url_includes: {
|
|
1522
|
+
type: "string",
|
|
1523
|
+
description: "可选,Chrome target URL 匹配片段;默认 Boss chat 页"
|
|
1524
|
+
},
|
|
1525
|
+
allow_navigate: {
|
|
1526
|
+
type: "boolean",
|
|
1527
|
+
description: "可选,未在 chat 页时允许通过 Page.navigate 切换;默认 true"
|
|
1528
|
+
},
|
|
1529
|
+
slow_live: {
|
|
1530
|
+
type: "boolean",
|
|
1531
|
+
description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
additionalProperties: false
|
|
1535
|
+
}
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
name: TOOL_BOSS_CHAT_LIST_JOBS,
|
|
1539
|
+
description: "只读读取 Boss 聊天页岗位列表;这是 chat-only 获取 job_options 的首选别名,等价于 prepare_boss_chat_run 的预备步骤但不会启动任务。聊天页/未读/全部聊天/求简历任务必须用本工具或 prepare_boss_chat_run,严禁用 list_recommend_jobs。",
|
|
1540
|
+
inputSchema: createBossChatStartInputSchema()
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
name: TOOL_BOSS_CHAT_PREPARE_RUN,
|
|
1544
|
+
description: "预备一次 boss-chat/chat-only 任务:只导航聊天页并返回岗位列表与待补字段,不会启动任务。用它先获取 job_options;不要用 list_recommend_jobs。",
|
|
1545
|
+
inputSchema: createBossChatStartInputSchema()
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
name: TOOL_BOSS_CHAT_START_RUN,
|
|
1549
|
+
description: "异步启动一次 boss-chat/chat-only 任务。必须一次性提供 job、start_from、target_count、criteria;若用户选择扫到底/不限/全部候选人,必须字面传 target_count=\"all\"。严禁改用 start_recommend_pipeline_run。",
|
|
1550
|
+
inputSchema: createBossChatStartInputSchema({ requireFullInput: true })
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
name: TOOL_BOSS_CHAT_GET_RUN,
|
|
1554
|
+
description: "查询 boss-chat run_id 的当前状态。chat-only 状态查询用本工具,不要用 get_recommend_pipeline_run。",
|
|
1555
|
+
inputSchema: {
|
|
1556
|
+
type: "object",
|
|
1557
|
+
properties: {
|
|
1558
|
+
run_id: { type: "string" },
|
|
1559
|
+
profile: { type: "string" }
|
|
1560
|
+
},
|
|
1561
|
+
required: ["run_id"],
|
|
1562
|
+
additionalProperties: false
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
name: TOOL_BOSS_CHAT_PAUSE_RUN,
|
|
1567
|
+
description: "暂停运行中的 boss-chat 任务。",
|
|
1568
|
+
inputSchema: {
|
|
1569
|
+
type: "object",
|
|
1570
|
+
properties: {
|
|
1571
|
+
run_id: { type: "string" },
|
|
1572
|
+
profile: { type: "string" }
|
|
1573
|
+
},
|
|
1574
|
+
required: ["run_id"],
|
|
1575
|
+
additionalProperties: false
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
name: TOOL_BOSS_CHAT_RESUME_RUN,
|
|
1580
|
+
description: "继续已暂停的 boss-chat 任务。",
|
|
1581
|
+
inputSchema: {
|
|
1582
|
+
type: "object",
|
|
1583
|
+
properties: {
|
|
1584
|
+
run_id: { type: "string" },
|
|
1585
|
+
profile: { type: "string" }
|
|
1586
|
+
},
|
|
1587
|
+
required: ["run_id"],
|
|
1588
|
+
additionalProperties: false
|
|
1589
|
+
}
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
name: TOOL_BOSS_CHAT_CANCEL_RUN,
|
|
1593
|
+
description: "取消运行中的 boss-chat 任务。",
|
|
1594
|
+
inputSchema: {
|
|
1595
|
+
type: "object",
|
|
1596
|
+
properties: {
|
|
1597
|
+
run_id: { type: "string" },
|
|
1598
|
+
profile: { type: "string" }
|
|
1599
|
+
},
|
|
1600
|
+
required: ["run_id"],
|
|
1601
|
+
additionalProperties: false
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1298
1604
|
{
|
|
1299
1605
|
name: TOOL_LIST_RECOMMEND_JOBS,
|
|
1300
|
-
description: "CDP-only 读取 Boss 推荐页岗位下拉框,返回所有可用岗位完整名称,方便 cron/一次性任务提前填写 job 参数。不会启动筛选任务。",
|
|
1606
|
+
description: "CDP-only 读取 Boss 推荐页岗位下拉框,返回所有可用岗位完整名称,方便 recommend/推荐页 cron/一次性任务提前填写 job 参数。不会启动筛选任务。chat-only、未读、全部聊天、求简历任务严禁调用本工具,必须用 list_boss_chat_jobs 或 prepare_boss_chat_run。",
|
|
1301
1607
|
inputSchema: createListRecommendJobsInputSchema()
|
|
1302
1608
|
},
|
|
1303
1609
|
{
|
|
1304
1610
|
name: TOOL_RUN_RECOMMEND,
|
|
1305
1611
|
description: "立即通过原生 MCP 启动 Boss 推荐页筛选。它是 start_recommend_pipeline_run 的短别名;用户已经确认并要现在启动时,优先调用本工具,不需要先调用 prepare_recommend_pipeline_run。必须作为 MCP tool call 调用,禁止通过 terminal/shell/run_command/PowerShell/CLI/manual JSON-RPC 代替,也不要用 schedule_recommend_pipeline_run 冒充立即启动。",
|
|
1306
|
-
inputSchema:
|
|
1612
|
+
inputSchema: runInputSchema
|
|
1307
1613
|
},
|
|
1308
1614
|
{
|
|
1309
1615
|
name: TOOL_START_RUN,
|
|
1310
1616
|
description: "立即通过原生 MCP 异步启动 Boss 推荐页流水线(含同步门禁预检)。用户已经确认并要现在启动时,优先调用本工具或 run_recommend,不需要先调用 prepare_recommend_pipeline_run。必须作为 MCP tool call 调用,禁止通过 terminal/shell/run_command/PowerShell/CLI/manual JSON-RPC 代替,也不要用 schedule_recommend_pipeline_run 冒充立即启动。",
|
|
1311
|
-
inputSchema:
|
|
1617
|
+
inputSchema: runInputSchema
|
|
1312
1618
|
},
|
|
1313
1619
|
{
|
|
1314
1620
|
name: TOOL_PREPARE_RUN,
|
|
1315
1621
|
description: "只校验 Boss 推荐页流水线参数是否完整;不会启动筛选任务。主要用于显式预检或稍后/cron/定时启动前校验。若用户要现在运行,READY/cron_ready=true 后必须继续调用本 MCP server 的 run_recommend 或 start_recommend_pipeline_run;prepare 能返回结果就证明原生 MCP 可用,禁止改用 terminal/shell/run_command/PowerShell/CLI/manual JSON-RPC,也禁止再次调用 prepare 试图启动。",
|
|
1316
|
-
inputSchema:
|
|
1622
|
+
inputSchema: runInputSchema
|
|
1317
1623
|
},
|
|
1318
1624
|
{
|
|
1319
1625
|
name: TOOL_SCHEDULE_RUN,
|
|
1320
1626
|
description: "只用于用户明确要求稍后/cron/定时启动的 package-owned Boss 推荐页定时任务。若用户要现在运行,必须调用 run_recommend 或 start_recommend_pipeline_run,不要用短延迟 schedule 冒充立即启动。schedule 会先校验 READY/cron_ready,再保存完整 payload,并由 detached scheduler 到点直接启动,不依赖 AI harness 自己拼 shell cron。",
|
|
1321
|
-
inputSchema:
|
|
1627
|
+
inputSchema: scheduleRunInputSchema
|
|
1322
1628
|
},
|
|
1323
1629
|
{
|
|
1324
1630
|
name: TOOL_GET_SCHEDULED_RUN,
|
|
@@ -1428,99 +1734,6 @@ function createToolsSchema() {
|
|
|
1428
1734
|
description: "手动运维自愈工具:扫描 Boss recommend 相关 selector / network 规则漂移,并在确认后应用高置信度修复。",
|
|
1429
1735
|
inputSchema: createRunRecommendSelfHealInputSchema()
|
|
1430
1736
|
},
|
|
1431
|
-
{
|
|
1432
|
-
name: TOOL_BOSS_CHAT_HEALTH_CHECK,
|
|
1433
|
-
description: "CDP-only 检查 Boss chat 页面、自愈 probes、共享 screening-config.json 与 chat runtime 目录是否可用。",
|
|
1434
|
-
inputSchema: {
|
|
1435
|
-
type: "object",
|
|
1436
|
-
properties: {
|
|
1437
|
-
host: {
|
|
1438
|
-
type: "string",
|
|
1439
|
-
description: "可选,Chrome 调试 host;默认 127.0.0.1"
|
|
1440
|
-
},
|
|
1441
|
-
port: {
|
|
1442
|
-
type: "integer",
|
|
1443
|
-
minimum: 1,
|
|
1444
|
-
description: "可选,Chrome 调试端口;默认读取 screening-config.json.debugPort 或 9222"
|
|
1445
|
-
},
|
|
1446
|
-
target_url_includes: {
|
|
1447
|
-
type: "string",
|
|
1448
|
-
description: "可选,Chrome target URL 匹配片段;默认 Boss chat 页"
|
|
1449
|
-
},
|
|
1450
|
-
allow_navigate: {
|
|
1451
|
-
type: "boolean",
|
|
1452
|
-
description: "可选,未在 chat 页时允许通过 Page.navigate 切换;默认 true"
|
|
1453
|
-
},
|
|
1454
|
-
slow_live: {
|
|
1455
|
-
type: "boolean",
|
|
1456
|
-
description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
|
|
1457
|
-
}
|
|
1458
|
-
},
|
|
1459
|
-
additionalProperties: false
|
|
1460
|
-
}
|
|
1461
|
-
},
|
|
1462
|
-
{
|
|
1463
|
-
name: TOOL_BOSS_CHAT_PREPARE_RUN,
|
|
1464
|
-
description: "预备一次 boss-chat 任务:只导航聊天页并返回岗位列表与待补字段,不会启动任务。用它先获取 job_options。",
|
|
1465
|
-
inputSchema: createBossChatStartInputSchema()
|
|
1466
|
-
},
|
|
1467
|
-
{
|
|
1468
|
-
name: TOOL_BOSS_CHAT_START_RUN,
|
|
1469
|
-
description: "异步启动一次 boss-chat 任务。必须一次性提供 job、start_from、target_count、criteria;若用户选择扫到底/不限/全部候选人,必须字面传 target_count=\"all\"。",
|
|
1470
|
-
inputSchema: createBossChatStartInputSchema({ requireFullInput: true })
|
|
1471
|
-
},
|
|
1472
|
-
{
|
|
1473
|
-
name: TOOL_BOSS_CHAT_GET_RUN,
|
|
1474
|
-
description: "查询 boss-chat run_id 的当前状态。",
|
|
1475
|
-
inputSchema: {
|
|
1476
|
-
type: "object",
|
|
1477
|
-
properties: {
|
|
1478
|
-
run_id: { type: "string" },
|
|
1479
|
-
profile: { type: "string" }
|
|
1480
|
-
},
|
|
1481
|
-
required: ["run_id"],
|
|
1482
|
-
additionalProperties: false
|
|
1483
|
-
}
|
|
1484
|
-
},
|
|
1485
|
-
{
|
|
1486
|
-
name: TOOL_BOSS_CHAT_PAUSE_RUN,
|
|
1487
|
-
description: "暂停运行中的 boss-chat 任务。",
|
|
1488
|
-
inputSchema: {
|
|
1489
|
-
type: "object",
|
|
1490
|
-
properties: {
|
|
1491
|
-
run_id: { type: "string" },
|
|
1492
|
-
profile: { type: "string" }
|
|
1493
|
-
},
|
|
1494
|
-
required: ["run_id"],
|
|
1495
|
-
additionalProperties: false
|
|
1496
|
-
}
|
|
1497
|
-
},
|
|
1498
|
-
{
|
|
1499
|
-
name: TOOL_BOSS_CHAT_RESUME_RUN,
|
|
1500
|
-
description: "继续已暂停的 boss-chat 任务。",
|
|
1501
|
-
inputSchema: {
|
|
1502
|
-
type: "object",
|
|
1503
|
-
properties: {
|
|
1504
|
-
run_id: { type: "string" },
|
|
1505
|
-
profile: { type: "string" }
|
|
1506
|
-
},
|
|
1507
|
-
required: ["run_id"],
|
|
1508
|
-
additionalProperties: false
|
|
1509
|
-
}
|
|
1510
|
-
},
|
|
1511
|
-
{
|
|
1512
|
-
name: TOOL_BOSS_CHAT_CANCEL_RUN,
|
|
1513
|
-
description: "取消运行中的 boss-chat 任务。",
|
|
1514
|
-
inputSchema: {
|
|
1515
|
-
type: "object",
|
|
1516
|
-
properties: {
|
|
1517
|
-
run_id: { type: "string" },
|
|
1518
|
-
profile: { type: "string" }
|
|
1519
|
-
},
|
|
1520
|
-
required: ["run_id"],
|
|
1521
|
-
additionalProperties: false
|
|
1522
|
-
}
|
|
1523
|
-
},
|
|
1524
1737
|
{
|
|
1525
1738
|
name: TOOL_RUN_RECRUIT_PIPELINE,
|
|
1526
1739
|
description: "兼容 Boss recruit 入口:默认 async;sync 模式会等待终态。所有浏览器动作走共享 CDP-only recruit service。",
|
|
@@ -1552,6 +1765,7 @@ function createToolsSchema() {
|
|
|
1552
1765
|
inputSchema: createRecruitRunIdInputSchema()
|
|
1553
1766
|
}
|
|
1554
1767
|
];
|
|
1768
|
+
return filterToolsForMcpToolset(tools, toolset);
|
|
1555
1769
|
}
|
|
1556
1770
|
|
|
1557
1771
|
function createToolResultResponse(id, payload, isError = false) {
|
|
@@ -2853,10 +3067,19 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2853
3067
|
};
|
|
2854
3068
|
}
|
|
2855
3069
|
|
|
2856
|
-
if (method === "tools/call") {
|
|
2857
|
-
const toolName = params?.name;
|
|
2858
|
-
const args = params?.arguments || {};
|
|
2859
|
-
|
|
3070
|
+
if (method === "tools/call") {
|
|
3071
|
+
const toolName = params?.name;
|
|
3072
|
+
const args = params?.arguments || {};
|
|
3073
|
+
const toolset = getConfiguredMcpToolset();
|
|
3074
|
+
const visibleToolNames = new Set(createToolsSchema(toolset).map((tool) => tool.name));
|
|
3075
|
+
if (!visibleToolNames.has(toolName)) {
|
|
3076
|
+
return createJsonRpcError(
|
|
3077
|
+
id,
|
|
3078
|
+
-32602,
|
|
3079
|
+
`Tool ${toolName || ""} is not available in the ${toolset} boss-recommend-mcp toolset. Use the MCP server/toolset that exposes this domain instead of terminal or CLI fallback.`
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
|
|
2860
3083
|
if ([TOOL_RUN_RECOMMEND, TOOL_START_RUN].includes(toolName)) {
|
|
2861
3084
|
const inputError = validateRunArgs(args);
|
|
2862
3085
|
if (inputError) {
|
|
@@ -2885,7 +3108,7 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2885
3108
|
}
|
|
2886
3109
|
}
|
|
2887
3110
|
|
|
2888
|
-
if ([TOOL_BOSS_CHAT_PREPARE_RUN, TOOL_BOSS_CHAT_START_RUN].includes(toolName)) {
|
|
3111
|
+
if ([TOOL_BOSS_CHAT_LIST_JOBS, TOOL_BOSS_CHAT_PREPARE_RUN, TOOL_BOSS_CHAT_START_RUN].includes(toolName)) {
|
|
2889
3112
|
const inputError = validateBossChatStartArgs(args);
|
|
2890
3113
|
if (inputError) {
|
|
2891
3114
|
return createJsonRpcError(id, -32602, inputError);
|
|
@@ -2941,6 +3164,8 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2941
3164
|
payload = await handleRunRecommendSelfHealTool({ workspaceRoot, args });
|
|
2942
3165
|
} else if (toolName === TOOL_BOSS_CHAT_HEALTH_CHECK) {
|
|
2943
3166
|
payload = await handleBossChatHealthCheckTool(workspaceRoot, args);
|
|
3167
|
+
} else if (toolName === TOOL_BOSS_CHAT_LIST_JOBS) {
|
|
3168
|
+
payload = await handleBossChatPrepareRunTool({ workspaceRoot, args });
|
|
2944
3169
|
} else if (toolName === TOOL_BOSS_CHAT_PREPARE_RUN) {
|
|
2945
3170
|
payload = await handleBossChatPrepareRunTool({ workspaceRoot, args });
|
|
2946
3171
|
} else if (toolName === TOOL_BOSS_CHAT_START_RUN) {
|
|
@@ -3097,6 +3322,9 @@ export function startServer() {
|
|
|
3097
3322
|
}
|
|
3098
3323
|
|
|
3099
3324
|
export const __testables = {
|
|
3325
|
+
createToolsSchema,
|
|
3326
|
+
getConfiguredMcpToolset,
|
|
3327
|
+
normalizeMcpToolset,
|
|
3100
3328
|
handleRequest,
|
|
3101
3329
|
runDetachedWorkerForTests(options = {}) {
|
|
3102
3330
|
return runDetachedWorker(options);
|
package/src/recommend-mcp.js
CHANGED
|
@@ -75,13 +75,119 @@ let recommendRunService = createRecommendRunService({
|
|
|
75
75
|
});
|
|
76
76
|
const recommendRunMeta = new Map();
|
|
77
77
|
|
|
78
|
-
function normalizeText(value) {
|
|
79
|
-
return String(value || "").replace(/\s+/g, " ").trim();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
function normalizeText(value) {
|
|
79
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function collectRecommendRouteText(args = {}) {
|
|
83
|
+
return normalizeText([
|
|
84
|
+
args.instruction,
|
|
85
|
+
args.criteria,
|
|
86
|
+
args.target_url_includes,
|
|
87
|
+
args.page_scope,
|
|
88
|
+
args.confirmation?.page_value,
|
|
89
|
+
args.confirmation?.job_value,
|
|
90
|
+
args.overrides?.criteria,
|
|
91
|
+
args.overrides?.page_scope,
|
|
92
|
+
args.overrides?.job,
|
|
93
|
+
args.overrides?.target_url_includes
|
|
94
|
+
].filter((value) => value !== undefined && value !== null).join(" "));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function findRouteSignals(text, patterns = []) {
|
|
98
|
+
const signals = [];
|
|
99
|
+
for (const { label, pattern } of patterns) {
|
|
100
|
+
if (pattern.test(text)) signals.push(label);
|
|
101
|
+
}
|
|
102
|
+
return signals;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function detectNonRecommendRoute(args = {}) {
|
|
106
|
+
const text = collectRecommendRouteText(args);
|
|
107
|
+
if (!text) return null;
|
|
108
|
+
const chatSignals = findRouteSignals(text, [
|
|
109
|
+
{ label: "chat-only", pattern: /\bchat[-\s]?only\b/i },
|
|
110
|
+
{ label: "boss-chat", pattern: /\bboss[-\s]?chat\b/i },
|
|
111
|
+
{ label: "chat page", pattern: /\bchat\s+page\b/i },
|
|
112
|
+
{ label: "chat/index", pattern: /(?:\/web\/chat\/index|chat\/index)/i },
|
|
113
|
+
{ label: "聊天页", pattern: /聊天页/ },
|
|
114
|
+
{ label: "聊天列表", pattern: /聊天列表/ },
|
|
115
|
+
{ label: "未读", pattern: /未读/ },
|
|
116
|
+
{ label: "全部聊天", pattern: /全部聊天|所有聊天/ },
|
|
117
|
+
{ label: "求简历", pattern: /求简历|索要简历|要简历|在线简历/ }
|
|
118
|
+
]);
|
|
119
|
+
if (chatSignals.length) {
|
|
120
|
+
return {
|
|
121
|
+
domain: "chat",
|
|
122
|
+
signals: chatSignals,
|
|
123
|
+
text
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const searchSignals = findRouteSignals(text, [
|
|
127
|
+
{ label: "search-only", pattern: /\bsearch[-\s]?only\b/i },
|
|
128
|
+
{ label: "search page", pattern: /\bsearch\s+page\b/i },
|
|
129
|
+
{ label: "recruit pipeline", pattern: /\brecruit\s+pipeline\b/i },
|
|
130
|
+
{ label: "chat/search", pattern: /(?:\/web\/chat\/search|chat\/search)/i },
|
|
131
|
+
{ label: "搜索页", pattern: /搜索页/ },
|
|
132
|
+
{ label: "招聘搜索", pattern: /招聘搜索/ }
|
|
133
|
+
]);
|
|
134
|
+
if (searchSignals.length) {
|
|
135
|
+
return {
|
|
136
|
+
domain: "search",
|
|
137
|
+
signals: searchSignals,
|
|
138
|
+
text
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildWrongRecommendRouteResponse(route) {
|
|
145
|
+
if (route?.domain === "chat") {
|
|
146
|
+
return {
|
|
147
|
+
status: "FAILED",
|
|
148
|
+
route_guard: true,
|
|
149
|
+
error: {
|
|
150
|
+
code: "WRONG_BOSS_TOOL_FOR_CHAT",
|
|
151
|
+
message: "This request is explicitly chat-only/chat-page work. Do not use recommend tools. Use boss_chat_health_check, then list_boss_chat_jobs or prepare_boss_chat_run, then start_boss_chat_run.",
|
|
152
|
+
retryable: false
|
|
153
|
+
},
|
|
154
|
+
detected_domain: "chat",
|
|
155
|
+
detected_signals: route.signals || [],
|
|
156
|
+
recommended_tool_sequence: [
|
|
157
|
+
"boss_chat_health_check",
|
|
158
|
+
"list_boss_chat_jobs",
|
|
159
|
+
"prepare_boss_chat_run",
|
|
160
|
+
"start_boss_chat_run"
|
|
161
|
+
]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (route?.domain === "search") {
|
|
165
|
+
return {
|
|
166
|
+
status: "FAILED",
|
|
167
|
+
route_guard: true,
|
|
168
|
+
error: {
|
|
169
|
+
code: "WRONG_BOSS_TOOL_FOR_SEARCH",
|
|
170
|
+
message: "This request is explicitly search/recruit-page work. Do not use recommend tools. Use run_recruit_pipeline or start_recruit_pipeline_run.",
|
|
171
|
+
retryable: false
|
|
172
|
+
},
|
|
173
|
+
detected_domain: "search",
|
|
174
|
+
detected_signals: route.signals || [],
|
|
175
|
+
recommended_tool_sequence: [
|
|
176
|
+
"run_recruit_pipeline",
|
|
177
|
+
"start_recruit_pipeline_run"
|
|
178
|
+
]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function guardRecommendRoute(args = {}) {
|
|
185
|
+
return buildWrongRecommendRouteResponse(detectNonRecommendRoute(args));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parsePositiveInteger(raw, fallback) {
|
|
189
|
+
const parsed = Number.parseInt(String(raw || ""), 10);
|
|
190
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
85
191
|
}
|
|
86
192
|
|
|
87
193
|
function parseNonNegativeInteger(raw, fallback) {
|
|
@@ -855,9 +961,17 @@ async function readRecommendJobOptionsFromSession(session) {
|
|
|
855
961
|
};
|
|
856
962
|
}
|
|
857
963
|
|
|
858
|
-
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
859
|
-
const
|
|
860
|
-
|
|
964
|
+
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
965
|
+
const routeGuard = guardRecommendRoute(args);
|
|
966
|
+
if (routeGuard) {
|
|
967
|
+
return {
|
|
968
|
+
...routeGuard,
|
|
969
|
+
stage: "recommend_job_list",
|
|
970
|
+
message: "list_recommend_jobs is recommend-page only. For chat-only job lists, call list_boss_chat_jobs or prepare_boss_chat_run."
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
974
|
+
const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
|
|
861
975
|
const port = parsePositiveInteger(
|
|
862
976
|
args.port,
|
|
863
977
|
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
@@ -1477,8 +1591,10 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1477
1591
|
};
|
|
1478
1592
|
}
|
|
1479
1593
|
|
|
1480
|
-
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
1481
|
-
const
|
|
1594
|
+
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
1595
|
+
const routeGuard = guardRecommendRoute(args);
|
|
1596
|
+
if (routeGuard) return { response: routeGuard };
|
|
1597
|
+
const parsed = parseRecommendPipelineRequest(args);
|
|
1482
1598
|
const gate = evaluateRecommendPipelineGate(parsed, args);
|
|
1483
1599
|
if (gate) return { response: gate };
|
|
1484
1600
|
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|