@reconcrap/boss-recommend-mcp 1.3.36 → 1.3.38

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/README.md CHANGED
@@ -188,7 +188,7 @@ node src/cli.js launch-chrome --port 9222
188
188
  node src/cli.js run --instruction-file request.txt --confirmation-file confirmation.json --overrides-file overrides.json
189
189
  node src/cli.js run --instruction-file request.txt --confirmation-file confirmation.json --overrides-file overrides.json --follow-up-file follow-up.json
190
190
  node src/cli.js chat health-check
191
- node src/cli.js chat run --job "算法工程师" --start-from unread --criteria "有 AI Agent 经验" --targetCount 20
191
+ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria "有 AI Agent 经验" --targetCount 20 --greeting-text "您好,方便发下简历吗?"
192
192
  ```
193
193
 
194
194
  ## Recommend + Chat Follow-up
@@ -201,6 +201,7 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
201
201
  "chat": {
202
202
  "criteria": "候选人需要继续在聊天页过滤有 AI Agent 经验的人选",
203
203
  "start_from": "unread",
204
+ "greeting_text": "您好,方便发下简历吗?",
204
205
  "target_count": 20,
205
206
  "profile": "default",
206
207
  "dry_run": false,
@@ -215,9 +216,11 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
215
216
  说明:
216
217
 
217
218
  - `criteria` / `start_from` / `target_count` 为必填
219
+ - `greeting_text` 可选(兼容 `greetingText`)
218
220
  - `profile` 可选,默认 `default`
219
221
  - `job` 与 `port` 继承 recommend run 已选岗位和调试端口
220
222
  - `baseUrl` / `apiKey` / `model` 不再单独传入,固定复用 recommend 的 `screening-config.json`
223
+ - `greeting_text` 默认优先级:本次显式值 > profile 历史值 > 内置默认招呼语(`Hi同学,能麻烦发下简历吗?`)
221
224
  - 若缺少 `follow_up.chat` 必填项,pipeline 会返回 `NEED_INPUT`
222
225
  - recommend 成功后,父 run 继续存活并进入 `chat_followup`;chat 结束后父 run 才会进入最终终态
223
226
  - `boss-chat` 子任务状态统一写入 `~/.boss-recommend-mcp/boss-chat`(或 `BOSS_CHAT_HOME` 指定目录),不再依赖工作区 `cwd`
@@ -229,7 +232,7 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
229
232
  - CLI:
230
233
  - `boss-recommend-mcp chat health-check`
231
234
  - `boss-recommend-mcp chat prepare-run`
232
- - `boss-recommend-mcp chat run --job "算法工程师" --start-from unread --targetCount 20 --criteria "有 AI Agent 经验"`(后台启动,不自动轮询)
235
+ - `boss-recommend-mcp chat run --job "算法工程师" --start-from unread --targetCount 20 --criteria "有 AI Agent 经验" [--greeting-text "您好,方便发下简历吗?"]`(后台启动,不自动轮询)
233
236
  - `boss-recommend-mcp chat start-run|get-run|pause-run|resume-run|cancel-run`
234
237
  - MCP:
235
238
  - `boss_chat_health_check`
@@ -247,6 +250,7 @@ chat-only 交互建议:
247
250
 
248
251
  - 先调用一次 `prepare_boss_chat_run`(可不带参数),服务会先导航到 `https://www.zhipin.com/web/chat/index` 并返回 `NEED_INPUT`,其中包含岗位 `job_options` 与待补字段。
249
252
  - 然后基于 `job_options` 让用户选择 `job`,并补齐 `start_from`、`target_count`、`criteria` 后调用 `start_boss_chat_run` 启动任务。
253
+ - `greeting_text` 可选;未传时会自动沿用 profile 上次输入,若无历史值则使用默认招呼语(`Hi同学,能麻烦发下简历吗?`)。
250
254
  - `target_count` 支持正整数、`all`、`-1`;若用户给出 `全部候选人` / `所有候选人`,会自动按不限(扫到底)处理。
251
255
 
252
256
  Trae-CN / 长对话防循环建议:
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.38",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -31,12 +31,19 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
31
31
  可选:
32
32
 
33
33
  - `profile`(默认 `default`)
34
+ - `greeting_text`(兼容 `greetingText`,可选自定义首条打招呼消息)
34
35
  - `port`
35
36
  - `dry_run`
36
37
  - `no_state`
37
38
  - `safe_pacing`
38
39
  - `batch_rest_enabled`
39
40
 
41
+ `greeting_text` 默认规则:
42
+
43
+ - 本次显式传入 `greeting_text`:使用本次值
44
+ - 本次未传,但当前 `profile` 有历史输入:使用历史值
45
+ - 两者都没有:使用内置默认招呼语(`Hi同学,能麻烦发下简历吗?`)
46
+
40
47
  `target_count` 填写规则(关键):
41
48
 
42
49
  - 正整数:如 `20`
@@ -55,6 +62,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
55
62
  - 当用户说“全部候选人/所有候选人”时,必须按“扫到底(unlimited)”处理,不要再追问正整数。
56
63
  - 参数名必须写 `target_count`(不要写“目标数量”等中文键名)。
57
64
  - 当用户选择“扫到底/全部候选人/所有候选人”时,调用参数优先写:`"target_count": "all"`;`-1` 只作为兼容输入和内部 CLI 表示。
65
+ - `greeting_text` 是可选项,不能因为缺少它阻塞启动或追加必填追问。
58
66
  - 若工具或提问选项里出现“扫到底(必须传 `target_count=\"all\"`)”之类字样,下一次工具调用时必须直接照抄这个字面量,不要只保留“扫到底”语义。
59
67
  - 禁止 agent 自行补全 `job/start_from/criteria` 并直接执行,必须由用户明确给出或确认。
60
68
  - chat-only 启动流程必须先进入聊天页并拉取岗位列表,再让用户从列表中选择 `job`。
@@ -72,8 +72,10 @@ description: "Use when users want Boss recommend-page filtering/screening via bo
72
72
  - `start_from`: `unread|all`
73
73
  - `target_count`
74
74
  - `follow_up.chat` 可选:
75
+ - `greeting_text`(兼容 `greetingText`,自定义首条打招呼消息)
75
76
  - `profile`(默认 `default`)
76
77
  - `dry_run/no_state/safe_pacing/batch_rest_enabled`
78
+ - `greeting_text` 未传时,boss-chat 会自动按默认优先级回退:本次显式值 > profile 历史值 > 内置默认招呼语
77
79
  - `job` / `port` 继承 recommend run 上下文;不要单独向用户再要一份
78
80
  - LLM 配置固定复用 recommend 的 `screening-config.json`;不要单独向用户再要 `baseUrl/apiKey/model`
79
81
  - 缺少 `follow_up.chat` 必填项时,按 `pending_questions` 补缺口;不要额外发起一轮独立的 chat 确认流程
package/src/boss-chat.js CHANGED
@@ -573,20 +573,22 @@ function resolveBossChatScreenConfig(workspaceRoot) {
573
573
  };
574
574
  }
575
575
 
576
- function normalizeBossChatStartInput(input = {}) {
577
- const profile = normalizeText(input.profile) || "default";
578
- const job = normalizeText(input.job);
579
- const startFromRaw = normalizeText(input.startFrom || input.start_from).toLowerCase();
580
- const startFrom = startFromRaw === "all" ? "all" : startFromRaw === "unread" ? "unread" : "";
581
- const criteria = normalizeText(input.criteria);
582
- const parsedTarget = normalizeTargetCountInput(getBossChatTargetCountValue(input));
583
- const port = parsePositiveInteger(input.port);
584
- return {
585
- profile,
586
- job,
587
- startFrom,
588
- criteria,
589
- targetCount: parsedTarget.targetCount,
576
+ function normalizeBossChatStartInput(input = {}) {
577
+ const profile = normalizeText(input.profile) || "default";
578
+ const job = normalizeText(input.job);
579
+ const startFromRaw = normalizeText(input.startFrom || input.start_from).toLowerCase();
580
+ const startFrom = startFromRaw === "all" ? "all" : startFromRaw === "unread" ? "unread" : "";
581
+ const criteria = normalizeText(input.criteria);
582
+ const greetingText = normalizeText(input.greeting_text || input.greetingText || input.greeting);
583
+ const parsedTarget = normalizeTargetCountInput(getBossChatTargetCountValue(input));
584
+ const port = parsePositiveInteger(input.port);
585
+ return {
586
+ profile,
587
+ job,
588
+ startFrom,
589
+ criteria,
590
+ greetingText,
591
+ targetCount: parsedTarget.targetCount,
590
592
  targetCountArg: parsedTarget.cliArg,
591
593
  targetCountProvided: parsedTarget.provided,
592
594
  targetCountPublicValue: parsedTarget.publicValue,
@@ -645,10 +647,11 @@ function buildNextCallExample(input = {}, missingFields = []) {
645
647
  const sample = {};
646
648
  if (normalized.job) sample.job = normalized.job;
647
649
  if (normalized.startFrom) sample.start_from = normalized.startFrom;
648
- if (normalized.criteria) sample.criteria = normalized.criteria;
649
- if (normalized.targetCountProvided) {
650
- sample.target_count = normalized.targetCountPublicValue || (normalized.targetCountArg === "-1" ? "all" : normalized.targetCount);
651
- } else if (missingFields.includes("target_count")) {
650
+ if (normalized.criteria) sample.criteria = normalized.criteria;
651
+ if (normalized.greetingText) sample.greeting_text = normalized.greetingText;
652
+ if (normalized.targetCountProvided) {
653
+ sample.target_count = normalized.targetCountPublicValue || (normalized.targetCountArg === "-1" ? "all" : normalized.targetCount);
654
+ } else if (missingFields.includes("target_count")) {
652
655
  sample.target_count = "all";
653
656
  }
654
657
  return Object.keys(sample).length > 0 ? sample : null;
@@ -678,9 +681,10 @@ function buildBossChatCliArgs(command, input, resolvedConfig, runtimeLayout = nu
678
681
  const normalized = normalizeBossChatStartInput(input);
679
682
  args.push("--profile", normalized.profile);
680
683
  if (normalized.job) args.push("--job", normalized.job);
681
- if (normalized.startFrom) args.push("--start-from", normalized.startFrom);
682
- if (normalized.criteria) args.push("--criteria", normalized.criteria);
683
- if (normalized.targetCountArg) args.push("--targetCount", normalized.targetCountArg);
684
+ if (normalized.startFrom) args.push("--start-from", normalized.startFrom);
685
+ if (normalized.criteria) args.push("--criteria", normalized.criteria);
686
+ if (normalized.greetingText) args.push("--greeting", normalized.greetingText);
687
+ if (normalized.targetCountArg) args.push("--targetCount", normalized.targetCountArg);
684
688
  args.push("--port", String(normalized.port || resolvedConfig.debugPort || 9222));
685
689
  args.push("--baseurl", resolvedConfig.baseUrl);
686
690
  args.push("--apikey", resolvedConfig.apiKey);
@@ -702,12 +706,13 @@ function buildBossChatCliArgs(command, input, resolvedConfig, runtimeLayout = nu
702
706
  args.push("--profile", normalized.profile);
703
707
  if (normalized.dryRun) args.push("--dry-run");
704
708
  if (normalized.noState) args.push("--no-state");
705
- args.push("--job", normalized.job);
706
- args.push("--start-from", normalized.startFrom);
707
- args.push("--criteria", normalized.criteria);
708
- if (normalized.targetCountArg) {
709
- args.push("--targetCount", normalized.targetCountArg);
710
- }
709
+ args.push("--job", normalized.job);
710
+ args.push("--start-from", normalized.startFrom);
711
+ args.push("--criteria", normalized.criteria);
712
+ if (normalized.greetingText) args.push("--greeting", normalized.greetingText);
713
+ if (normalized.targetCountArg) {
714
+ args.push("--targetCount", normalized.targetCountArg);
715
+ }
711
716
  args.push("--baseurl", resolvedConfig.baseUrl);
712
717
  args.push("--apikey", resolvedConfig.apiKey);
713
718
  args.push("--model", resolvedConfig.model);
package/src/cli.js CHANGED
@@ -1377,7 +1377,7 @@ function printHelp() {
1377
1377
  console.log("");
1378
1378
  console.log("Run command:");
1379
1379
  console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" [--confirmation-json '{...}'] [--overrides-json '{...}'] [--follow-up-json '{...}']");
1380
- console.log(" boss-recommend-mcp chat run --job \"算法工程师\" --start-from unread --criteria \"有 AI Agent 经验\" --targetCount 20 # 后台启动,不自动轮询");
1380
+ console.log(" boss-recommend-mcp chat run --job \"算法工程师\" --start-from unread --criteria \"有 AI Agent 经验\" --targetCount 20 [--greeting-text \"你好,方便发下简历吗?\"] # 后台启动,不自动轮询");
1381
1381
  console.log(" boss-recommend-mcp config set --base-url <url> --api-key <key> --model <model> [--thinking-level off|low|medium|high|current] [--openai-organization <id>] [--openai-project <id>]");
1382
1382
  console.log(" boss-recommend-mcp install --agent trae-cn");
1383
1383
  console.log(" boss-recommend-mcp doctor --agent trae-cn --page-scope featured");
@@ -1481,11 +1481,18 @@ async function runPipelineOnce(options) {
1481
1481
  }
1482
1482
 
1483
1483
  function buildBossChatCliInput(options = {}) {
1484
+ const greetingTextRaw =
1485
+ options["greeting-text"]
1486
+ ?? options.greeting_text
1487
+ ?? options.greetingText
1488
+ ?? options.greeting;
1489
+ const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
1484
1490
  return {
1485
1491
  profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
1486
1492
  job: typeof options.job === "string" ? options.job.trim() : undefined,
1487
1493
  start_from: String(options["start-from"] || options.start_from || "").trim().toLowerCase() || undefined,
1488
1494
  criteria: typeof options.criteria === "string" ? options.criteria.trim() : undefined,
1495
+ greeting_text: greetingText || undefined,
1489
1496
  target_count: parseBossChatTargetCountOption(options.targetCount || options["target-count"] || options.target_count),
1490
1497
  port: parsePositivePort(options.port),
1491
1498
  dry_run: options["dry-run"] === true || options.dryRun === true,
package/src/index.js CHANGED
@@ -437,6 +437,14 @@ function createRunInputSchema() {
437
437
  type: "string",
438
438
  enum: ["unread", "all"]
439
439
  },
440
+ greeting_text: {
441
+ type: "string",
442
+ description: "可选,首条打招呼消息;未传时按 profile 历史值/默认值自动回退"
443
+ },
444
+ greetingText: {
445
+ type: "string",
446
+ description: "兼容字段;优先使用 greeting_text。可选首条打招呼消息"
447
+ },
440
448
  target_count: createTargetCountInputSchema("boss-chat follow-up 本次处理人数上限;支持正整数、all 或 -1(扫到底)"),
441
449
  dry_run: { type: "boolean" },
442
450
  no_state: { type: "boolean" },
@@ -475,6 +483,14 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
475
483
  type: "string",
476
484
  description: "boss-chat 的筛选 criteria"
477
485
  },
486
+ greeting_text: {
487
+ type: "string",
488
+ description: "可选,首条打招呼消息;未传时按 profile 历史值/默认值自动回退"
489
+ },
490
+ greetingText: {
491
+ type: "string",
492
+ description: "兼容字段;优先使用 greeting_text。可选首条打招呼消息"
493
+ },
478
494
  target_count: createTargetCountInputSchema("本次处理人数上限;支持正整数、all 或 -1(扫到底),也兼容 { value: \"all\" } 等包装对象"),
479
495
  targetCount: createTargetCountInputSchema("兼容字段;优先使用 target_count。本次处理人数上限,支持正整数、all 或 -1(扫到底)"),
480
496
  port: {
@@ -773,6 +789,18 @@ function validateBossChatStartArgs(args) {
773
789
  return "criteria must be a non-empty string when provided";
774
790
  }
775
791
  }
792
+ if (
793
+ Object.prototype.hasOwnProperty.call(args, "greeting_text")
794
+ && typeof args.greeting_text !== "string"
795
+ ) {
796
+ return "greeting_text must be a string when provided";
797
+ }
798
+ if (
799
+ Object.prototype.hasOwnProperty.call(args, "greetingText")
800
+ && typeof args.greetingText !== "string"
801
+ ) {
802
+ return "greetingText must be a string when provided";
803
+ }
776
804
  if (
777
805
  Object.prototype.hasOwnProperty.call(args, "target_count")
778
806
  || Object.prototype.hasOwnProperty.call(args, "targetCount")
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,13 @@ 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 explicitGreetingText = normalizeText(raw.greeting_text || raw.greetingText);
480
+ const explicitTarget = normalizeFollowUpTargetCountInput(raw.target_count);
481
+ const explicitTargetCount = explicitTarget.launchValue;
404
482
 
405
483
  const hasExplicitCriteria = Boolean(explicitCriteria);
406
484
  const hasExplicitStartFrom = Boolean(explicitStartFrom);
@@ -408,14 +486,16 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
408
486
 
409
487
  const criteria = explicitCriteria || defaultCriteria;
410
488
  const startFrom = explicitStartFrom || defaultStartFrom;
411
- const targetCount = explicitTargetCount || defaultTargetCount;
489
+ const targetCount = explicitTargetCount || FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN;
490
+ const targetCountSummaryValue = explicitTarget.publicValue || FOLLOW_UP_TARGET_COUNT_PASSED_LABEL;
412
491
 
413
492
  const profile = normalizeText(raw.profile) || "default";
414
493
  const summary = {
415
494
  profile,
416
495
  criteria: criteria || null,
417
496
  start_from: startFrom || null,
418
- target_count: targetCount,
497
+ greeting_text: explicitGreetingText || null,
498
+ target_count: targetCountSummaryValue,
419
499
  dry_run: raw.dry_run === true,
420
500
  no_state: raw.no_state === true,
421
501
  safe_pacing: typeof raw.safe_pacing === "boolean" ? raw.safe_pacing : null,
@@ -446,21 +526,39 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
446
526
  });
447
527
  }
448
528
  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
- }
529
+ const recommendedTargetCountPatch = {
530
+ follow_up: {
531
+ chat: {
532
+ target_count: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL
456
533
  }
457
534
  }
535
+ };
536
+ const targetCountHints = buildTargetCountCompatibilityHints({
537
+ argumentName: "follow_up.chat.target_count",
538
+ recommendedArgumentPatch: recommendedTargetCountPatch
458
539
  });
459
540
  missing_fields.push("follow_up.chat.target_count");
460
541
  pending_questions.push({
461
542
  ...targetCountHints,
543
+ answer_format: `follow_up.chat.target_count = "${FOLLOW_UP_TARGET_COUNT_PASSED_LABEL}" | 正整数 | "all"`,
544
+ recommended_value: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
545
+ recommended_argument_patch: recommendedTargetCountPatch,
546
+ canonical_passed_count_value: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
547
+ accepted_examples: [
548
+ FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
549
+ ...targetCountHints.accepted_examples
550
+ ],
551
+ options: [
552
+ {
553
+ label: `通过筛选数(推荐)`,
554
+ value: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
555
+ canonical_value: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
556
+ argument_patch: recommendedTargetCountPatch
557
+ },
558
+ ...(Array.isArray(targetCountHints.options) ? targetCountHints.options : [])
559
+ ],
462
560
  field: "follow_up.chat.target_count",
463
- question: "请填写 boss-chat follow-up 本次处理人数上限。若扫到底,请在 follow_up.chat.target_count 里字面填写 \"all\"。",
561
+ question: "请填写 boss-chat follow-up 目标人数。默认建议填写“通过筛选数”;若要扫到底,请在 follow_up.chat.target_count 里字面填写 \"all\"。",
464
562
  value: summary.target_count,
465
563
  ...(explicitTarget.rawValue !== undefined ? { received_target_count: explicitTarget.rawValue } : {}),
466
564
  ...(explicitTarget.parseError ? { target_count_parse_error: explicitTarget.parseError } : {})
@@ -476,6 +574,7 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
476
574
  profile,
477
575
  criteria: criteria || null,
478
576
  start_from: startFrom || null,
577
+ greeting_text: explicitGreetingText || undefined,
479
578
  target_count: targetCount,
480
579
  dry_run: raw.dry_run === true,
481
580
  no_state: raw.no_state === true,
@@ -523,6 +622,7 @@ function buildResolvedFollowUpChatInput(followUpChat, { selectedJob, debugPort }
523
622
  job: selectedJob?.title || selectedJob?.label || selectedJob?.value || null,
524
623
  start_from: followUpChat?.input?.start_from || null,
525
624
  criteria: followUpChat?.input?.criteria || null,
625
+ greeting_text: followUpChat?.input?.greeting_text || null,
526
626
  target_count: followUpChat?.input?.target_count || null,
527
627
  port: Number.isFinite(debugPort) ? debugPort : null,
528
628
  dry_run: followUpChat?.input?.dry_run === true,
@@ -543,6 +643,7 @@ function buildBossChatFollowUpStatus({ payload, runId, fallbackInput = null, sta
543
643
  job: normalizeText(fallbackInput?.job) || null,
544
644
  start_from: normalizeText(fallbackInput?.start_from) || null,
545
645
  criteria: normalizeText(fallbackInput?.criteria) || null,
646
+ greeting_text: normalizeText(fallbackInput?.greeting_text) || null,
546
647
  target_count: normalizePipelineTargetCountValue(fallbackInput?.target_count),
547
648
  port: parsePositiveIntegerValue(fallbackInput?.port),
548
649
  progress: {
@@ -857,10 +958,32 @@ async function runBossChatFollowUpPhase({
857
958
  cancelChatRun
858
959
  }) {
859
960
  const recommendSummary = recommendResult?.result || null;
860
- const resolvedChatInput = buildResolvedFollowUpChatInput(followUpChat, {
961
+ const requestedChatInput = buildResolvedFollowUpChatInput(followUpChat, {
861
962
  selectedJob,
862
963
  debugPort
863
964
  });
965
+ const targetCountResolution = resolveFollowUpChatTargetCountForLaunch(
966
+ requestedChatInput.target_count,
967
+ recommendSummary
968
+ );
969
+ if (!targetCountResolution.ok) {
970
+ return buildFollowUpFailedResponse(
971
+ targetCountResolution.code || "BOSS_CHAT_FOLLOW_UP_TARGET_COUNT_INVALID",
972
+ targetCountResolution.message || "boss-chat follow-up target_count 无法解析。",
973
+ recommendResult,
974
+ {
975
+ enabled: true,
976
+ input: requestedChatInput,
977
+ target_count_resolution: targetCountResolution
978
+ }
979
+ );
980
+ }
981
+ const resolvedChatInput = {
982
+ ...requestedChatInput,
983
+ target_count: targetCountResolution.target_count,
984
+ target_count_source: targetCountResolution.resolved_from,
985
+ target_count_requested: requestedChatInput.target_count
986
+ };
864
987
  let chatRunId = normalizeText(resume?.chat_run_id || "");
865
988
  const resumeFromChatPhase = resume?.resume === true && normalizeText(resume?.follow_up_phase) === "chat_followup";
866
989
  let pauseRequested = false;
@@ -877,6 +1000,7 @@ async function runBossChatFollowUpPhase({
877
1000
  profile: resolvedChatInput.profile,
878
1001
  job: resolvedChatInput.job,
879
1002
  start_from: resolvedChatInput.start_from,
1003
+ greeting_text: resolvedChatInput.greeting_text,
880
1004
  target_count: resolvedChatInput.target_count
881
1005
  });
882
1006
 
@@ -888,6 +1012,7 @@ async function runBossChatFollowUpPhase({
888
1012
  job: resolvedChatInput.job,
889
1013
  start_from: resolvedChatInput.start_from,
890
1014
  criteria: resolvedChatInput.criteria,
1015
+ greeting_text: resolvedChatInput.greeting_text,
891
1016
  target_count: resolvedChatInput.target_count,
892
1017
  port: resolvedChatInput.port,
893
1018
  dry_run: resolvedChatInput.dry_run,