@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/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 !== "featured") {
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 === "featured"
1077
- ? "search 完成,已切换到精选 tab,开始执行 recommend screen。"
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
 
@@ -6,7 +6,8 @@ import { __testables } from "./index.js";
6
6
 
7
7
  const {
8
8
  handleRequest,
9
- activeAsyncRuns,
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
- activeAsyncRuns.clear();
254
+ setSpawnProcessImplForTests(null);
227
255
  if (previousHome === undefined) {
228
256
  delete process.env.BOSS_RECOMMEND_HOME;
229
257
  } else {
@@ -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");
@@ -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
- || (featuredCandidates.length > 0 && recommendCandidates.length === 0 ? '3' : recommendCandidates.length > 0 ? '0' : '');
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 === '0'
846
- ? 'recommend'
847
- : (featuredCandidates.length > 0 && recommendCandidates.length === 0 ? 'featured' : 'recommend');
848
- const candidates = activeLayout === 'featured' ? featuredCandidates : recommendCandidates;
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
- || (featuredCandidates.length > 0 && candidateCards.length === 0 ? '3' : candidateCards.length > 0 ? '0' : '');
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 === '0'
882
- ? candidateCards.length
883
- : Math.max(candidateCards.length, featuredCandidates.length);
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
- totalCards: Math.max(cards.length, featuredCards.length),
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
- || (featuredCards.length > 0 && recommendCards.length === 0 ? '3' : recommendCards.length > 0 ? '0' : '');
922
- const activeCards = inferredStatus === '3' ? featuredCards : recommendCards;
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
- status: "COMPLETED",
3365
- result: {
3366
- 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|featured --calibration <favorite-calibration.json> --port 9222 --output <csv-path> --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
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