@reconcrap/boss-recommend-mcp 1.3.38 → 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.
- package/README.md +53 -33
- package/package.json +61 -9
- package/skills/boss-recommend-pipeline/SKILL.md +4 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1095 -196
- package/src/core/browser/index.js +378 -0
- package/src/core/capture/index.js +298 -0
- package/src/core/cv-acquisition/index.js +219 -0
- package/src/core/greet-quota/index.js +54 -0
- package/src/core/infinite-list/index.js +459 -0
- package/src/core/reporting/legacy-csv.js +332 -0
- package/src/core/run/index.js +286 -0
- package/src/core/screening/index.js +1166 -0
- package/src/core/self-heal/index.js +848 -0
- package/src/domains/chat/cards.js +129 -0
- package/src/domains/chat/constants.js +183 -0
- package/src/domains/chat/detail.js +1369 -0
- package/src/domains/chat/index.js +7 -0
- package/src/domains/chat/jobs.js +334 -0
- package/src/domains/chat/page-guard.js +88 -0
- package/src/domains/chat/roots.js +56 -0
- package/src/domains/chat/run-service.js +1101 -0
- package/src/domains/recommend/actions.js +457 -0
- package/src/domains/recommend/cards.js +228 -0
- package/src/domains/recommend/constants.js +141 -0
- package/src/domains/recommend/detail.js +341 -0
- package/src/domains/recommend/filters.js +581 -0
- package/src/domains/recommend/index.js +10 -0
- package/src/domains/recommend/jobs.js +232 -0
- package/src/domains/recommend/refresh.js +204 -0
- package/src/domains/recommend/roots.js +78 -0
- package/src/domains/recommend/run-service.js +903 -0
- package/src/domains/recommend/scopes.js +245 -0
- package/src/domains/recruit/actions.js +277 -0
- package/src/domains/recruit/cards.js +67 -0
- package/src/domains/recruit/constants.js +130 -0
- package/src/domains/recruit/detail.js +414 -0
- package/src/domains/recruit/index.js +9 -0
- package/src/domains/recruit/instruction-parser.js +451 -0
- package/src/domains/recruit/refresh.js +40 -0
- package/src/domains/recruit/roots.js +68 -0
- package/src/domains/recruit/run-service.js +580 -0
- package/src/domains/recruit/search.js +1149 -0
- package/src/index.js +578 -419
- package/src/recommend-mcp.js +1257 -0
- package/src/recruit-mcp.js +1035 -0
- package/src/adapters.js +0 -3079
- package/src/boss-chat.js +0 -1037
- package/src/pipeline.js +0 -2249
- package/src/recommend-healing-config.js +0 -131
- package/src/recommend-healing-rules.json +0 -261
- package/src/self-heal.js +0 -2237
- package/src/test-adapters-runtime.js +0 -628
- package/src/test-boss-chat.js +0 -3196
- package/src/test-index-async.js +0 -498
- package/src/test-parser.js +0 -742
- package/src/test-pipeline.js +0 -2703
- package/src/test-run-state.js +0 -152
- package/src/test-self-heal.js +0 -224
- package/vendor/boss-chat-cli/README.md +0 -134
- package/vendor/boss-chat-cli/package.json +0 -53
- package/vendor/boss-chat-cli/src/app.js +0 -1501
- package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
- package/vendor/boss-chat-cli/src/cli.js +0 -1713
- package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
- package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
- package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
- package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
- package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
- package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
- package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
- package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
- package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
- package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -6927
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
- package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2294
- package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
- 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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 =
|
|
73
|
-
let runSelfHealImpl =
|
|
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("
|
|
495
|
-
targetCount: createTargetCountInputSchema("兼容字段;优先使用 target_count
|
|
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:
|
|
596
|
-
description: "
|
|
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: "
|
|
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
|
|
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
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
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
|
-
|
|
1635
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
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:
|
|
1830
|
-
message:
|
|
1831
|
-
retryable:
|
|
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
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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
|
-
|
|
1853
|
-
|
|
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
|
|
1957
|
+
async function handleBossChatHealthCheckTool(workspaceRoot, args) {
|
|
1958
|
+
return bossChatHealthCheckTool({ workspaceRoot, args });
|
|
1858
1959
|
}
|
|
1859
1960
|
|
|
1860
1961
|
async function handleBossChatPrepareRunTool({ workspaceRoot, args }) {
|
|
1861
|
-
return
|
|
1962
|
+
return prepareBossChatRunTool({ workspaceRoot, args });
|
|
1862
1963
|
}
|
|
1863
1964
|
|
|
1864
1965
|
async function handleBossChatStartRunTool({ workspaceRoot, args }) {
|
|
1865
|
-
return
|
|
1966
|
+
return startBossChatRunTool({ workspaceRoot, args });
|
|
1866
1967
|
}
|
|
1867
1968
|
|
|
1868
1969
|
async function handleBossChatGetRunTool({ workspaceRoot, args }) {
|
|
1869
|
-
return
|
|
1970
|
+
return getBossChatRunTool({ workspaceRoot, args });
|
|
1870
1971
|
}
|
|
1871
1972
|
|
|
1872
1973
|
async function handleBossChatPauseRunTool({ workspaceRoot, args }) {
|
|
1873
|
-
return
|
|
1974
|
+
return pauseBossChatRunTool({ workspaceRoot, args });
|
|
1874
1975
|
}
|
|
1875
1976
|
|
|
1876
1977
|
async function handleBossChatResumeRunTool({ workspaceRoot, args }) {
|
|
1877
|
-
return
|
|
1978
|
+
return resumeBossChatRunTool({ workspaceRoot, args });
|
|
1878
1979
|
}
|
|
1879
1980
|
|
|
1880
1981
|
async function handleBossChatCancelRunTool({ workspaceRoot, args }) {
|
|
1881
|
-
return
|
|
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 ===
|
|
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 :
|
|
2261
|
+
runPipelineImpl = typeof nextImpl === "function" ? nextImpl : null;
|
|
2136
2262
|
},
|
|
2137
2263
|
setRunSelfHealImplForTests(nextImpl) {
|
|
2138
|
-
runSelfHealImpl = typeof nextImpl === "function" ? nextImpl :
|
|
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
|
|