@reconcrap/boss-recommend-mcp 2.0.6 → 2.0.7

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.
@@ -73,6 +73,27 @@ function parseNonNegativeInteger(raw, fallback) {
73
73
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
74
74
  }
75
75
 
76
+ function isDebugTestMode(args = {}) {
77
+ return args.debug_test_mode === true || args.allow_debug_test_mode === true;
78
+ }
79
+
80
+ function normalizeScreeningModeArg(args = {}) {
81
+ const raw = normalizeText(args.screening_mode || args.screeningMode || "");
82
+ if (args.use_llm === false) return "deterministic";
83
+ return ["deterministic", "local", "local_scorer"].includes(raw.toLowerCase())
84
+ ? "deterministic"
85
+ : "llm";
86
+ }
87
+
88
+ function collectRecruitDebugTestOptions(args = {}) {
89
+ const reasons = [];
90
+ if (normalizeScreeningModeArg(args) === "deterministic") reasons.push("deterministic_screening");
91
+ if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
92
+ if (args.dry_run_post_action === true) reasons.push("dry_run_post_action");
93
+ if (args.execute_post_action === false) reasons.push("execute_post_action=false");
94
+ return reasons;
95
+ }
96
+
76
97
  function methodSummary(methodLog = []) {
77
98
  const summary = {};
78
99
  for (const entry of methodLog || []) {
@@ -203,8 +224,15 @@ function ensureRecruitRunArtifacts(snapshot) {
203
224
  if (meta) meta.checkpointPath = artifacts.checkpoint_path;
204
225
 
205
226
  const summary = snapshot?.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
206
- if (summary) {
207
- const rows = Array.isArray(summary.results) ? summary.results : [];
227
+ const checkpointResults = Array.isArray(checkpoint.results) ? checkpoint.results : [];
228
+ const artifactSummary = summary || (checkpointResults.length ? {
229
+ domain: "recruit",
230
+ partial: true,
231
+ partial_reason: snapshot?.status || snapshot?.state || "non_terminal",
232
+ results: checkpointResults
233
+ } : null);
234
+ if (artifactSummary) {
235
+ const rows = Array.isArray(artifactSummary.results) ? artifactSummary.results : [];
208
236
  writeRecruitLegacyCsvAtomic(artifacts.output_csv, rows, snapshot, meta);
209
237
  writeJsonAtomic(artifacts.report_json, {
210
238
  run_id: snapshot.runId || snapshot.run_id,
@@ -213,7 +241,7 @@ function ensureRecruitRunArtifacts(snapshot) {
213
241
  progress: snapshot.progress || {},
214
242
  context: snapshot.context || {},
215
243
  checkpoint,
216
- summary,
244
+ summary: artifactSummary,
217
245
  generated_at: new Date().toISOString()
218
246
  });
219
247
  if (meta) {
@@ -270,6 +298,12 @@ function buildLegacyRunResult(snapshot) {
270
298
  const artifacts = ensureRecruitRunArtifacts(snapshot);
271
299
  const meta = getRecruitRunMeta(snapshot.runId);
272
300
  const summary = snapshot.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
301
+ const checkpoint = snapshot.checkpoint && typeof snapshot.checkpoint === "object" ? snapshot.checkpoint : {};
302
+ const resultRows = Array.isArray(summary?.results)
303
+ ? summary.results
304
+ : Array.isArray(checkpoint.results)
305
+ ? checkpoint.results
306
+ : [];
273
307
  const progress = normalizeLegacyProgress(snapshot.progress, summary);
274
308
  const targetCount = Number.isInteger(progress.target_count)
275
309
  ? progress.target_count
@@ -302,7 +336,8 @@ function buildLegacyRunResult(snapshot) {
302
336
  || null,
303
337
  completion_reason: completionReason(snapshot.status),
304
338
  target_count_semantics: TARGET_COUNT_SEMANTICS,
305
- run_id: snapshot.runId
339
+ run_id: snapshot.runId,
340
+ results: resultRows
306
341
  };
307
342
  }
308
343
 
@@ -387,7 +422,34 @@ export function createRecruitPipelineInputSchema() {
387
422
  detail_limit: {
388
423
  type: "integer",
389
424
  minimum: 0,
390
- description: "打开详情的人数上限;默认 1,0 表示只用卡片信息"
425
+ description: "打开详情/CV 的人数上限;默认跟随 max_candidates。detail_limit=0 属于调试路径,需要 debug_test_mode=true"
426
+ },
427
+ debug_test_mode: {
428
+ type: "boolean",
429
+ description: "高级测试开关;默认 false。只有显式为 true 时才允许 deterministic/local scorer、detail_limit=0 等调试路径"
430
+ },
431
+ screening_mode: {
432
+ type: "string",
433
+ enum: ["llm", "deterministic"],
434
+ description: "筛选引擎;默认 llm。deterministic 仅限 debug_test_mode=true"
435
+ },
436
+ use_llm: {
437
+ type: "boolean",
438
+ description: "兼容字段;默认 true。use_llm=false 等同 deterministic,仅限 debug_test_mode=true"
439
+ },
440
+ llm_timeout_ms: {
441
+ type: "integer",
442
+ minimum: 1000,
443
+ description: "可选,单个候选人的 LLM 调用超时"
444
+ },
445
+ llm_image_limit: {
446
+ type: "integer",
447
+ minimum: 1,
448
+ description: "可选,传给 LLM 的图片简历截图页数上限"
449
+ },
450
+ llm_image_detail: {
451
+ type: "string",
452
+ description: "可选,图片输入 detail,默认 high"
391
453
  },
392
454
  delay_ms: {
393
455
  type: "integer",
@@ -751,22 +813,30 @@ async function connectRecruitChromeSession({
751
813
  };
752
814
  }
753
815
 
754
- function getRunOptions(args, parsed, session) {
816
+ function getRunOptions(args, parsed, session, configResolution = null) {
755
817
  const slowLive = args.slow_live === true;
756
818
  const targetCount = parsePositiveInteger(args.max_candidates, parsed.screenParams.target_count || 10);
819
+ const screeningMode = normalizeScreeningModeArg(args);
757
820
  return {
758
821
  client: session.client,
759
822
  targetUrl: RECRUIT_TARGET_URL,
760
823
  criteria: parsed.screenParams.criteria,
761
824
  searchParams: parsed.searchParams,
762
825
  maxCandidates: targetCount,
763
- detailLimit: parseNonNegativeInteger(args.detail_limit, 1),
826
+ detailLimit: parseNonNegativeInteger(args.detail_limit, targetCount),
764
827
  closeDetail: true,
765
828
  delayMs: Math.max(0, parsePositiveInteger(args.delay_ms, 0)),
766
829
  cardTimeoutMs: slowLive ? 180000 : 90000,
767
830
  resetBeforeSearch: args.reset_search !== false,
768
831
  resetTimeoutMs: slowLive ? 300000 : 180000,
769
832
  cityOptionTimeoutMs: slowLive ? 60000 : 30000,
833
+ screeningMode,
834
+ llmConfig: screeningMode === "llm" && configResolution?.ok ? {
835
+ ...configResolution.config
836
+ } : null,
837
+ llmTimeoutMs: parsePositiveInteger(args.llm_timeout_ms, slowLive ? 180000 : 120000),
838
+ llmImageLimit: parsePositiveInteger(args.llm_image_limit, 8),
839
+ llmImageDetail: normalizeText(args.llm_image_detail) || "high",
770
840
  name: "mcp-recruit-pipeline-run"
771
841
  };
772
842
  }
@@ -814,6 +884,31 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
814
884
  const gate = evaluateRecruitPipelineGate(parsed);
815
885
  if (gate) return gate;
816
886
  const configResolution = resolveBossScreeningConfig(workspaceRoot);
887
+ const screeningMode = normalizeScreeningModeArg(args);
888
+ const debugTestOptions = collectRecruitDebugTestOptions(args);
889
+ if (debugTestOptions.length && !isDebugTestMode(args)) {
890
+ return {
891
+ status: "FAILED",
892
+ error: {
893
+ code: "DEBUG_TEST_MODE_REQUIRED",
894
+ message: `这些参数属于调试/测试路径,正式 live run 不会默认启用:${debugTestOptions.join(", ")}。如确需测试,请显式传 debug_test_mode=true。`,
895
+ retryable: false
896
+ },
897
+ debug_test_options: debugTestOptions
898
+ };
899
+ }
900
+ if (screeningMode === "llm" && !configResolution.ok) {
901
+ return {
902
+ status: "FAILED",
903
+ error: {
904
+ code: "SCREEN_CONFIG_ERROR",
905
+ message: configResolution.error?.message || "screening-config.json is required for LLM screening.",
906
+ retryable: true
907
+ },
908
+ config_path: configResolution.config_path || null,
909
+ candidate_paths: configResolution.candidate_paths || []
910
+ };
911
+ }
817
912
  const host = normalizeText(args.host) || DEFAULT_RECRUIT_HOST;
818
913
  const port = parsePositiveInteger(
819
914
  args.port,
@@ -850,7 +945,7 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
850
945
 
851
946
  let started;
852
947
  try {
853
- started = recruitRunService.startRecruitRun(getRunOptions(args, parsed, session));
948
+ started = recruitRunService.startRecruitRun(getRunOptions(args, parsed, session, configResolution));
854
949
  } catch (error) {
855
950
  await session.close?.();
856
951
  return {