@reconcrap/boss-recommend-mcp 1.1.12 → 1.2.1
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/skills/boss-recommend-pipeline/SKILL.md +4 -3
- package/src/adapters.js +33 -19
- package/src/cli.js +1 -0
- package/src/index.js +327 -225
- package/src/parser.js +9 -4
- package/src/pipeline.js +7 -3
- package/src/test-adapters-runtime.js +107 -0
- package/src/test-index-async.js +33 -5
- package/src/test-parser.js +16 -0
- package/src/test-pipeline.js +78 -1
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +95 -18
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +16 -0
- package/vendor/boss-recommend-search-cli/src/cli.js +14 -6
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +2 -0
package/src/parser.js
CHANGED
|
@@ -34,10 +34,11 @@ const POST_ACTION_LABELS = {
|
|
|
34
34
|
greet: "直接沟通",
|
|
35
35
|
none: "什么也不做"
|
|
36
36
|
};
|
|
37
|
-
const PAGE_SCOPE_OPTIONS = ["recommend", "featured"];
|
|
37
|
+
const PAGE_SCOPE_OPTIONS = ["recommend", "featured", "latest"];
|
|
38
38
|
const PAGE_SCOPE_LABELS = {
|
|
39
39
|
recommend: "推荐",
|
|
40
|
-
featured: "精选"
|
|
40
|
+
featured: "精选",
|
|
41
|
+
latest: "最新"
|
|
41
42
|
};
|
|
42
43
|
const LEADING_NOISE_PATTERNS = [
|
|
43
44
|
/^使用boss-recommend-pipeline skills/i,
|
|
@@ -103,6 +104,7 @@ const META_CLAUSE_PATTERNS = [
|
|
|
103
104
|
/帮我|请|运行|skill/i
|
|
104
105
|
];
|
|
105
106
|
const FEATURED_SCOPE_PATTERN = /(?:精选牛人|精选页|精选页面|精选tab|精选标签|tab[^。;;\n]{0,6}精选|精选)/i;
|
|
107
|
+
const LATEST_SCOPE_PATTERN = /(?:最新页|最新页面|最新tab|最新标签|tab[^。;;\n]{0,6}最新|最新)/i;
|
|
106
108
|
const RECOMMEND_SCOPE_PATTERN = /(?:推荐页|推荐页面|推荐tab|推荐标签|tab[^。;;\n]{0,6}推荐|推荐)/i;
|
|
107
109
|
|
|
108
110
|
function normalizeText(input) {
|
|
@@ -278,11 +280,13 @@ function normalizePageScope(value) {
|
|
|
278
280
|
if (!normalized) return null;
|
|
279
281
|
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
280
282
|
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
283
|
+
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
281
284
|
return PAGE_SCOPE_OPTIONS.includes(normalized) ? normalized : null;
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
function extractPageScope(text) {
|
|
285
288
|
if (FEATURED_SCOPE_PATTERN.test(text)) return "featured";
|
|
289
|
+
if (LATEST_SCOPE_PATTERN.test(text)) return "latest";
|
|
286
290
|
if (RECOMMEND_SCOPE_PATTERN.test(text)) return "recommend";
|
|
287
291
|
return null;
|
|
288
292
|
}
|
|
@@ -612,11 +616,12 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
612
616
|
if (needs_page_confirmation) {
|
|
613
617
|
pending_questions.push({
|
|
614
618
|
field: "page_scope",
|
|
615
|
-
question: "请确认本次在推荐里的哪个页面执行筛选:推荐
|
|
619
|
+
question: "请确认本次在推荐里的哪个页面执行筛选:推荐 / 精选 / 最新。",
|
|
616
620
|
value: pageScopeResolution.proposed_page_scope,
|
|
617
621
|
options: [
|
|
618
622
|
{ label: PAGE_SCOPE_LABELS.recommend, value: "recommend" },
|
|
619
|
-
{ label: PAGE_SCOPE_LABELS.featured, value: "featured" }
|
|
623
|
+
{ label: PAGE_SCOPE_LABELS.featured, value: "featured" },
|
|
624
|
+
{ label: PAGE_SCOPE_LABELS.latest, value: "latest" }
|
|
620
625
|
]
|
|
621
626
|
});
|
|
622
627
|
}
|
package/src/pipeline.js
CHANGED
|
@@ -18,14 +18,17 @@ const FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY = "近14天没有";
|
|
|
18
18
|
const MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS = 5;
|
|
19
19
|
const PAGE_SCOPE_TO_TAB_STATUS = {
|
|
20
20
|
recommend: "0",
|
|
21
|
+
latest: "1",
|
|
21
22
|
featured: "3"
|
|
22
23
|
};
|
|
23
24
|
const TAB_STATUS_TO_PAGE_SCOPE = {
|
|
24
25
|
"0": "recommend",
|
|
26
|
+
"1": "latest",
|
|
25
27
|
"3": "featured"
|
|
26
28
|
};
|
|
27
29
|
const PAGE_SCOPE_LABELS = {
|
|
28
30
|
recommend: "推荐",
|
|
31
|
+
latest: "最新",
|
|
29
32
|
featured: "精选"
|
|
30
33
|
};
|
|
31
34
|
|
|
@@ -41,6 +44,7 @@ function normalizePageScope(value) {
|
|
|
41
44
|
const normalized = normalizeText(value).toLowerCase();
|
|
42
45
|
if (!normalized) return null;
|
|
43
46
|
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
47
|
+
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
44
48
|
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
45
49
|
return null;
|
|
46
50
|
}
|
|
@@ -892,7 +896,7 @@ export async function runRecommendPipeline(
|
|
|
892
896
|
};
|
|
893
897
|
|
|
894
898
|
const ensureSelectedPageTab = async () => {
|
|
895
|
-
if (selectedPage
|
|
899
|
+
if (selectedPage === "recommend") {
|
|
896
900
|
activeTabStatus = selectedTabStatus;
|
|
897
901
|
return {
|
|
898
902
|
ok: true,
|
|
@@ -1073,8 +1077,8 @@ export async function runRecommendPipeline(
|
|
|
1073
1077
|
ensurePipelineNotAborted(runtimeHooks.signal);
|
|
1074
1078
|
runtimeHooks.setStage(
|
|
1075
1079
|
"screen",
|
|
1076
|
-
selectedPage
|
|
1077
|
-
?
|
|
1080
|
+
selectedPage !== "recommend"
|
|
1081
|
+
? `search 完成,已切换到${PAGE_SCOPE_LABELS[selectedPage]} tab,开始执行 recommend screen。`
|
|
1078
1082
|
: "search 完成,开始执行 recommend screen。"
|
|
1079
1083
|
);
|
|
1080
1084
|
} else {
|
|
@@ -177,6 +177,13 @@ function testPreflightRecommendShouldKeepFavoriteCalibrationOptional() {
|
|
|
177
177
|
assert.equal(check.optional, true);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
function testPreflightLatestShouldKeepFavoriteCalibrationOptional() {
|
|
181
|
+
const preflight = runPipelinePreflight(process.cwd(), { pageScope: "latest" });
|
|
182
|
+
const check = (preflight.checks || []).find((item) => item?.key === "favorite_calibration");
|
|
183
|
+
assert.equal(Boolean(check), true);
|
|
184
|
+
assert.equal(check.optional, true);
|
|
185
|
+
}
|
|
186
|
+
|
|
180
187
|
async function testEnsureFeaturedCalibrationReadyShouldAutoCalibrate() {
|
|
181
188
|
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
182
189
|
const previousCodexHome = process.env.CODEX_HOME;
|
|
@@ -282,6 +289,43 @@ async function testSearchCliShouldPassPageScopeArgument() {
|
|
|
282
289
|
}
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
async function testSearchCliShouldPassLatestPageScopeWithoutCalibration() {
|
|
293
|
+
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-search-page-scope-latest-"));
|
|
294
|
+
const cliDir = path.join(workspaceRoot, "boss-recommend-search-cli", "src");
|
|
295
|
+
fs.mkdirSync(cliDir, { recursive: true });
|
|
296
|
+
const cliPath = path.join(cliDir, "cli.js");
|
|
297
|
+
fs.writeFileSync(
|
|
298
|
+
cliPath,
|
|
299
|
+
[
|
|
300
|
+
"#!/usr/bin/env node",
|
|
301
|
+
"console.log(JSON.stringify({ status: 'COMPLETED', result: { argv: process.argv.slice(2) } }));"
|
|
302
|
+
].join("\n"),
|
|
303
|
+
"utf8"
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const result = await runRecommendSearchCli({
|
|
308
|
+
workspaceRoot,
|
|
309
|
+
searchParams: {
|
|
310
|
+
school_tag: ["不限"],
|
|
311
|
+
degree: ["不限"],
|
|
312
|
+
gender: "不限",
|
|
313
|
+
recent_not_view: "不限"
|
|
314
|
+
},
|
|
315
|
+
selectedJob: null,
|
|
316
|
+
pageScope: "latest"
|
|
317
|
+
});
|
|
318
|
+
assert.equal(result.ok, true);
|
|
319
|
+
const argv = result.summary?.argv || [];
|
|
320
|
+
const pageScopeIndex = argv.indexOf("--page-scope");
|
|
321
|
+
assert.equal(pageScopeIndex >= 0, true);
|
|
322
|
+
assert.equal(argv[pageScopeIndex + 1], "latest");
|
|
323
|
+
assert.equal(argv.includes("--calibration"), false);
|
|
324
|
+
} finally {
|
|
325
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
285
329
|
async function testScreenCliShouldPassPageScopeArgument() {
|
|
286
330
|
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
287
331
|
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-page-scope-home-"));
|
|
@@ -342,6 +386,66 @@ async function testScreenCliShouldPassPageScopeArgument() {
|
|
|
342
386
|
}
|
|
343
387
|
}
|
|
344
388
|
|
|
389
|
+
async function testScreenCliShouldPassLatestPageScopeArgument() {
|
|
390
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
391
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-page-scope-latest-home-"));
|
|
392
|
+
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-page-scope-latest-workspace-"));
|
|
393
|
+
const cliDir = path.join(workspaceRoot, "boss-recommend-screen-cli");
|
|
394
|
+
fs.mkdirSync(cliDir, { recursive: true });
|
|
395
|
+
const cliPath = path.join(cliDir, "boss-recommend-screen-cli.cjs");
|
|
396
|
+
fs.writeFileSync(
|
|
397
|
+
cliPath,
|
|
398
|
+
[
|
|
399
|
+
"#!/usr/bin/env node",
|
|
400
|
+
"console.log(JSON.stringify({",
|
|
401
|
+
" status: 'COMPLETED',",
|
|
402
|
+
" result: {",
|
|
403
|
+
" processed_count: 0,",
|
|
404
|
+
" passed_count: 0,",
|
|
405
|
+
" skipped_count: 0,",
|
|
406
|
+
" argv: process.argv.slice(2),",
|
|
407
|
+
" resume_source: 'image_fallback',",
|
|
408
|
+
" active_tab_status: '1'",
|
|
409
|
+
" }",
|
|
410
|
+
"}));"
|
|
411
|
+
].join("\n"),
|
|
412
|
+
"utf8"
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
416
|
+
fs.writeFileSync(path.join(tempHome, "screening-config.json"), JSON.stringify({
|
|
417
|
+
baseUrl: "https://api.openai.com/v1",
|
|
418
|
+
apiKey: "sk-valid-test",
|
|
419
|
+
model: "gpt-4.1-mini"
|
|
420
|
+
}, null, 2));
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const result = await runRecommendScreenCli({
|
|
424
|
+
workspaceRoot,
|
|
425
|
+
screenParams: {
|
|
426
|
+
criteria: "有 MCP 经验",
|
|
427
|
+
target_count: null,
|
|
428
|
+
post_action: "none",
|
|
429
|
+
max_greet_count: null
|
|
430
|
+
},
|
|
431
|
+
pageScope: "latest"
|
|
432
|
+
});
|
|
433
|
+
assert.equal(result.ok, true);
|
|
434
|
+
const argv = result.summary?.argv || [];
|
|
435
|
+
const pageScopeIndex = argv.indexOf("--page-scope");
|
|
436
|
+
assert.equal(pageScopeIndex >= 0, true);
|
|
437
|
+
assert.equal(argv[pageScopeIndex + 1], "latest");
|
|
438
|
+
} finally {
|
|
439
|
+
if (previousHome === undefined) {
|
|
440
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
441
|
+
} else {
|
|
442
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
443
|
+
}
|
|
444
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
445
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
345
449
|
async function main() {
|
|
346
450
|
await testRunProcessHeartbeatAndOutput();
|
|
347
451
|
await testRunProcessAbortSignal();
|
|
@@ -353,9 +457,12 @@ async function main() {
|
|
|
353
457
|
testPreflightShouldCheckSharpInsteadOfPython();
|
|
354
458
|
testPreflightFeaturedShouldRequireFavoriteCalibration();
|
|
355
459
|
testPreflightRecommendShouldKeepFavoriteCalibrationOptional();
|
|
460
|
+
testPreflightLatestShouldKeepFavoriteCalibrationOptional();
|
|
356
461
|
await testEnsureFeaturedCalibrationReadyShouldAutoCalibrate();
|
|
357
462
|
await testSearchCliShouldPassPageScopeArgument();
|
|
463
|
+
await testSearchCliShouldPassLatestPageScopeWithoutCalibration();
|
|
358
464
|
await testScreenCliShouldPassPageScopeArgument();
|
|
465
|
+
await testScreenCliShouldPassLatestPageScopeArgument();
|
|
359
466
|
console.log("adapters runtime tests passed");
|
|
360
467
|
}
|
|
361
468
|
|
package/src/test-index-async.js
CHANGED
|
@@ -6,7 +6,8 @@ import { __testables } from "./index.js";
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
handleRequest,
|
|
9
|
-
|
|
9
|
+
runDetachedWorkerForTests,
|
|
10
|
+
setSpawnProcessImplForTests,
|
|
10
11
|
setRunPipelineImplForTests
|
|
11
12
|
} = __testables;
|
|
12
13
|
|
|
@@ -141,6 +142,35 @@ function setupPipelineMock() {
|
|
|
141
142
|
});
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
function parseDetachedSpawnArgs(argv = []) {
|
|
146
|
+
const normalized = Array.isArray(argv) ? argv.map((item) => String(item || "")) : [];
|
|
147
|
+
const runIdFlagIndex = normalized.indexOf("--run-id");
|
|
148
|
+
return {
|
|
149
|
+
runId: runIdFlagIndex >= 0 ? String(normalized[runIdFlagIndex + 1] || "").trim() : "",
|
|
150
|
+
resumeRun: normalized.includes("--resume")
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function setupDetachedWorkerStub() {
|
|
155
|
+
setSpawnProcessImplForTests((command, argv = []) => {
|
|
156
|
+
assert.equal(typeof command, "string");
|
|
157
|
+
const { runId, resumeRun } = parseDetachedSpawnArgs(argv);
|
|
158
|
+
assert.equal(Boolean(runId), true, "detached worker spawn must include --run-id");
|
|
159
|
+
const pid = process.pid;
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
runDetachedWorkerForTests({
|
|
162
|
+
runId,
|
|
163
|
+
resumeRun,
|
|
164
|
+
workerPid: pid
|
|
165
|
+
}).catch(() => {});
|
|
166
|
+
}, 0);
|
|
167
|
+
return {
|
|
168
|
+
pid,
|
|
169
|
+
unref() {}
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
async function testPauseAndResumeFlow() {
|
|
145
175
|
const runId = await startAcceptedRun("run for pause and resume", 11);
|
|
146
176
|
await waitForRunState(runId, ["running"]);
|
|
@@ -170,9 +200,6 @@ async function testResumeAfterProcessRestartSimulation() {
|
|
|
170
200
|
assert.equal(pausePayload.status, "PAUSE_REQUESTED");
|
|
171
201
|
await waitForRunState(runId, ["paused"]);
|
|
172
202
|
|
|
173
|
-
// 模拟服务重启后内存态丢失:active map 为空,仅依赖 run-state 持久化恢复。
|
|
174
|
-
activeAsyncRuns.clear();
|
|
175
|
-
|
|
176
203
|
const resumePayload = await callTool(TOOL_RESUME_RUN, { run_id: runId }, 23);
|
|
177
204
|
assert.equal(resumePayload.status, "RESUME_REQUESTED");
|
|
178
205
|
assert.equal(resumePayload.run.run_id, runId);
|
|
@@ -214,6 +241,7 @@ async function main() {
|
|
|
214
241
|
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-index-async-"));
|
|
215
242
|
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
216
243
|
setupPipelineMock();
|
|
244
|
+
setupDetachedWorkerStub();
|
|
217
245
|
|
|
218
246
|
try {
|
|
219
247
|
await testPauseAndResumeFlow();
|
|
@@ -223,7 +251,7 @@ async function main() {
|
|
|
223
251
|
console.log("index async tests passed");
|
|
224
252
|
} finally {
|
|
225
253
|
setRunPipelineImplForTests(null);
|
|
226
|
-
|
|
254
|
+
setSpawnProcessImplForTests(null);
|
|
227
255
|
if (previousHome === undefined) {
|
|
228
256
|
delete process.env.BOSS_RECOMMEND_HOME;
|
|
229
257
|
} else {
|
package/src/test-parser.js
CHANGED
|
@@ -488,6 +488,21 @@ function testFeaturedKeywordShouldProposeFeaturedPageScope() {
|
|
|
488
488
|
assert.equal(result.pending_questions.some((item) => item.field === "page_scope"), true);
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
+
function testLatestKeywordShouldProposeLatestPageScope() {
|
|
492
|
+
const result = parseRecommendInstruction({
|
|
493
|
+
instruction: "在推荐页最新里筛选候选人,有 Agent 经验,符合标准收藏",
|
|
494
|
+
confirmation: null,
|
|
495
|
+
overrides: null
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
assert.equal(result.proposed_page_scope, "latest");
|
|
499
|
+
assert.equal(result.needs_page_confirmation, true);
|
|
500
|
+
const pageQuestion = result.pending_questions.find((item) => item.field === "page_scope");
|
|
501
|
+
assert.equal(Boolean(pageQuestion), true);
|
|
502
|
+
assert.equal(Array.isArray(pageQuestion.options), true);
|
|
503
|
+
assert.equal(pageQuestion.options.some((item) => item.value === "latest"), true);
|
|
504
|
+
}
|
|
505
|
+
|
|
491
506
|
function testConfirmedPageScopeShouldBeResolved() {
|
|
492
507
|
const result = parseRecommendInstruction({
|
|
493
508
|
instruction: "在推荐页筛选候选人,有 Agent 经验,符合标准收藏",
|
|
@@ -543,6 +558,7 @@ function main() {
|
|
|
543
558
|
testPostActionNoneCanBeConfirmed();
|
|
544
559
|
testJobSelectionHintCanComeFromOverrides();
|
|
545
560
|
testFeaturedKeywordShouldProposeFeaturedPageScope();
|
|
561
|
+
testLatestKeywordShouldProposeLatestPageScope();
|
|
546
562
|
testConfirmedPageScopeShouldBeResolved();
|
|
547
563
|
testPageScopeOverrideShouldNotBypassConfirmation();
|
|
548
564
|
console.log("parser tests passed");
|
package/src/test-pipeline.js
CHANGED
|
@@ -1066,7 +1066,7 @@ async function testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen() {
|
|
|
1066
1066
|
active_status: "0",
|
|
1067
1067
|
tab_state: { active_status: "0" }
|
|
1068
1068
|
}),
|
|
1069
|
-
switchRecommendTab: async ({ target_status }) => {
|
|
1069
|
+
switchRecommendTab: async (_workspaceRoot, { target_status }) => {
|
|
1070
1070
|
calls.push({ type: "switch", target_status });
|
|
1071
1071
|
return {
|
|
1072
1072
|
ok: true,
|
|
@@ -1102,6 +1102,82 @@ async function testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen() {
|
|
|
1102
1102
|
assert.equal(result.result.resume_source, "network");
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
|
+
async function testLatestPipelineShouldRunSearchThenSwitchTabThenScreen() {
|
|
1106
|
+
const calls = [];
|
|
1107
|
+
const result = await runRecommendPipeline(
|
|
1108
|
+
{
|
|
1109
|
+
workspaceRoot: process.cwd(),
|
|
1110
|
+
instruction: "test",
|
|
1111
|
+
confirmation: {
|
|
1112
|
+
...createJobConfirmedConfirmation(),
|
|
1113
|
+
page_confirmed: true,
|
|
1114
|
+
page_value: "latest"
|
|
1115
|
+
},
|
|
1116
|
+
overrides: {
|
|
1117
|
+
page_scope: "latest"
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
parseRecommendInstruction: () => createParsed({
|
|
1122
|
+
page_scope: "latest",
|
|
1123
|
+
proposed_page_scope: "latest"
|
|
1124
|
+
}),
|
|
1125
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
1126
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
|
|
1127
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
1128
|
+
runRecommendSearchCli: async ({ pageScope }) => {
|
|
1129
|
+
calls.push({ type: "search", pageScope });
|
|
1130
|
+
return {
|
|
1131
|
+
ok: true,
|
|
1132
|
+
summary: {
|
|
1133
|
+
candidate_count: 8,
|
|
1134
|
+
applied_filters: { degree: ["本科"] },
|
|
1135
|
+
page_state: { state: "RECOMMEND_READY" }
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
},
|
|
1139
|
+
readRecommendTabState: async () => ({
|
|
1140
|
+
ok: true,
|
|
1141
|
+
active_status: "0",
|
|
1142
|
+
tab_state: { active_status: "0" }
|
|
1143
|
+
}),
|
|
1144
|
+
switchRecommendTab: async (_workspaceRoot, { target_status }) => {
|
|
1145
|
+
calls.push({ type: "switch", target_status });
|
|
1146
|
+
return {
|
|
1147
|
+
ok: true,
|
|
1148
|
+
state: "TAB_SWITCHED",
|
|
1149
|
+
active_status: "1",
|
|
1150
|
+
tab_state: { active_status: "1" }
|
|
1151
|
+
};
|
|
1152
|
+
},
|
|
1153
|
+
runRecommendScreenCli: async ({ pageScope }) => {
|
|
1154
|
+
calls.push({ type: "screen", pageScope });
|
|
1155
|
+
return {
|
|
1156
|
+
ok: true,
|
|
1157
|
+
summary: {
|
|
1158
|
+
processed_count: 8,
|
|
1159
|
+
passed_count: 3,
|
|
1160
|
+
skipped_count: 5,
|
|
1161
|
+
output_csv: "C:/temp/result.csv",
|
|
1162
|
+
completion_reason: "page_exhausted",
|
|
1163
|
+
active_tab_status: "1",
|
|
1164
|
+
resume_source: "image_fallback"
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
assert.equal(result.status, "COMPLETED");
|
|
1172
|
+
assert.deepEqual(calls.map((item) => item.type), ["search", "switch", "screen"]);
|
|
1173
|
+
assert.equal(calls[0].pageScope, "latest");
|
|
1174
|
+
assert.equal(calls[1].target_status, "1");
|
|
1175
|
+
assert.equal(calls[2].pageScope, "latest");
|
|
1176
|
+
assert.equal(result.result.selected_page, "latest");
|
|
1177
|
+
assert.equal(result.result.active_tab_status, "1");
|
|
1178
|
+
assert.equal(result.result.resume_source, "image_fallback");
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1105
1181
|
async function testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue() {
|
|
1106
1182
|
const calls = [];
|
|
1107
1183
|
let preflightCallCount = 0;
|
|
@@ -1855,6 +1931,7 @@ async function main() {
|
|
|
1855
1931
|
await testNeedMaxGreetCountConfirmationGate();
|
|
1856
1932
|
await testNeedInputGate();
|
|
1857
1933
|
await testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen();
|
|
1934
|
+
await testLatestPipelineShouldRunSearchThenSwitchTabThenScreen();
|
|
1858
1935
|
await testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue();
|
|
1859
1936
|
await testFeaturedCalibrationFailureShouldReturnCalibrationRequired();
|
|
1860
1937
|
await testFeaturedTabSwitchFailureShouldReturnRetryableError();
|
|
@@ -15,6 +15,7 @@ const RESUME_CAPTURE_RETRY_DELAY_MS = 1200;
|
|
|
15
15
|
const MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES = 10;
|
|
16
16
|
const PAGE_SCOPE_TAB_STATUS = {
|
|
17
17
|
recommend: "0",
|
|
18
|
+
latest: "1",
|
|
18
19
|
featured: "3"
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -79,6 +80,7 @@ function normalizePageScope(value) {
|
|
|
79
80
|
const normalized = normalizeText(value).toLowerCase();
|
|
80
81
|
if (!normalized) return null;
|
|
81
82
|
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
83
|
+
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
82
84
|
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
83
85
|
return null;
|
|
84
86
|
}
|
|
@@ -774,6 +776,7 @@ function buildListCandidatesExpr(processedKeys) {
|
|
|
774
776
|
const processed = new Set(processedKeys || []);
|
|
775
777
|
const cards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item'));
|
|
776
778
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
|
|
779
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
|
|
777
780
|
const textOf = (el) => String(el ? el.textContent : '').replace(/\s+/g, ' ').trim();
|
|
778
781
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
779
782
|
const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
|
|
@@ -838,19 +841,71 @@ function buildListCandidatesExpr(processedKeys) {
|
|
|
838
841
|
layout: 'featured'
|
|
839
842
|
};
|
|
840
843
|
}).filter(Boolean);
|
|
844
|
+
const latestCandidates = latestCards.map((card, index) => {
|
|
845
|
+
const inner = card.querySelector('.card-inner[data-geek]') || card.querySelector('[data-geek]');
|
|
846
|
+
if (!inner) return null;
|
|
847
|
+
const geekId = inner.getAttribute('data-geek');
|
|
848
|
+
if (!geekId) return null;
|
|
849
|
+
const rect = card.getBoundingClientRect();
|
|
850
|
+
const nameEl = card.querySelector('.name, .name-wrap .name, .name-wrap');
|
|
851
|
+
const tags = Array.from(card.querySelectorAll('.base-info span, .edu-wrap span, .desc span, .tag-wrap span, .tag-item'))
|
|
852
|
+
.map((item) => textOf(item))
|
|
853
|
+
.filter(Boolean);
|
|
854
|
+
const latestWork = card.querySelector('.timeline-wrap.work-exps .timeline-item');
|
|
855
|
+
const workSpans = latestWork
|
|
856
|
+
? Array.from(latestWork.querySelectorAll('.join-text-wrap.content span')).map((item) => textOf(item)).filter(Boolean)
|
|
857
|
+
: [];
|
|
858
|
+
return {
|
|
859
|
+
found: true,
|
|
860
|
+
index,
|
|
861
|
+
key: geekId,
|
|
862
|
+
geek_id: geekId,
|
|
863
|
+
name: textOf(nameEl),
|
|
864
|
+
school: tags[0] || '',
|
|
865
|
+
major: tags[1] || '',
|
|
866
|
+
degree: tags[2] || '',
|
|
867
|
+
last_company: workSpans[0] || '',
|
|
868
|
+
last_position: workSpans[1] || '',
|
|
869
|
+
x: frameRect.left + rect.left + Math.min(Math.max(rect.width / 2, 80), Math.max(rect.width - 40, 80)),
|
|
870
|
+
y: frameRect.top + rect.top + Math.min(Math.max(rect.height / 2, 24), Math.max(rect.height - 12, 24)),
|
|
871
|
+
width: rect.width,
|
|
872
|
+
height: rect.height,
|
|
873
|
+
layout: 'latest'
|
|
874
|
+
};
|
|
875
|
+
}).filter(Boolean);
|
|
841
876
|
const inferredStatus = activeStatus
|
|
842
|
-
|| (
|
|
877
|
+
|| (
|
|
878
|
+
featuredCandidates.length > 0 && recommendCandidates.length === 0 && latestCandidates.length === 0
|
|
879
|
+
? '3'
|
|
880
|
+
: latestCandidates.length > 0 && recommendCandidates.length === 0 && featuredCandidates.length === 0
|
|
881
|
+
? '1'
|
|
882
|
+
: recommendCandidates.length > 0 && featuredCandidates.length === 0 && latestCandidates.length === 0
|
|
883
|
+
? '0'
|
|
884
|
+
: ''
|
|
885
|
+
);
|
|
843
886
|
const activeLayout = inferredStatus === '3'
|
|
844
887
|
? 'featured'
|
|
845
|
-
: inferredStatus === '
|
|
846
|
-
? '
|
|
847
|
-
:
|
|
848
|
-
|
|
888
|
+
: inferredStatus === '1'
|
|
889
|
+
? 'latest'
|
|
890
|
+
: inferredStatus === '0'
|
|
891
|
+
? 'recommend'
|
|
892
|
+
: (
|
|
893
|
+
featuredCandidates.length > 0 && recommendCandidates.length === 0 && latestCandidates.length === 0
|
|
894
|
+
? 'featured'
|
|
895
|
+
: latestCandidates.length > 0 && featuredCandidates.length === 0 && recommendCandidates.length === 0
|
|
896
|
+
? 'latest'
|
|
897
|
+
: 'recommend'
|
|
898
|
+
);
|
|
899
|
+
const candidates = activeLayout === 'featured'
|
|
900
|
+
? featuredCandidates
|
|
901
|
+
: activeLayout === 'latest'
|
|
902
|
+
? latestCandidates
|
|
903
|
+
: recommendCandidates;
|
|
849
904
|
return {
|
|
850
905
|
ok: true,
|
|
851
906
|
candidates: candidates.filter((candidate) => !processed.has(candidate.key)),
|
|
852
907
|
candidate_count: candidates.length,
|
|
853
|
-
total_cards: activeLayout === 'featured' ? featuredCards.length : cards.length,
|
|
908
|
+
total_cards: activeLayout === 'featured' ? featuredCards.length : activeLayout === 'latest' ? latestCards.length : cards.length,
|
|
854
909
|
active_tab_status: inferredStatus || null,
|
|
855
910
|
layout: activeLayout
|
|
856
911
|
};
|
|
@@ -871,16 +926,28 @@ const jsGetListState = `(() => {
|
|
|
871
926
|
const candidateCards = cards.filter((card) => card.querySelector('.card-inner[data-geekid]'));
|
|
872
927
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
|
|
873
928
|
const featuredCandidates = featuredCards.filter((card) => card.querySelector('a[data-geekid]'));
|
|
929
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
|
|
930
|
+
const latestCandidates = latestCards.filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
|
|
874
931
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
875
932
|
const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
|
|
876
933
|
const activeTabStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
|
|
877
934
|
const inferredStatus = activeTabStatus
|
|
878
|
-
|| (
|
|
935
|
+
|| (
|
|
936
|
+
featuredCandidates.length > 0 && candidateCards.length === 0 && latestCandidates.length === 0
|
|
937
|
+
? '3'
|
|
938
|
+
: latestCandidates.length > 0 && candidateCards.length === 0 && featuredCandidates.length === 0
|
|
939
|
+
? '1'
|
|
940
|
+
: candidateCards.length > 0 && featuredCandidates.length === 0 && latestCandidates.length === 0
|
|
941
|
+
? '0'
|
|
942
|
+
: ''
|
|
943
|
+
);
|
|
879
944
|
const effectiveCount = inferredStatus === '3'
|
|
880
945
|
? featuredCandidates.length
|
|
881
|
-
: inferredStatus === '
|
|
882
|
-
?
|
|
883
|
-
:
|
|
946
|
+
: inferredStatus === '1'
|
|
947
|
+
? latestCandidates.length
|
|
948
|
+
: inferredStatus === '0'
|
|
949
|
+
? candidateCards.length
|
|
950
|
+
: Math.max(candidateCards.length, featuredCandidates.length, latestCandidates.length);
|
|
884
951
|
return {
|
|
885
952
|
ok: true,
|
|
886
953
|
scrollTop: body ? body.scrollTop : 0,
|
|
@@ -898,7 +965,8 @@ const jsGetListState = `(() => {
|
|
|
898
965
|
candidateCount: effectiveCount,
|
|
899
966
|
recommendCandidateCount: candidateCards.length,
|
|
900
967
|
featuredCandidateCount: featuredCandidates.length,
|
|
901
|
-
|
|
968
|
+
latestCandidateCount: latestCandidates.length,
|
|
969
|
+
totalCards: Math.max(cards.length, featuredCards.length, latestCards.length),
|
|
902
970
|
activeTabStatus: inferredStatus || null
|
|
903
971
|
};
|
|
904
972
|
})()`;
|
|
@@ -914,12 +982,21 @@ const jsScrollList = `(() => {
|
|
|
914
982
|
const body = doc.body;
|
|
915
983
|
const recommendCards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item')).filter((card) => card.querySelector('.card-inner[data-geekid]'));
|
|
916
984
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card')).filter((card) => card.querySelector('a[data-geekid]'));
|
|
985
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap')).filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
|
|
917
986
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
918
987
|
const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
|
|
919
988
|
const activeStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
|
|
920
989
|
const inferredStatus = activeStatus
|
|
921
|
-
|| (
|
|
922
|
-
|
|
990
|
+
|| (
|
|
991
|
+
featuredCards.length > 0 && recommendCards.length === 0 && latestCards.length === 0
|
|
992
|
+
? '3'
|
|
993
|
+
: latestCards.length > 0 && recommendCards.length === 0 && featuredCards.length === 0
|
|
994
|
+
? '1'
|
|
995
|
+
: recommendCards.length > 0 && featuredCards.length === 0 && latestCards.length === 0
|
|
996
|
+
? '0'
|
|
997
|
+
: ''
|
|
998
|
+
);
|
|
999
|
+
const activeCards = inferredStatus === '3' ? featuredCards : inferredStatus === '1' ? latestCards : recommendCards;
|
|
923
1000
|
const lastCard = activeCards[activeCards.length - 1];
|
|
924
1001
|
const before = {
|
|
925
1002
|
scrollTop: body ? body.scrollTop : 0,
|
|
@@ -3361,11 +3438,11 @@ async function main() {
|
|
|
3361
3438
|
const args = parseArgs(process.argv.slice(2));
|
|
3362
3439
|
if (args.help) {
|
|
3363
3440
|
console.log(JSON.stringify({
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3441
|
+
status: "COMPLETED",
|
|
3442
|
+
result: {
|
|
3443
|
+
usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --page-scope recommend|latest|featured --calibration <favorite-calibration.json> --port 9222 --output <csv-path> --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
|
|
3444
|
+
}
|
|
3445
|
+
}));
|
|
3369
3446
|
return;
|
|
3370
3447
|
}
|
|
3371
3448
|
|
|
@@ -702,6 +702,21 @@ function testParseArgsShouldSupportFeaturedAliasesAndInlinePort() {
|
|
|
702
702
|
assert.equal(parsed.__provided.port, true);
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
+
function testParseArgsShouldSupportLatestPageScope() {
|
|
706
|
+
const parsed = parseArgs([
|
|
707
|
+
"--criteria", "test criteria",
|
|
708
|
+
"--baseurl", "https://example.com/v1",
|
|
709
|
+
"--apikey", "key",
|
|
710
|
+
"--model", "test-model",
|
|
711
|
+
"--page-scope", "latest",
|
|
712
|
+
"--port", "9222",
|
|
713
|
+
"--post-action", "none",
|
|
714
|
+
"--post-action-confirmed", "true"
|
|
715
|
+
]);
|
|
716
|
+
assert.equal(parsed.pageScope, "latest");
|
|
717
|
+
assert.equal(parsed.port, 9222);
|
|
718
|
+
}
|
|
719
|
+
|
|
705
720
|
async function main() {
|
|
706
721
|
testShouldAbortResumeProbeEarly();
|
|
707
722
|
await testSingleResumeCaptureFailureIsSkipped();
|
|
@@ -725,6 +740,7 @@ async function main() {
|
|
|
725
740
|
testStitchWithAvailablePythonShouldFallbackToPython();
|
|
726
741
|
testStitchWithAvailablePythonShouldFailWhenScriptMissing();
|
|
727
742
|
testParseArgsShouldSupportFeaturedAliasesAndInlinePort();
|
|
743
|
+
testParseArgsShouldSupportLatestPageScope();
|
|
728
744
|
console.log("recoverable resume failure tests passed");
|
|
729
745
|
}
|
|
730
746
|
|