@reconcrap/boss-recommend-mcp 1.0.19 → 1.1.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/package.json +1 -1
- package/src/adapters.js +33 -1
- package/src/index.js +10 -1
- package/src/pipeline.js +97 -84
- package/src/test-adapters-runtime.js +47 -1
- package/src/test-pipeline.js +100 -0
package/package.json
CHANGED
package/src/adapters.js
CHANGED
|
@@ -1599,6 +1599,38 @@ export async function runRecommendScreenCli({ workspaceRoot, screenParams, resum
|
|
|
1599
1599
|
const pauseControlPath = normalizeText(resume?.pause_control_path || "")
|
|
1600
1600
|
? path.resolve(String(resume.pause_control_path))
|
|
1601
1601
|
: null;
|
|
1602
|
+
const resumeRequested = resume?.resume === true;
|
|
1603
|
+
const requireCheckpoint = resume?.require_checkpoint === true;
|
|
1604
|
+
if (resumeRequested && requireCheckpoint) {
|
|
1605
|
+
if (!checkpointPath) {
|
|
1606
|
+
return {
|
|
1607
|
+
ok: false,
|
|
1608
|
+
paused: false,
|
|
1609
|
+
stdout: "",
|
|
1610
|
+
stderr: "",
|
|
1611
|
+
structured: null,
|
|
1612
|
+
summary: null,
|
|
1613
|
+
error: {
|
|
1614
|
+
code: "RESUME_CHECKPOINT_MISSING",
|
|
1615
|
+
message: "恢复执行缺少 checkpoint_path,无法从上次进度继续。"
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
if (!fs.existsSync(checkpointPath)) {
|
|
1620
|
+
return {
|
|
1621
|
+
ok: false,
|
|
1622
|
+
paused: false,
|
|
1623
|
+
stdout: "",
|
|
1624
|
+
stderr: "",
|
|
1625
|
+
structured: null,
|
|
1626
|
+
summary: null,
|
|
1627
|
+
error: {
|
|
1628
|
+
code: "RESUME_CHECKPOINT_MISSING",
|
|
1629
|
+
message: `恢复执行未找到 checkpoint 文件:${checkpointPath}`
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1602
1634
|
|
|
1603
1635
|
const cliPath = resolveRecommendScreenCliEntry(screenDir);
|
|
1604
1636
|
const args = [
|
|
@@ -1641,7 +1673,7 @@ export async function runRecommendScreenCli({ workspaceRoot, screenParams, resum
|
|
|
1641
1673
|
if (pauseControlPath) {
|
|
1642
1674
|
args.push("--pause-control-path", pauseControlPath);
|
|
1643
1675
|
}
|
|
1644
|
-
if (
|
|
1676
|
+
if (resumeRequested) {
|
|
1645
1677
|
args.push("--resume");
|
|
1646
1678
|
}
|
|
1647
1679
|
|
package/src/index.js
CHANGED
|
@@ -110,6 +110,14 @@ function getOutputCsvFromResult(result) {
|
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
function getCompletionReasonFromResult(result) {
|
|
114
|
+
const direct = normalizeText(result?.result?.completion_reason || "");
|
|
115
|
+
if (direct) return direct;
|
|
116
|
+
const partial = normalizeText(result?.partial_result?.completion_reason || "");
|
|
117
|
+
if (partial) return partial;
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
function writeMessage(message, framing = FRAMING_LINE) {
|
|
114
122
|
const body = JSON.stringify(message);
|
|
115
123
|
if (framing === FRAMING_HEADER) {
|
|
@@ -496,7 +504,8 @@ async function executeTrackedPipeline({
|
|
|
496
504
|
resume: resumeRun === true,
|
|
497
505
|
checkpoint_path: normalizeText(existingSnapshot?.resume?.checkpoint_path || artifacts.checkpoint_path),
|
|
498
506
|
pause_control_path: normalizeText(existingSnapshot?.resume?.pause_control_path || artifacts.run_state_path),
|
|
499
|
-
output_csv: normalizeText(existingSnapshot?.resume?.output_csv || "") || null
|
|
507
|
+
output_csv: normalizeText(existingSnapshot?.resume?.output_csv || "") || null,
|
|
508
|
+
previous_completion_reason: getCompletionReasonFromResult(existingSnapshot?.result || null)
|
|
500
509
|
};
|
|
501
510
|
safeUpdateRunState(runId, {
|
|
502
511
|
state: RUN_STATE_RUNNING,
|
package/src/pipeline.js
CHANGED
|
@@ -805,90 +805,102 @@ export async function runRecommendPipeline(
|
|
|
805
805
|
};
|
|
806
806
|
}
|
|
807
807
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
808
|
+
const resumeCompletionReason = normalizeText(resume?.previous_completion_reason || "").toLowerCase();
|
|
809
|
+
const isResumeRun = resume?.resume === true;
|
|
810
|
+
const resumeFromPausedBeforeScreen = isResumeRun && resumeCompletionReason === "paused_before_screen";
|
|
811
|
+
const skipSearchOnResume = isResumeRun && !resumeFromPausedBeforeScreen;
|
|
812
|
+
let searchSummary = null;
|
|
813
|
+
|
|
814
|
+
if (!skipSearchOnResume) {
|
|
815
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
816
|
+
runtimeHooks.setStage("search", "岗位已确认,开始执行 recommend search。");
|
|
817
|
+
runtimeHooks.heartbeat("search");
|
|
818
|
+
const searchResult = await searchCli({
|
|
819
|
+
workspaceRoot,
|
|
820
|
+
searchParams: parsed.searchParams,
|
|
821
|
+
selectedJob: selectedJobToken,
|
|
822
|
+
runtime: runtimeHooks.adapterRuntime("search")
|
|
823
|
+
});
|
|
824
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
825
|
+
if (isProcessAbortError(searchResult.error)) {
|
|
826
|
+
throw new PipelineAbortError(searchResult.error?.message || "推荐筛选已取消。");
|
|
827
|
+
}
|
|
828
|
+
if (!searchResult.ok) {
|
|
829
|
+
const searchErrorCode = String(searchResult.error?.code || "");
|
|
830
|
+
const searchErrorMessage = String(searchResult.error?.message || "");
|
|
831
|
+
const loginRelatedSearchFailure = (
|
|
832
|
+
searchErrorCode === "LOGIN_REQUIRED"
|
|
833
|
+
|| searchErrorCode === "NO_RECOMMEND_IFRAME"
|
|
834
|
+
|| searchErrorMessage.includes("LOGIN_REQUIRED")
|
|
835
|
+
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
836
|
+
);
|
|
837
|
+
if (loginRelatedSearchFailure) {
|
|
838
|
+
const recheck = await ensureRecommendPageReady(workspaceRoot, {
|
|
839
|
+
port: preflight.debug_port
|
|
838
840
|
});
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
841
|
+
if (recheck.state === "LOGIN_REQUIRED" || recheck.state === "LOGIN_REQUIRED_AFTER_REDIRECT") {
|
|
842
|
+
const guidance = buildChromeSetupGuidance({
|
|
843
|
+
debugPort: preflight.debug_port,
|
|
844
|
+
pageState: recheck.page_state
|
|
845
|
+
});
|
|
846
|
+
return buildFailedResponse(
|
|
847
|
+
"BOSS_LOGIN_REQUIRED",
|
|
848
|
+
"检测到当前 Boss 处于未登录状态,请先登录后再继续。登录页:https://www.zhipin.com/web/user/?ka=bticket",
|
|
849
|
+
{
|
|
850
|
+
search_params: parsed.searchParams,
|
|
851
|
+
screen_params: parsed.screenParams,
|
|
852
|
+
selected_job: selectedJob,
|
|
853
|
+
required_user_action: "prepare_boss_recommend_page",
|
|
854
|
+
guidance,
|
|
855
|
+
diagnostics: {
|
|
856
|
+
debug_port: preflight.debug_port,
|
|
857
|
+
page_state: recheck.page_state,
|
|
858
|
+
stdout: searchResult.stdout?.slice(-1000),
|
|
859
|
+
stderr: searchResult.stderr?.slice(-1000),
|
|
860
|
+
result: searchResult.structured || null
|
|
861
|
+
}
|
|
854
862
|
}
|
|
855
|
-
|
|
856
|
-
|
|
863
|
+
);
|
|
864
|
+
}
|
|
857
865
|
}
|
|
866
|
+
return buildFailedResponse(
|
|
867
|
+
searchResult.error?.code || "RECOMMEND_SEARCH_FAILED",
|
|
868
|
+
searchResult.error?.message || "推荐页筛选执行失败。",
|
|
869
|
+
{
|
|
870
|
+
search_params: parsed.searchParams,
|
|
871
|
+
screen_params: parsed.screenParams,
|
|
872
|
+
selected_job: selectedJob,
|
|
873
|
+
diagnostics: {
|
|
874
|
+
debug_port: preflight.debug_port,
|
|
875
|
+
stdout: searchResult.stdout?.slice(-1000),
|
|
876
|
+
stderr: searchResult.stderr?.slice(-1000),
|
|
877
|
+
result: searchResult.structured || null
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
);
|
|
858
881
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
{
|
|
882
|
+
|
|
883
|
+
searchSummary = searchResult.summary || {};
|
|
884
|
+
if (isPauseRequested(runtimeHooks)) {
|
|
885
|
+
return buildPausedResponse("已在 screen 阶段开始前暂停 Recommend 流水线。", {
|
|
863
886
|
search_params: parsed.searchParams,
|
|
864
887
|
screen_params: parsed.screenParams,
|
|
865
888
|
selected_job: selectedJob,
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
screen_params: parsed.screenParams,
|
|
880
|
-
selected_job: selectedJob,
|
|
881
|
-
partial_result: {
|
|
882
|
-
candidate_count: searchResult.summary?.candidate_count ?? null,
|
|
883
|
-
applied_filters: searchResult.summary?.applied_filters || parsed.searchParams,
|
|
884
|
-
output_csv: resume?.output_csv || null,
|
|
885
|
-
completion_reason: "paused_before_screen"
|
|
886
|
-
}
|
|
887
|
-
});
|
|
889
|
+
partial_result: {
|
|
890
|
+
candidate_count: searchSummary.candidate_count ?? null,
|
|
891
|
+
applied_filters: searchSummary.applied_filters || parsed.searchParams,
|
|
892
|
+
output_csv: resume?.output_csv || null,
|
|
893
|
+
completion_reason: "paused_before_screen"
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
898
|
+
runtimeHooks.setStage("screen", "search 完成,开始执行 recommend screen。");
|
|
899
|
+
} else {
|
|
900
|
+
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
901
|
+
runtimeHooks.setStage("screen", "检测到可续跑 checkpoint,跳过 search,直接恢复 recommend screen。");
|
|
888
902
|
}
|
|
889
903
|
|
|
890
|
-
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
891
|
-
runtimeHooks.setStage("screen", "search 完成,开始执行 recommend screen。");
|
|
892
904
|
runtimeHooks.heartbeat("screen");
|
|
893
905
|
const screenResult = await screenCli({
|
|
894
906
|
workspaceRoot,
|
|
@@ -897,7 +909,8 @@ export async function runRecommendPipeline(
|
|
|
897
909
|
checkpoint_path: resume?.checkpoint_path || null,
|
|
898
910
|
pause_control_path: resume?.pause_control_path || null,
|
|
899
911
|
output_csv: resume?.output_csv || null,
|
|
900
|
-
resume: resume?.resume === true
|
|
912
|
+
resume: resume?.resume === true,
|
|
913
|
+
require_checkpoint: skipSearchOnResume
|
|
901
914
|
},
|
|
902
915
|
runtime: runtimeHooks.adapterRuntime("screen")
|
|
903
916
|
});
|
|
@@ -936,7 +949,7 @@ export async function runRecommendPipeline(
|
|
|
936
949
|
runtimeHooks.setStage("finalize", "screen 完成,正在汇总结果。");
|
|
937
950
|
runtimeHooks.heartbeat("finalize");
|
|
938
951
|
const durationSec = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
939
|
-
const
|
|
952
|
+
const finalSearchSummary = searchSummary || {};
|
|
940
953
|
const screenSummary = screenResult.summary || {};
|
|
941
954
|
runtimeHooks.progress("finalize", {
|
|
942
955
|
processed: screenSummary.processed_count ?? 0,
|
|
@@ -949,17 +962,17 @@ export async function runRecommendPipeline(
|
|
|
949
962
|
status: "COMPLETED",
|
|
950
963
|
search_params: parsed.searchParams,
|
|
951
964
|
screen_params: parsed.screenParams,
|
|
952
|
-
result: {
|
|
953
|
-
candidate_count:
|
|
954
|
-
applied_filters:
|
|
955
|
-
processed_count: screenSummary.processed_count ?? 0,
|
|
956
|
-
passed_count: screenSummary.passed_count ?? 0,
|
|
965
|
+
result: {
|
|
966
|
+
candidate_count: finalSearchSummary.candidate_count ?? null,
|
|
967
|
+
applied_filters: finalSearchSummary.applied_filters || parsed.searchParams,
|
|
968
|
+
processed_count: screenSummary.processed_count ?? 0,
|
|
969
|
+
passed_count: screenSummary.passed_count ?? 0,
|
|
957
970
|
skipped_count: screenSummary.skipped_count ?? 0,
|
|
958
971
|
duration_sec: durationSec,
|
|
959
972
|
output_csv: screenSummary.output_csv || null,
|
|
960
973
|
completion_reason: screenSummary.completion_reason || "screen_completed",
|
|
961
|
-
page_state:
|
|
962
|
-
selected_job:
|
|
974
|
+
page_state: finalSearchSummary.page_state || pageCheck.page_state,
|
|
975
|
+
selected_job: finalSearchSummary.selected_job || selectedJob,
|
|
963
976
|
post_action: parsed.screenParams.post_action,
|
|
964
977
|
max_greet_count: parsed.screenParams.max_greet_count,
|
|
965
978
|
greet_count: screenSummary.greet_count ?? 0,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { runRecommendScreenCli, __testables as adapterTestables } from "./adapters.js";
|
|
3
6
|
|
|
4
7
|
const { runProcess, parseJsonOutput } = adapterTestables;
|
|
5
8
|
|
|
@@ -54,10 +57,53 @@ function testParsePausedStructuredOutput() {
|
|
|
54
57
|
assert.equal(parsed?.result?.processed_count, 3);
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
async function testResumeRequiresCheckpointFile() {
|
|
61
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
62
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-resume-"));
|
|
63
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
64
|
+
try {
|
|
65
|
+
const configPath = path.join(tempHome, "screening-config.json");
|
|
66
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
67
|
+
baseUrl: "https://api.openai.com/v1",
|
|
68
|
+
apiKey: "sk-test-valid",
|
|
69
|
+
model: "gpt-4.1-mini"
|
|
70
|
+
}, null, 2));
|
|
71
|
+
|
|
72
|
+
const missingCheckpoint = path.join(tempHome, "missing-checkpoint.json");
|
|
73
|
+
const result = await runRecommendScreenCli({
|
|
74
|
+
workspaceRoot: process.cwd(),
|
|
75
|
+
screenParams: {
|
|
76
|
+
criteria: "有MCP经验",
|
|
77
|
+
target_count: 10,
|
|
78
|
+
post_action: "favorite",
|
|
79
|
+
max_greet_count: null
|
|
80
|
+
},
|
|
81
|
+
resume: {
|
|
82
|
+
resume: true,
|
|
83
|
+
require_checkpoint: true,
|
|
84
|
+
checkpoint_path: missingCheckpoint,
|
|
85
|
+
pause_control_path: path.join(tempHome, "run-state.json"),
|
|
86
|
+
output_csv: path.join(tempHome, "resume.csv")
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
assert.equal(result.ok, false);
|
|
91
|
+
assert.equal(result.error?.code, "RESUME_CHECKPOINT_MISSING");
|
|
92
|
+
} finally {
|
|
93
|
+
if (previousHome === undefined) {
|
|
94
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
95
|
+
} else {
|
|
96
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
97
|
+
}
|
|
98
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
57
102
|
async function main() {
|
|
58
103
|
await testRunProcessHeartbeatAndOutput();
|
|
59
104
|
await testRunProcessAbortSignal();
|
|
60
105
|
testParsePausedStructuredOutput();
|
|
106
|
+
await testResumeRequiresCheckpointFile();
|
|
61
107
|
console.log("adapters runtime tests passed");
|
|
62
108
|
}
|
|
63
109
|
|
package/src/test-pipeline.js
CHANGED
|
@@ -154,6 +154,104 @@ async function testPausedScreenResultShouldBubbleUp() {
|
|
|
154
154
|
assert.equal(result.partial_result.completion_reason, "paused");
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
async function testResumeFromScreenPauseShouldSkipSearch() {
|
|
158
|
+
let searchCalled = false;
|
|
159
|
+
let receivedResume = null;
|
|
160
|
+
const result = await runRecommendPipeline(
|
|
161
|
+
{
|
|
162
|
+
workspaceRoot: process.cwd(),
|
|
163
|
+
instruction: "test",
|
|
164
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
165
|
+
overrides: {},
|
|
166
|
+
resume: {
|
|
167
|
+
resume: true,
|
|
168
|
+
output_csv: "C:/temp/resume.csv",
|
|
169
|
+
checkpoint_path: "C:/temp/checkpoint.json",
|
|
170
|
+
pause_control_path: "C:/temp/run.json",
|
|
171
|
+
previous_completion_reason: "paused"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
parseRecommendInstruction: () => createParsed(),
|
|
176
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
177
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
178
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
179
|
+
runRecommendSearchCli: async () => {
|
|
180
|
+
searchCalled = true;
|
|
181
|
+
return { ok: true, summary: { candidate_count: 9, applied_filters: {} } };
|
|
182
|
+
},
|
|
183
|
+
runRecommendScreenCli: async ({ resume }) => {
|
|
184
|
+
receivedResume = resume;
|
|
185
|
+
return {
|
|
186
|
+
ok: true,
|
|
187
|
+
summary: {
|
|
188
|
+
processed_count: 6,
|
|
189
|
+
passed_count: 2,
|
|
190
|
+
skipped_count: 4,
|
|
191
|
+
output_csv: "C:/temp/resume.csv",
|
|
192
|
+
completion_reason: "page_exhausted"
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
assert.equal(result.status, "COMPLETED");
|
|
200
|
+
assert.equal(searchCalled, false);
|
|
201
|
+
assert.equal(receivedResume.resume, true);
|
|
202
|
+
assert.equal(receivedResume.require_checkpoint, true);
|
|
203
|
+
assert.equal(result.result.candidate_count, null);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function testResumeFromPausedBeforeScreenShouldRerunSearch() {
|
|
207
|
+
let searchCalled = false;
|
|
208
|
+
let receivedResume = null;
|
|
209
|
+
const result = await runRecommendPipeline(
|
|
210
|
+
{
|
|
211
|
+
workspaceRoot: process.cwd(),
|
|
212
|
+
instruction: "test",
|
|
213
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
214
|
+
overrides: {},
|
|
215
|
+
resume: {
|
|
216
|
+
resume: true,
|
|
217
|
+
output_csv: "C:/temp/resume.csv",
|
|
218
|
+
checkpoint_path: "C:/temp/checkpoint.json",
|
|
219
|
+
pause_control_path: "C:/temp/run.json",
|
|
220
|
+
previous_completion_reason: "paused_before_screen"
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
parseRecommendInstruction: () => createParsed(),
|
|
225
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
226
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
227
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
228
|
+
runRecommendSearchCli: async () => {
|
|
229
|
+
searchCalled = true;
|
|
230
|
+
return { ok: true, summary: { candidate_count: 9, applied_filters: { degree: ["本科"] } } };
|
|
231
|
+
},
|
|
232
|
+
runRecommendScreenCli: async ({ resume }) => {
|
|
233
|
+
receivedResume = resume;
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
summary: {
|
|
237
|
+
processed_count: 4,
|
|
238
|
+
passed_count: 1,
|
|
239
|
+
skipped_count: 3,
|
|
240
|
+
output_csv: "C:/temp/resume.csv",
|
|
241
|
+
completion_reason: "page_exhausted"
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
assert.equal(result.status, "COMPLETED");
|
|
249
|
+
assert.equal(searchCalled, true);
|
|
250
|
+
assert.equal(receivedResume.resume, true);
|
|
251
|
+
assert.equal(receivedResume.require_checkpoint, false);
|
|
252
|
+
assert.equal(result.result.candidate_count, 9);
|
|
253
|
+
}
|
|
254
|
+
|
|
157
255
|
async function testNeedConfirmationGate() {
|
|
158
256
|
let preflightCalled = false;
|
|
159
257
|
const result = await runRecommendPipeline(
|
|
@@ -833,6 +931,8 @@ async function testScreenConfigRecoveryStepShouldBeFirst() {
|
|
|
833
931
|
async function main() {
|
|
834
932
|
await testPauseRequestedBeforeScreenShouldReturnPaused();
|
|
835
933
|
await testPausedScreenResultShouldBubbleUp();
|
|
934
|
+
await testResumeFromScreenPauseShouldSkipSearch();
|
|
935
|
+
await testResumeFromPausedBeforeScreenShouldRerunSearch();
|
|
836
936
|
await testNeedConfirmationGate();
|
|
837
937
|
await testNeedSchoolTagConfirmationGate();
|
|
838
938
|
await testNeedTargetCountConfirmationGate();
|