@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.0

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.
Files changed (85) hide show
  1. package/README.md +53 -33
  2. package/package.json +61 -9
  3. package/skills/boss-recommend-pipeline/SKILL.md +4 -0
  4. package/src/chat-mcp.js +1333 -0
  5. package/src/chat-runtime-config.js +559 -0
  6. package/src/cli.js +1095 -196
  7. package/src/core/browser/index.js +378 -0
  8. package/src/core/capture/index.js +298 -0
  9. package/src/core/cv-acquisition/index.js +219 -0
  10. package/src/core/greet-quota/index.js +54 -0
  11. package/src/core/infinite-list/index.js +459 -0
  12. package/src/core/reporting/legacy-csv.js +332 -0
  13. package/src/core/run/index.js +286 -0
  14. package/src/core/screening/index.js +1166 -0
  15. package/src/core/self-heal/index.js +848 -0
  16. package/src/domains/chat/cards.js +129 -0
  17. package/src/domains/chat/constants.js +183 -0
  18. package/src/domains/chat/detail.js +1369 -0
  19. package/src/domains/chat/index.js +7 -0
  20. package/src/domains/chat/jobs.js +334 -0
  21. package/src/domains/chat/page-guard.js +88 -0
  22. package/src/domains/chat/roots.js +56 -0
  23. package/src/domains/chat/run-service.js +1101 -0
  24. package/src/domains/recommend/actions.js +457 -0
  25. package/src/domains/recommend/cards.js +228 -0
  26. package/src/domains/recommend/constants.js +141 -0
  27. package/src/domains/recommend/detail.js +341 -0
  28. package/src/domains/recommend/filters.js +581 -0
  29. package/src/domains/recommend/index.js +10 -0
  30. package/src/domains/recommend/jobs.js +232 -0
  31. package/src/domains/recommend/refresh.js +204 -0
  32. package/src/domains/recommend/roots.js +78 -0
  33. package/src/domains/recommend/run-service.js +903 -0
  34. package/src/domains/recommend/scopes.js +245 -0
  35. package/src/domains/recruit/actions.js +277 -0
  36. package/src/domains/recruit/cards.js +67 -0
  37. package/src/domains/recruit/constants.js +130 -0
  38. package/src/domains/recruit/detail.js +414 -0
  39. package/src/domains/recruit/index.js +9 -0
  40. package/src/domains/recruit/instruction-parser.js +451 -0
  41. package/src/domains/recruit/refresh.js +40 -0
  42. package/src/domains/recruit/roots.js +68 -0
  43. package/src/domains/recruit/run-service.js +580 -0
  44. package/src/domains/recruit/search.js +1149 -0
  45. package/src/index.js +578 -419
  46. package/src/recommend-mcp.js +1257 -0
  47. package/src/recruit-mcp.js +1035 -0
  48. package/src/adapters.js +0 -3079
  49. package/src/boss-chat.js +0 -1037
  50. package/src/pipeline.js +0 -2249
  51. package/src/recommend-healing-config.js +0 -131
  52. package/src/recommend-healing-rules.json +0 -261
  53. package/src/self-heal.js +0 -2237
  54. package/src/test-adapters-runtime.js +0 -628
  55. package/src/test-boss-chat.js +0 -3196
  56. package/src/test-index-async.js +0 -498
  57. package/src/test-parser.js +0 -742
  58. package/src/test-pipeline.js +0 -2703
  59. package/src/test-run-state.js +0 -152
  60. package/src/test-self-heal.js +0 -224
  61. package/vendor/boss-chat-cli/README.md +0 -134
  62. package/vendor/boss-chat-cli/package.json +0 -53
  63. package/vendor/boss-chat-cli/src/app.js +0 -1501
  64. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  65. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  66. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  67. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  68. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  69. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  70. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  71. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  72. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  73. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  74. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  75. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  76. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  77. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  78. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  79. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  80. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
  81. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  82. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  83. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
  84. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  85. package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
package/src/index.js CHANGED
@@ -6,21 +6,61 @@ import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import {
8
8
  getFeaturedCalibrationResolution,
9
- runRecommendCalibration
10
- } from "./adapters.js";
11
- import {
12
- cancelBossChatRun,
13
- getBossChatHealthCheck,
14
- getBossChatRun,
15
9
  getBossChatTargetCountValue,
16
- normalizeTargetCountInput,
17
- pauseBossChatRun,
18
- prepareBossChatRun,
19
- resumeBossChatRun,
20
- startBossChatRun
21
- } from "./boss-chat.js";
22
- import { runRecommendPipeline } from "./pipeline.js";
23
- import { runRecommendSelfHeal } from "./self-heal.js";
10
+ normalizeTargetCountInput
11
+ } from "./chat-runtime-config.js";
12
+ import {
13
+ __resetChatMcpStateForTests,
14
+ __setChatMcpConnectorForTests,
15
+ __setChatMcpJobReaderForTests,
16
+ __setChatMcpWorkflowForTests,
17
+ bossChatHealthCheckTool,
18
+ cancelBossChatRunTool,
19
+ getBossChatRunTool,
20
+ pauseBossChatRunTool,
21
+ prepareBossChatRunTool,
22
+ resumeBossChatRunTool,
23
+ startBossChatRunTool
24
+ } from "./chat-mcp.js";
25
+ import {
26
+ __resetRecruitMcpStateForTests,
27
+ __setRecruitMcpConnectorForTests,
28
+ __setRecruitMcpWorkflowForTests,
29
+ cancelRecruitPipelineRunTool,
30
+ createRecruitPipelineInputSchema,
31
+ createRecruitRunIdInputSchema,
32
+ getRecruitPipelineRunTool,
33
+ pauseRecruitPipelineRunTool,
34
+ resumeRecruitPipelineRunTool,
35
+ runRecruitPipelineTool,
36
+ startRecruitPipelineRunTool,
37
+ validateRecruitPipelineArgs
38
+ } from "./recruit-mcp.js";
39
+ import {
40
+ __resetRecommendMcpStateForTests,
41
+ __setRecommendMcpConnectorForTests,
42
+ __setRecommendMcpJobReaderForTests,
43
+ __setRecommendMcpWorkflowForTests,
44
+ cancelRecommendPipelineRunTool,
45
+ getRecommendPipelineRunTool,
46
+ listRecommendJobsTool,
47
+ pauseRecommendPipelineRunTool,
48
+ resumeRecommendPipelineRunTool,
49
+ startRecommendPipelineRunTool
50
+ } from "./recommend-mcp.js";
51
+ import {
52
+ assertNoForbiddenCdpCalls,
53
+ bringPageToFront,
54
+ connectToChromeTarget,
55
+ enableDomains,
56
+ sleep as sleepMs
57
+ } from "./core/browser/index.js";
58
+ import {
59
+ buildRecommendSelfHealConfig,
60
+ HEALTH_STATUS,
61
+ resolveRecommendSelfHealRoots,
62
+ runSelfHealCheck
63
+ } from "./core/self-heal/index.js";
24
64
  import {
25
65
  RUN_MODE_ASYNC,
26
66
  RUN_STAGE_CHAT_FOLLOWUP,
@@ -50,6 +90,7 @@ const TOOL_GET_RUN = "get_recommend_pipeline_run";
50
90
  const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
51
91
  const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
52
92
  const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
93
+ const TOOL_LIST_RECOMMEND_JOBS = "list_recommend_jobs";
53
94
  const TOOL_RUN_FEATURED_CALIBRATION = "run_featured_calibration";
54
95
  const TOOL_GET_FEATURED_CALIBRATION_STATUS = "get_featured_calibration_status";
55
96
  const TOOL_RUN_RECOMMEND_SELF_HEAL = "run_recommend_self_heal";
@@ -60,6 +101,12 @@ const TOOL_BOSS_CHAT_GET_RUN = "get_boss_chat_run";
60
101
  const TOOL_BOSS_CHAT_PAUSE_RUN = "pause_boss_chat_run";
61
102
  const TOOL_BOSS_CHAT_RESUME_RUN = "resume_boss_chat_run";
62
103
  const TOOL_BOSS_CHAT_CANCEL_RUN = "cancel_boss_chat_run";
104
+ const TOOL_RUN_RECRUIT_PIPELINE = "run_recruit_pipeline";
105
+ const TOOL_START_RECRUIT_PIPELINE_RUN = "start_recruit_pipeline_run";
106
+ const TOOL_GET_RECRUIT_PIPELINE_RUN = "get_recruit_pipeline_run";
107
+ const TOOL_CANCEL_RECRUIT_PIPELINE_RUN = "cancel_recruit_pipeline_run";
108
+ const TOOL_PAUSE_RECRUIT_PIPELINE_RUN = "pause_recruit_pipeline_run";
109
+ const TOOL_RESUME_RECRUIT_PIPELINE_RUN = "resume_recruit_pipeline_run";
63
110
 
64
111
  const SERVER_NAME = "boss-recommend-mcp";
65
112
  const FRAMING_UNKNOWN = "unknown";
@@ -68,11 +115,23 @@ const FRAMING_LINE = "line";
68
115
  const DETACHED_WORKER_FLAG = "--detached-worker";
69
116
  const DETACHED_WORKER_RUN_ID_FLAG = "--run-id";
70
117
  const DETACHED_WORKER_RESUME_FLAG = "--resume";
118
+ const featuredCalibrationUnsupportedCode = "FEATURED_CALIBRATION_UNSUPPORTED_CDP_ONLY";
119
+ const recommendSelfHealApplyUnsupportedCode = "RECOMMEND_SELF_HEAL_APPLY_UNSUPPORTED_CDP_ONLY";
120
+ const detachedLegacyPipelineUnsupportedCode = "DETACHED_LEGACY_PIPELINE_UNSUPPORTED_CDP_ONLY";
121
+ const recommendTargetUrl = "https://www.zhipin.com/web/chat/recommend";
71
122
 
72
- let runPipelineImpl = runRecommendPipeline;
73
- let runSelfHealImpl = runRecommendSelfHeal;
123
+ let runPipelineImpl = null;
124
+ let runSelfHealImpl = null;
74
125
  let spawnProcessImpl = spawn;
75
126
  const TERMINAL_RUN_STATES = new Set([RUN_STATE_COMPLETED, RUN_STATE_FAILED, RUN_STATE_CANCELED]);
127
+
128
+ async function getRunPipelineImpl() {
129
+ if (typeof runPipelineImpl === "function") return runPipelineImpl;
130
+ const error = new Error("Detached legacy recommend workers are fenced during the CDP-only rewrite. Active recommend execution must use start_recommend_pipeline_run, which routes through the shared CDP-only recommend run service.");
131
+ error.code = detachedLegacyPipelineUnsupportedCode;
132
+ error.retryable = false;
133
+ throw error;
134
+ }
76
135
 
77
136
  function normalizeText(value) {
78
137
  return String(value || "").replace(/\s+/g, " ").trim();
@@ -455,6 +514,78 @@ function createRunInputSchema() {
455
514
  }
456
515
  },
457
516
  additionalProperties: false
517
+ },
518
+ host: {
519
+ type: "string",
520
+ description: "可选,Chrome 调试 host;默认 127.0.0.1"
521
+ },
522
+ port: {
523
+ type: "integer",
524
+ minimum: 1,
525
+ description: "可选,Chrome 调试端口;默认 9222"
526
+ },
527
+ target_url_includes: {
528
+ type: "string",
529
+ description: "可选,Chrome target URL 匹配片段;默认 Boss recommend 页"
530
+ },
531
+ allow_navigate: {
532
+ type: "boolean",
533
+ description: "可选,未在 recommend 页时允许通过 Page.navigate 切换;默认 true"
534
+ },
535
+ slow_live: {
536
+ type: "boolean",
537
+ description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
538
+ },
539
+ max_candidates: createTargetCountInputSchema("本次最多处理候选人数;默认使用确认后的 target_count,未设置时为 5"),
540
+ detail_limit: {
541
+ type: "integer",
542
+ minimum: 0,
543
+ description: "打开详情的人数上限;默认 0,0 表示只用卡片信息"
544
+ },
545
+ delay_ms: {
546
+ type: "integer",
547
+ minimum: 0,
548
+ description: "候选人之间的延迟;live pause/resume 测试可增大它"
549
+ },
550
+ execute_post_action: {
551
+ type: "boolean",
552
+ description: "可选,是否实际执行通过后的 recommend 后置动作 favorite/greet;默认 true"
553
+ },
554
+ dry_run_post_action: {
555
+ type: "boolean",
556
+ description: "可选,只验证 recommend 后置动作发现/配额/可点击路径,不实际点击 favorite/greet"
557
+ },
558
+ action_timeout_ms: {
559
+ type: "integer",
560
+ minimum: 1000,
561
+ description: "可选,等待详情页 favorite/greet 控件出现的超时时间"
562
+ },
563
+ action_interval_ms: {
564
+ type: "integer",
565
+ minimum: 100,
566
+ description: "可选,轮询详情页 favorite/greet 控件的间隔"
567
+ },
568
+ action_after_click_delay_ms: {
569
+ type: "integer",
570
+ minimum: 0,
571
+ description: "可选,点击 favorite/greet 后等待页面状态稳定的时间"
572
+ },
573
+ no_filter: {
574
+ type: "boolean",
575
+ description: "开发/live gate 专用:跳过本次筛选器点击,默认 false"
576
+ },
577
+ filter_enabled: {
578
+ type: "boolean",
579
+ description: "开发/live gate 专用:false 时跳过本次筛选器点击"
580
+ },
581
+ refresh_on_end: {
582
+ type: "boolean",
583
+ description: "列表到底且目标数未达成时是否刷新/重新应用筛选;默认 true"
584
+ },
585
+ max_refresh_rounds: {
586
+ type: "integer",
587
+ minimum: 0,
588
+ description: "列表到底后的最大刷新轮数;默认 2"
458
589
  }
459
590
  },
460
591
  required: ["instruction"],
@@ -491,13 +622,97 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
491
622
  type: "string",
492
623
  description: "兼容字段;优先使用 greeting_text。可选首条打招呼消息"
493
624
  },
494
- target_count: createTargetCountInputSchema("本次处理人数上限;支持正整数、all 或 -1(扫到底),也兼容 { value: \"all\" } 等包装对象"),
495
- targetCount: createTargetCountInputSchema("兼容字段;优先使用 target_count。本次处理人数上限,支持正整数、all 或 -1(扫到底)"),
625
+ target_count: createTargetCountInputSchema("通过筛选的人数目标;数字表示通过人数目标,未达标但列表扫到底也算完成;all/全部/扫到底 表示处理完整列表"),
626
+ targetCount: createTargetCountInputSchema("兼容字段;优先使用 target_count。数字表示通过人数目标,all/全部/扫到底 表示处理完整列表"),
496
627
  port: {
497
628
  type: "integer",
498
629
  minimum: 1,
499
630
  description: "可选,覆盖 Chrome 调试端口;未传时读取 screening-config.json.debugPort"
500
631
  },
632
+ host: {
633
+ type: "string",
634
+ description: "可选,Chrome 调试 host;默认 127.0.0.1"
635
+ },
636
+ target_url_includes: {
637
+ type: "string",
638
+ description: "可选,Chrome target URL 匹配片段;默认 Boss chat index"
639
+ },
640
+ allow_navigate: {
641
+ type: "boolean",
642
+ description: "可选,未在 chat index 时允许通过 Page.navigate 切换到 chat 页面;默认 true"
643
+ },
644
+ slow_live: {
645
+ type: "boolean",
646
+ description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
647
+ },
648
+ max_candidates: {
649
+ type: "integer",
650
+ minimum: 1,
651
+ description: "可选,仅用于 target_count=all 时给 CDP-only run 设置安全上限"
652
+ },
653
+ detail_limit: {
654
+ type: "integer",
655
+ minimum: 0,
656
+ description: "可选,打开在线简历详情的人数上限;LLM 或求简历任务默认跟随安全处理上限"
657
+ },
658
+ detail_source: {
659
+ type: "string",
660
+ enum: ["cascade", "network", "dom", "image"],
661
+ description: "可选,详情/CV 抽取来源;默认 cascade"
662
+ },
663
+ delay_ms: {
664
+ type: "integer",
665
+ minimum: 0,
666
+ description: "可选,每个候选人之间的等待毫秒数"
667
+ },
668
+ use_llm: {
669
+ type: "boolean",
670
+ description: "可选,是否使用 screening-config.json 中的 LLM 配置筛选;求简历任务默认使用"
671
+ },
672
+ request_cv: {
673
+ type: "boolean",
674
+ description: "可选,通过筛选后发送消息并点击求简历"
675
+ },
676
+ request_resume: {
677
+ type: "boolean",
678
+ description: "request_cv 的兼容别名"
679
+ },
680
+ ask_cv: {
681
+ type: "boolean",
682
+ description: "request_cv 的兼容别名"
683
+ },
684
+ execute_post_action: {
685
+ type: "boolean",
686
+ description: "可选,执行通过后的后置动作;chat 中等同 request_cv"
687
+ },
688
+ post_action: {
689
+ type: "string",
690
+ description: "可选,支持 request_cv / ask_cv / request_resume / 求简历"
691
+ },
692
+ dry_run_request_cv: {
693
+ type: "boolean",
694
+ description: "可选,只验证求简历动作路径,不实际发送消息或点击求简历"
695
+ },
696
+ llm_timeout_ms: {
697
+ type: "integer",
698
+ minimum: 1000,
699
+ description: "可选,单个候选人的 LLM 调用超时"
700
+ },
701
+ online_resume_button_timeout_ms: {
702
+ type: "integer",
703
+ minimum: 1000,
704
+ description: "可选,选中 chat 候选人后等待在线简历按钮出现的毫秒数;慢 VPN 默认 30000"
705
+ },
706
+ max_image_pages: {
707
+ type: "integer",
708
+ minimum: 1,
709
+ description: "可选,图片简历 fallback 的滚动截图页数上限"
710
+ },
711
+ list_max_scrolls: {
712
+ type: "integer",
713
+ minimum: 1,
714
+ description: "可选,聊天列表无限滚动最大次数"
715
+ },
501
716
  dry_run: { type: "boolean" },
502
717
  no_state: { type: "boolean" },
503
718
  safe_pacing: { type: "boolean" },
@@ -589,11 +804,46 @@ function createRunRecommendSelfHealInputSchema() {
589
804
  };
590
805
  }
591
806
 
807
+ function createListRecommendJobsInputSchema() {
808
+ return {
809
+ type: "object",
810
+ properties: {
811
+ host: {
812
+ type: "string",
813
+ description: "可选,Chrome 调试 host;默认 127.0.0.1"
814
+ },
815
+ port: {
816
+ type: "integer",
817
+ minimum: 1,
818
+ description: "可选,Chrome 调试端口;默认 9222"
819
+ },
820
+ target_url_includes: {
821
+ type: "string",
822
+ description: "可选,Chrome target URL 匹配片段;默认 Boss recommend 页"
823
+ },
824
+ allow_navigate: {
825
+ type: "boolean",
826
+ description: "可选,未在 recommend 页时允许通过 Page.navigate 切换;默认 true"
827
+ },
828
+ slow_live: {
829
+ type: "boolean",
830
+ description: "可选,VPN/慢页面模式,放宽等待时间"
831
+ }
832
+ },
833
+ additionalProperties: false
834
+ };
835
+ }
836
+
592
837
  function createToolsSchema() {
593
838
  return [
594
- {
595
- name: TOOL_START_RUN,
596
- description: "异步启动 Boss 推荐页流水线(含同步门禁预检);只有在前置确认与页面就绪通过后才返回 run_id。",
839
+ {
840
+ name: TOOL_LIST_RECOMMEND_JOBS,
841
+ description: "CDP-only 读取 Boss 推荐页岗位下拉框,返回所有可用岗位完整名称,方便 cron/一次性任务提前填写 job 参数。不会启动筛选任务。",
842
+ inputSchema: createListRecommendJobsInputSchema()
843
+ },
844
+ {
845
+ name: TOOL_START_RUN,
846
+ description: "异步启动 Boss 推荐页流水线(含同步门禁预检);只有在前置确认与页面就绪通过后才返回 run_id。",
597
847
  inputSchema: createRunInputSchema()
598
848
  },
599
849
  {
@@ -665,13 +915,30 @@ function createToolsSchema() {
665
915
  },
666
916
  {
667
917
  name: TOOL_BOSS_CHAT_HEALTH_CHECK,
668
- description: "检查内置 boss-chat 运行时与共享 screening-config.json 是否可用。",
918
+ description: "CDP-only 检查 Boss chat 页面、自愈 probes、共享 screening-config.json 与 chat runtime 目录是否可用。",
669
919
  inputSchema: {
670
920
  type: "object",
671
921
  properties: {
922
+ host: {
923
+ type: "string",
924
+ description: "可选,Chrome 调试 host;默认 127.0.0.1"
925
+ },
672
926
  port: {
673
927
  type: "integer",
674
- minimum: 1
928
+ minimum: 1,
929
+ description: "可选,Chrome 调试端口;默认读取 screening-config.json.debugPort 或 9222"
930
+ },
931
+ target_url_includes: {
932
+ type: "string",
933
+ description: "可选,Chrome target URL 匹配片段;默认 Boss chat 页"
934
+ },
935
+ allow_navigate: {
936
+ type: "boolean",
937
+ description: "可选,未在 chat 页时允许通过 Page.navigate 切换;默认 true"
938
+ },
939
+ slow_live: {
940
+ type: "boolean",
941
+ description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
675
942
  }
676
943
  },
677
944
  additionalProperties: false
@@ -738,6 +1005,36 @@ function createToolsSchema() {
738
1005
  required: ["run_id"],
739
1006
  additionalProperties: false
740
1007
  }
1008
+ },
1009
+ {
1010
+ name: TOOL_RUN_RECRUIT_PIPELINE,
1011
+ description: "兼容 Boss recruit 入口:默认 async;sync 模式会等待终态。所有浏览器动作走共享 CDP-only recruit service。",
1012
+ inputSchema: createRecruitPipelineInputSchema()
1013
+ },
1014
+ {
1015
+ name: TOOL_START_RECRUIT_PIPELINE_RUN,
1016
+ description: "异步启动 Boss recruit 流水线;先完成参数/criteria/default 确认门禁,再连接 Chrome search 页执行。",
1017
+ inputSchema: createRecruitPipelineInputSchema()
1018
+ },
1019
+ {
1020
+ name: TOOL_GET_RECRUIT_PIPELINE_RUN,
1021
+ description: "查询 Boss recruit run_id 的当前状态。",
1022
+ inputSchema: createRecruitRunIdInputSchema()
1023
+ },
1024
+ {
1025
+ name: TOOL_CANCEL_RECRUIT_PIPELINE_RUN,
1026
+ description: "取消运行中的 Boss recruit 任务。",
1027
+ inputSchema: createRecruitRunIdInputSchema()
1028
+ },
1029
+ {
1030
+ name: TOOL_PAUSE_RECRUIT_PIPELINE_RUN,
1031
+ description: "暂停运行中的 Boss recruit 任务。",
1032
+ inputSchema: createRecruitRunIdInputSchema()
1033
+ },
1034
+ {
1035
+ name: TOOL_RESUME_RECRUIT_PIPELINE_RUN,
1036
+ description: "继续已暂停的 Boss recruit 任务。",
1037
+ inputSchema: createRecruitRunIdInputSchema()
741
1038
  }
742
1039
  ];
743
1040
  }
@@ -881,14 +1178,48 @@ function validateRunRecommendSelfHealArgs(args) {
881
1178
  }
882
1179
 
883
1180
  function getLastOutputLine(text) {
884
- const lines = String(text || "")
885
- .split(/\r?\n/)
886
- .map((line) => normalizeText(line))
887
- .filter(Boolean);
888
- return lines.length > 0 ? lines[lines.length - 1] : null;
889
- }
890
-
891
- function normalizeRequiredConfirmations(value) {
1181
+ const lines = String(text || "")
1182
+ .split(/\r?\n/)
1183
+ .map((line) => normalizeText(line))
1184
+ .filter(Boolean);
1185
+ return lines.length > 0 ? lines[lines.length - 1] : null;
1186
+ }
1187
+
1188
+ function buildCdpMethodSummary(methodLog = []) {
1189
+ const summary = {};
1190
+ for (const entry of methodLog) {
1191
+ const method = typeof entry === "string" ? entry : entry?.method;
1192
+ if (!method) continue;
1193
+ summary[method] = (summary[method] || 0) + 1;
1194
+ }
1195
+ return summary;
1196
+ }
1197
+
1198
+ function compactSelfHealCheck(check) {
1199
+ return {
1200
+ domain: check?.domain || null,
1201
+ status: check?.status || null,
1202
+ summary: check?.summary || null,
1203
+ probes: Array.isArray(check?.probes)
1204
+ ? check.probes.map((probe) => ({
1205
+ id: probe.id,
1206
+ type: probe.type,
1207
+ status: probe.status,
1208
+ ok: probe.ok,
1209
+ required: probe.required,
1210
+ count: probe.count,
1211
+ root: probe.root || null,
1212
+ matched_selectors: probe.matched_selectors || undefined,
1213
+ selector_counts: probe.selector_counts || undefined,
1214
+ total_ax_nodes: probe.total_ax_nodes || undefined,
1215
+ error: probe.error || undefined
1216
+ }))
1217
+ : [],
1218
+ drift_report: check?.drift_report || null
1219
+ };
1220
+ }
1221
+
1222
+ function normalizeRequiredConfirmations(value) {
892
1223
  if (!Array.isArray(value)) return [];
893
1224
  return value
894
1225
  .map((item) => normalizeText(item))
@@ -1243,12 +1574,13 @@ async function executeTrackedPipeline({
1243
1574
  : "流水线已启动,等待 preflight。",
1244
1575
  resume: resumeConfig
1245
1576
  });
1246
-
1247
- let result;
1248
- try {
1249
- result = await runPipelineImpl(
1250
- {
1251
- workspaceRoot,
1577
+
1578
+ let result;
1579
+ try {
1580
+ const pipelineImpl = await getRunPipelineImpl();
1581
+ result = await pipelineImpl(
1582
+ {
1583
+ workspaceRoot,
1252
1584
  instruction: args.instruction,
1253
1585
  confirmation: args.confirmation,
1254
1586
  overrides: args.overrides,
@@ -1304,14 +1636,14 @@ async function executeTrackedPipeline({
1304
1636
  };
1305
1637
  }
1306
1638
 
1307
- const failedResult = {
1308
- status: "FAILED",
1309
- error: {
1310
- code: "UNEXPECTED_ERROR",
1311
- message: error?.message || "Unexpected error",
1312
- retryable: true
1313
- }
1314
- };
1639
+ const failedResult = {
1640
+ status: "FAILED",
1641
+ error: {
1642
+ code: error?.code || "UNEXPECTED_ERROR",
1643
+ message: error?.message || "Unexpected error",
1644
+ retryable: error?.retryable !== false
1645
+ }
1646
+ };
1315
1647
  safeUpdateRunState(runId, {
1316
1648
  mode,
1317
1649
  state: RUN_STATE_FAILED,
@@ -1456,348 +1788,24 @@ async function runDetachedWorker({ runId, resumeRun = false, workerPid = process
1456
1788
  return { ok: true };
1457
1789
  }
1458
1790
 
1459
- async function handleStartRunTool({ workspaceRoot, args }) {
1460
- const precheckArgs = buildAsyncPrecheckArgs(args);
1461
- let precheckResult;
1462
- try {
1463
- precheckResult = await runPipelineImpl(
1464
- {
1465
- workspaceRoot,
1466
- instruction: precheckArgs.instruction,
1467
- confirmation: precheckArgs.confirmation,
1468
- overrides: precheckArgs.overrides,
1469
- followUp: precheckArgs.follow_up
1470
- },
1471
- undefined,
1472
- null
1473
- );
1474
- } catch (error) {
1475
- precheckResult = {
1476
- status: "FAILED",
1477
- error: {
1478
- code: "UNEXPECTED_ERROR",
1479
- message: error?.message || "Unexpected error",
1480
- retryable: true
1481
- }
1482
- };
1483
- }
1484
-
1485
- if (precheckResult?.status !== "NEED_CONFIRMATION") {
1486
- return precheckResult;
1487
- }
1488
- if (!hasExplicitFinalConfirmation(args) || !isFinalReviewOnlyConfirmation(precheckResult)) {
1489
- return precheckResult;
1490
- }
1491
-
1492
- cleanupExpiredRuns();
1493
- const runId = createRunId();
1494
- try {
1495
- initializeRunStateOrThrow(runId, RUN_MODE_ASYNC, workspaceRoot, args, process.pid);
1496
- } catch (error) {
1497
- return {
1498
- status: "FAILED",
1499
- error: {
1500
- code: "RUN_STATE_IO_ERROR",
1501
- message: `无法写入运行状态目录:${error.message || "unknown"}`,
1502
- retryable: false
1503
- }
1504
- };
1505
- }
1506
-
1507
- let worker;
1508
- try {
1509
- worker = launchDetachedRunWorker({
1510
- runId,
1511
- resumeRun: false
1512
- });
1513
- } catch (error) {
1514
- const failedMessage = `无法启动 detached 运行进程:${error?.message || "unknown"}`;
1515
- safeUpdateRunState(runId, {
1516
- state: RUN_STATE_FAILED,
1517
- stage: RUN_STAGE_PREFLIGHT,
1518
- last_message: failedMessage,
1519
- error: {
1520
- code: "RUN_WORKER_LAUNCH_FAILED",
1521
- message: failedMessage,
1522
- retryable: true
1523
- },
1524
- result: buildWorkerLaunchFailedPayload(failedMessage)
1525
- });
1526
- return buildWorkerLaunchFailedPayload(failedMessage);
1527
- }
1528
-
1529
- safeUpdateRunState(runId, {
1530
- pid: worker?.pid,
1531
- state: "queued",
1532
- last_message: "异步流水线已启动(detached)。"
1533
- });
1534
-
1535
- return {
1536
- status: "ACCEPTED",
1537
- run_id: runId,
1538
- state: "queued",
1539
- poll_after_sec: getRecommendedPollAfterSec(args),
1540
- message: getDefaultAcceptedMessage(args)
1541
- };
1791
+ async function handleStartRunTool({ workspaceRoot, args }) {
1792
+ return startRecommendPipelineRunTool({ workspaceRoot, args });
1542
1793
  }
1543
-
1794
+
1544
1795
  function handleGetRunTool(args) {
1545
- cleanupExpiredRuns();
1546
- const runId = normalizeText(args?.run_id);
1547
- if (!runId) {
1548
- return {
1549
- status: "FAILED",
1550
- error: {
1551
- code: "INVALID_RUN_ID",
1552
- message: "run_id is required",
1553
- retryable: false
1554
- }
1555
- };
1556
- }
1557
- const snapshot = readRunState(runId);
1558
- if (!snapshot) {
1559
- return {
1560
- status: "FAILED",
1561
- error: {
1562
- code: "RUN_NOT_FOUND",
1563
- message: `未找到 run_id=${runId} 的运行记录。`,
1564
- retryable: false
1565
- }
1566
- };
1567
- }
1568
- const reconciled = reconcileOrphanRunIfNeeded(runId, snapshot);
1569
- return {
1570
- status: "RUN_STATUS",
1571
- run: reconciled
1572
- };
1796
+ return getRecommendPipelineRunTool({ args });
1573
1797
  }
1574
-
1575
- function handleCancelRunTool(args) {
1576
- const runId = normalizeText(args?.run_id);
1577
- if (!runId) {
1578
- return {
1579
- status: "FAILED",
1580
- error: {
1581
- code: "INVALID_RUN_ID",
1582
- message: "run_id is required",
1583
- retryable: false
1584
- }
1585
- };
1586
- }
1587
- const snapshot = readRunState(runId);
1588
- if (!snapshot) {
1589
- return {
1590
- status: "FAILED",
1591
- error: {
1592
- code: "RUN_NOT_FOUND",
1593
- message: `未找到 run_id=${runId} 的运行记录。`,
1594
- retryable: false
1595
- }
1596
- };
1597
- }
1598
- const reconciled = reconcileOrphanRunIfNeeded(runId, snapshot) || snapshot;
1599
1798
 
1600
- if (TERMINAL_RUN_STATES.has(reconciled.state)) {
1601
- return {
1602
- status: "CANCEL_IGNORED",
1603
- run: reconciled,
1604
- message: "目标任务已结束,无需取消。"
1605
- };
1606
- }
1607
-
1608
- if (reconciled.state === RUN_STATE_PAUSED || !isProcessAlive(reconciled.pid)) {
1609
- const canceledRun = finalizeCanceledRun(runId, reconciled);
1610
- return {
1611
- status: "CANCEL_REQUESTED",
1612
- run: canceledRun
1613
- };
1614
- }
1615
- safeUpdateRunState(runId, {
1616
- stage: reconciled.stage || RUN_STAGE_PREFLIGHT,
1617
- last_message: "已收到取消请求,将在当前候选人处理完成后安全停止并落盘 CSV。",
1618
- control: {
1619
- pause_requested: true,
1620
- pause_requested_at: new Date().toISOString(),
1621
- pause_requested_by: TOOL_CANCEL_RUN,
1622
- cancel_requested: true
1623
- }
1624
- });
1799
+ function handleCancelRunTool(args) {
1800
+ return cancelRecommendPipelineRunTool({ args });
1801
+ }
1625
1802
 
1626
- const latest = readRunState(runId) || reconciled;
1627
- return {
1628
- status: "CANCEL_REQUESTED",
1629
- run: latest
1630
- };
1631
- }
1632
-
1633
1803
  function handlePauseRunTool(args) {
1634
- const runId = normalizeText(args?.run_id);
1635
- if (!runId) {
1636
- return {
1637
- status: "FAILED",
1638
- error: {
1639
- code: "INVALID_RUN_ID",
1640
- message: "run_id is required",
1641
- retryable: false
1642
- }
1643
- };
1644
- }
1645
- const snapshot = readRunState(runId);
1646
- if (!snapshot) {
1647
- return {
1648
- status: "FAILED",
1649
- error: {
1650
- code: "RUN_NOT_FOUND",
1651
- message: `未找到 run_id=${runId} 的运行记录。`,
1652
- retryable: false
1653
- }
1654
- };
1655
- }
1656
- const reconciled = reconcileOrphanRunIfNeeded(runId, snapshot) || snapshot;
1804
+ return pauseRecommendPipelineRunTool({ args });
1805
+ }
1657
1806
 
1658
- if (TERMINAL_RUN_STATES.has(reconciled.state)) {
1659
- return {
1660
- status: "PAUSE_IGNORED",
1661
- run: reconciled,
1662
- message: "目标任务已结束,无需暂停。"
1663
- };
1664
- }
1665
- if (reconciled.state === RUN_STATE_PAUSED) {
1666
- return {
1667
- status: "PAUSE_IGNORED",
1668
- run: reconciled,
1669
- message: "目标任务已经处于 paused 状态。"
1670
- };
1671
- }
1672
-
1673
- const requestedRun = safeUpdateRunState(runId, {
1674
- control: {
1675
- pause_requested: true,
1676
- pause_requested_at: new Date().toISOString(),
1677
- pause_requested_by: TOOL_PAUSE_RUN,
1678
- cancel_requested: false
1679
- },
1680
- last_message: "已收到暂停请求,将在当前候选人处理完成后暂停。"
1681
- }) || readRunState(runId) || reconciled;
1682
- return {
1683
- status: "PAUSE_REQUESTED",
1684
- run: requestedRun,
1685
- message: "暂停请求已接收,将在当前候选人处理完成后进入 paused。"
1686
- };
1687
- }
1688
-
1689
1807
  function handleResumeRunTool(args) {
1690
- const runId = normalizeText(args?.run_id);
1691
- if (!runId) {
1692
- return {
1693
- status: "FAILED",
1694
- error: {
1695
- code: "INVALID_RUN_ID",
1696
- message: "run_id is required",
1697
- retryable: false
1698
- }
1699
- };
1700
- }
1701
- const snapshot = readRunState(runId);
1702
- if (!snapshot) {
1703
- return {
1704
- status: "FAILED",
1705
- error: {
1706
- code: "RUN_NOT_FOUND",
1707
- message: `未找到 run_id=${runId} 的运行记录。`,
1708
- retryable: false
1709
- }
1710
- };
1711
- }
1712
- const reconciled = reconcileOrphanRunIfNeeded(runId, snapshot) || snapshot;
1713
- if (TERMINAL_RUN_STATES.has(reconciled.state)) {
1714
- return {
1715
- status: "FAILED",
1716
- error: {
1717
- code: "RUN_ALREADY_TERMINATED",
1718
- message: "目标任务已结束,无法继续。",
1719
- retryable: false
1720
- }
1721
- };
1722
- }
1723
- if (reconciled.state !== RUN_STATE_PAUSED) {
1724
- return {
1725
- status: "FAILED",
1726
- error: {
1727
- code: "RUN_NOT_PAUSED",
1728
- message: "仅 paused 状态的 run 才能继续。",
1729
- retryable: true
1730
- },
1731
- run: reconciled
1732
- };
1733
- }
1734
-
1735
- const executionContext = resolveRunContext(reconciled);
1736
- if (!executionContext) {
1737
- return {
1738
- status: "FAILED",
1739
- error: {
1740
- code: "RUN_CONTEXT_MISSING",
1741
- message: "run 缺少可恢复的执行上下文,无法继续。",
1742
- retryable: false
1743
- }
1744
- };
1745
- }
1746
-
1747
- const updated = safeUpdateRunState(runId, (current) => ({
1748
- state: "queued",
1749
- last_message: "已收到继续请求,准备恢复执行。",
1750
- control: {
1751
- pause_requested: false,
1752
- pause_requested_at: null,
1753
- pause_requested_by: null,
1754
- cancel_requested: false
1755
- },
1756
- resume: {
1757
- checkpoint_path: current?.resume?.checkpoint_path || getRunArtifacts(runId).checkpoint_path,
1758
- pause_control_path: current?.resume?.pause_control_path || getRunArtifacts(runId).run_state_path,
1759
- output_csv: current?.resume?.output_csv || null,
1760
- resume_count: Number.isInteger(current?.resume?.resume_count) ? current.resume.resume_count + 1 : 1,
1761
- last_resumed_at: new Date().toISOString()
1762
- }
1763
- })) || readRunState(runId) || reconciled;
1764
-
1765
- let worker;
1766
- try {
1767
- worker = launchDetachedRunWorker({
1768
- runId,
1769
- resumeRun: true
1770
- });
1771
- } catch (error) {
1772
- const failedMessage = `无法启动 detached 恢复进程:${error?.message || "unknown"}`;
1773
- safeUpdateRunState(runId, {
1774
- state: RUN_STATE_FAILED,
1775
- stage: reconciled.stage || RUN_STAGE_PREFLIGHT,
1776
- last_message: failedMessage,
1777
- error: {
1778
- code: "RUN_WORKER_LAUNCH_FAILED",
1779
- message: failedMessage,
1780
- retryable: true
1781
- },
1782
- result: buildWorkerLaunchFailedPayload(failedMessage)
1783
- });
1784
- return buildWorkerLaunchFailedPayload(failedMessage);
1785
- }
1786
-
1787
- const started = safeUpdateRunState(runId, {
1788
- pid: worker?.pid,
1789
- state: "queued",
1790
- last_message: "已恢复 Recommend 流水线(detached)。"
1791
- }) || readRunState(runId) || updated;
1792
-
1793
- return {
1794
- status: "RESUME_REQUESTED",
1795
- run: started,
1796
- poll_after_sec: getRecommendedPollAfterSec(executionContext?.args || {}),
1797
- message: hasFollowUpChatRequest(executionContext?.args || {})
1798
- ? "已恢复 Recommend 流水线(detached)。recommend+chat 联动任务建议按 30 分钟间隔查询状态;手动查询到完成时会立即衔接聊天流程。"
1799
- : "已恢复 Recommend 流水线(detached)。默认不自动轮询;如需进度请按需调用 get_recommend_pipeline_run。"
1800
- };
1808
+ return resumeRecommendPipelineRunTool({ args });
1801
1809
  }
1802
1810
 
1803
1811
  function handleGetFeaturedCalibrationStatusTool(workspaceRoot) {
@@ -1816,69 +1824,162 @@ function handleGetFeaturedCalibrationStatusTool(workspaceRoot) {
1816
1824
  }
1817
1825
 
1818
1826
  async function handleRunFeaturedCalibrationTool({ workspaceRoot, args }) {
1819
- const result = await runRecommendCalibration(workspaceRoot, {
1820
- port: args.port,
1821
- timeoutMs: args.timeout_ms,
1822
- output: args.output
1823
- });
1827
+ return {
1828
+ status: "FAILED",
1829
+ error: {
1830
+ code: featuredCalibrationUnsupportedCode,
1831
+ message: "run_featured_calibration is fenced during the CDP-only rewrite because the legacy handler delegates to Runtime/page-JS adapter calibration. A replacement must discover and validate featured detail/action controls with CDP DOM/Input only and pass a user-approved live safe-action gate before this tool is re-enabled.",
1832
+ retryable: false
1833
+ },
1834
+ cdp_only: true,
1835
+ runtime_evaluate_used: false,
1836
+ method_summary: {},
1837
+ method_log: [],
1838
+ port: args.port ?? null,
1839
+ timeout_ms: args.timeout_ms ?? null,
1840
+ output: args.output ?? null,
1841
+ calibration_resolution: getFeaturedCalibrationResolution(workspaceRoot),
1842
+ guidance: {
1843
+ current_workaround: "Use an existing favorite-calibration.json if present; get_featured_calibration_status reports whether it is usable.",
1844
+ next_development_task: "Implement CDP-only featured calibration with explicit user approval for any mutating favorite/greet action."
1845
+ }
1846
+ };
1847
+ }
1848
+
1849
+ async function resolveRecommendSelfHealRootsWithRetry(client, config, {
1850
+ timeoutMs = 30000,
1851
+ intervalMs = 1000
1852
+ } = {}) {
1853
+ const startedAt = Date.now();
1854
+ let lastState = null;
1855
+ while (Date.now() - startedAt <= timeoutMs) {
1856
+ lastState = await resolveRecommendSelfHealRoots(client, config);
1857
+ if (lastState?.roots?.top && lastState?.roots?.frame) return lastState;
1858
+ await sleepMs(intervalMs);
1859
+ }
1860
+ return lastState;
1861
+ }
1824
1862
 
1825
- if (!result?.ok) {
1863
+ async function handleRunRecommendSelfHealTool({ workspaceRoot, args }) {
1864
+ if (typeof runSelfHealImpl === "function") {
1865
+ return runSelfHealImpl({ workspaceRoot, args });
1866
+ }
1867
+
1868
+ const mode = normalizeText(args.mode || "scan").toLowerCase() || "scan";
1869
+ if (mode === "apply") {
1826
1870
  return {
1827
1871
  status: "FAILED",
1828
1872
  error: {
1829
- code: result?.error?.code || "CALIBRATION_REQUIRED",
1830
- message: result?.error?.message || "精选页收藏校准失败,请在推荐页精选 tab 打开候选人详情后点击收藏按钮再重试。",
1831
- retryable: true
1873
+ code: recommendSelfHealApplyUnsupportedCode,
1874
+ message: "run_recommend_self_heal apply mode is fenced during the CDP-only rewrite. The shared CDP self-heal scan route is available, but repair application needs a dedicated safe-action/live-review gate before it can mutate browser or project state.",
1875
+ retryable: false
1832
1876
  },
1833
- calibration_path: result?.calibration_path || null,
1834
- calibration_script_path: result?.calibration_script_path || null,
1835
- debug_port: result?.debug_port || null,
1836
- diagnostics: {
1837
- stdout_last_line: getLastOutputLine(result?.stdout),
1838
- stderr_last_line: getLastOutputLine(result?.stderr)
1877
+ cdp_only: true,
1878
+ runtime_evaluate_used: false,
1879
+ method_summary: {},
1880
+ method_log: [],
1881
+ guidance: {
1882
+ supported_mode: "scan",
1883
+ next_development_task: "Add config-driven CDP-only repair sessions with explicit user approval and live verification before re-enabling apply mode."
1839
1884
  }
1840
1885
  };
1841
1886
  }
1842
1887
 
1843
- return {
1844
- status: "CALIBRATED",
1845
- message: "精选页收藏按钮校准完成,可重新执行 start_recommend_pipeline_run。",
1846
- calibration_path: result.calibration_path,
1847
- calibration_script_path: result.calibration_script_path,
1848
- debug_port: result.debug_port
1849
- };
1850
- }
1888
+ const host = "127.0.0.1";
1889
+ const port = parsePositiveInteger(args.port, 9222);
1890
+ let session = null;
1891
+ try {
1892
+ session = await connectToChromeTarget({
1893
+ host,
1894
+ port,
1895
+ targetUrlIncludes: recommendTargetUrl
1896
+ });
1897
+ const { client, methodLog, target } = session;
1898
+ await enableDomains(client, ["Page", "DOM", "Accessibility"]);
1899
+ await bringPageToFront(client);
1900
+
1901
+ const config = buildRecommendSelfHealConfig();
1902
+ const rootState = await resolveRecommendSelfHealRootsWithRetry(client, config);
1903
+ const check = await runSelfHealCheck({
1904
+ client,
1905
+ domain: "recommend",
1906
+ roots: rootState?.roots || {},
1907
+ selectorProbes: config.selectorProbes,
1908
+ accessibilityProbes: config.accessibilityProbes
1909
+ });
1910
+ assertNoForbiddenCdpCalls(methodLog);
1851
1911
 
1852
- async function handleRunRecommendSelfHealTool({ workspaceRoot, args }) {
1853
- return runSelfHealImpl({ workspaceRoot, args });
1912
+ const healthy = check.status === HEALTH_STATUS.HEALTHY;
1913
+ return {
1914
+ status: healthy ? "OK" : "DEGRADED",
1915
+ cdp_only: true,
1916
+ runtime_evaluate_used: false,
1917
+ workspace_root: workspaceRoot,
1918
+ chrome: {
1919
+ host,
1920
+ port,
1921
+ target: {
1922
+ id: target?.id || null,
1923
+ type: target?.type || null,
1924
+ url: target?.url || null,
1925
+ title: target?.title || null
1926
+ }
1927
+ },
1928
+ mode,
1929
+ scope: normalizeText(args.scope || "full") || "full",
1930
+ validation_profile: normalizeText(args.validation_profile || "full") || "full",
1931
+ self_heal: {
1932
+ recommend: compactSelfHealCheck(check)
1933
+ },
1934
+ method_summary: buildCdpMethodSummary(methodLog),
1935
+ method_log: methodLog
1936
+ };
1937
+ } catch (error) {
1938
+ const methodLog = session?.methodLog || [];
1939
+ return {
1940
+ status: "FAILED",
1941
+ error: {
1942
+ code: "RECOMMEND_SELF_HEAL_CDP_FAILED",
1943
+ message: error?.message || String(error),
1944
+ retryable: true
1945
+ },
1946
+ cdp_only: true,
1947
+ runtime_evaluate_used: methodLog.some((entry) => String(entry?.method || entry).startsWith("Runtime.")),
1948
+ method_summary: buildCdpMethodSummary(methodLog),
1949
+ method_log: methodLog,
1950
+ chrome: { host, port, target_url_includes: recommendTargetUrl }
1951
+ };
1952
+ } finally {
1953
+ if (session) await session.close();
1954
+ }
1854
1955
  }
1855
1956
 
1856
- function handleBossChatHealthCheckTool(workspaceRoot, args) {
1857
- return getBossChatHealthCheck(workspaceRoot, args);
1957
+ async function handleBossChatHealthCheckTool(workspaceRoot, args) {
1958
+ return bossChatHealthCheckTool({ workspaceRoot, args });
1858
1959
  }
1859
1960
 
1860
1961
  async function handleBossChatPrepareRunTool({ workspaceRoot, args }) {
1861
- return prepareBossChatRun({ workspaceRoot, input: args });
1962
+ return prepareBossChatRunTool({ workspaceRoot, args });
1862
1963
  }
1863
1964
 
1864
1965
  async function handleBossChatStartRunTool({ workspaceRoot, args }) {
1865
- return startBossChatRun({ workspaceRoot, input: args });
1966
+ return startBossChatRunTool({ workspaceRoot, args });
1866
1967
  }
1867
1968
 
1868
1969
  async function handleBossChatGetRunTool({ workspaceRoot, args }) {
1869
- return getBossChatRun({ workspaceRoot, input: args });
1970
+ return getBossChatRunTool({ workspaceRoot, args });
1870
1971
  }
1871
1972
 
1872
1973
  async function handleBossChatPauseRunTool({ workspaceRoot, args }) {
1873
- return pauseBossChatRun({ workspaceRoot, input: args });
1974
+ return pauseBossChatRunTool({ workspaceRoot, args });
1874
1975
  }
1875
1976
 
1876
1977
  async function handleBossChatResumeRunTool({ workspaceRoot, args }) {
1877
- return resumeBossChatRun({ workspaceRoot, input: args });
1978
+ return resumeBossChatRunTool({ workspaceRoot, args });
1878
1979
  }
1879
1980
 
1880
1981
  async function handleBossChatCancelRunTool({ workspaceRoot, args }) {
1881
- return cancelBossChatRun({ workspaceRoot, input: args });
1982
+ return cancelBossChatRunTool({ workspaceRoot, args });
1882
1983
  }
1883
1984
 
1884
1985
  async function handleRequest(message, workspaceRoot) {
@@ -1930,6 +2031,13 @@ async function handleRequest(message, workspaceRoot) {
1930
2031
  }
1931
2032
  }
1932
2033
 
2034
+ if ([TOOL_RUN_RECRUIT_PIPELINE, TOOL_START_RECRUIT_PIPELINE_RUN].includes(toolName)) {
2035
+ const inputError = validateRecruitPipelineArgs(args);
2036
+ if (inputError) {
2037
+ return createJsonRpcError(id, -32602, inputError);
2038
+ }
2039
+ }
2040
+
1933
2041
  if (toolName === TOOL_RUN_FEATURED_CALIBRATION) {
1934
2042
  const inputError = validateRunFeaturedCalibrationArgs(args);
1935
2043
  if (inputError) {
@@ -1959,7 +2067,11 @@ async function handleRequest(message, workspaceRoot) {
1959
2067
  TOOL_BOSS_CHAT_GET_RUN,
1960
2068
  TOOL_BOSS_CHAT_CANCEL_RUN,
1961
2069
  TOOL_BOSS_CHAT_PAUSE_RUN,
1962
- TOOL_BOSS_CHAT_RESUME_RUN
2070
+ TOOL_BOSS_CHAT_RESUME_RUN,
2071
+ TOOL_GET_RECRUIT_PIPELINE_RUN,
2072
+ TOOL_CANCEL_RECRUIT_PIPELINE_RUN,
2073
+ TOOL_PAUSE_RECRUIT_PIPELINE_RUN,
2074
+ TOOL_RESUME_RECRUIT_PIPELINE_RUN
1963
2075
  ].includes(toolName)) {
1964
2076
  if (!args || typeof args.run_id !== "string" || !normalizeText(args.run_id)) {
1965
2077
  return createJsonRpcError(id, -32602, "run_id is required and must be a string");
@@ -1967,8 +2079,10 @@ async function handleRequest(message, workspaceRoot) {
1967
2079
  }
1968
2080
 
1969
2081
  try {
1970
- let payload;
1971
- if (toolName === TOOL_START_RUN) {
2082
+ let payload;
2083
+ if (toolName === TOOL_LIST_RECOMMEND_JOBS) {
2084
+ payload = await listRecommendJobsTool({ workspaceRoot, args });
2085
+ } else if (toolName === TOOL_START_RUN) {
1972
2086
  payload = await handleStartRunTool({ workspaceRoot, args });
1973
2087
  } else if (toolName === TOOL_GET_RUN) {
1974
2088
  payload = handleGetRunTool(args);
@@ -1979,13 +2093,13 @@ async function handleRequest(message, workspaceRoot) {
1979
2093
  } else if (toolName === TOOL_RESUME_RUN) {
1980
2094
  payload = handleResumeRunTool(args);
1981
2095
  } else if (toolName === TOOL_GET_FEATURED_CALIBRATION_STATUS) {
1982
- payload = handleGetFeaturedCalibrationStatusTool(workspaceRoot);
2096
+ payload = await handleGetFeaturedCalibrationStatusTool(workspaceRoot);
1983
2097
  } else if (toolName === TOOL_RUN_FEATURED_CALIBRATION) {
1984
2098
  payload = await handleRunFeaturedCalibrationTool({ workspaceRoot, args });
1985
2099
  } else if (toolName === TOOL_RUN_RECOMMEND_SELF_HEAL) {
1986
2100
  payload = await handleRunRecommendSelfHealTool({ workspaceRoot, args });
1987
2101
  } else if (toolName === TOOL_BOSS_CHAT_HEALTH_CHECK) {
1988
- payload = handleBossChatHealthCheckTool(workspaceRoot, args);
2102
+ payload = await handleBossChatHealthCheckTool(workspaceRoot, args);
1989
2103
  } else if (toolName === TOOL_BOSS_CHAT_PREPARE_RUN) {
1990
2104
  payload = await handleBossChatPrepareRunTool({ workspaceRoot, args });
1991
2105
  } else if (toolName === TOOL_BOSS_CHAT_START_RUN) {
@@ -1998,6 +2112,18 @@ async function handleRequest(message, workspaceRoot) {
1998
2112
  payload = await handleBossChatResumeRunTool({ workspaceRoot, args });
1999
2113
  } else if (toolName === TOOL_BOSS_CHAT_CANCEL_RUN) {
2000
2114
  payload = await handleBossChatCancelRunTool({ workspaceRoot, args });
2115
+ } else if (toolName === TOOL_RUN_RECRUIT_PIPELINE) {
2116
+ payload = await runRecruitPipelineTool({ workspaceRoot, args });
2117
+ } else if (toolName === TOOL_START_RECRUIT_PIPELINE_RUN) {
2118
+ payload = await startRecruitPipelineRunTool({ workspaceRoot, args });
2119
+ } else if (toolName === TOOL_GET_RECRUIT_PIPELINE_RUN) {
2120
+ payload = getRecruitPipelineRunTool({ workspaceRoot, args });
2121
+ } else if (toolName === TOOL_CANCEL_RECRUIT_PIPELINE_RUN) {
2122
+ payload = cancelRecruitPipelineRunTool({ workspaceRoot, args });
2123
+ } else if (toolName === TOOL_PAUSE_RECRUIT_PIPELINE_RUN) {
2124
+ payload = pauseRecruitPipelineRunTool({ workspaceRoot, args });
2125
+ } else if (toolName === TOOL_RESUME_RECRUIT_PIPELINE_RUN) {
2126
+ payload = resumeRecruitPipelineRunTool({ workspaceRoot, args });
2001
2127
  } else {
2002
2128
  return createJsonRpcError(id, -32602, `Unknown tool: ${toolName || ""}`);
2003
2129
  }
@@ -2132,10 +2258,43 @@ export const __testables = {
2132
2258
  spawnProcessImpl = typeof nextImpl === "function" ? nextImpl : spawn;
2133
2259
  },
2134
2260
  setRunPipelineImplForTests(nextImpl) {
2135
- runPipelineImpl = typeof nextImpl === "function" ? nextImpl : runRecommendPipeline;
2261
+ runPipelineImpl = typeof nextImpl === "function" ? nextImpl : null;
2136
2262
  },
2137
2263
  setRunSelfHealImplForTests(nextImpl) {
2138
- runSelfHealImpl = typeof nextImpl === "function" ? nextImpl : runRecommendSelfHeal;
2264
+ runSelfHealImpl = typeof nextImpl === "function" ? nextImpl : null;
2265
+ },
2266
+ setRecommendMcpConnectorForTests(nextImpl) {
2267
+ __setRecommendMcpConnectorForTests(nextImpl);
2268
+ },
2269
+ setRecommendMcpJobReaderForTests(nextImpl) {
2270
+ __setRecommendMcpJobReaderForTests(nextImpl);
2271
+ },
2272
+ setRecommendMcpWorkflowForTests(nextImpl) {
2273
+ __setRecommendMcpWorkflowForTests(nextImpl);
2274
+ },
2275
+ resetRecommendMcpStateForTests() {
2276
+ __resetRecommendMcpStateForTests();
2277
+ },
2278
+ setChatMcpConnectorForTests(nextImpl) {
2279
+ __setChatMcpConnectorForTests(nextImpl);
2280
+ },
2281
+ setChatMcpJobReaderForTests(nextImpl) {
2282
+ __setChatMcpJobReaderForTests(nextImpl);
2283
+ },
2284
+ setChatMcpWorkflowForTests(nextImpl) {
2285
+ __setChatMcpWorkflowForTests(nextImpl);
2286
+ },
2287
+ resetChatMcpStateForTests() {
2288
+ __resetChatMcpStateForTests();
2289
+ },
2290
+ setRecruitMcpConnectorForTests(nextImpl) {
2291
+ __setRecruitMcpConnectorForTests(nextImpl);
2292
+ },
2293
+ setRecruitMcpWorkflowForTests(nextImpl) {
2294
+ __setRecruitMcpWorkflowForTests(nextImpl);
2295
+ },
2296
+ resetRecruitMcpStateForTests() {
2297
+ __resetRecruitMcpStateForTests();
2139
2298
  }
2140
2299
  };
2141
2300