@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.
package/README.md CHANGED
@@ -98,6 +98,8 @@ boss-recommend-mcp list-jobs --slow-live --port 9222
98
98
  - 不会对每位候选人重复确认
99
99
  - 推荐页详情处理完成后,会强制关闭详情页并确认已关闭
100
100
  - 简历提取优先使用 Network 响应;没有可解析 Network CV 时,回退到完整滚动截图序列再交给多模态模型判断
101
+ - recommend / search / chat 正式运行默认全部使用 `screening-config.json` 配置的 LLM 筛选;deterministic/local scorer 只保留给明确测试场景,必须显式传 `debug_test_mode=true` 且 `screening_mode=deterministic` 或 `use_llm=false`。
102
+ - `detail_limit=0`、`no_filter`、`filter_enabled=false`、后置动作 dry-run、chat 求简历 dry-run 等调试路径不会在正式 live run 默认启用;需要测试时必须显式传 `debug_test_mode=true`。
101
103
  - 提供显式运维自愈工具:只在手动调用 `run_recommend_self_heal` 时运行,不会接入正常 run / doctor / preflight 自动链路
102
104
  - 运行前会自动做依赖体检(Node.js、Python、Pillow、`chrome-remote-interface`、`ws`),缺失时会在 `doctor` 与流水线失败诊断中明确提示
103
105
  - 若 preflight 失败,返回 `diagnostics.recovery`(含有序修复步骤与 `agent_prompt`),可直接交给 AI agent 自动按顺序安装依赖
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
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
@@ -308,8 +308,15 @@ function ensureChatRunArtifacts(snapshot) {
308
308
  if (meta) meta.checkpointPath = artifacts.checkpoint_path;
309
309
 
310
310
  const summary = snapshot?.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
311
- if (summary) {
312
- const rows = Array.isArray(summary.results) ? summary.results : [];
311
+ const checkpointResults = Array.isArray(checkpoint.results) ? checkpoint.results : [];
312
+ const artifactSummary = summary || (checkpointResults.length ? {
313
+ domain: "chat",
314
+ partial: true,
315
+ partial_reason: snapshot?.status || snapshot?.state || "non_terminal",
316
+ results: checkpointResults
317
+ } : null);
318
+ if (artifactSummary) {
319
+ const rows = Array.isArray(artifactSummary.results) ? artifactSummary.results : [];
313
320
  writeChatLegacyCsvAtomic(artifacts.output_csv, rows, snapshot, meta);
314
321
  writeJsonAtomic(artifacts.report_json, {
315
322
  run_id: snapshot.runId || snapshot.run_id,
@@ -318,7 +325,7 @@ function ensureChatRunArtifacts(snapshot) {
318
325
  progress: snapshot.progress || {},
319
326
  context: snapshot.context || {},
320
327
  checkpoint,
321
- summary,
328
+ summary: artifactSummary,
322
329
  generated_at: new Date().toISOString()
323
330
  });
324
331
  if (meta) {
@@ -335,6 +342,12 @@ function buildLegacyChatResult(snapshot) {
335
342
  const artifacts = ensureChatRunArtifacts(snapshot);
336
343
  const meta = getChatRunMeta(snapshot.runId);
337
344
  const summary = snapshot.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
345
+ const checkpoint = snapshot.checkpoint && typeof snapshot.checkpoint === "object" ? snapshot.checkpoint : {};
346
+ const resultRows = Array.isArray(summary?.results)
347
+ ? summary.results
348
+ : Array.isArray(checkpoint.results)
349
+ ? checkpoint.results
350
+ : [];
338
351
  const progress = normalizeLegacyProgress(snapshot.progress, summary);
339
352
  return {
340
353
  run_id: snapshot.runId,
@@ -358,7 +371,7 @@ function buildLegacyChatResult(snapshot) {
358
371
  completed_at: snapshot.completedAt || null,
359
372
  duration_sec: secondsBetween(snapshot.startedAt, snapshot.completedAt),
360
373
  error: snapshot.error || null,
361
- results: Array.isArray(summary?.results) ? summary.results : []
374
+ results: resultRows
362
375
  };
363
376
  }
364
377
 
@@ -788,16 +801,31 @@ function shouldRequestChatResume(args = {}) {
788
801
  );
789
802
  }
790
803
 
791
- function shouldUseChatLlm(args = {}, shouldRequestResume = false) {
792
- if (args.use_llm === false) return false;
793
- return (
794
- args.use_llm === true
795
- || shouldRequestResume
796
- || parseNonNegativeInteger(args.detail_limit, 0) > 0
797
- );
804
+ function isDebugTestMode(args = {}) {
805
+ return args.debug_test_mode === true || args.allow_debug_test_mode === true;
806
+ }
807
+
808
+ function normalizeScreeningModeArg(args = {}) {
809
+ const raw = normalizeText(args.screening_mode || args.screeningMode || "");
810
+ if (args.use_llm === false) return "deterministic";
811
+ return ["deterministic", "local", "local_scorer"].includes(raw.toLowerCase())
812
+ ? "deterministic"
813
+ : "llm";
814
+ }
815
+
816
+ function collectChatDebugTestOptions(args = {}) {
817
+ const reasons = [];
818
+ if (normalizeScreeningModeArg(args) === "deterministic") reasons.push("deterministic_screening");
819
+ if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
820
+ if (args.dry_run === true || args.dry_run_request_cv === true) reasons.push("dry_run_request_cv");
821
+ return reasons;
822
+ }
823
+
824
+ function shouldUseChatLlm(args = {}) {
825
+ return normalizeScreeningModeArg(args) !== "deterministic";
798
826
  }
799
827
 
800
- function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
828
+ function getRunOptions(args, normalized, session, { workspaceRoot = "", configResolution = null } = {}) {
801
829
  const slowLive = args.slow_live === true;
802
830
  const isAllTarget = normalized.publicTargetCount === "all";
803
831
  const processedLimit = parsePositiveInteger(
@@ -805,8 +833,8 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
805
833
  isAllTarget ? CHAT_ALL_MAX_CANDIDATES : CHAT_ALL_MAX_CANDIDATES
806
834
  );
807
835
  const shouldRequestResume = shouldRequestChatResume(args);
808
- const useLlm = shouldUseChatLlm(args, shouldRequestResume);
809
- const configResolution = useLlm ? resolveBossScreeningConfig(workspaceRoot) : { ok: false };
836
+ const useLlm = shouldUseChatLlm(args);
837
+ const resolvedConfig = configResolution || (useLlm ? resolveBossScreeningConfig(workspaceRoot) : { ok: false });
810
838
  return {
811
839
  client: session.client,
812
840
  targetUrl: CHAT_TARGET_URL,
@@ -832,12 +860,13 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
832
860
  resumeDomTimeoutMs: slowLive ? 120000 : 60000,
833
861
  maxImagePages: parsePositiveInteger(args.max_image_pages, 8),
834
862
  imageWheelDeltaY: parsePositiveInteger(args.image_wheel_delta_y, 650),
835
- llmConfig: configResolution.ok ? {
836
- ...configResolution.config
863
+ llmConfig: resolvedConfig.ok ? {
864
+ ...resolvedConfig.config
837
865
  } : null,
838
866
  llmTimeoutMs: parsePositiveInteger(args.llm_timeout_ms, slowLive ? 180000 : 120000),
839
867
  llmImageLimit: parsePositiveInteger(args.llm_image_limit, 8),
840
868
  llmImageDetail: normalizeText(args.llm_image_detail) || "high",
869
+ screeningMode: normalizeScreeningModeArg(args),
841
870
  listMaxScrolls: parsePositiveInteger(args.list_max_scrolls, 200),
842
871
  listStableSignatureLimit: parsePositiveInteger(args.list_stable_signature_limit, 2),
843
872
  listWheelDeltaY: parsePositiveInteger(args.list_wheel_delta_y, 850),
@@ -905,7 +934,19 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {})
905
934
  }
906
935
 
907
936
  const shouldRequestResume = shouldRequestChatResume(args);
908
- const useLlm = shouldUseChatLlm(args, shouldRequestResume);
937
+ const useLlm = shouldUseChatLlm(args);
938
+ const debugTestOptions = collectChatDebugTestOptions(args);
939
+ if (debugTestOptions.length && !isDebugTestMode(args)) {
940
+ return {
941
+ status: "FAILED",
942
+ error: {
943
+ code: "DEBUG_TEST_MODE_REQUIRED",
944
+ message: `这些参数属于调试/测试路径,正式 live run 不会默认启用:${debugTestOptions.join(", ")}。如确需测试,请显式传 debug_test_mode=true。`,
945
+ retryable: false
946
+ },
947
+ debug_test_options: debugTestOptions
948
+ };
949
+ }
909
950
  const configResolution = useLlm ? resolveBossScreeningConfig(workspaceRoot) : null;
910
951
  if (useLlm && !configResolution?.ok) {
911
952
  return {
@@ -948,7 +989,7 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {})
948
989
 
949
990
  let started;
950
991
  try {
951
- started = chatRunService.startChatRun(getRunOptions(args, normalized, session, { workspaceRoot }));
992
+ started = chatRunService.startChatRun(getRunOptions(args, normalized, session, { workspaceRoot, configResolution }));
952
993
  } catch (error) {
953
994
  await session.close?.();
954
995
  return {
@@ -284,8 +284,8 @@ export function legacyScreenResultRow(row = {}) {
284
284
  totalEvidence,
285
285
  totalEvidence,
286
286
  "",
287
- row.error_code || error.code || error.name || "",
288
- row.error_message || error.message || "",
287
+ row.error_code || error.code || error.name || (llm.error ? "LLM_SCREENING_ERROR" : ""),
288
+ row.error_message || error.message || llm.error || "",
289
289
  candidate.id || row.candidate_id || "",
290
290
  timingValue(row, "total_ms"),
291
291
  timingValue(row, "card_read_ms"),
@@ -1003,6 +1003,54 @@ export function screenCandidate(candidateInput, criteria = {}) {
1003
1003
  };
1004
1004
  }
1005
1005
 
1006
+ export function compactScreeningLlmResult(llmResult) {
1007
+ if (!llmResult) return null;
1008
+ return {
1009
+ ok: Boolean(llmResult.ok),
1010
+ provider: llmResult.provider || null,
1011
+ passed: llmResult.passed,
1012
+ cot: llmResult.cot || llmResult.decision_cot || "",
1013
+ reasoning_content: llmResult.reasoning_content || "",
1014
+ raw_model_output: llmResult.raw_model_output || "",
1015
+ evidence_count: Array.isArray(llmResult.evidence) ? llmResult.evidence.length : 0,
1016
+ usage: llmResult.usage || null,
1017
+ finish_reason: llmResult.finish_reason || null,
1018
+ image_input_count: llmResult.image_input_count || 0,
1019
+ error: llmResult.error || null,
1020
+ screened_at: llmResult.screened_at || null
1021
+ };
1022
+ }
1023
+
1024
+ export function llmResultToScreening(llmResult, candidate) {
1025
+ return {
1026
+ status: llmResult?.passed ? "pass" : "fail",
1027
+ passed: Boolean(llmResult?.passed),
1028
+ score: llmResult?.passed ? 100 : 0,
1029
+ reasons: llmResult?.error ? ["llm_invalid_response"] : [],
1030
+ candidate
1031
+ };
1032
+ }
1033
+
1034
+ export function isRecoverableLlmScreeningError(error) {
1035
+ return /(?:LLM response missing boolean passed decision|LLM response was not valid JSON)/i
1036
+ .test(String(error?.message || error || ""));
1037
+ }
1038
+
1039
+ export function createFailedLlmScreeningResult(error) {
1040
+ return {
1041
+ ok: false,
1042
+ passed: false,
1043
+ reason: "",
1044
+ evidence: [],
1045
+ cot: "",
1046
+ decision_cot: "",
1047
+ reasoning_content: "",
1048
+ raw_model_output: "",
1049
+ error: error?.message || String(error || "unknown"),
1050
+ screened_at: nowIso()
1051
+ };
1052
+ }
1053
+
1006
1054
  export function buildScreeningLlmMessages({
1007
1055
  candidate,
1008
1056
  criteria,
@@ -167,6 +167,17 @@ function createFailedLlmResult(error) {
167
167
  };
168
168
  }
169
169
 
170
+ function normalizeScreeningMode(value) {
171
+ const normalized = String(value || "llm").trim().toLowerCase();
172
+ return ["deterministic", "local", "local_scorer"].includes(normalized)
173
+ ? "deterministic"
174
+ : "llm";
175
+ }
176
+
177
+ function createMissingLlmConfigResult() {
178
+ return createFailedLlmResult(new Error("LLM screening config is required for production chat runs"));
179
+ }
180
+
170
181
  function createSkippedDetailResult(cardCandidate, reason, error = null) {
171
182
  return {
172
183
  candidate: cardCandidate,
@@ -334,7 +345,7 @@ export async function runChatWorkflow({
334
345
  maxCandidates = 5,
335
346
  targetPassCount = null,
336
347
  processUntilListEnd = false,
337
- detailLimit = 0,
348
+ detailLimit = null,
338
349
  detailSource = "cascade",
339
350
  closeResume = true,
340
351
  requestResumeForPassed = false,
@@ -353,6 +364,7 @@ export async function runChatWorkflow({
353
364
  llmTimeoutMs = 120000,
354
365
  llmImageLimit = 8,
355
366
  llmImageDetail = "high",
367
+ screeningMode = "llm",
356
368
  listMaxScrolls = 20,
357
369
  listStableSignatureLimit = 2,
358
370
  listWheelDeltaY = 850,
@@ -361,12 +373,14 @@ export async function runChatWorkflow({
361
373
  } = {}, runControl) {
362
374
  if (!client) throw new Error("runChatWorkflow requires a guarded CDP client");
363
375
  const normalizedDetailSource = normalizeDetailSource(detailSource);
376
+ const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
377
+ const useLlmScreening = normalizedScreeningMode !== "deterministic";
364
378
  const processedLimit = Math.max(1, Number(maxCandidates) || 1);
365
379
  const passTarget = Number.isFinite(Number(targetPassCount)) && Number(targetPassCount) > 0
366
380
  ? Number(targetPassCount)
367
381
  : null;
368
382
  const normalizedStartFrom = normalizeText(startFrom).toLowerCase() === "unread" ? "unread" : "all";
369
- const detailCountLimit = Math.max(0, Number(detailLimit) || 0);
383
+ const detailCountLimit = detailLimit == null ? processedLimit : Math.max(0, Number(detailLimit) || 0);
370
384
  const networkRecorder = detailCountLimit > 0
371
385
  ? createChatProfileNetworkRecorder(client)
372
386
  : null;
@@ -556,6 +570,7 @@ export async function runChatWorkflow({
556
570
  requested: 0,
557
571
  request_satisfied: 0,
558
572
  request_skipped: 0,
573
+ screening_mode: normalizedScreeningMode,
559
574
  unique_seen: compactInfiniteListState(listState).seen_count,
560
575
  scroll_count: 0,
561
576
  viewport_checks: viewportGuard.getStats().checks,
@@ -799,21 +814,23 @@ export async function runChatWorkflow({
799
814
  imageEvidence
800
815
  });
801
816
  if (callLlmOnImage) {
802
- if (!llmConfig) throw new Error("callLlmOnImage requires llmConfig");
803
817
  detailStep = "llm_image_screening";
804
- try {
805
- llmResult = await callScreeningLlm({
806
- candidate: detailResult.candidate,
807
- criteria,
808
- config: llmConfig,
809
- timeoutMs: llmTimeoutMs,
810
- imageEvidence,
811
- maxImages: llmImageLimit,
812
- imageDetail: llmImageDetail
813
- });
814
- } catch (error) {
815
- if (!isRecoverableLlmScreeningError(error)) throw error;
816
- llmResult = createFailedLlmResult(error);
818
+ if (!llmConfig) {
819
+ llmResult = createMissingLlmConfigResult();
820
+ } else {
821
+ try {
822
+ llmResult = await callScreeningLlm({
823
+ candidate: detailResult.candidate,
824
+ criteria,
825
+ config: llmConfig,
826
+ timeoutMs: llmTimeoutMs,
827
+ imageEvidence,
828
+ maxImages: llmImageLimit,
829
+ imageDetail: llmImageDetail
830
+ });
831
+ } catch (error) {
832
+ llmResult = createFailedLlmResult(error);
833
+ }
817
834
  }
818
835
  }
819
836
  } else {
@@ -838,21 +855,24 @@ export async function runChatWorkflow({
838
855
  });
839
856
  }
840
857
 
841
- if (llmConfig && !llmResult) {
858
+ if (useLlmScreening && !llmResult) {
842
859
  detailStep = "llm_screening";
843
- try {
844
- llmResult = await callScreeningLlm({
845
- candidate: detailResult.candidate,
846
- criteria,
847
- config: llmConfig,
848
- timeoutMs: llmTimeoutMs,
849
- imageEvidence,
850
- maxImages: llmImageLimit,
851
- imageDetail: llmImageDetail
852
- });
853
- } catch (error) {
854
- if (!isRecoverableLlmScreeningError(error)) throw error;
855
- llmResult = createFailedLlmResult(error);
860
+ if (!llmConfig) {
861
+ llmResult = createMissingLlmConfigResult();
862
+ } else {
863
+ try {
864
+ llmResult = await callScreeningLlm({
865
+ candidate: detailResult.candidate,
866
+ criteria,
867
+ config: llmConfig,
868
+ timeoutMs: llmTimeoutMs,
869
+ imageEvidence,
870
+ maxImages: llmImageLimit,
871
+ imageDetail: llmImageDetail
872
+ });
873
+ } catch (error) {
874
+ llmResult = createFailedLlmResult(error);
875
+ }
856
876
  }
857
877
  }
858
878
 
@@ -901,6 +921,26 @@ export async function runChatWorkflow({
901
921
  await runControl.waitIfPaused();
902
922
  runControl.throwIfCanceled();
903
923
  runControl.setPhase("chat:screening");
924
+ let cardOnlyLlmResult = null;
925
+ if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
926
+ if (!llmConfig) {
927
+ cardOnlyLlmResult = createMissingLlmConfigResult();
928
+ } else {
929
+ try {
930
+ cardOnlyLlmResult = await callScreeningLlm({
931
+ candidate: screeningCandidate,
932
+ criteria,
933
+ config: llmConfig,
934
+ timeoutMs: llmTimeoutMs,
935
+ maxImages: llmImageLimit,
936
+ imageDetail: llmImageDetail
937
+ });
938
+ } catch (error) {
939
+ cardOnlyLlmResult = createFailedLlmResult(error);
940
+ }
941
+ }
942
+ }
943
+ const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
904
944
  const screening = detailUnavailableReason
905
945
  ? {
906
946
  status: "skip",
@@ -909,8 +949,8 @@ export async function runChatWorkflow({
909
949
  reasons: [detailUnavailableReason],
910
950
  candidate: screeningCandidate
911
951
  }
912
- : detailResult?.llm_result
913
- ? llmToScreening(detailResult.llm_result, screeningCandidate)
952
+ : useLlmScreening
953
+ ? llmToScreening(effectiveLlmResult, screeningCandidate)
914
954
  : screenCandidate(screeningCandidate, { criteria });
915
955
  let postAction = null;
916
956
  if (requestResumeForPassed && screening.passed) {
@@ -931,6 +971,7 @@ export async function runChatWorkflow({
931
971
  card_node_id: effectiveCardNodeId,
932
972
  candidate: compactCandidate(screeningCandidate),
933
973
  detail: compactDetail(detailResult),
974
+ llm_screening: detailResult ? null : compactLlmResult(cardOnlyLlmResult),
934
975
  screening: compactScreening(screening),
935
976
  post_action: postAction,
936
977
  pre_action_state: preActionState
@@ -951,7 +992,7 @@ export async function runChatWorkflow({
951
992
  processed: results.length,
952
993
  screened: results.length,
953
994
  detail_opened: results.filter(resultOpenedDetail).length,
954
- llm_screened: results.filter((item) => item.detail?.llm_screening).length,
995
+ llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
955
996
  passed: results.filter((item) => item.screening.passed).length,
956
997
  requested: requestedCount,
957
998
  request_satisfied: requestSatisfiedCount,
@@ -966,6 +1007,7 @@ export async function runChatWorkflow({
966
1007
  last_score: screening.score
967
1008
  });
968
1009
  runControl.checkpoint({
1010
+ results,
969
1011
  last_candidate: {
970
1012
  id: screeningCandidate.id || null,
971
1013
  key: candidateKey,
@@ -974,7 +1016,8 @@ export async function runChatWorkflow({
974
1016
  status: screening.status,
975
1017
  passed: screening.passed,
976
1018
  score: screening.score
977
- }
1019
+ },
1020
+ llm_screening: compactLlmResult(effectiveLlmResult)
978
1021
  }
979
1022
  });
980
1023
 
@@ -1002,7 +1045,7 @@ export async function runChatWorkflow({
1002
1045
  processed: results.length,
1003
1046
  screened: results.length,
1004
1047
  detail_opened: results.filter(resultOpenedDetail).length,
1005
- llm_screened: results.filter((item) => item.detail?.llm_screening).length,
1048
+ llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
1006
1049
  passed: results.filter((item) => item.screening.passed).length,
1007
1050
  requested: requestedCount,
1008
1051
  request_satisfied: requestSatisfiedCount,
@@ -1027,7 +1070,7 @@ export function createChatRunService({
1027
1070
  maxCandidates = 5,
1028
1071
  targetPassCount = null,
1029
1072
  processUntilListEnd = false,
1030
- detailLimit = 0,
1073
+ detailLimit = null,
1031
1074
  detailSource = "cascade",
1032
1075
  closeResume = true,
1033
1076
  requestResumeForPassed = false,
@@ -1046,6 +1089,7 @@ export function createChatRunService({
1046
1089
  llmTimeoutMs = 120000,
1047
1090
  llmImageLimit = 8,
1048
1091
  llmImageDetail = "high",
1092
+ screeningMode = "llm",
1049
1093
  listMaxScrolls = 20,
1050
1094
  listStableSignatureLimit = 2,
1051
1095
  listWheelDeltaY = 850,
@@ -1055,6 +1099,9 @@ export function createChatRunService({
1055
1099
  } = {}) {
1056
1100
  if (!client) throw new Error("startChatRun requires a guarded CDP client");
1057
1101
  const normalizedDetailSource = normalizeDetailSource(detailSource);
1102
+ const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
1103
+ const processedLimit = Math.max(1, Number(maxCandidates) || 1);
1104
+ const normalizedDetailLimit = detailLimit == null ? processedLimit : Math.max(0, Number(detailLimit) || 0);
1058
1105
  return manager.startRun({
1059
1106
  name,
1060
1107
  context: {
@@ -1066,11 +1113,16 @@ export function createChatRunService({
1066
1113
  max_candidates: maxCandidates,
1067
1114
  target_pass_count: targetPassCount,
1068
1115
  process_until_list_end: Boolean(processUntilListEnd),
1069
- detail_limit: detailLimit,
1116
+ detail_limit: normalizedDetailLimit,
1070
1117
  detail_source: normalizedDetailSource,
1071
1118
  close_resume: closeResume,
1072
1119
  cv_acquisition_mode: cvAcquisitionMode,
1073
1120
  call_llm_on_image: Boolean(callLlmOnImage),
1121
+ screening_mode: normalizedScreeningMode,
1122
+ llm_configured: Boolean(llmConfig),
1123
+ llm_timeout_ms: llmTimeoutMs,
1124
+ llm_image_limit: llmImageLimit,
1125
+ llm_image_detail: llmImageDetail,
1074
1126
  max_image_pages: maxImagePages,
1075
1127
  image_wheel_delta_y: imageWheelDeltaY,
1076
1128
  list_max_scrolls: listMaxScrolls,
@@ -1082,9 +1134,9 @@ export function createChatRunService({
1082
1134
  },
1083
1135
  progress: {
1084
1136
  card_count: 0,
1085
- target_count: targetPassCount || (processUntilListEnd ? "all" : Math.max(1, Number(maxCandidates) || 1)),
1137
+ target_count: targetPassCount || (processUntilListEnd ? "all" : processedLimit),
1086
1138
  target_pass_count: targetPassCount,
1087
- processed_limit: Math.max(1, Number(maxCandidates) || 1),
1139
+ processed_limit: processedLimit,
1088
1140
  processed: 0,
1089
1141
  screened: 0,
1090
1142
  detail_opened: 0,
@@ -1104,7 +1156,7 @@ export function createChatRunService({
1104
1156
  maxCandidates,
1105
1157
  targetPassCount,
1106
1158
  processUntilListEnd,
1107
- detailLimit,
1159
+ detailLimit: normalizedDetailLimit,
1108
1160
  detailSource: normalizedDetailSource,
1109
1161
  closeResume,
1110
1162
  requestResumeForPassed,
@@ -1123,6 +1175,7 @@ export function createChatRunService({
1123
1175
  llmTimeoutMs,
1124
1176
  llmImageLimit,
1125
1177
  llmImageDetail,
1178
+ screeningMode: normalizedScreeningMode,
1126
1179
  listMaxScrolls,
1127
1180
  listStableSignatureLimit,
1128
1181
  listWheelDeltaY,