@reconcrap/boss-recommend-mcp 2.0.2 → 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.2",
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",
@@ -57,6 +57,9 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
57
57
  - 只在用户明确是 chat-only 任务时使用本 skill。
58
58
  - 只要用户提到推荐页、先找人后沟通、或需要推荐筛选阶段,禁止直接调用 `start_boss_chat_run`;必须先交给 `boss-recommend-pipeline` 完成推荐页任务。
59
59
  - 不得在 recommend 任务尚未完成时并行启动独立 chat run。
60
+ - 启动或准备 chat run 时,若本机默认 `127.0.0.1:9222` Chrome DevTools 端口不可连,工具会自动打开 Chrome 并导航到 `https://www.zhipin.com/web/chat/index`。
61
+ - 只有工具返回 `BOSS_LOGIN_REQUIRED` / `requires_login=true` 时,才要求用户在自动打开的 Chrome 窗口人工登录 Boss 后重试;不要把“没开 9222 Chrome”当作缺参。
62
+ - 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
60
63
  - `job` / `start_from` / `criteria` 缺一不可;缺参时只补缺口。
61
64
  - `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
62
65
  - 当用户说“全部候选人/所有候选人”时,必须按“扫到底(unlimited)”处理,不要再追问正整数。
@@ -118,8 +118,14 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
118
118
 
119
119
  - 执行前必须通过:
120
120
  - `screening-config.json` 可用且非占位值(`baseUrl/apiKey/model`)
121
- - Chrome DevTools 端口可连
122
- - Boss 已登录且位于 `https://www.zhipin.com/web/chat/recommend`
121
+ - 工具可连接或自动启动本机 Chrome DevTools 端口(默认 `127.0.0.1:9222`)
122
+ - Boss 已登录;若当前没有 9222 Chrome,工具会自动打开 Chrome 并导航到 `https://www.zhipin.com/web/chat/recommend`
123
+ - 只有工具返回 `BOSS_LOGIN_REQUIRED` / `requires_login=true` 时,才要求用户人工登录 Boss 后重试
124
+
125
+ - 不要在运行前要求用户手动打开 9222 Chrome。只有这些情况需要人工介入:
126
+ - 工具明确报告 `BOSS_LOGIN_REQUIRED`
127
+ - 本机找不到 Chrome 可执行文件,并提示设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`
128
+ - 用户配置的是非本机 debug host,工具无法安全自动启动
123
129
 
124
130
  - `PIPELINE_PREFLIGHT_FAILED` 处理顺序:
125
131
  1. 若 `screen_config` 失败:让用户提供真实 `baseUrl/apiKey/model`,并在 `guidance.config_path` 修改后明确回复“已修改完成”。
@@ -153,3 +159,4 @@ MCP 不可用时:
153
159
  - 页面就绪失败提示必须包含 `debug_port`、recommend URL、以及登录 URL(若未登录):
154
160
  - `https://www.zhipin.com/web/chat/recommend`
155
161
  - `https://www.zhipin.com/web/user/?ka=bticket`
162
+ - 若错误是 `BOSS_LOGIN_REQUIRED`,提示用户在自动打开的 Chrome 窗口完成登录,然后原参数重试;不要改用 search/recruit 路径。
@@ -25,6 +25,9 @@ description: "Use when users want Boss search/recruit-page screening via the uni
25
25
  - 如果用户说聊天页、未读、全部聊天、求简历,必须交给 `boss-chat`。
26
26
  - 禁止调用旧包:`@reconcrap/boss-recruit-mcp`、`boss-recruit-mcp`、旧本地 recruit repo、旧 vendor 脚本。
27
27
  - 浏览器自动化必须走 CDP-only 2.x MCP 工具;不得要求用户启用 legacy page-JS 或 `Runtime.evaluate` 路径。
28
+ - 启动 search/recruit run 时,若本机默认 `127.0.0.1:9222` Chrome DevTools 端口不可连,工具会自动打开 Chrome 并导航到 `https://www.zhipin.com/web/chat/search`。
29
+ - 只有工具返回 `BOSS_LOGIN_REQUIRED` / `requires_login=true` 时,才要求用户在自动打开的 Chrome 窗口人工登录 Boss 后重试;不要把“没开 9222 Chrome”当作缺参。
30
+ - 若本机找不到 Chrome,可提示用户设置 `BOSS_MCP_CHROME_PATH` 或 `BOSS_RECOMMEND_CHROME_PATH`;非本机 debug host 不自动启动。
28
31
  - 若用户未提供岗位,必须先询问岗位。搜索页岗位选择在关键词输入框旁边;不要猜测默认岗位。
29
32
  - 若用户提供城市、学历、学校、关键词、过滤已看、人选目标数、筛选条件、post action、max greet 等参数,必须逐项传入或确认。
30
33
  - `post_action=greet` 时必须确认 `max_greet_count`;不要默认等于 `target_count`。
package/src/chat-mcp.js CHANGED
@@ -4,8 +4,13 @@ import process from "node:process";
4
4
  import {
5
5
  assertNoForbiddenCdpCalls,
6
6
  bringPageToFront,
7
- connectToChromeTarget,
7
+ connectToChromeTargetOrOpen,
8
+ createBossLoginRequiredError,
9
+ detectBossLoginState,
8
10
  enableDomains,
11
+ getMainFrameUrl,
12
+ isBossLoginUrl,
13
+ waitForMainFrameUrl,
9
14
  sleep
10
15
  } from "./core/browser/index.js";
11
16
  import {
@@ -40,6 +45,7 @@ import {
40
45
  getBossChatDataDir,
41
46
  getBossChatTargetCountValue,
42
47
  normalizeTargetCountInput,
48
+ resolveBossConfiguredOutputDir,
43
49
  resolveBossChatRuntimeLayout,
44
50
  resolveBossScreeningConfig
45
51
  } from "./chat-runtime-config.js";
@@ -118,12 +124,14 @@ function getChatRunArtifacts(runId) {
118
124
  const normalized = normalizeRunId(runId);
119
125
  if (!normalized) return null;
120
126
  const runsDir = getChatRunsDir();
127
+ const outputDir = resolveBossConfiguredOutputDir("", runsDir);
121
128
  return {
122
129
  runs_dir: runsDir,
130
+ output_dir: outputDir,
123
131
  run_state_path: path.join(runsDir, `${normalized}.json`),
124
132
  checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
125
- output_csv: path.join(runsDir, `${normalized}.results.csv`),
126
- 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`)
127
135
  };
128
136
  }
129
137
 
@@ -472,6 +480,14 @@ async function waitForHealthyChat(client, config, {
472
480
  const started = Date.now();
473
481
  let lastCheck = null;
474
482
  while (Date.now() - started <= timeoutMs) {
483
+ const loginDetection = await detectBossLoginState(client).catch(() => null);
484
+ if (loginDetection?.requires_login) {
485
+ return {
486
+ status: "login_required",
487
+ summary: "Boss login is required",
488
+ loginDetection
489
+ };
490
+ }
475
491
  const roots = await resolveChatSelfHealRoots(client, config);
476
492
  lastCheck = await runSelfHealCheck({
477
493
  client,
@@ -493,24 +509,18 @@ async function connectChatChromeSession({
493
509
  allowNavigate = true,
494
510
  slowLive = false
495
511
  } = {}) {
496
- let session;
497
- try {
498
- session = await connectToChromeTarget({
499
- host,
500
- port,
501
- targetUrlIncludes
502
- });
503
- } catch (error) {
504
- if (!allowNavigate) throw error;
505
- session = await connectToChromeTarget({
506
- host,
507
- port,
508
- targetPredicate: (target) => (
509
- target?.type === "page"
510
- && isRecoverableChatTargetUrl(target?.url)
511
- )
512
- });
513
- }
512
+ const session = await connectToChromeTargetOrOpen({
513
+ host,
514
+ port,
515
+ targetUrlIncludes,
516
+ targetUrl: CHAT_TARGET_URL,
517
+ allowNavigate,
518
+ slowLive,
519
+ fallbackTargetPredicate: (target) => (
520
+ target?.type === "page"
521
+ && (isRecoverableChatTargetUrl(target?.url) || String(target?.url || "").includes("zhipin.com"))
522
+ )
523
+ });
514
524
 
515
525
  const { client, target } = session;
516
526
  await enableDomains(client, ["Page", "DOM", "Input", "Network", "Accessibility"]);
@@ -527,20 +537,90 @@ async function connectChatChromeSession({
527
537
  if (allowNavigate && shouldNavigateToChat(targetUrl)) {
528
538
  await client.Page.navigate({ url: CHAT_TARGET_URL });
529
539
  const settleMs = slowLive ? 10000 : 5000;
530
- await sleep(settleMs);
540
+ const waited = await waitForMainFrameUrl(
541
+ client,
542
+ (url) => isBossLoginUrl(url) || !shouldNavigateToChat(url),
543
+ { timeoutMs: settleMs, intervalMs: 500 }
544
+ );
531
545
  navigation = {
532
546
  navigated: true,
533
547
  url: CHAT_TARGET_URL,
534
- settle_ms: settleMs
548
+ settle_ms: settleMs,
549
+ observed_url: waited.url || null,
550
+ observed_url_ok: waited.ok
535
551
  };
536
552
  }
553
+ let currentUrl = await getMainFrameUrl(client).catch(() => navigation.url || targetUrl);
554
+ if (allowNavigate && shouldNavigateToChat(currentUrl) && !isBossLoginUrl(currentUrl)) {
555
+ await client.Page.navigate({ url: CHAT_TARGET_URL });
556
+ const settleMs = slowLive ? 10000 : 5000;
557
+ const waited = await waitForMainFrameUrl(
558
+ client,
559
+ (url) => isBossLoginUrl(url) || !shouldNavigateToChat(url),
560
+ { timeoutMs: settleMs, intervalMs: 500 }
561
+ );
562
+ navigation = {
563
+ navigated: true,
564
+ url: CHAT_TARGET_URL,
565
+ settle_ms: settleMs,
566
+ observed_url: waited.url || null,
567
+ observed_url_ok: waited.ok,
568
+ reason: "observed_url_mismatch"
569
+ };
570
+ currentUrl = await getMainFrameUrl(client).catch(() => waited.url || currentUrl);
571
+ }
572
+ const loginDetection = await detectBossLoginState(client, { currentUrl }).catch(() => ({
573
+ requires_login: isBossLoginUrl(currentUrl),
574
+ reason: "login_detection_failed",
575
+ current_url: currentUrl
576
+ }));
577
+ if (loginDetection.requires_login) {
578
+ await session.close?.();
579
+ throw createBossLoginRequiredError({
580
+ domain: "chat",
581
+ currentUrl: loginDetection.current_url || currentUrl,
582
+ targetUrl: CHAT_TARGET_URL,
583
+ loginDetection,
584
+ chrome: session.chrome || null
585
+ });
586
+ }
587
+ if (shouldNavigateToChat(currentUrl)) {
588
+ await session.close?.();
589
+ throw new Error(`Boss chat page did not navigate to ${CHAT_TARGET_URL}; current URL: ${currentUrl || "unknown"}`);
590
+ }
537
591
 
538
592
  const selfHealConfig = buildChatSelfHealConfig();
539
593
  const health = await waitForHealthyChat(client, selfHealConfig, {
540
594
  timeoutMs: slowLive ? 180000 : 90000,
541
595
  intervalMs: slowLive ? 1200 : 800
542
596
  });
597
+ if (health?.loginDetection?.requires_login) {
598
+ await session.close?.();
599
+ throw createBossLoginRequiredError({
600
+ domain: "chat",
601
+ currentUrl: health.loginDetection.current_url || currentUrl,
602
+ targetUrl: CHAT_TARGET_URL,
603
+ loginDetection: health.loginDetection,
604
+ chrome: session.chrome || null
605
+ });
606
+ }
543
607
  if (!health || health.status !== HEALTH_STATUS.HEALTHY) {
608
+ const latestUrl = await getMainFrameUrl(client).catch(() => currentUrl);
609
+ const latestLoginDetection = await detectBossLoginState(client, { currentUrl: latestUrl }).catch(() => ({
610
+ requires_login: isBossLoginUrl(latestUrl),
611
+ reason: "login_detection_failed",
612
+ current_url: latestUrl
613
+ }));
614
+ if (latestLoginDetection.requires_login) {
615
+ await session.close?.();
616
+ throw createBossLoginRequiredError({
617
+ domain: "chat",
618
+ currentUrl: latestLoginDetection.current_url || latestUrl,
619
+ targetUrl: CHAT_TARGET_URL,
620
+ loginDetection: latestLoginDetection,
621
+ chrome: session.chrome || null
622
+ });
623
+ }
544
624
  throw new Error(`Boss chat page is not healthy: ${health?.status || "missing"}`);
545
625
  }
546
626
 
@@ -556,7 +636,7 @@ async function readChatJobOptionsFromSession(session) {
556
636
  return readChatJobOptions(session.client, roots.rootNodes.top);
557
637
  }
558
638
 
559
- function normalizeChatStartInput(args = {}) {
639
+ function normalizeChatStartInput(args = {}, configResolution = null) {
560
640
  const target = normalizeTargetCountInput(getBossChatTargetCountValue(args));
561
641
  return {
562
642
  profile: normalizeText(args.profile) || "default",
@@ -568,7 +648,10 @@ function normalizeChatStartInput(args = {}) {
568
648
  targetCount: target.targetCount,
569
649
  publicTargetCount: target.publicValue,
570
650
  host: normalizeText(args.host) || DEFAULT_CHAT_HOST,
571
- port: parsePositiveInteger(args.port, DEFAULT_CHAT_PORT),
651
+ port: parsePositiveInteger(
652
+ args.port,
653
+ configResolution?.ok ? configResolution.config.debugPort : DEFAULT_CHAT_PORT
654
+ ),
572
655
  targetUrlIncludes: normalizeText(args.target_url_includes) || CHAT_TARGET_URL,
573
656
  allowNavigate: args.allow_navigate !== false,
574
657
  slowLive: args.slow_live === true
@@ -723,7 +806,6 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
723
806
  const shouldRequestResume = shouldRequestChatResume(args);
724
807
  const useLlm = shouldUseChatLlm(args, shouldRequestResume);
725
808
  const configResolution = useLlm ? resolveBossScreeningConfig(workspaceRoot) : { ok: false };
726
- const configFile = configResolution.ok ? readJsonFile(configResolution.config_path) : null;
727
809
  return {
728
810
  client: session.client,
729
811
  targetUrl: CHAT_TARGET_URL,
@@ -750,8 +832,7 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
750
832
  maxImagePages: parsePositiveInteger(args.max_image_pages, 8),
751
833
  imageWheelDeltaY: parsePositiveInteger(args.image_wheel_delta_y, 650),
752
834
  llmConfig: configResolution.ok ? {
753
- ...configResolution.config,
754
- apiKey: configFile?.apiKey || ""
835
+ ...configResolution.config
755
836
  } : null,
756
837
  llmTimeoutMs: parsePositiveInteger(args.llm_timeout_ms, slowLive ? 180000 : 120000),
757
838
  llmImageLimit: parsePositiveInteger(args.llm_image_limit, 8),
@@ -811,7 +892,8 @@ function trackChatRun(runId) {
811
892
  }
812
893
 
813
894
  async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {}) {
814
- const normalized = normalizeChatStartInput(args);
895
+ const defaultConfigResolution = resolveBossScreeningConfig(workspaceRoot);
896
+ const normalized = normalizeChatStartInput(args, defaultConfigResolution);
815
897
  const missingFields = getMissingChatStartFields(args, normalized);
816
898
  if (missingFields.length) {
817
899
  return buildNeedInputResponse({
@@ -845,13 +927,21 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {})
845
927
  slowLive: normalized.slowLive
846
928
  });
847
929
  } catch (error) {
930
+ const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
848
931
  return {
849
932
  status: "FAILED",
850
933
  error: {
851
- code: "BOSS_CHAT_PAGE_NOT_READY",
934
+ code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "BOSS_CHAT_PAGE_NOT_READY",
852
935
  message: error?.message || "Boss chat page is not ready",
936
+ requires_login: Boolean(error?.requires_login),
937
+ login_url: error?.login_url || null,
938
+ login_detection: error?.login_detection || null,
939
+ chrome: error?.chrome || null,
940
+ current_url: error?.current_url || null,
941
+ target_url: error?.target_url || CHAT_TARGET_URL,
853
942
  retryable: true
854
- }
943
+ },
944
+ chrome: error?.chrome || null
855
945
  };
856
946
  }
857
947
 
@@ -880,7 +970,8 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {})
880
970
  host: normalized.host,
881
971
  port: normalized.port,
882
972
  target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
883
- target_id: session.target?.id || null
973
+ target_id: session.target?.id || null,
974
+ auto_launch: session.chrome || null
884
975
  },
885
976
  health: session.health || null
886
977
  });
@@ -901,7 +992,8 @@ async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {})
901
992
  }
902
993
 
903
994
  export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } = {}) {
904
- const normalized = normalizeChatStartInput(args);
995
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
996
+ const normalized = normalizeChatStartInput(args, configResolution);
905
997
  let session;
906
998
  try {
907
999
  session = await chatConnectorImpl({
@@ -912,12 +1004,19 @@ export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } =
912
1004
  slowLive: normalized.slowLive
913
1005
  });
914
1006
  } catch (error) {
1007
+ const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
915
1008
  return {
916
1009
  status: "FAILED",
917
1010
  stage: "chat_run_setup",
918
1011
  error: {
919
- code: "BOSS_CHAT_PAGE_NOT_READY",
1012
+ code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "BOSS_CHAT_PAGE_NOT_READY",
920
1013
  message: error?.message || "Boss chat page is not ready",
1014
+ requires_login: Boolean(error?.requires_login),
1015
+ login_url: error?.login_url || null,
1016
+ login_detection: error?.login_detection || null,
1017
+ chrome: error?.chrome || null,
1018
+ current_url: error?.current_url || null,
1019
+ target_url: error?.target_url || CHAT_TARGET_URL,
921
1020
  retryable: true
922
1021
  },
923
1022
  runtime_evaluate_used: false,
@@ -926,7 +1025,8 @@ export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } =
926
1025
  chrome: {
927
1026
  host: normalized.host,
928
1027
  port: normalized.port,
929
- target_url: CHAT_TARGET_URL
1028
+ target_url: CHAT_TARGET_URL,
1029
+ auto_launch: error?.chrome || null
930
1030
  }
931
1031
  };
932
1032
  }
@@ -979,16 +1079,24 @@ export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } =
979
1079
  host: normalized.host,
980
1080
  port: normalized.port,
981
1081
  target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
982
- target_id: session.target?.id || null
1082
+ target_id: session.target?.id || null,
1083
+ auto_launch: session.chrome || null
983
1084
  }
984
1085
  };
985
1086
  } catch (error) {
1087
+ const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
986
1088
  return {
987
1089
  status: "FAILED",
988
1090
  stage: "chat_run_setup",
989
1091
  error: {
990
- code: "BOSS_CHAT_PREPARE_FAILED",
1092
+ code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "BOSS_CHAT_PREPARE_FAILED",
991
1093
  message: error?.message || "Boss chat CDP-only prepare failed",
1094
+ requires_login: Boolean(error?.requires_login),
1095
+ login_url: error?.login_url || null,
1096
+ login_detection: error?.login_detection || null,
1097
+ chrome: error?.chrome || null,
1098
+ current_url: error?.current_url || null,
1099
+ target_url: error?.target_url || CHAT_TARGET_URL,
992
1100
  retryable: true
993
1101
  },
994
1102
  runtime_evaluate_used: false,
@@ -998,7 +1106,8 @@ export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } =
998
1106
  host: normalized.host,
999
1107
  port: normalized.port,
1000
1108
  target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
1001
- target_id: session.target?.id || null
1109
+ target_id: session.target?.id || null,
1110
+ auto_launch: session.chrome || null
1002
1111
  }
1003
1112
  };
1004
1113
  } finally {
@@ -1026,6 +1135,7 @@ export async function bossChatHealthCheckTool({ workspaceRoot = "", args = {} }
1026
1135
  cli_path: null,
1027
1136
  config_path: configResolution.config_path || null,
1028
1137
  config_dir: configResolution.config_dir || null,
1138
+ output_dir: configResolution.ok ? configResolution.config.outputDir || null : null,
1029
1139
  debug_port: port,
1030
1140
  shared_llm_config: configResolution.ok === true,
1031
1141
  data_dir: runtimeLayout.data_dir,
@@ -1073,17 +1183,25 @@ export async function bossChatHealthCheckTool({ workspaceRoot = "", args = {} }
1073
1183
  host,
1074
1184
  port,
1075
1185
  target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
1076
- target_id: session.target?.id || null
1186
+ target_id: session.target?.id || null,
1187
+ auto_launch: session.chrome || null
1077
1188
  },
1078
1189
  message: "Boss chat CDP-only health check passed with shared self-heal probes."
1079
1190
  };
1080
1191
  } catch (error) {
1192
+ const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
1081
1193
  return {
1082
1194
  status: "FAILED",
1083
1195
  ...basePayload,
1084
1196
  error: {
1085
- code: "BOSS_CHAT_PAGE_NOT_READY",
1197
+ code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "BOSS_CHAT_PAGE_NOT_READY",
1086
1198
  message: error?.message || "Boss chat page is not ready",
1199
+ requires_login: Boolean(error?.requires_login),
1200
+ login_url: error?.login_url || null,
1201
+ login_detection: error?.login_detection || null,
1202
+ chrome: error?.chrome || null,
1203
+ current_url: error?.current_url || null,
1204
+ target_url: error?.target_url || CHAT_TARGET_URL,
1087
1205
  retryable: true
1088
1206
  },
1089
1207
  runtime_evaluate_used: false,
@@ -1093,7 +1211,8 @@ export async function bossChatHealthCheckTool({ workspaceRoot = "", args = {} }
1093
1211
  host,
1094
1212
  port,
1095
1213
  target_url: session?.navigation?.url || session?.target?.url || targetUrlIncludes,
1096
- target_id: session?.target?.id || null
1214
+ target_id: session?.target?.id || null,
1215
+ auto_launch: error?.chrome || session?.chrome || null
1097
1216
  }
1098
1217
  };
1099
1218
  } finally {
@@ -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;