@reconcrap/boss-recommend-mcp 1.3.19 → 1.3.21

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
@@ -167,7 +167,8 @@ config/screening-config.example.json
167
167
  - `openaiProject`
168
168
  - `debugPort`
169
169
  - `outputDir`
170
- - `llmThinkingLevel`:默认 `off`。可设为 `off/minimal/low/medium/high/auto/current`,用于控制 OpenAI-compatible LLM 的 thinking/reasoning 强度。
170
+ - `llmThinkingLevel`:默认 `low`。可设为 `off/minimal/low/medium/high/auto/current`,用于控制 OpenAI-compatible LLM 的 thinking/reasoning 强度。
171
+ - `humanRestEnabled`:默认 `false`。`false` 时 recommend-screen 随机休息/批次休息与 boss-chat 批次休息均为 `0ms`;`true` 时恢复随机休息节奏。
171
172
 
172
173
  ## 常用命令
173
174
 
@@ -2,7 +2,8 @@
2
2
  "baseUrl": "https://api.openai.com/v1",
3
3
  "apiKey": "replace-with-openai-api-key",
4
4
  "model": "gpt-4.1-mini",
5
- "llmThinkingLevel": "off",
5
+ "llmThinkingLevel": "low",
6
+ "humanRestEnabled": false,
6
7
  "openaiOrganization": "optional-org-id",
7
8
  "openaiProject": "optional-project-id"
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.19",
3
+ "version": "1.3.21",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
package/src/adapters.js CHANGED
@@ -121,6 +121,30 @@ function normalizeText(value) {
121
121
  return String(value || "").replace(/\s+/g, " ").trim();
122
122
  }
123
123
 
124
+ function parseBooleanValue(value) {
125
+ if (typeof value === "boolean") return value;
126
+ const normalized = normalizeText(value).toLowerCase();
127
+ if (!normalized) return null;
128
+ if (["1", "true", "yes", "y", "on", "是"].includes(normalized)) return true;
129
+ if (["0", "false", "no", "n", "off", "否"].includes(normalized)) return false;
130
+ return null;
131
+ }
132
+
133
+ function resolveHumanRestEnabled(config = {}) {
134
+ if (!config || typeof config !== "object" || Array.isArray(config)) return false;
135
+ const candidates = [
136
+ config.humanRestEnabled,
137
+ config.human_rest_enabled,
138
+ config.humanLikeRestEnabled,
139
+ config.human_like_rest_enabled
140
+ ];
141
+ for (const candidate of candidates) {
142
+ const parsed = parseBooleanValue(candidate);
143
+ if (typeof parsed === "boolean") return parsed;
144
+ }
145
+ return false;
146
+ }
147
+
124
148
  function serializeInputSummary(value) {
125
149
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
126
150
  try {
@@ -2937,6 +2961,7 @@ export async function runRecommendScreenCli({
2937
2961
  if (llmThinkingLevel) {
2938
2962
  args.push("--thinking-level", llmThinkingLevel);
2939
2963
  }
2964
+ args.push("--human-rest", String(resolveHumanRestEnabled(loaded.config)));
2940
2965
  if (Number.isInteger(screenParams.target_count) && screenParams.target_count > 0) {
2941
2966
  args.push("--targetCount", String(screenParams.target_count));
2942
2967
  }
package/src/boss-chat.js CHANGED
@@ -40,6 +40,30 @@ function parsePositiveInteger(value, fallback = null) {
40
40
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
41
41
  }
42
42
 
43
+ function parseBooleanValue(value) {
44
+ if (typeof value === "boolean") return value;
45
+ const normalized = normalizeText(value).toLowerCase();
46
+ if (!normalized) return null;
47
+ if (["1", "true", "yes", "y", "on", "是"].includes(normalized)) return true;
48
+ if (["0", "false", "no", "n", "off", "否"].includes(normalized)) return false;
49
+ return null;
50
+ }
51
+
52
+ function resolveHumanRestEnabled(config = {}) {
53
+ if (!config || typeof config !== "object" || Array.isArray(config)) return false;
54
+ const candidates = [
55
+ config.humanRestEnabled,
56
+ config.human_rest_enabled,
57
+ config.humanLikeRestEnabled,
58
+ config.human_like_rest_enabled
59
+ ];
60
+ for (const candidate of candidates) {
61
+ const parsed = parseBooleanValue(candidate);
62
+ if (typeof parsed === "boolean") return parsed;
63
+ }
64
+ return false;
65
+ }
66
+
43
67
  function isUnlimitedTargetCountToken(value) {
44
68
  const token = normalizeText(value).toLowerCase();
45
69
  if (!token) return false;
@@ -290,7 +314,8 @@ function resolveBossChatScreenConfig(workspaceRoot) {
290
314
  apiKey: normalizeText(parsed.apiKey),
291
315
  model: normalizeText(parsed.model),
292
316
  llmThinkingLevel: resolveLlmThinkingLevel(parsed),
293
- debugPort: parsePositiveInteger(parsed.debugPort, 9222)
317
+ debugPort: parsePositiveInteger(parsed.debugPort, 9222),
318
+ humanRestEnabled: resolveHumanRestEnabled(parsed)
294
319
  },
295
320
  config_path: configPath,
296
321
  config_dir: path.dirname(configPath)
@@ -430,6 +455,8 @@ function buildBossChatCliArgs(command, input, resolvedConfig) {
430
455
  }
431
456
  if (typeof normalized.batchRestEnabled === "boolean") {
432
457
  args.push("--batch-rest", String(normalized.batchRestEnabled));
458
+ } else if (typeof resolvedConfig?.humanRestEnabled === "boolean") {
459
+ args.push("--batch-rest", String(resolvedConfig.humanRestEnabled));
433
460
  }
434
461
  return args;
435
462
  }
package/src/cli.js CHANGED
@@ -45,6 +45,10 @@ const recommendMcpBinaryName = "boss-recommend-mcp";
45
45
  const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h"]);
46
46
  const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
47
47
  const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
48
+ const installConfigDefaults = Object.freeze({
49
+ llmThinkingLevel: "low",
50
+ humanRestEnabled: false
51
+ });
48
52
 
49
53
  function getSkillSourceDir(name = skillName) {
50
54
  return path.join(packageRoot, "skills", name);
@@ -655,6 +659,21 @@ function resolveCliConfigTarget(options = {}) {
655
659
  };
656
660
  }
657
661
 
662
+ function applyMissingInstallConfigDefaults(config = {}) {
663
+ const nextConfig = { ...config };
664
+ const patchedKeys = [];
665
+ for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
666
+ if (!Object.prototype.hasOwnProperty.call(nextConfig, key)) {
667
+ nextConfig[key] = defaultValue;
668
+ patchedKeys.push(key);
669
+ }
670
+ }
671
+ return {
672
+ nextConfig,
673
+ patchedKeys
674
+ };
675
+ }
676
+
658
677
  function ensureUserConfig(options = {}) {
659
678
  const { configPath, workspacePreferred } = resolveCliConfigTarget(options);
660
679
  const writeTargets = dedupePaths([configPath, workspacePreferred]).filter(Boolean);
@@ -671,7 +690,27 @@ function ensureUserConfig(options = {}) {
671
690
  }
672
691
  const stat = fs.statSync(targetPath);
673
692
  if (stat.isFile()) {
674
- return { path: targetPath, created: false };
693
+ try {
694
+ const existingConfig = readJsonObjectFile(targetPath);
695
+ const patched = applyMissingInstallConfigDefaults(existingConfig);
696
+ if (patched.patchedKeys.length > 0) {
697
+ fs.writeFileSync(targetPath, JSON.stringify(patched.nextConfig, null, 2), "utf8");
698
+ }
699
+ return {
700
+ path: targetPath,
701
+ created: false,
702
+ patched: patched.patchedKeys.length > 0,
703
+ patched_keys: patched.patchedKeys
704
+ };
705
+ } catch (error) {
706
+ return {
707
+ path: targetPath,
708
+ created: false,
709
+ patched: false,
710
+ patched_keys: [],
711
+ patch_error: error?.message || "screening-config.json 解析失败,跳过自动补字段。"
712
+ };
713
+ }
675
714
  }
676
715
  lastError = new Error(`Config target is a directory and cannot be used as file: ${targetPath}`);
677
716
  } catch (error) {
@@ -1286,6 +1325,11 @@ function installAll(options = {}) {
1286
1325
  ? `screening-config.json created: ${configResult.path}`
1287
1326
  : `screening-config.json already exists: ${configResult.path}`
1288
1327
  );
1328
+ if (Array.isArray(configResult.patched_keys) && configResult.patched_keys.length > 0) {
1329
+ console.log(`screening-config.json patched missing defaults: ${configResult.patched_keys.join(", ")}`);
1330
+ } else if (configResult.patch_error) {
1331
+ console.warn(`screening-config.json skip default patch: ${configResult.patch_error}`);
1332
+ }
1289
1333
  console.log(`请在该目录修改 baseUrl/apiKey/model 并替换占位词后再运行:${path.dirname(configResult.path)}`);
1290
1334
  console.log(`MCP config templates exported to: ${mcpTemplateResult.outputDir}`);
1291
1335
  for (const item of mcpTemplateResult.files) {
@@ -1482,6 +1526,11 @@ export async function runCli(argv = process.argv) {
1482
1526
  case "init-config": {
1483
1527
  const result = ensureUserConfig(options);
1484
1528
  console.log(result.created ? `Config template created at: ${result.path}` : `Config already exists at: ${result.path}`);
1529
+ if (Array.isArray(result.patched_keys) && result.patched_keys.length > 0) {
1530
+ console.log(`Config patched missing defaults: ${result.patched_keys.join(", ")}`);
1531
+ } else if (result.patch_error) {
1532
+ console.warn(`Config skip default patch: ${result.patch_error}`);
1533
+ }
1485
1534
  break;
1486
1535
  }
1487
1536
  case "set-port": {
@@ -755,8 +755,8 @@ async function testBossChatLlmShouldApplyThinkingDefaultsAndOverrides() {
755
755
  },
756
756
  });
757
757
  await volcClient.requestCompletions({ prompt: "prompt", evidenceCorpus: "resume" });
758
- assert.deepEqual(volcCompletionPayload.thinking, { type: "disabled" });
759
- assert.equal(volcCompletionPayload.reasoning_effort, "minimal");
758
+ assert.deepEqual(volcCompletionPayload.thinking, { type: "enabled" });
759
+ assert.equal(volcCompletionPayload.reasoning_effort, "low");
760
760
 
761
761
  let lowCompletionPayload = null;
762
762
  const lowClient = new LlmClient({
@@ -787,7 +787,7 @@ async function testBossChatLlmShouldApplyThinkingDefaultsAndOverrides() {
787
787
  });
788
788
  await openaiClient.requestCompletions({ prompt: "prompt", evidenceCorpus: "resume" });
789
789
  assert.equal(openaiCompletionPayload.thinking, undefined);
790
- assert.equal(openaiCompletionPayload.reasoning_effort, "minimal");
790
+ assert.equal(openaiCompletionPayload.reasoning_effort, "low");
791
791
 
792
792
  let responsesPayload = null;
793
793
  const responsesClient = new LlmClient({
@@ -47,7 +47,7 @@ export class InteractionController {
47
47
  return 0;
48
48
  }
49
49
 
50
- const restMs = 0;
50
+ const restMs = 4000 + Math.floor(Math.random() * 4000);
51
51
  logger.log(`短暂休息 ${restMs}ms,保持处理节奏稳定...`);
52
52
  await this.wait(restMs);
53
53
  this.nextRestAt = processedCount + this.randomRestThreshold();
@@ -104,7 +104,7 @@ function resolveLlmThinkingLevel(config = {}, options = {}) {
104
104
  normalizeLlmThinkingLevel(config.reasoningEffort) ||
105
105
  normalizeLlmThinkingLevel(config.reasoning_effort) ||
106
106
  getEnvLlmThinkingLevel() ||
107
- 'off'
107
+ 'low'
108
108
  );
109
109
  }
110
110
 
@@ -118,7 +118,7 @@ function isVolcengineModel(baseUrl, model) {
118
118
  }
119
119
 
120
120
  function applyChatCompletionThinking(payload, { baseUrl = '', model = '', thinkingLevel = '' } = {}) {
121
- const level = normalizeLlmThinkingLevel(thinkingLevel) || 'off';
121
+ const level = normalizeLlmThinkingLevel(thinkingLevel) || 'low';
122
122
  if (isProviderDefaultThinkingLevel(level)) return payload;
123
123
  const isVolc = isVolcengineModel(baseUrl, model);
124
124
  if (isVolc) {
@@ -142,7 +142,7 @@ function applyChatCompletionThinking(payload, { baseUrl = '', model = '', thinki
142
142
  }
143
143
 
144
144
  function applyResponsesThinking(payload, { thinkingLevel = '' } = {}) {
145
- const level = normalizeLlmThinkingLevel(thinkingLevel) || 'off';
145
+ const level = normalizeLlmThinkingLevel(thinkingLevel) || 'low';
146
146
  if (isProviderDefaultThinkingLevel(level) || level === 'auto') return payload;
147
147
  payload.reasoning = {
148
148
  ...(payload.reasoning || {}),
@@ -1288,7 +1288,7 @@ function getEnvLlmThinkingLevel() {
1288
1288
  }
1289
1289
 
1290
1290
  function resolveLlmThinkingLevel(value) {
1291
- return normalizeLlmThinkingLevel(value) || getEnvLlmThinkingLevel() || "off";
1291
+ return normalizeLlmThinkingLevel(value) || getEnvLlmThinkingLevel() || "low";
1292
1292
  }
1293
1293
 
1294
1294
  function isVolcengineModel(baseUrl, model) {
@@ -1339,6 +1339,7 @@ function parseArgs(argv) {
1339
1339
  checkpointPath: null,
1340
1340
  pauseControlPath: null,
1341
1341
  resume: false,
1342
+ humanRestEnabled: false,
1342
1343
  postAction: null,
1343
1344
  postActionConfirmed: null,
1344
1345
  help: false,
@@ -1353,6 +1354,7 @@ function parseArgs(argv) {
1353
1354
  pageScope: false,
1354
1355
  calibrationPath: false,
1355
1356
  port: false,
1357
+ humanRest: false,
1356
1358
  postAction: false,
1357
1359
  postActionConfirmed: false
1358
1360
  }
@@ -1421,6 +1423,11 @@ function parseArgs(argv) {
1421
1423
  } else if (token === "--pause-control-path" && (inlineValue || next)) {
1422
1424
  parsed.pauseControlPath = path.resolve(inlineValue || next);
1423
1425
  if (!inlineValue) index += 1;
1426
+ } else if ((token === "--human-rest" || token === "--humanRest" || token === "--human_rest") && (inlineValue || next)) {
1427
+ const parsedBoolean = parseBoolean(inlineValue || next);
1428
+ parsed.humanRestEnabled = parsedBoolean === true;
1429
+ parsed.__provided.humanRest = parsedBoolean !== null;
1430
+ if (!inlineValue) index += 1;
1424
1431
  } else if (token === "--resume") {
1425
1432
  parsed.resume = true;
1426
1433
  } else if ((token === "--post-action" || token === "--postAction") && (inlineValue || next)) {
@@ -5743,12 +5750,12 @@ class RecommendScreenCli {
5743
5750
  async takeBreakIfNeeded() {
5744
5751
  this.restCounter += 1;
5745
5752
  if (Math.random() < 0.08) {
5746
- const pauseMs = 0;
5753
+ const pauseMs = this.args.humanRestEnabled ? 3000 + Math.floor(Math.random() * 4000) : 0;
5747
5754
  log(`[随机休息] 暂停 ${Math.round(pauseMs / 1000)} 秒`);
5748
5755
  await sleep(pauseMs);
5749
5756
  }
5750
5757
  if (this.restCounter >= this.restThreshold) {
5751
- const pauseMs = 0;
5758
+ const pauseMs = this.args.humanRestEnabled ? 15000 + Math.floor(Math.random() * 15000) : 0;
5752
5759
  log(`[批次休息] 已连续处理 ${this.restCounter} 人,暂停 ${Math.round(pauseMs / 1000)} 秒`);
5753
5760
  await sleep(pauseMs);
5754
5761
  this.restCounter = 0;
@@ -6469,7 +6476,7 @@ async function main() {
6469
6476
  console.log(JSON.stringify({
6470
6477
  status: "COMPLETED",
6471
6478
  result: {
6472
- usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --thinking-level off|low|medium|high|current --page-scope recommend|latest|featured --calibration <favorite-calibration.json> --port 9222 --output <csv-path> [--input-summary-json <json>] --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
6479
+ usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --thinking-level off|low|medium|high|current --page-scope recommend|latest|featured --calibration <favorite-calibration.json> --port 9222 --human-rest <true|false> --output <csv-path> [--input-summary-json <json>] --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
6473
6480
  }
6474
6481
  }));
6475
6482
  return;
@@ -1525,8 +1525,8 @@ async function testCallTextModelShouldFallbackToChunkModeOnContextLimit() {
1525
1525
  }
1526
1526
  }
1527
1527
 
1528
- async function testTextModelShouldDefaultThinkingOffForVolcengine() {
1529
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-off-"));
1528
+ async function testTextModelShouldDefaultThinkingLowForVolcengine() {
1529
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-low-default-"));
1530
1530
  const cli = new RecommendScreenCli(createArgs(tempDir));
1531
1531
  cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
1532
1532
  cli.args.model = "doubao-seed-2-0-mini-260215";
@@ -1552,8 +1552,8 @@ async function testTextModelShouldDefaultThinkingOffForVolcengine() {
1552
1552
  };
1553
1553
  try {
1554
1554
  await cli.callTextModel("resume");
1555
- assert.deepEqual(capturedPayload?.thinking, { type: "disabled" });
1556
- assert.equal(capturedPayload?.reasoning_effort, "minimal");
1555
+ assert.deepEqual(capturedPayload?.thinking, { type: "enabled" });
1556
+ assert.equal(capturedPayload?.reasoning_effort, "low");
1557
1557
  } finally {
1558
1558
  global.fetch = originalFetch;
1559
1559
  }
@@ -1812,7 +1812,7 @@ async function main() {
1812
1812
  testParseArgsShouldSupportInputSummaryJson();
1813
1813
  await testCallTextModelShouldNotTruncateLongResume();
1814
1814
  await testCallTextModelShouldFallbackToChunkModeOnContextLimit();
1815
- await testTextModelShouldDefaultThinkingOffForVolcengine();
1815
+ await testTextModelShouldDefaultThinkingLowForVolcengine();
1816
1816
  await testTextModelShouldSupportLowThinkingForVolcengine();
1817
1817
  await testPrepareVisionImageSegmentsShouldSplitLongImage();
1818
1818
  await testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence();