@reconcrap/boss-recommend-mcp 1.3.36 → 1.3.37

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.36",
3
+ "version": "1.3.37",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
package/src/pipeline.js CHANGED
@@ -30,6 +30,15 @@ const SEARCH_NO_IFRAME_RETRY_DELAY_MS = 1200;
30
30
  const MAX_SEARCH_FILTER_AUTO_RETRY_ATTEMPTS = 2;
31
31
  const SEARCH_FILTER_AUTO_RETRY_DELAY_MS = 1200;
32
32
  const BOSS_CHAT_FOLLOW_UP_POLL_MS = 1500;
33
+ const FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN = "passed_count";
34
+ const FOLLOW_UP_TARGET_COUNT_PASSED_LABEL = "通过筛选数";
35
+ const FOLLOW_UP_TARGET_COUNT_PASSED_ALIASES = new Set([
36
+ FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
37
+ "passed",
38
+ "通过筛选数",
39
+ "筛选通过数",
40
+ "通过数"
41
+ ]);
33
42
  const SEARCH_FILTER_RETRY_TOKENS = [
34
43
  "FILTER_CONFIRM_FAILED",
35
44
  "FILTER_DOM_CLASS_VERIFY_FAILED",
@@ -72,6 +81,75 @@ function normalizePipelineTargetCountValue(value) {
72
81
  return normalizeTargetCountInput(value).publicValue;
73
82
  }
74
83
 
84
+ function isFollowUpPassedTargetCountToken(value) {
85
+ const normalized = normalizeText(value).toLowerCase().replace(/\s+/g, "");
86
+ if (!normalized) return false;
87
+ return FOLLOW_UP_TARGET_COUNT_PASSED_ALIASES.has(normalized);
88
+ }
89
+
90
+ function normalizeFollowUpTargetCountInput(value) {
91
+ const normalized = normalizeTargetCountInput(value);
92
+ if (normalized.provided) {
93
+ return {
94
+ ...normalized,
95
+ launchValue: normalized.publicValue,
96
+ passedCountMode: false
97
+ };
98
+ }
99
+ if (isFollowUpPassedTargetCountToken(value)) {
100
+ return {
101
+ provided: true,
102
+ targetCount: null,
103
+ cliArg: null,
104
+ publicValue: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
105
+ rawValue: value,
106
+ parseError: null,
107
+ launchValue: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
108
+ passedCountMode: true
109
+ };
110
+ }
111
+ return {
112
+ ...normalized,
113
+ launchValue: null,
114
+ passedCountMode: false
115
+ };
116
+ }
117
+
118
+ function resolveFollowUpChatTargetCountForLaunch(targetCount, recommendSummary = null) {
119
+ if (isFollowUpPassedTargetCountToken(targetCount)) {
120
+ const passedCount = parsePositiveIntegerValue(recommendSummary?.passed_count);
121
+ if (passedCount) {
122
+ return {
123
+ ok: true,
124
+ target_count: passedCount,
125
+ resolved_from: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
126
+ passed_count: recommendSummary?.passed_count ?? null
127
+ };
128
+ }
129
+ return {
130
+ ok: false,
131
+ code: "FOLLOW_UP_TARGET_COUNT_PASSED_UNAVAILABLE",
132
+ message: "boss-chat follow-up 选择了“通过筛选数”,但本次通过人数为空或 0。请改为正整数,或填写 all(扫到底)。",
133
+ passed_count: recommendSummary?.passed_count ?? null
134
+ };
135
+ }
136
+ const normalized = normalizeTargetCountInput(targetCount);
137
+ if (normalized.provided) {
138
+ return {
139
+ ok: true,
140
+ target_count: normalized.publicValue,
141
+ resolved_from: "explicit",
142
+ passed_count: recommendSummary?.passed_count ?? null
143
+ };
144
+ }
145
+ return {
146
+ ok: false,
147
+ code: "FOLLOW_UP_TARGET_COUNT_INVALID",
148
+ message: normalized.parseError || "boss-chat follow-up target_count 无效。",
149
+ passed_count: recommendSummary?.passed_count ?? null
150
+ };
151
+ }
152
+
75
153
  function sleep(ms) {
76
154
  return new Promise((resolve) => setTimeout(resolve, ms));
77
155
  }
@@ -394,13 +472,12 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
394
472
  const defaultCriteria = normalizeText(defaults?.criteria || "");
395
473
  const defaultStartFromRaw = normalizeText(defaults?.start_from || "").toLowerCase();
396
474
  const defaultStartFrom = defaultStartFromRaw === "all" ? "all" : "unread";
397
- const defaultTargetCount = normalizePipelineTargetCountValue(defaults?.target_count);
398
475
 
399
476
  const explicitCriteria = normalizeText(raw.criteria);
400
477
  const explicitStartFromRaw = normalizeText(raw.start_from).toLowerCase();
401
478
  const explicitStartFrom = explicitStartFromRaw === "all" ? "all" : explicitStartFromRaw === "unread" ? "unread" : "";
402
- const explicitTarget = normalizeTargetCountInput(raw.target_count);
403
- const explicitTargetCount = explicitTarget.publicValue;
479
+ const explicitTarget = normalizeFollowUpTargetCountInput(raw.target_count);
480
+ const explicitTargetCount = explicitTarget.launchValue;
404
481
 
405
482
  const hasExplicitCriteria = Boolean(explicitCriteria);
406
483
  const hasExplicitStartFrom = Boolean(explicitStartFrom);
@@ -408,14 +485,15 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
408
485
 
409
486
  const criteria = explicitCriteria || defaultCriteria;
410
487
  const startFrom = explicitStartFrom || defaultStartFrom;
411
- const targetCount = explicitTargetCount || defaultTargetCount;
488
+ const targetCount = explicitTargetCount || FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN;
489
+ const targetCountSummaryValue = explicitTarget.publicValue || FOLLOW_UP_TARGET_COUNT_PASSED_LABEL;
412
490
 
413
491
  const profile = normalizeText(raw.profile) || "default";
414
492
  const summary = {
415
493
  profile,
416
494
  criteria: criteria || null,
417
495
  start_from: startFrom || null,
418
- target_count: targetCount,
496
+ target_count: targetCountSummaryValue,
419
497
  dry_run: raw.dry_run === true,
420
498
  no_state: raw.no_state === true,
421
499
  safe_pacing: typeof raw.safe_pacing === "boolean" ? raw.safe_pacing : null,
@@ -446,21 +524,39 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
446
524
  });
447
525
  }
448
526
  if (!hasExplicitTargetCount) {
449
- const targetCountHints = buildTargetCountCompatibilityHints({
450
- argumentName: "follow_up.chat.target_count",
451
- recommendedArgumentPatch: {
452
- follow_up: {
453
- chat: {
454
- target_count: "all"
455
- }
527
+ const recommendedTargetCountPatch = {
528
+ follow_up: {
529
+ chat: {
530
+ target_count: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL
456
531
  }
457
532
  }
533
+ };
534
+ const targetCountHints = buildTargetCountCompatibilityHints({
535
+ argumentName: "follow_up.chat.target_count",
536
+ recommendedArgumentPatch: recommendedTargetCountPatch
458
537
  });
459
538
  missing_fields.push("follow_up.chat.target_count");
460
539
  pending_questions.push({
461
540
  ...targetCountHints,
541
+ answer_format: `follow_up.chat.target_count = "${FOLLOW_UP_TARGET_COUNT_PASSED_LABEL}" | 正整数 | "all"`,
542
+ recommended_value: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
543
+ recommended_argument_patch: recommendedTargetCountPatch,
544
+ canonical_passed_count_value: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
545
+ accepted_examples: [
546
+ FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
547
+ ...targetCountHints.accepted_examples
548
+ ],
549
+ options: [
550
+ {
551
+ label: `通过筛选数(推荐)`,
552
+ value: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
553
+ canonical_value: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
554
+ argument_patch: recommendedTargetCountPatch
555
+ },
556
+ ...(Array.isArray(targetCountHints.options) ? targetCountHints.options : [])
557
+ ],
462
558
  field: "follow_up.chat.target_count",
463
- question: "请填写 boss-chat follow-up 本次处理人数上限。若扫到底,请在 follow_up.chat.target_count 里字面填写 \"all\"。",
559
+ question: "请填写 boss-chat follow-up 目标人数。默认建议填写“通过筛选数”;若要扫到底,请在 follow_up.chat.target_count 里字面填写 \"all\"。",
464
560
  value: summary.target_count,
465
561
  ...(explicitTarget.rawValue !== undefined ? { received_target_count: explicitTarget.rawValue } : {}),
466
562
  ...(explicitTarget.parseError ? { target_count_parse_error: explicitTarget.parseError } : {})
@@ -857,10 +953,32 @@ async function runBossChatFollowUpPhase({
857
953
  cancelChatRun
858
954
  }) {
859
955
  const recommendSummary = recommendResult?.result || null;
860
- const resolvedChatInput = buildResolvedFollowUpChatInput(followUpChat, {
956
+ const requestedChatInput = buildResolvedFollowUpChatInput(followUpChat, {
861
957
  selectedJob,
862
958
  debugPort
863
959
  });
960
+ const targetCountResolution = resolveFollowUpChatTargetCountForLaunch(
961
+ requestedChatInput.target_count,
962
+ recommendSummary
963
+ );
964
+ if (!targetCountResolution.ok) {
965
+ return buildFollowUpFailedResponse(
966
+ targetCountResolution.code || "BOSS_CHAT_FOLLOW_UP_TARGET_COUNT_INVALID",
967
+ targetCountResolution.message || "boss-chat follow-up target_count 无法解析。",
968
+ recommendResult,
969
+ {
970
+ enabled: true,
971
+ input: requestedChatInput,
972
+ target_count_resolution: targetCountResolution
973
+ }
974
+ );
975
+ }
976
+ const resolvedChatInput = {
977
+ ...requestedChatInput,
978
+ target_count: targetCountResolution.target_count,
979
+ target_count_source: targetCountResolution.resolved_from,
980
+ target_count_requested: requestedChatInput.target_count
981
+ };
864
982
  let chatRunId = normalizeText(resume?.chat_run_id || "");
865
983
  const resumeFromChatPhase = resume?.resume === true && normalizeText(resume?.follow_up_phase) === "chat_followup";
866
984
  let pauseRequested = false;
@@ -2183,8 +2183,9 @@ async function testFollowUpChatMissingFieldsShouldExposeRecommendDefaults() {
2183
2183
  const targetCountQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2184
2184
  assert.equal(criteriaQuestion?.value, "默认沿用 recommend 的筛选条件");
2185
2185
  assert.equal(startFromQuestion?.value, "unread");
2186
- assert.equal(targetCountQuestion?.value, 18);
2187
- assert.equal(targetCountQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "all");
2186
+ assert.equal(targetCountQuestion?.value, "通过筛选数");
2187
+ assert.equal(targetCountQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2188
+ assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes("通过筛选数(推荐)")), true);
2188
2189
  assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes('follow_up.chat.target_count="all"')), true);
2189
2190
  }
2190
2191
 
@@ -2225,7 +2226,9 @@ async function testFollowUpChatMissingTargetCountShouldNeedInput() {
2225
2226
  assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2226
2227
  const targetQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2227
2228
  assert.equal(Boolean(targetQuestion), true);
2228
- assert.equal(targetQuestion.recommended_argument_patch?.follow_up?.chat?.target_count, "all");
2229
+ assert.equal(targetQuestion.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2230
+ assert.equal(targetQuestion.options?.some((item) => item.value === "通过筛选数"), true);
2231
+ assert.equal(targetQuestion.options?.some((item) => item.value === "all"), true);
2229
2232
  }
2230
2233
 
2231
2234
  async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics() {
@@ -2248,7 +2251,8 @@ async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics(
2248
2251
  assert.equal(targetQuestion?.received_target_count, "not a target");
2249
2252
  assert.equal(Boolean(targetQuestion?.target_count_parse_error), true);
2250
2253
  assert.equal(targetQuestion?.accepted_examples.includes("all"), true);
2251
- assert.equal(targetQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "all");
2254
+ assert.equal(targetQuestion?.accepted_examples.includes("通过筛选数"), true);
2255
+ assert.equal(targetQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2252
2256
  }
2253
2257
 
2254
2258
  async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
@@ -2317,6 +2321,63 @@ async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
2317
2321
  assert.equal(result.follow_up?.chat?.target_count, "all");
2318
2322
  }
2319
2323
 
2324
+ async function testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount() {
2325
+ let capturedChatInput = null;
2326
+ const result = await runRecommendPipeline(
2327
+ {
2328
+ workspaceRoot: process.cwd(),
2329
+ instruction: "test",
2330
+ confirmation: createJobConfirmedConfirmation(),
2331
+ overrides: {},
2332
+ followUp: createFollowUpChat({ target_count: "通过筛选数" })
2333
+ },
2334
+ {
2335
+ parseRecommendInstruction: () => createParsed(),
2336
+ runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
2337
+ ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2338
+ listRecommendJobs: async () => createJobListResult(),
2339
+ runRecommendSearchCli: async () => ({
2340
+ ok: true,
2341
+ summary: {
2342
+ candidate_count: 6,
2343
+ applied_filters: {},
2344
+ page_state: {}
2345
+ }
2346
+ }),
2347
+ runRecommendScreenCli: async () => ({
2348
+ ok: true,
2349
+ summary: {
2350
+ processed_count: 6,
2351
+ passed_count: 2,
2352
+ skipped_count: 0
2353
+ }
2354
+ }),
2355
+ startBossChatRun: async ({ input }) => {
2356
+ capturedChatInput = input;
2357
+ return {
2358
+ status: "ACCEPTED",
2359
+ run_id: "chat-run-pass-count",
2360
+ message: "chat started"
2361
+ };
2362
+ },
2363
+ getBossChatRun: async () => ({
2364
+ status: "COMPLETED",
2365
+ run: {
2366
+ runId: "chat-run-pass-count",
2367
+ state: "completed",
2368
+ lastMessage: "chat completed",
2369
+ progress: { processed: 2, matched: 2 }
2370
+ }
2371
+ })
2372
+ }
2373
+ );
2374
+
2375
+ assert.equal(result.status, "COMPLETED");
2376
+ assert.equal(capturedChatInput?.target_count, 2);
2377
+ assert.equal(result.follow_up?.chat?.input?.target_count, 2);
2378
+ assert.equal(result.follow_up?.chat?.input?.target_count_requested, "passed_count");
2379
+ }
2380
+
2320
2381
  async function testFinalReviewShouldIncludeFollowUpChatSummary() {
2321
2382
  const result = await runRecommendPipeline(
2322
2383
  {
@@ -2576,6 +2637,7 @@ async function main() {
2576
2637
  await testFinalReviewShouldIncludeFollowUpChatSummary();
2577
2638
  await testCompletedPipelineShouldRunChatFollowUp();
2578
2639
  await testFollowUpChatAllTargetCountShouldLaunchUnlimited();
2640
+ await testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount();
2579
2641
  await testCompletedPipelineShouldFailWhenChatLaunchFails();
2580
2642
  await testCompletedPipelineShouldFailWhenChatRunFails();
2581
2643
  console.log("pipeline tests passed");