@reconcrap/boss-recommend-mcp 2.0.3 → 2.0.4

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
@@ -221,10 +221,10 @@ config/screening-config.example.json
221
221
 
222
222
  - `openaiOrganization`
223
223
  - `openaiProject`
224
- - `debugPort`
225
- - `outputDir`
224
+ - `debugPort`:未显式传 `port` 时,recommend / search / chat CDP-only MCP run 和健康检查默认连接这个 Chrome 调试端口。
225
+ - `outputDir`:recommend / search / chat 完成后的最终 CSV 与 report JSON 会写入这里;run state / checkpoint 仍保留在各自状态目录,方便 pause/resume/cancel。
226
226
  - `llmThinkingLevel`:默认 `low`。可设为 `off/minimal/low/medium/high/auto/current`,用于控制 OpenAI-compatible LLM 的 thinking/reasoning 强度。
227
- - `humanRestEnabled`:默认 `false`。`false` recommend-screen 随机休息/批次休息与 boss-chat 批次休息均为 `0ms`;`true` 时恢复随机休息节奏。
227
+ - `humanRestEnabled`:默认 `false`。当前 CDP-only recommend / search / chat run 尚未实现随机休息层,因此会读取并保留该字段但不改变节奏;如后续重新加入 human rest,应以此字段为默认值。
228
228
 
229
229
  ## 常用命令
230
230
 
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "baseUrl": "https://api.openai.com/v1",
3
3
  "apiKey": "replace-with-openai-api-key",
4
- "model": "gpt-4.1-mini",
5
- "llmThinkingLevel": "low",
6
- "llmTimeoutMs": 60000,
7
- "llmMaxRetries": 3,
8
- "humanRestEnabled": false,
9
- "openaiOrganization": "optional-org-id",
10
- "openaiProject": "optional-project-id"
11
- }
4
+ "model": "gpt-4.1-mini",
5
+ "llmThinkingLevel": "low",
6
+ "llmTimeoutMs": 60000,
7
+ "llmMaxRetries": 3,
8
+ "debugPort": 9222,
9
+ "outputDir": "",
10
+ "humanRestEnabled": false,
11
+ "openaiOrganization": "optional-org-id",
12
+ "openaiProject": "optional-project-id"
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
package/src/chat-mcp.js CHANGED
@@ -45,6 +45,7 @@ import {
45
45
  getBossChatDataDir,
46
46
  getBossChatTargetCountValue,
47
47
  normalizeTargetCountInput,
48
+ resolveBossConfiguredOutputDir,
48
49
  resolveBossChatRuntimeLayout,
49
50
  resolveBossScreeningConfig
50
51
  } from "./chat-runtime-config.js";
@@ -123,12 +124,14 @@ function getChatRunArtifacts(runId) {
123
124
  const normalized = normalizeRunId(runId);
124
125
  if (!normalized) return null;
125
126
  const runsDir = getChatRunsDir();
127
+ const outputDir = resolveBossConfiguredOutputDir("", runsDir);
126
128
  return {
127
129
  runs_dir: runsDir,
130
+ output_dir: outputDir,
128
131
  run_state_path: path.join(runsDir, `${normalized}.json`),
129
132
  checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
130
- output_csv: path.join(runsDir, `${normalized}.results.csv`),
131
- report_json: path.join(runsDir, `${normalized}.report.json`)
133
+ output_csv: path.join(outputDir, `${normalized}.results.csv`),
134
+ report_json: path.join(outputDir, `${normalized}.report.json`)
132
135
  };
133
136
  }
134
137
 
@@ -633,7 +636,7 @@ async function readChatJobOptionsFromSession(session) {
633
636
  return readChatJobOptions(session.client, roots.rootNodes.top);
634
637
  }
635
638
 
636
- function normalizeChatStartInput(args = {}) {
639
+ function normalizeChatStartInput(args = {}, configResolution = null) {
637
640
  const target = normalizeTargetCountInput(getBossChatTargetCountValue(args));
638
641
  return {
639
642
  profile: normalizeText(args.profile) || "default",
@@ -645,7 +648,10 @@ function normalizeChatStartInput(args = {}) {
645
648
  targetCount: target.targetCount,
646
649
  publicTargetCount: target.publicValue,
647
650
  host: normalizeText(args.host) || DEFAULT_CHAT_HOST,
648
- port: parsePositiveInteger(args.port, DEFAULT_CHAT_PORT),
651
+ port: parsePositiveInteger(
652
+ args.port,
653
+ configResolution?.ok ? configResolution.config.debugPort : DEFAULT_CHAT_PORT
654
+ ),
649
655
  targetUrlIncludes: normalizeText(args.target_url_includes) || CHAT_TARGET_URL,
650
656
  allowNavigate: args.allow_navigate !== false,
651
657
  slowLive: args.slow_live === true
@@ -800,7 +806,6 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
800
806
  const shouldRequestResume = shouldRequestChatResume(args);
801
807
  const useLlm = shouldUseChatLlm(args, shouldRequestResume);
802
808
  const configResolution = useLlm ? resolveBossScreeningConfig(workspaceRoot) : { ok: false };
803
- const configFile = configResolution.ok ? readJsonFile(configResolution.config_path) : null;
804
809
  return {
805
810
  client: session.client,
806
811
  targetUrl: CHAT_TARGET_URL,
@@ -827,8 +832,7 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
827
832
  maxImagePages: parsePositiveInteger(args.max_image_pages, 8),
828
833
  imageWheelDeltaY: parsePositiveInteger(args.image_wheel_delta_y, 650),
829
834
  llmConfig: configResolution.ok ? {
830
- ...configResolution.config,
831
- apiKey: configFile?.apiKey || ""
835
+ ...configResolution.config
832
836
  } : null,
833
837
  llmTimeoutMs: parsePositiveInteger(args.llm_timeout_ms, slowLive ? 180000 : 120000),
834
838
  llmImageLimit: parsePositiveInteger(args.llm_image_limit, 8),
@@ -888,7 +892,8 @@ function trackChatRun(runId) {
888
892
  }
889
893
 
890
894
  async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {}) {
891
- const normalized = normalizeChatStartInput(args);
895
+ const defaultConfigResolution = resolveBossScreeningConfig(workspaceRoot);
896
+ const normalized = normalizeChatStartInput(args, defaultConfigResolution);
892
897
  const missingFields = getMissingChatStartFields(args, normalized);
893
898
  if (missingFields.length) {
894
899
  return buildNeedInputResponse({
@@ -987,7 +992,8 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {})
987
992
  }
988
993
 
989
994
  export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } = {}) {
990
- const normalized = normalizeChatStartInput(args);
995
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
996
+ const normalized = normalizeChatStartInput(args, configResolution);
991
997
  let session;
992
998
  try {
993
999
  session = await chatConnectorImpl({
@@ -1129,6 +1135,7 @@ export async function bossChatHealthCheckTool({ workspaceRoot = "", args = {} }
1129
1135
  cli_path: null,
1130
1136
  config_path: configResolution.config_path || null,
1131
1137
  config_dir: configResolution.config_dir || null,
1138
+ output_dir: configResolution.ok ? configResolution.config.outputDir || null : null,
1132
1139
  debug_port: port,
1133
1140
  shared_llm_config: configResolution.ok === true,
1134
1141
  data_dir: runtimeLayout.data_dir,
@@ -223,6 +223,22 @@ function parsePositiveInteger(raw, fallback = null) {
223
223
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
224
224
  }
225
225
 
226
+ function parseConfigBoolean(raw, fallback = false) {
227
+ if (typeof raw === "boolean") return raw;
228
+ const normalized = normalizeText(raw).toLowerCase();
229
+ if (["true", "1", "yes", "y", "on", "enabled"].includes(normalized)) return true;
230
+ if (["false", "0", "no", "n", "off", "disabled"].includes(normalized)) return false;
231
+ return fallback;
232
+ }
233
+
234
+ function resolveConfigPathValue(raw, configDir) {
235
+ const normalized = normalizeText(raw);
236
+ if (!normalized) return "";
237
+ return path.isAbsolute(normalized)
238
+ ? path.resolve(normalized)
239
+ : path.resolve(configDir || process.cwd(), normalized);
240
+ }
241
+
226
242
  function validateScreeningConfig(config) {
227
243
  if (!config || typeof config !== "object" || Array.isArray(config)) {
228
244
  return {
@@ -369,8 +385,12 @@ export function resolveBossScreeningConfig(workspaceRoot) {
369
385
  ok: true,
370
386
  config: {
371
387
  baseUrl: normalizeText(parsed.baseUrl).replace(/\/+$/, ""),
388
+ apiKey: normalizeText(parsed.apiKey),
372
389
  model: normalizeText(parsed.model),
373
- debugPort: parsePositiveInteger(parsed.debugPort, 9222)
390
+ debugPort: parsePositiveInteger(parsed.debugPort, 9222),
391
+ llmThinkingLevel: normalizeText(parsed.llmThinkingLevel || parsed.thinkingLevel || parsed.reasoningEffort),
392
+ outputDir: resolveConfigPathValue(parsed.outputDir, configDir),
393
+ humanRestEnabled: parseConfigBoolean(parsed.humanRestEnabled, false)
374
394
  },
375
395
  config_path: configPath,
376
396
  config_dir: configDir,
@@ -378,6 +398,13 @@ export function resolveBossScreeningConfig(workspaceRoot) {
378
398
  };
379
399
  }
380
400
 
401
+ export function resolveBossConfiguredOutputDir(workspaceRoot, fallbackDir = "") {
402
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
403
+ const configuredDir = configResolution.ok ? normalizeText(configResolution.config.outputDir) : "";
404
+ if (configuredDir) return configuredDir;
405
+ return fallbackDir ? path.resolve(fallbackDir) : "";
406
+ }
407
+
381
408
  function isUnlimitedTargetCountToken(value) {
382
409
  const token = normalizeText(value).toLowerCase();
383
410
  if (!token) return false;
@@ -37,7 +37,7 @@ const GENDER_CODE_MAP = {
37
37
  2: "女"
38
38
  };
39
39
 
40
- const LLM_THINKING_LEVELS = new Set(["off", "low", "medium", "high", "current"]);
40
+ const LLM_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "auto", "current"]);
41
41
 
42
42
  function nowIso() {
43
43
  return new Date().toISOString();
@@ -64,9 +64,9 @@ function isVolcengineModel(baseUrl, model) {
64
64
 
65
65
  function applyChatCompletionThinking(payload, { baseUrl = "", model = "", thinkingLevel = "" } = {}) {
66
66
  const level = normalizeLlmThinkingLevel(thinkingLevel);
67
- if (!level || level === "current") return payload;
67
+ if (!level || level === "current" || level === "auto") return payload;
68
68
  if (isVolcengineModel(baseUrl, model)) {
69
- if (level === "off") {
69
+ if (level === "off" || level === "minimal") {
70
70
  payload.thinking = { type: "disabled" };
71
71
  } else {
72
72
  payload.thinking = { type: "enabled" };
package/src/index.js CHANGED
@@ -7,7 +7,8 @@ import { fileURLToPath } from "node:url";
7
7
  import {
8
8
  getFeaturedCalibrationResolution,
9
9
  getBossChatTargetCountValue,
10
- normalizeTargetCountInput
10
+ normalizeTargetCountInput,
11
+ resolveBossScreeningConfig
11
12
  } from "./chat-runtime-config.js";
12
13
  import {
13
14
  __resetChatMcpStateForTests,
@@ -1890,7 +1891,8 @@ async function handleRunRecommendSelfHealTool({ workspaceRoot, args }) {
1890
1891
  }
1891
1892
 
1892
1893
  const host = "127.0.0.1";
1893
- const port = parsePositiveInteger(args.port, 9222);
1894
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
1895
+ const port = parsePositiveInteger(args.port, configResolution.ok ? configResolution.config.debugPort : 9222);
1894
1896
  let session = null;
1895
1897
  try {
1896
1898
  session = await connectToChromeTarget({
@@ -44,6 +44,10 @@ import {
44
44
  parseRecommendInstruction
45
45
  } from "./parser.js";
46
46
  import { getRunsDir } from "./run-state.js";
47
+ import {
48
+ resolveBossConfiguredOutputDir,
49
+ resolveBossScreeningConfig
50
+ } from "./chat-runtime-config.js";
47
51
 
48
52
  const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
49
53
  const DEFAULT_RECOMMEND_PORT = 9222;
@@ -115,12 +119,14 @@ function getRecommendRunArtifacts(runId) {
115
119
  const normalized = normalizeRunId(runId);
116
120
  if (!normalized) return null;
117
121
  const runsDir = getRunsDir();
122
+ const outputDir = resolveBossConfiguredOutputDir("", runsDir);
118
123
  return {
119
124
  runs_dir: runsDir,
125
+ output_dir: outputDir,
120
126
  run_state_path: path.join(runsDir, `${normalized}.json`),
121
127
  checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
122
- output_csv: path.join(runsDir, `${normalized}.results.csv`),
123
- report_json: path.join(runsDir, `${normalized}.report.json`)
128
+ output_csv: path.join(outputDir, `${normalized}.results.csv`),
129
+ report_json: path.join(outputDir, `${normalized}.report.json`)
124
130
  };
125
131
  }
126
132
 
@@ -484,8 +490,12 @@ async function readRecommendJobOptionsFromSession(session) {
484
490
  }
485
491
 
486
492
  export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
493
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
487
494
  const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
488
- const port = parsePositiveInteger(args.port, DEFAULT_RECOMMEND_PORT);
495
+ const port = parsePositiveInteger(
496
+ args.port,
497
+ configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
498
+ );
489
499
  const targetUrlIncludes = normalizeText(args.target_url_includes) || RECOMMEND_TARGET_URL;
490
500
  const allowNavigate = args.allow_navigate !== false;
491
501
  const slowLive = args.slow_live === true;
@@ -922,7 +932,7 @@ function buildRecommendFilter(parsed, args = {}) {
922
932
  return groups.length ? { filterGroups: groups } : { enabled: false };
923
933
  }
924
934
 
925
- function normalizeRecommendStartInput(args = {}, parsed) {
935
+ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
926
936
  const confirmation = args.confirmation || {};
927
937
  const overrides = args.overrides || {};
928
938
  const slowLive = args.slow_live === true;
@@ -932,7 +942,10 @@ function normalizeRecommendStartInput(args = {}, parsed) {
932
942
  );
933
943
  return {
934
944
  host: normalizeText(args.host) || DEFAULT_RECOMMEND_HOST,
935
- port: parsePositiveInteger(args.port, DEFAULT_RECOMMEND_PORT),
945
+ port: parsePositiveInteger(
946
+ args.port,
947
+ configResolution?.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
948
+ ),
936
949
  targetUrlIncludes: normalizeText(args.target_url_includes) || RECOMMEND_TARGET_URL,
937
950
  allowNavigate: args.allow_navigate !== false,
938
951
  slowLive,
@@ -1038,7 +1051,8 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
1038
1051
  const parsed = parseRecommendPipelineRequest(args);
1039
1052
  const gate = evaluateRecommendPipelineGate(parsed, args);
1040
1053
  if (gate) return gate;
1041
- const normalized = normalizeRecommendStartInput(args, parsed);
1054
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
1055
+ const normalized = normalizeRecommendStartInput(args, parsed, configResolution);
1042
1056
 
1043
1057
  let session;
1044
1058
  try {
@@ -32,6 +32,10 @@ import {
32
32
  runRecruitWorkflow,
33
33
  waitForRecruitSearchControls
34
34
  } from "./domains/recruit/index.js";
35
+ import {
36
+ resolveBossConfiguredOutputDir,
37
+ resolveBossScreeningConfig
38
+ } from "./chat-runtime-config.js";
35
39
 
36
40
  const RUN_MODE_ASYNC = "async";
37
41
  const RUN_MODE_SYNC = "sync";
@@ -108,12 +112,14 @@ function getRecruitRunArtifacts(runId) {
108
112
  const normalized = normalizeRunId(runId);
109
113
  if (!normalized) return null;
110
114
  const runsDir = getRecruitRunsDir();
115
+ const outputDir = resolveBossConfiguredOutputDir("", runsDir);
111
116
  return {
112
117
  runs_dir: runsDir,
118
+ output_dir: outputDir,
113
119
  run_state_path: path.join(runsDir, `${normalized}.json`),
114
120
  checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
115
- output_csv: path.join(runsDir, `${normalized}.results.csv`),
116
- report_json: path.join(runsDir, `${normalized}.report.json`)
121
+ output_csv: path.join(outputDir, `${normalized}.results.csv`),
122
+ report_json: path.join(outputDir, `${normalized}.report.json`)
117
123
  };
118
124
  }
119
125
 
@@ -807,12 +813,18 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
807
813
  const parsed = parseRecruitPipelineRequest(args);
808
814
  const gate = evaluateRecruitPipelineGate(parsed);
809
815
  if (gate) return gate;
816
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
817
+ const host = normalizeText(args.host) || DEFAULT_RECRUIT_HOST;
818
+ const port = parsePositiveInteger(
819
+ args.port,
820
+ configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECRUIT_PORT
821
+ );
810
822
 
811
823
  let session;
812
824
  try {
813
825
  session = await recruitConnectorImpl({
814
- host: normalizeText(args.host) || DEFAULT_RECRUIT_HOST,
815
- port: parsePositiveInteger(args.port, DEFAULT_RECRUIT_PORT),
826
+ host,
827
+ port,
816
828
  targetUrlIncludes: normalizeText(args.target_url_includes) || RECRUIT_TARGET_URL,
817
829
  allowNavigate: args.allow_navigate !== false,
818
830
  slowLive: args.slow_live === true
@@ -858,8 +870,8 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
858
870
  workspaceRoot: normalizeText(workspaceRoot) || globalThis.process?.cwd?.() || "",
859
871
  args: clonePlain(args, {}),
860
872
  chrome: {
861
- host: normalizeText(args.host) || DEFAULT_RECRUIT_HOST,
862
- port: parsePositiveInteger(args.port, DEFAULT_RECRUIT_PORT),
873
+ host,
874
+ port,
863
875
  target_url: session.target?.url || RECRUIT_TARGET_URL,
864
876
  target_id: session.target?.id || null,
865
877
  auto_launch: session.chrome || null