@reconcrap/boss-recommend-mcp 1.1.4 → 1.1.6
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/package.json +13 -2
- package/skills/boss-recommend-pipeline/SKILL.md +1 -4
- package/src/adapters.js +307 -203
- package/src/pipeline.js +450 -375
- package/src/test-adapters-runtime.js +10 -1
- package/src/test-pipeline.js +506 -4
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +113 -34
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +287 -89
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +413 -245
package/src/pipeline.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { parseRecommendInstruction } from "./parser.js";
|
|
3
|
-
import {
|
|
4
|
-
attemptPipelineAutoRepair,
|
|
5
|
-
ensureBossRecommendPageReady,
|
|
6
|
-
listRecommendJobs,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
3
|
+
import {
|
|
4
|
+
attemptPipelineAutoRepair,
|
|
5
|
+
ensureBossRecommendPageReady,
|
|
6
|
+
listRecommendJobs,
|
|
7
|
+
refreshBossRecommendList,
|
|
8
|
+
reloadBossRecommendPage,
|
|
9
|
+
runPipelinePreflight,
|
|
10
|
+
runRecommendSearchCli,
|
|
11
|
+
runRecommendScreenCli
|
|
12
|
+
} from "./adapters.js";
|
|
13
|
+
|
|
14
|
+
const FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY = "近14天没有";
|
|
15
|
+
const MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS = 5;
|
|
15
16
|
|
|
16
17
|
function dedupe(values = []) {
|
|
17
18
|
return [...new Set(values.filter(Boolean))];
|
|
@@ -114,7 +115,8 @@ function collectNpmInstallDirs(checks = [], workspaceRoot) {
|
|
|
114
115
|
const npmCheckKeys = new Set([
|
|
115
116
|
"npm_dep_chrome_remote_interface_search",
|
|
116
117
|
"npm_dep_chrome_remote_interface_screen",
|
|
117
|
-
"npm_dep_ws"
|
|
118
|
+
"npm_dep_ws",
|
|
119
|
+
"npm_dep_sharp"
|
|
118
120
|
]);
|
|
119
121
|
const dirs = checks
|
|
120
122
|
.filter((item) => item && item.ok === false && npmCheckKeys.has(item.key))
|
|
@@ -156,26 +158,6 @@ function getNodeInstallCommands() {
|
|
|
156
158
|
];
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
function getPythonInstallCommands() {
|
|
160
|
-
if (process.platform === "win32") {
|
|
161
|
-
return [
|
|
162
|
-
"winget install Python.Python.3.12",
|
|
163
|
-
"python --version"
|
|
164
|
-
];
|
|
165
|
-
}
|
|
166
|
-
if (process.platform === "darwin") {
|
|
167
|
-
return [
|
|
168
|
-
"brew install python",
|
|
169
|
-
"python3 --version",
|
|
170
|
-
"若系统无 python 命令,请在当前终端建立 python -> python3 别名后重试。"
|
|
171
|
-
];
|
|
172
|
-
}
|
|
173
|
-
return [
|
|
174
|
-
"使用系统包管理器安装 Python(例如 apt / yum / brew)",
|
|
175
|
-
"python --version"
|
|
176
|
-
];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
161
|
function formatCommandBlock(commands = []) {
|
|
180
162
|
return commands.map((command) => `- ${command}`).join("\n");
|
|
181
163
|
}
|
|
@@ -190,9 +172,8 @@ function buildPreflightRecovery(checks = [], workspaceRoot) {
|
|
|
190
172
|
failed.has("npm_dep_chrome_remote_interface_search")
|
|
191
173
|
|| failed.has("npm_dep_chrome_remote_interface_screen")
|
|
192
174
|
|| failed.has("npm_dep_ws")
|
|
175
|
+
|| failed.has("npm_dep_sharp")
|
|
193
176
|
);
|
|
194
|
-
const needPython = failed.has("python_cli");
|
|
195
|
-
const needPillow = failed.has("python_pillow");
|
|
196
177
|
|
|
197
178
|
const ordered_steps = [];
|
|
198
179
|
if (needScreenConfig) {
|
|
@@ -218,37 +199,16 @@ function buildPreflightRecovery(checks = [], workspaceRoot) {
|
|
|
218
199
|
if (needNpm) {
|
|
219
200
|
ordered_steps.push({
|
|
220
201
|
id: "install_npm_dependencies",
|
|
221
|
-
title: "安装 npm 依赖(chrome-remote-interface / ws)",
|
|
202
|
+
title: "安装 npm 依赖(chrome-remote-interface / ws / sharp)",
|
|
222
203
|
blocked_by: needNode ? ["install_nodejs"] : [],
|
|
223
204
|
commands: buildNpmInstallCommands(checks, workspaceRoot)
|
|
224
205
|
});
|
|
225
206
|
}
|
|
226
|
-
if (needPython) {
|
|
227
|
-
ordered_steps.push({
|
|
228
|
-
id: "install_python",
|
|
229
|
-
title: "安装 Python(确保 python 命令可用)",
|
|
230
|
-
blocked_by: [],
|
|
231
|
-
commands: getPythonInstallCommands()
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
if (needPillow) {
|
|
235
|
-
ordered_steps.push({
|
|
236
|
-
id: "install_pillow",
|
|
237
|
-
title: "安装 Pillow",
|
|
238
|
-
blocked_by: needPython ? ["install_python"] : [],
|
|
239
|
-
commands: [
|
|
240
|
-
"python -m pip install --upgrade pip",
|
|
241
|
-
"python -m pip install pillow"
|
|
242
|
-
]
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
207
|
|
|
246
208
|
const promptLines = [
|
|
247
209
|
"你是环境修复 agent。请先读取 diagnostics.checks,再严格按下面顺序执行,不要并行跳步:",
|
|
248
210
|
"1) node_cli 失败 -> 先安装 Node.js,未成功前禁止执行 npm install。",
|
|
249
|
-
"2) npm_dep_* 失败 -> 再安装 npm 依赖(chrome-remote-interface / ws)。",
|
|
250
|
-
"3) python_cli 失败 -> 安装 Python 并确保 python 命令可用。",
|
|
251
|
-
"4) python_pillow 失败 -> 最后安装 Pillow。",
|
|
211
|
+
"2) npm_dep_* 失败 -> 再安装 npm 依赖(chrome-remote-interface / ws / sharp)。",
|
|
252
212
|
"每一步完成后都重新运行 doctor,直到所有检查通过后再重试流水线。"
|
|
253
213
|
];
|
|
254
214
|
if (needScreenConfig) {
|
|
@@ -494,16 +454,17 @@ function buildChromeSetupGuidance({ debugPort, pageState }) {
|
|
|
494
454
|
};
|
|
495
455
|
}
|
|
496
456
|
|
|
497
|
-
const defaultDependencies = {
|
|
498
|
-
attemptPipelineAutoRepair,
|
|
499
|
-
parseRecommendInstruction,
|
|
500
|
-
ensureBossRecommendPageReady,
|
|
501
|
-
listRecommendJobs,
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
457
|
+
const defaultDependencies = {
|
|
458
|
+
attemptPipelineAutoRepair,
|
|
459
|
+
parseRecommendInstruction,
|
|
460
|
+
ensureBossRecommendPageReady,
|
|
461
|
+
listRecommendJobs,
|
|
462
|
+
refreshBossRecommendList,
|
|
463
|
+
reloadBossRecommendPage,
|
|
464
|
+
runPipelinePreflight,
|
|
465
|
+
runRecommendSearchCli,
|
|
466
|
+
runRecommendScreenCli
|
|
467
|
+
};
|
|
507
468
|
|
|
508
469
|
export async function runRecommendPipeline(
|
|
509
470
|
{ workspaceRoot, instruction, confirmation, overrides, resume = null },
|
|
@@ -512,16 +473,17 @@ export async function runRecommendPipeline(
|
|
|
512
473
|
) {
|
|
513
474
|
const injectedDependencies = dependencies || {};
|
|
514
475
|
const resolvedDependencies = { ...defaultDependencies, ...(dependencies || {}) };
|
|
515
|
-
const {
|
|
516
|
-
attemptPipelineAutoRepair: attemptAutoRepair,
|
|
517
|
-
parseRecommendInstruction: parseInstruction,
|
|
518
|
-
ensureBossRecommendPageReady: ensureRecommendPageReady,
|
|
519
|
-
listRecommendJobs: listJobs,
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
476
|
+
const {
|
|
477
|
+
attemptPipelineAutoRepair: attemptAutoRepair,
|
|
478
|
+
parseRecommendInstruction: parseInstruction,
|
|
479
|
+
ensureBossRecommendPageReady: ensureRecommendPageReady,
|
|
480
|
+
listRecommendJobs: listJobs,
|
|
481
|
+
refreshBossRecommendList: refreshRecommendList,
|
|
482
|
+
reloadBossRecommendPage: reloadRecommendPage,
|
|
483
|
+
runPipelinePreflight: runPreflight,
|
|
484
|
+
runRecommendSearchCli: searchCli,
|
|
485
|
+
runRecommendScreenCli: screenCli
|
|
486
|
+
} = resolvedDependencies;
|
|
525
487
|
const runtimeHooks = createPipelineRuntime(runtime);
|
|
526
488
|
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
527
489
|
|
|
@@ -811,300 +773,413 @@ export async function runRecommendPipeline(
|
|
|
811
773
|
};
|
|
812
774
|
}
|
|
813
775
|
|
|
814
|
-
const resumeCompletionReason = normalizeText(resume?.previous_completion_reason || "").toLowerCase();
|
|
815
|
-
const isResumeRun = resume?.resume === true;
|
|
816
|
-
const resumeFromPausedBeforeScreen = isResumeRun && resumeCompletionReason === "paused_before_screen";
|
|
817
|
-
const skipSearchOnResume = isResumeRun && !resumeFromPausedBeforeScreen;
|
|
818
|
-
let effectiveSearchParams = { ...parsed.searchParams };
|
|
819
|
-
let searchSummary = null;
|
|
820
|
-
let shouldRunSearch = !skipSearchOnResume;
|
|
821
|
-
let screenAutoRecoveryCount = 0;
|
|
822
|
-
let lastAutoRecovery = null;
|
|
823
|
-
let currentResumeConfig = {
|
|
824
|
-
checkpoint_path: resume?.checkpoint_path || null,
|
|
825
|
-
pause_control_path: resume?.pause_control_path || null,
|
|
826
|
-
output_csv: resume?.output_csv || null,
|
|
827
|
-
resume: resume?.resume === true,
|
|
828
|
-
require_checkpoint: skipSearchOnResume
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
while (true) {
|
|
832
|
-
if (shouldRunSearch) {
|
|
833
|
-
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
834
|
-
runtimeHooks.setStage(
|
|
835
|
-
"search",
|
|
836
|
-
screenAutoRecoveryCount > 0
|
|
837
|
-
? `自动恢复第 ${screenAutoRecoveryCount} 次:重新执行 recommend search(强制 recent_not_view=${FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY})。`
|
|
838
|
-
: "岗位已确认,开始执行 recommend search。"
|
|
839
|
-
);
|
|
840
|
-
runtimeHooks.heartbeat("search", lastAutoRecovery);
|
|
841
|
-
const searchResult = await searchCli({
|
|
842
|
-
workspaceRoot,
|
|
843
|
-
searchParams: effectiveSearchParams,
|
|
844
|
-
selectedJob: selectedJobToken,
|
|
845
|
-
runtime: runtimeHooks.adapterRuntime("search")
|
|
846
|
-
});
|
|
847
|
-
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
848
|
-
if (isProcessAbortError(searchResult.error)) {
|
|
849
|
-
throw new PipelineAbortError(searchResult.error?.message || "推荐筛选已取消。");
|
|
850
|
-
}
|
|
851
|
-
if (!searchResult.ok) {
|
|
852
|
-
const searchErrorCode = String(searchResult.error?.code || "");
|
|
853
|
-
const searchErrorMessage = String(searchResult.error?.message || "");
|
|
854
|
-
const loginRelatedSearchFailure = (
|
|
855
|
-
searchErrorCode === "LOGIN_REQUIRED"
|
|
856
|
-
|| searchErrorCode === "NO_RECOMMEND_IFRAME"
|
|
857
|
-
|| searchErrorMessage.includes("LOGIN_REQUIRED")
|
|
858
|
-
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
859
|
-
);
|
|
860
|
-
if (loginRelatedSearchFailure) {
|
|
861
|
-
const recheck = await ensureRecommendPageReady(workspaceRoot, {
|
|
862
|
-
port: preflight.debug_port
|
|
863
|
-
});
|
|
864
|
-
if (recheck.state === "LOGIN_REQUIRED" || recheck.state === "LOGIN_REQUIRED_AFTER_REDIRECT") {
|
|
865
|
-
const guidance = buildChromeSetupGuidance({
|
|
866
|
-
debugPort: preflight.debug_port,
|
|
867
|
-
pageState: recheck.page_state
|
|
868
|
-
});
|
|
869
|
-
return buildFailedResponse(
|
|
870
|
-
"BOSS_LOGIN_REQUIRED",
|
|
871
|
-
"检测到当前 Boss 处于未登录状态,请先登录后再继续。登录页:https://www.zhipin.com/web/user/?ka=bticket",
|
|
872
|
-
{
|
|
873
|
-
search_params: effectiveSearchParams,
|
|
874
|
-
screen_params: parsed.screenParams,
|
|
875
|
-
selected_job: selectedJob,
|
|
876
|
-
required_user_action: "prepare_boss_recommend_page",
|
|
877
|
-
guidance,
|
|
878
|
-
diagnostics: {
|
|
879
|
-
debug_port: preflight.debug_port,
|
|
880
|
-
page_state: recheck.page_state,
|
|
881
|
-
stdout: searchResult.stdout?.slice(-1000),
|
|
882
|
-
stderr: searchResult.stderr?.slice(-1000),
|
|
883
|
-
result: searchResult.structured || null,
|
|
884
|
-
auto_recovery: lastAutoRecovery
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
return buildFailedResponse(
|
|
891
|
-
searchResult.error?.code || "RECOMMEND_SEARCH_FAILED",
|
|
892
|
-
searchResult.error?.message || "推荐页筛选执行失败。",
|
|
893
|
-
{
|
|
894
|
-
search_params: effectiveSearchParams,
|
|
895
|
-
screen_params: parsed.screenParams,
|
|
896
|
-
selected_job: selectedJob,
|
|
897
|
-
diagnostics: {
|
|
898
|
-
debug_port: preflight.debug_port,
|
|
899
|
-
stdout: searchResult.stdout?.slice(-1000),
|
|
900
|
-
stderr: searchResult.stderr?.slice(-1000),
|
|
901
|
-
result: searchResult.structured || null,
|
|
902
|
-
auto_recovery: lastAutoRecovery
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
searchSummary = searchResult.summary || {};
|
|
909
|
-
if (isPauseRequested(runtimeHooks)) {
|
|
910
|
-
return buildPausedResponse("已在 screen 阶段开始前暂停 Recommend 流水线。", {
|
|
911
|
-
search_params: effectiveSearchParams,
|
|
912
|
-
screen_params: parsed.screenParams,
|
|
913
|
-
selected_job: selectedJob,
|
|
914
|
-
partial_result: {
|
|
915
|
-
candidate_count: searchSummary.candidate_count ?? null,
|
|
916
|
-
applied_filters: searchSummary.applied_filters || effectiveSearchParams,
|
|
917
|
-
output_csv: currentResumeConfig.output_csv || null,
|
|
918
|
-
completion_reason: "paused_before_screen"
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
923
|
-
runtimeHooks.setStage("screen", "search 完成,开始执行 recommend screen。");
|
|
924
|
-
} else {
|
|
925
|
-
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
926
|
-
runtimeHooks.setStage("screen", "检测到可续跑 checkpoint,跳过 search,直接恢复 recommend screen。");
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
runtimeHooks.heartbeat("screen", lastAutoRecovery);
|
|
930
|
-
const screenResult = await screenCli({
|
|
931
|
-
workspaceRoot,
|
|
932
|
-
screenParams: parsed.screenParams,
|
|
933
|
-
resume: currentResumeConfig,
|
|
934
|
-
runtime: runtimeHooks.adapterRuntime("screen")
|
|
935
|
-
});
|
|
936
|
-
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
937
|
-
if (isProcessAbortError(screenResult.error)) {
|
|
938
|
-
throw new PipelineAbortError(screenResult.error?.message || "推荐筛选已取消。");
|
|
939
|
-
}
|
|
940
|
-
if (screenResult.paused) {
|
|
941
|
-
return buildPausedResponse("Recommend 流水线已暂停,可使用 resume 继续。", {
|
|
942
|
-
search_params: effectiveSearchParams,
|
|
943
|
-
screen_params: parsed.screenParams,
|
|
944
|
-
selected_job: selectedJob,
|
|
945
|
-
partial_result: screenResult.summary || screenResult.structured?.result || null
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
if (!screenResult.ok) {
|
|
949
|
-
const
|
|
950
|
-
const
|
|
951
|
-
const
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
:
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
776
|
+
const resumeCompletionReason = normalizeText(resume?.previous_completion_reason || "").toLowerCase();
|
|
777
|
+
const isResumeRun = resume?.resume === true;
|
|
778
|
+
const resumeFromPausedBeforeScreen = isResumeRun && resumeCompletionReason === "paused_before_screen";
|
|
779
|
+
const skipSearchOnResume = isResumeRun && !resumeFromPausedBeforeScreen;
|
|
780
|
+
let effectiveSearchParams = { ...parsed.searchParams };
|
|
781
|
+
let searchSummary = null;
|
|
782
|
+
let shouldRunSearch = !skipSearchOnResume;
|
|
783
|
+
let screenAutoRecoveryCount = 0;
|
|
784
|
+
let lastAutoRecovery = null;
|
|
785
|
+
let currentResumeConfig = {
|
|
786
|
+
checkpoint_path: resume?.checkpoint_path || null,
|
|
787
|
+
pause_control_path: resume?.pause_control_path || null,
|
|
788
|
+
output_csv: resume?.output_csv || null,
|
|
789
|
+
resume: resume?.resume === true,
|
|
790
|
+
require_checkpoint: skipSearchOnResume
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
while (true) {
|
|
794
|
+
if (shouldRunSearch) {
|
|
795
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
796
|
+
runtimeHooks.setStage(
|
|
797
|
+
"search",
|
|
798
|
+
screenAutoRecoveryCount > 0
|
|
799
|
+
? `自动恢复第 ${screenAutoRecoveryCount} 次:重新执行 recommend search(强制 recent_not_view=${FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY})。`
|
|
800
|
+
: "岗位已确认,开始执行 recommend search。"
|
|
801
|
+
);
|
|
802
|
+
runtimeHooks.heartbeat("search", lastAutoRecovery);
|
|
803
|
+
const searchResult = await searchCli({
|
|
804
|
+
workspaceRoot,
|
|
805
|
+
searchParams: effectiveSearchParams,
|
|
806
|
+
selectedJob: selectedJobToken,
|
|
807
|
+
runtime: runtimeHooks.adapterRuntime("search")
|
|
808
|
+
});
|
|
809
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
810
|
+
if (isProcessAbortError(searchResult.error)) {
|
|
811
|
+
throw new PipelineAbortError(searchResult.error?.message || "推荐筛选已取消。");
|
|
812
|
+
}
|
|
813
|
+
if (!searchResult.ok) {
|
|
814
|
+
const searchErrorCode = String(searchResult.error?.code || "");
|
|
815
|
+
const searchErrorMessage = String(searchResult.error?.message || "");
|
|
816
|
+
const loginRelatedSearchFailure = (
|
|
817
|
+
searchErrorCode === "LOGIN_REQUIRED"
|
|
818
|
+
|| searchErrorCode === "NO_RECOMMEND_IFRAME"
|
|
819
|
+
|| searchErrorMessage.includes("LOGIN_REQUIRED")
|
|
820
|
+
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
821
|
+
);
|
|
822
|
+
if (loginRelatedSearchFailure) {
|
|
823
|
+
const recheck = await ensureRecommendPageReady(workspaceRoot, {
|
|
824
|
+
port: preflight.debug_port
|
|
825
|
+
});
|
|
826
|
+
if (recheck.state === "LOGIN_REQUIRED" || recheck.state === "LOGIN_REQUIRED_AFTER_REDIRECT") {
|
|
827
|
+
const guidance = buildChromeSetupGuidance({
|
|
828
|
+
debugPort: preflight.debug_port,
|
|
829
|
+
pageState: recheck.page_state
|
|
830
|
+
});
|
|
831
|
+
return buildFailedResponse(
|
|
832
|
+
"BOSS_LOGIN_REQUIRED",
|
|
833
|
+
"检测到当前 Boss 处于未登录状态,请先登录后再继续。登录页:https://www.zhipin.com/web/user/?ka=bticket",
|
|
834
|
+
{
|
|
835
|
+
search_params: effectiveSearchParams,
|
|
836
|
+
screen_params: parsed.screenParams,
|
|
837
|
+
selected_job: selectedJob,
|
|
838
|
+
required_user_action: "prepare_boss_recommend_page",
|
|
839
|
+
guidance,
|
|
840
|
+
diagnostics: {
|
|
841
|
+
debug_port: preflight.debug_port,
|
|
842
|
+
page_state: recheck.page_state,
|
|
843
|
+
stdout: searchResult.stdout?.slice(-1000),
|
|
844
|
+
stderr: searchResult.stderr?.slice(-1000),
|
|
845
|
+
result: searchResult.structured || null,
|
|
846
|
+
auto_recovery: lastAutoRecovery
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return buildFailedResponse(
|
|
853
|
+
searchResult.error?.code || "RECOMMEND_SEARCH_FAILED",
|
|
854
|
+
searchResult.error?.message || "推荐页筛选执行失败。",
|
|
855
|
+
{
|
|
856
|
+
search_params: effectiveSearchParams,
|
|
857
|
+
screen_params: parsed.screenParams,
|
|
858
|
+
selected_job: selectedJob,
|
|
859
|
+
diagnostics: {
|
|
860
|
+
debug_port: preflight.debug_port,
|
|
861
|
+
stdout: searchResult.stdout?.slice(-1000),
|
|
862
|
+
stderr: searchResult.stderr?.slice(-1000),
|
|
863
|
+
result: searchResult.structured || null,
|
|
864
|
+
auto_recovery: lastAutoRecovery
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
searchSummary = searchResult.summary || {};
|
|
871
|
+
if (isPauseRequested(runtimeHooks)) {
|
|
872
|
+
return buildPausedResponse("已在 screen 阶段开始前暂停 Recommend 流水线。", {
|
|
873
|
+
search_params: effectiveSearchParams,
|
|
874
|
+
screen_params: parsed.screenParams,
|
|
875
|
+
selected_job: selectedJob,
|
|
876
|
+
partial_result: {
|
|
877
|
+
candidate_count: searchSummary.candidate_count ?? null,
|
|
878
|
+
applied_filters: searchSummary.applied_filters || effectiveSearchParams,
|
|
879
|
+
output_csv: currentResumeConfig.output_csv || null,
|
|
880
|
+
completion_reason: "paused_before_screen"
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
885
|
+
runtimeHooks.setStage("screen", "search 完成,开始执行 recommend screen。");
|
|
886
|
+
} else {
|
|
887
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
888
|
+
runtimeHooks.setStage("screen", "检测到可续跑 checkpoint,跳过 search,直接恢复 recommend screen。");
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
runtimeHooks.heartbeat("screen", lastAutoRecovery);
|
|
892
|
+
const screenResult = await screenCli({
|
|
893
|
+
workspaceRoot,
|
|
894
|
+
screenParams: parsed.screenParams,
|
|
895
|
+
resume: currentResumeConfig,
|
|
896
|
+
runtime: runtimeHooks.adapterRuntime("screen")
|
|
897
|
+
});
|
|
898
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
899
|
+
if (isProcessAbortError(screenResult.error)) {
|
|
900
|
+
throw new PipelineAbortError(screenResult.error?.message || "推荐筛选已取消。");
|
|
901
|
+
}
|
|
902
|
+
if (screenResult.paused) {
|
|
903
|
+
return buildPausedResponse("Recommend 流水线已暂停,可使用 resume 继续。", {
|
|
904
|
+
search_params: effectiveSearchParams,
|
|
905
|
+
screen_params: parsed.screenParams,
|
|
906
|
+
selected_job: selectedJob,
|
|
907
|
+
partial_result: screenResult.summary || screenResult.structured?.result || null
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
if (!screenResult.ok) {
|
|
911
|
+
const screenErrorCode = String(screenResult.error?.code || "");
|
|
912
|
+
const partialScreenResult = screenResult.summary || screenResult.structured?.result || null;
|
|
913
|
+
const resumeOutputCsv = normalizeText(partialScreenResult?.output_csv || currentResumeConfig.output_csv || "");
|
|
914
|
+
const hasCheckpointForRecovery = Boolean(normalizeText(currentResumeConfig.checkpoint_path || ""));
|
|
915
|
+
const screenPartialForRecovery = partialScreenResult
|
|
916
|
+
? {
|
|
917
|
+
processed_count: partialScreenResult.processed_count ?? null,
|
|
918
|
+
passed_count: partialScreenResult.passed_count ?? null,
|
|
919
|
+
skipped_count: partialScreenResult.skipped_count ?? null,
|
|
920
|
+
output_csv: partialScreenResult.output_csv || currentResumeConfig.output_csv || null,
|
|
921
|
+
checkpoint_path: partialScreenResult.checkpoint_path || currentResumeConfig.checkpoint_path || null,
|
|
922
|
+
completion_reason: partialScreenResult.completion_reason || null
|
|
923
|
+
}
|
|
924
|
+
: null;
|
|
925
|
+
const isResumeCaptureRecovery = screenErrorCode === "RESUME_CAPTURE_FAILED_CONSECUTIVE_LIMIT";
|
|
926
|
+
const isPageExhaustedRecovery = screenErrorCode === "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED";
|
|
927
|
+
const isRecoverableScreenFailure = isResumeCaptureRecovery || isPageExhaustedRecovery;
|
|
928
|
+
const canRecoverSafely = hasCheckpointForRecovery && Boolean(resumeOutputCsv);
|
|
929
|
+
const hasRecoveryAttemptsRemaining = screenAutoRecoveryCount < MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS;
|
|
930
|
+
|
|
931
|
+
if (isRecoverableScreenFailure && !canRecoverSafely) {
|
|
932
|
+
return buildFailedResponse(
|
|
933
|
+
"SCREEN_AUTO_RECOVERY_UNSAFE",
|
|
934
|
+
"检测到 recommend 自动恢复触发,但缺少 checkpoint 或 output_csv,无法安全续跑。",
|
|
935
|
+
{
|
|
936
|
+
search_params: effectiveSearchParams,
|
|
937
|
+
screen_params: parsed.screenParams,
|
|
938
|
+
selected_job: selectedJob,
|
|
939
|
+
partial_result: partialScreenResult,
|
|
940
|
+
diagnostics: {
|
|
941
|
+
debug_port: preflight.debug_port,
|
|
942
|
+
stdout: screenResult.stdout?.slice(-1000),
|
|
943
|
+
stderr: screenResult.stderr?.slice(-1000),
|
|
944
|
+
result: screenResult.structured || null,
|
|
945
|
+
auto_recovery: {
|
|
946
|
+
trigger: screenErrorCode,
|
|
947
|
+
attempt: screenAutoRecoveryCount,
|
|
948
|
+
max_attempts: MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS,
|
|
949
|
+
original_recent_not_view: parsed.searchParams.recent_not_view,
|
|
950
|
+
effective_recent_not_view: effectiveSearchParams.recent_not_view,
|
|
951
|
+
partial_result: screenPartialForRecovery
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (isRecoverableScreenFailure && !hasRecoveryAttemptsRemaining) {
|
|
959
|
+
return buildFailedResponse(
|
|
960
|
+
screenResult.error?.code || "RECOMMEND_SCREEN_FAILED",
|
|
961
|
+
`${screenResult.error?.message || "推荐页筛选执行失败。"} 已达到自动恢复上限 ${MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS} 次。`,
|
|
962
|
+
{
|
|
963
|
+
search_params: effectiveSearchParams,
|
|
964
|
+
screen_params: parsed.screenParams,
|
|
965
|
+
selected_job: selectedJob,
|
|
966
|
+
partial_result: partialScreenResult,
|
|
967
|
+
diagnostics: {
|
|
968
|
+
debug_port: preflight.debug_port,
|
|
969
|
+
stdout: screenResult.stdout?.slice(-1000),
|
|
970
|
+
stderr: screenResult.stderr?.slice(-1000),
|
|
971
|
+
result: screenResult.structured || null,
|
|
972
|
+
auto_recovery: lastAutoRecovery
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (isRecoverableScreenFailure && canRecoverSafely && hasRecoveryAttemptsRemaining) {
|
|
979
|
+
screenAutoRecoveryCount += 1;
|
|
980
|
+
lastAutoRecovery = {
|
|
981
|
+
trigger: screenErrorCode,
|
|
982
|
+
attempt: screenAutoRecoveryCount,
|
|
983
|
+
max_attempts: MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS,
|
|
984
|
+
original_recent_not_view: parsed.searchParams.recent_not_view,
|
|
985
|
+
effective_recent_not_view: effectiveSearchParams.recent_not_view,
|
|
986
|
+
partial_result: screenPartialForRecovery,
|
|
987
|
+
page_exhaustion: screenResult.error?.page_exhaustion || null
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
if (isPageExhaustedRecovery) {
|
|
991
|
+
runtimeHooks.setStage(
|
|
992
|
+
"screen_recovery",
|
|
993
|
+
`推荐列表已到底但未达目标,开始自动补货(第 ${screenAutoRecoveryCount} 次):优先尝试页内刷新。`
|
|
994
|
+
);
|
|
995
|
+
runtimeHooks.heartbeat("screen_recovery", lastAutoRecovery);
|
|
996
|
+
|
|
997
|
+
const refreshResult = typeof refreshRecommendList === "function"
|
|
998
|
+
? await refreshRecommendList(workspaceRoot, {
|
|
999
|
+
port: preflight.debug_port
|
|
1000
|
+
})
|
|
1001
|
+
: {
|
|
1002
|
+
ok: false,
|
|
1003
|
+
action: "in_page_refresh",
|
|
1004
|
+
state: "REFRESH_ADAPTER_MISSING",
|
|
1005
|
+
message: "缺少页内刷新适配器。"
|
|
1006
|
+
};
|
|
1007
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
1008
|
+
|
|
1009
|
+
lastAutoRecovery = {
|
|
1010
|
+
...lastAutoRecovery,
|
|
1011
|
+
refresh: refreshResult
|
|
1012
|
+
? {
|
|
1013
|
+
ok: refreshResult.ok,
|
|
1014
|
+
state: refreshResult.state || null,
|
|
1015
|
+
message: refreshResult.message || null,
|
|
1016
|
+
before_state: refreshResult.before_state || null,
|
|
1017
|
+
after_state: refreshResult.after_state || null
|
|
1018
|
+
}
|
|
1019
|
+
: null
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
if (refreshResult?.ok) {
|
|
1023
|
+
lastAutoRecovery = {
|
|
1024
|
+
...lastAutoRecovery,
|
|
1025
|
+
action: "in_page_refresh"
|
|
1026
|
+
};
|
|
1027
|
+
currentResumeConfig = {
|
|
1028
|
+
checkpoint_path: currentResumeConfig.checkpoint_path || null,
|
|
1029
|
+
pause_control_path: currentResumeConfig.pause_control_path || null,
|
|
1030
|
+
output_csv: resumeOutputCsv || null,
|
|
1031
|
+
resume: true,
|
|
1032
|
+
require_checkpoint: true
|
|
1033
|
+
};
|
|
1034
|
+
shouldRunSearch = false;
|
|
1035
|
+
searchSummary = null;
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
runtimeHooks.setStage(
|
|
1040
|
+
"screen_recovery",
|
|
1041
|
+
`页内刷新不可用(${refreshResult?.state || "unknown"}),改为刷新 recommend 页面并重跑 search。`
|
|
1042
|
+
);
|
|
1043
|
+
runtimeHooks.heartbeat("screen_recovery", lastAutoRecovery);
|
|
1044
|
+
} else {
|
|
1045
|
+
runtimeHooks.setStage(
|
|
1046
|
+
"screen_recovery",
|
|
1047
|
+
`screen 连续截图失败,开始自动恢复(第 ${screenAutoRecoveryCount} 次):刷新 recommend 页面并重跑 search。`
|
|
1048
|
+
);
|
|
1049
|
+
runtimeHooks.heartbeat("screen_recovery", lastAutoRecovery);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
effectiveSearchParams = {
|
|
1053
|
+
...effectiveSearchParams,
|
|
1054
|
+
recent_not_view: FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY
|
|
1055
|
+
};
|
|
1056
|
+
lastAutoRecovery = {
|
|
1057
|
+
...lastAutoRecovery,
|
|
1058
|
+
action: "reload_page_and_rerun_search",
|
|
1059
|
+
effective_recent_not_view: effectiveSearchParams.recent_not_view
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const reloadResult = typeof reloadRecommendPage === "function"
|
|
1063
|
+
? await reloadRecommendPage(workspaceRoot, {
|
|
1064
|
+
port: preflight.debug_port
|
|
1065
|
+
})
|
|
1066
|
+
: null;
|
|
1067
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
1068
|
+
|
|
1069
|
+
lastAutoRecovery = {
|
|
1070
|
+
...lastAutoRecovery,
|
|
1071
|
+
reload: reloadResult
|
|
1072
|
+
? {
|
|
1073
|
+
ok: reloadResult.ok,
|
|
1074
|
+
state: reloadResult.state || null,
|
|
1075
|
+
message: reloadResult.message || null,
|
|
1076
|
+
reloaded_url: reloadResult.reloaded_url || null
|
|
1077
|
+
}
|
|
1078
|
+
: null
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const recoveryPageCheck = await ensureRecommendPageReady(workspaceRoot, {
|
|
1082
|
+
port: preflight.debug_port
|
|
1083
|
+
});
|
|
1084
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
1085
|
+
if (!recoveryPageCheck.ok) {
|
|
1086
|
+
const guidance = buildChromeSetupGuidance({
|
|
1087
|
+
debugPort: preflight.debug_port,
|
|
1088
|
+
pageState: recoveryPageCheck.page_state
|
|
1089
|
+
});
|
|
1090
|
+
return buildFailedResponse(
|
|
1091
|
+
recoveryPageCheck.state === "LOGIN_REQUIRED" || recoveryPageCheck.state === "LOGIN_REQUIRED_AFTER_REDIRECT"
|
|
1092
|
+
? "BOSS_LOGIN_REQUIRED"
|
|
1093
|
+
: recoveryPageCheck.state === "DEBUG_PORT_UNREACHABLE"
|
|
1094
|
+
? "BOSS_CHROME_NOT_CONNECTED"
|
|
1095
|
+
: "BOSS_RECOMMEND_PAGE_NOT_READY",
|
|
1096
|
+
"自动恢复时无法重新就绪 recommend 页面,请先处理页面状态后再继续。",
|
|
1097
|
+
{
|
|
1098
|
+
search_params: effectiveSearchParams,
|
|
1099
|
+
screen_params: parsed.screenParams,
|
|
1100
|
+
selected_job: selectedJob,
|
|
1101
|
+
partial_result: partialScreenResult,
|
|
1102
|
+
required_user_action: "prepare_boss_recommend_page",
|
|
1103
|
+
guidance,
|
|
1104
|
+
diagnostics: {
|
|
1105
|
+
debug_port: preflight.debug_port,
|
|
1106
|
+
page_state: recoveryPageCheck.page_state,
|
|
1107
|
+
stdout: screenResult.stdout?.slice(-1000),
|
|
1108
|
+
stderr: screenResult.stderr?.slice(-1000),
|
|
1109
|
+
result: screenResult.structured || null,
|
|
1110
|
+
auto_recovery: lastAutoRecovery
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
currentResumeConfig = {
|
|
1117
|
+
checkpoint_path: currentResumeConfig.checkpoint_path || null,
|
|
1118
|
+
pause_control_path: currentResumeConfig.pause_control_path || null,
|
|
1119
|
+
output_csv: resumeOutputCsv || null,
|
|
1120
|
+
resume: true,
|
|
1121
|
+
require_checkpoint: true
|
|
1122
|
+
};
|
|
1123
|
+
shouldRunSearch = true;
|
|
1124
|
+
searchSummary = null;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
return buildFailedResponse(
|
|
1129
|
+
screenResult.error?.code || "RECOMMEND_SCREEN_FAILED",
|
|
1130
|
+
screenResult.error?.message || "推荐页筛选执行失败。",
|
|
1131
|
+
{
|
|
1132
|
+
search_params: effectiveSearchParams,
|
|
1133
|
+
screen_params: parsed.screenParams,
|
|
1134
|
+
selected_job: selectedJob,
|
|
1135
|
+
partial_result: partialScreenResult,
|
|
1136
|
+
diagnostics: {
|
|
1137
|
+
debug_port: preflight.debug_port,
|
|
1138
|
+
stdout: screenResult.stdout?.slice(-1000),
|
|
1139
|
+
stderr: screenResult.stderr?.slice(-1000),
|
|
1140
|
+
result: screenResult.structured || null,
|
|
1141
|
+
auto_recovery: lastAutoRecovery
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
runtimeHooks.setStage("finalize", "screen 完成,正在汇总结果。");
|
|
1148
|
+
runtimeHooks.heartbeat("finalize");
|
|
1149
|
+
const durationSec = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
1150
|
+
const finalSearchSummary = searchSummary || {};
|
|
1151
|
+
const screenSummary = screenResult.summary || {};
|
|
1152
|
+
runtimeHooks.progress("finalize", {
|
|
1153
|
+
processed: screenSummary.processed_count ?? 0,
|
|
1154
|
+
passed: screenSummary.passed_count ?? 0,
|
|
1155
|
+
skipped: screenSummary.skipped_count ?? 0,
|
|
1156
|
+
greet_count: screenSummary.greet_count ?? 0
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
return {
|
|
1160
|
+
status: "COMPLETED",
|
|
1161
|
+
search_params: effectiveSearchParams,
|
|
1162
|
+
screen_params: parsed.screenParams,
|
|
1163
|
+
result: {
|
|
1164
|
+
candidate_count: finalSearchSummary.candidate_count ?? null,
|
|
1165
|
+
applied_filters: finalSearchSummary.applied_filters || effectiveSearchParams,
|
|
1166
|
+
processed_count: screenSummary.processed_count ?? 0,
|
|
1167
|
+
passed_count: screenSummary.passed_count ?? 0,
|
|
1168
|
+
skipped_count: screenSummary.skipped_count ?? 0,
|
|
1169
|
+
duration_sec: durationSec,
|
|
1170
|
+
output_csv: screenSummary.output_csv || null,
|
|
1171
|
+
completion_reason: screenSummary.completion_reason || "screen_completed",
|
|
1172
|
+
page_state: finalSearchSummary.page_state || pageCheck.page_state,
|
|
1173
|
+
selected_job: finalSearchSummary.selected_job || selectedJob,
|
|
1174
|
+
post_action: parsed.screenParams.post_action,
|
|
1175
|
+
max_greet_count: parsed.screenParams.max_greet_count,
|
|
1176
|
+
greet_count: screenSummary.greet_count ?? 0,
|
|
1177
|
+
greet_limit_fallback_count: screenSummary.greet_limit_fallback_count ?? 0,
|
|
1178
|
+
auto_recovery: lastAutoRecovery
|
|
1179
|
+
},
|
|
1180
|
+
message: parsed.screenParams.post_action === "none"
|
|
1181
|
+
? "Recommend 流水线已完成。本次 post_action=none:符合条件的人选仅记录到 CSV,不执行收藏或打招呼。"
|
|
1182
|
+
: "Recommend 流水线已完成。post_action 在运行开始时已一次性确认;若选择打招呼并设置上限,超出上限后会自动改为收藏。"
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
}
|