@reconcrap/boss-recommend-mcp 1.3.37 → 1.3.39

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.37",
3
+ "version": "1.3.39",
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
@@ -476,6 +476,7 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
476
476
  const explicitCriteria = normalizeText(raw.criteria);
477
477
  const explicitStartFromRaw = normalizeText(raw.start_from).toLowerCase();
478
478
  const explicitStartFrom = explicitStartFromRaw === "all" ? "all" : explicitStartFromRaw === "unread" ? "unread" : "";
479
+ const explicitGreetingText = normalizeText(raw.greeting_text || raw.greetingText);
479
480
  const explicitTarget = normalizeFollowUpTargetCountInput(raw.target_count);
480
481
  const explicitTargetCount = explicitTarget.launchValue;
481
482
 
@@ -493,6 +494,7 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
493
494
  profile,
494
495
  criteria: criteria || null,
495
496
  start_from: startFrom || null,
497
+ greeting_text: explicitGreetingText || null,
496
498
  target_count: targetCountSummaryValue,
497
499
  dry_run: raw.dry_run === true,
498
500
  no_state: raw.no_state === true,
@@ -572,6 +574,7 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
572
574
  profile,
573
575
  criteria: criteria || null,
574
576
  start_from: startFrom || null,
577
+ greeting_text: explicitGreetingText || undefined,
575
578
  target_count: targetCount,
576
579
  dry_run: raw.dry_run === true,
577
580
  no_state: raw.no_state === true,
@@ -619,6 +622,7 @@ function buildResolvedFollowUpChatInput(followUpChat, { selectedJob, debugPort }
619
622
  job: selectedJob?.title || selectedJob?.label || selectedJob?.value || null,
620
623
  start_from: followUpChat?.input?.start_from || null,
621
624
  criteria: followUpChat?.input?.criteria || null,
625
+ greeting_text: followUpChat?.input?.greeting_text || null,
622
626
  target_count: followUpChat?.input?.target_count || null,
623
627
  port: Number.isFinite(debugPort) ? debugPort : null,
624
628
  dry_run: followUpChat?.input?.dry_run === true,
@@ -639,6 +643,7 @@ function buildBossChatFollowUpStatus({ payload, runId, fallbackInput = null, sta
639
643
  job: normalizeText(fallbackInput?.job) || null,
640
644
  start_from: normalizeText(fallbackInput?.start_from) || null,
641
645
  criteria: normalizeText(fallbackInput?.criteria) || null,
646
+ greeting_text: normalizeText(fallbackInput?.greeting_text) || null,
642
647
  target_count: normalizePipelineTargetCountValue(fallbackInput?.target_count),
643
648
  port: parsePositiveIntegerValue(fallbackInput?.port),
644
649
  progress: {
@@ -995,6 +1000,7 @@ async function runBossChatFollowUpPhase({
995
1000
  profile: resolvedChatInput.profile,
996
1001
  job: resolvedChatInput.job,
997
1002
  start_from: resolvedChatInput.start_from,
1003
+ greeting_text: resolvedChatInput.greeting_text,
998
1004
  target_count: resolvedChatInput.target_count
999
1005
  });
1000
1006
 
@@ -1006,6 +1012,7 @@ async function runBossChatFollowUpPhase({
1006
1012
  job: resolvedChatInput.job,
1007
1013
  start_from: resolvedChatInput.start_from,
1008
1014
  criteria: resolvedChatInput.criteria,
1015
+ greeting_text: resolvedChatInput.greeting_text,
1009
1016
  target_count: resolvedChatInput.target_count,
1010
1017
  port: resolvedChatInput.port,
1011
1018
  dry_run: resolvedChatInput.dry_run,
@@ -287,16 +287,17 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
287
287
  assert.equal(stateAfterPrepare.last_prepare_args["llm-timeout-ms"], "65000");
288
288
  assert.equal(stateAfterPrepare.last_prepare_args["llm-max-retries"], "4");
289
289
 
290
- const started = await startBossChatRun({
291
- workspaceRoot,
292
- input: {
293
- profile: "default",
294
- job: "算法工程师",
295
- start_from: "unread",
296
- criteria: "有 AI Agent 经验",
297
- target_count: 2
298
- }
299
- });
290
+ const started = await startBossChatRun({
291
+ workspaceRoot,
292
+ input: {
293
+ profile: "default",
294
+ job: "算法工程师",
295
+ start_from: "unread",
296
+ criteria: "有 AI Agent 经验",
297
+ greeting_text: "你好,方便发下简历吗?",
298
+ target_count: 2
299
+ }
300
+ });
300
301
  assert.equal(started.status, "ACCEPTED");
301
302
  assert.equal(Boolean(started.run_id), true);
302
303
 
@@ -304,9 +305,10 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
304
305
  assert.equal(stateAfterStart.last_start_args.profile, "default");
305
306
  assert.equal(stateAfterStart.last_start_args["data-dir"], getTestChatDataDir(workspaceRoot));
306
307
  assert.equal(stateAfterStart.last_start_args.job, "算法工程师");
307
- assert.equal(stateAfterStart.last_start_args["start-from"], "unread");
308
- assert.equal(stateAfterStart.last_start_args.criteria, "有 AI Agent 经验");
309
- assert.equal(stateAfterStart.last_start_args.targetCount, "2");
308
+ assert.equal(stateAfterStart.last_start_args["start-from"], "unread");
309
+ assert.equal(stateAfterStart.last_start_args.criteria, "有 AI Agent 经验");
310
+ assert.equal(stateAfterStart.last_start_args.greeting, "你好,方便发下简历吗?");
311
+ assert.equal(stateAfterStart.last_start_args.targetCount, "2");
310
312
  assert.equal(stateAfterStart.last_start_args.baseurl, "https://api.example.com/v1");
311
313
  assert.equal(stateAfterStart.last_start_args.apikey, "sk-test-key");
312
314
  assert.equal(stateAfterStart.last_start_args.model, "gpt-4.1-mini");
@@ -908,15 +910,20 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
908
910
  method: "tools/list",
909
911
  params: {}
910
912
  }, workspaceRoot);
911
- const tools = toolsResponse.result.tools;
912
- const prepareToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_PREPARE_RUN).inputSchema;
913
- const startToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_START_RUN).inputSchema;
914
- assert.equal(prepareToolSchema.required, undefined);
915
- assert.deepEqual(startToolSchema.required, ["job", "start_from", "criteria"]);
916
- assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("target_count")), true);
917
- assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("targetCount")), true);
918
- assert.equal(startToolSchema.properties.target_count.examples.includes("all"), true);
919
- assert.equal(startToolSchema.examples.some((item) => item.target_count === "all"), true);
913
+ const tools = toolsResponse.result.tools;
914
+ const runToolSchema = tools.find((item) => item.name === "start_recommend_pipeline_run").inputSchema;
915
+ const prepareToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_PREPARE_RUN).inputSchema;
916
+ const startToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_START_RUN).inputSchema;
917
+ assert.equal(prepareToolSchema.required, undefined);
918
+ assert.deepEqual(startToolSchema.required, ["job", "start_from", "criteria"]);
919
+ assert.equal(typeof startToolSchema.properties.greeting_text, "object");
920
+ assert.equal(typeof startToolSchema.properties.greetingText, "object");
921
+ assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("target_count")), true);
922
+ assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("targetCount")), true);
923
+ assert.equal(startToolSchema.properties.target_count.examples.includes("all"), true);
924
+ assert.equal(startToolSchema.examples.some((item) => item.target_count === "all"), true);
925
+ assert.equal(typeof runToolSchema.properties.follow_up.properties.chat.properties.greeting_text, "object");
926
+ assert.equal(typeof runToolSchema.properties.follow_up.properties.chat.properties.greetingText, "object");
920
927
 
921
928
  const prepared = await callTool(workspaceRoot, TOOL_BOSS_CHAT_PREPARE_RUN, {}, 101);
922
929
  assert.equal(prepared.status, "NEED_INPUT");
@@ -960,13 +967,20 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
960
967
  assert.equal(invalidTargetOnly.next_call_example.target_count, "all");
961
968
  assert.equal(invalidTargetOnly.recommended_argument_patch.target_count, "all");
962
969
 
963
- const invalidStartResponse = await handleRequest(
964
- makeToolCall(11, TOOL_BOSS_CHAT_START_RUN, {
965
- start_from: "invalid-value"
970
+ const invalidStartResponse = await handleRequest(
971
+ makeToolCall(11, TOOL_BOSS_CHAT_START_RUN, {
972
+ start_from: "invalid-value"
966
973
  }),
967
974
  workspaceRoot
968
- );
969
- assert.equal(invalidStartResponse.error.code, -32602);
975
+ );
976
+ assert.equal(invalidStartResponse.error.code, -32602);
977
+ const invalidGreetingResponse = await handleRequest(
978
+ makeToolCall(1112, TOOL_BOSS_CHAT_START_RUN, {
979
+ greeting_text: 123
980
+ }),
981
+ workspaceRoot
982
+ );
983
+ assert.equal(invalidGreetingResponse.error.code, -32602);
970
984
 
971
985
  const invalidGetResponse = await handleRequest(
972
986
  makeToolCall(12, TOOL_BOSS_CHAT_GET_RUN, {}),
@@ -977,13 +991,15 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
977
991
  const health = await callTool(workspaceRoot, TOOL_BOSS_CHAT_HEALTH_CHECK, {}, 13);
978
992
  assert.equal(health.status, "OK");
979
993
 
980
- const started = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
981
- job: "算法工程师",
982
- start_from: "unread",
983
- criteria: "有 AI Agent 经验",
984
- target_count: 2
985
- }, 14);
986
- assert.equal(started.status, "ACCEPTED");
994
+ const started = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
995
+ job: "算法工程师",
996
+ start_from: "unread",
997
+ criteria: "有 AI Agent 经验",
998
+ greeting_text: "您好,方便投递一份简历吗?",
999
+ target_count: 2
1000
+ }, 14);
1001
+ assert.equal(started.status, "ACCEPTED");
1002
+ assert.equal(readStubState(workspaceRoot).last_start_args.greeting, "您好,方便投递一份简历吗?");
987
1003
 
988
1004
  const startedAll = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
989
1005
  job: "算法工程师",
@@ -1033,15 +1049,17 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
1033
1049
  async function testBossChatCliShouldSupportRunAndFollowUpParsing() {
1034
1050
  const followUpJson = cliTestables.getRunFollowUp({
1035
1051
  "follow-up-json": JSON.stringify({
1036
- chat: {
1037
- criteria: "有 AI Agent 经验",
1038
- start_from: "unread",
1039
- target_count: 2
1040
- }
1041
- })
1042
- });
1043
- assert.equal(followUpJson.chat.criteria, "有 AI Agent 经验");
1044
- assert.equal(followUpJson.chat.target_count, 2);
1052
+ chat: {
1053
+ criteria: "有 AI Agent 经验",
1054
+ start_from: "unread",
1055
+ greeting_text: "您好,方便发下简历吗?",
1056
+ target_count: 2
1057
+ }
1058
+ })
1059
+ });
1060
+ assert.equal(followUpJson.chat.criteria, "有 AI Agent 经验");
1061
+ assert.equal(followUpJson.chat.greeting_text, "您好,方便发下简历吗?");
1062
+ assert.equal(followUpJson.chat.target_count, 2);
1045
1063
 
1046
1064
  const tempFile = path.join(os.tmpdir(), `boss-recommend-follow-up-${Date.now()}.json`);
1047
1065
  fs.writeFileSync(tempFile, JSON.stringify({
@@ -1074,18 +1092,20 @@ async function testBossChatCliShouldSupportRunAndFollowUpParsing() {
1074
1092
  const logs = await captureConsoleLogs(async () => {
1075
1093
  await cliTestables.runBossChatCliCommand("run", {
1076
1094
  "workspace-root": workspaceRoot,
1077
- job: "算法工程师",
1078
- "start-from": "unread",
1079
- criteria: "有 AI Agent 经验",
1080
- targetCount: "2"
1081
- });
1095
+ job: "算法工程师",
1096
+ "start-from": "unread",
1097
+ criteria: "有 AI Agent 经验",
1098
+ "greeting-text": "您好,方便发下简历吗?",
1099
+ targetCount: "2"
1100
+ });
1082
1101
  });
1083
1102
  assert.equal(logs.length > 0, true);
1084
- const payload = JSON.parse(logs[0]);
1085
- assert.equal(payload.status, "ACCEPTED");
1086
- assert.equal(typeof payload.run_id, "string");
1087
- const state = readStubState(workspaceRoot);
1088
- assert.equal(state.get_calls[payload.run_id] || 0, 0);
1103
+ const payload = JSON.parse(logs[0]);
1104
+ assert.equal(payload.status, "ACCEPTED");
1105
+ assert.equal(typeof payload.run_id, "string");
1106
+ const state = readStubState(workspaceRoot);
1107
+ assert.equal(state.last_start_args.greeting, "您好,方便发下简历吗?");
1108
+ assert.equal(state.get_calls[payload.run_id] || 0, 0);
1089
1109
 
1090
1110
  await captureConsoleLogs(async () => {
1091
1111
  await cliTestables.runBossChatCliCommand("run", {
@@ -1179,14 +1199,106 @@ async function testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile() {
1179
1199
  overrides: {
1180
1200
  jobSelection: "AI应用开发工程师(2026) _ 杭州",
1181
1201
  startFrom: "unread",
1182
- screeningCriteria: "小样本联通性验证",
1183
- targetCount: 1,
1184
- },
1185
- });
1186
- assert.equal(profile.jobSelection.label, "AI应用开发工程师(2026) _ 杭州");
1187
- assert.equal(profile.startFrom, "unread");
1188
- assert.equal(profile.targetCount, 1);
1189
- }
1202
+ screeningCriteria: "小样本联通性验证",
1203
+ greetingText: "你好,方便发下简历吗?",
1204
+ targetCount: 1,
1205
+ },
1206
+ });
1207
+ assert.equal(profile.jobSelection.label, "AI应用开发工程师(2026) _ 杭州");
1208
+ assert.equal(profile.startFrom, "unread");
1209
+ assert.equal(profile.greetingText, "你好,方便发下简历吗?");
1210
+ assert.equal(profile.targetCount, 1);
1211
+ }
1212
+
1213
+ async function testVendorBossChatCliShouldUseGreetingFallbacksInPromptRunProfile() {
1214
+ const page = {
1215
+ async ensureOnChatPage() {
1216
+ return {
1217
+ href: "https://www.zhipin.com/web/chat/index",
1218
+ hasListContainer: true,
1219
+ listItemCount: 8,
1220
+ };
1221
+ },
1222
+ async listJobs() {
1223
+ return [
1224
+ {
1225
+ value: "job-1",
1226
+ label: "算法工程师 _ 杭州",
1227
+ active: true,
1228
+ },
1229
+ ];
1230
+ },
1231
+ };
1232
+
1233
+ const fromLastProfile = await vendorCliTestables.promptRunProfile({
1234
+ page,
1235
+ persistentProfile: {
1236
+ greetingText: "上次招呼语",
1237
+ llm: {
1238
+ baseUrl: "https://api.example.com/v1",
1239
+ apiKey: "sk-test-key",
1240
+ model: "gpt-4.1-mini",
1241
+ },
1242
+ chrome: {
1243
+ port: 9222,
1244
+ },
1245
+ runtime: {},
1246
+ },
1247
+ overrides: {
1248
+ jobSelection: "算法工程师 _ 杭州",
1249
+ startFrom: "unread",
1250
+ screeningCriteria: "有 AI Agent 经验",
1251
+ targetCount: 1,
1252
+ },
1253
+ });
1254
+ assert.equal(fromLastProfile.greetingText, "上次招呼语");
1255
+
1256
+ const fromExplicitOverride = await vendorCliTestables.promptRunProfile({
1257
+ page,
1258
+ persistentProfile: {
1259
+ greetingText: "上次招呼语",
1260
+ llm: {
1261
+ baseUrl: "https://api.example.com/v1",
1262
+ apiKey: "sk-test-key",
1263
+ model: "gpt-4.1-mini",
1264
+ },
1265
+ chrome: {
1266
+ port: 9222,
1267
+ },
1268
+ runtime: {},
1269
+ },
1270
+ overrides: {
1271
+ jobSelection: "算法工程师 _ 杭州",
1272
+ startFrom: "unread",
1273
+ screeningCriteria: "有 AI Agent 经验",
1274
+ greetingText: "本次新招呼语",
1275
+ targetCount: 1,
1276
+ },
1277
+ });
1278
+ assert.equal(fromExplicitOverride.greetingText, "本次新招呼语");
1279
+
1280
+ const fromBuiltInDefault = await vendorCliTestables.promptRunProfile({
1281
+ page,
1282
+ persistentProfile: {
1283
+ llm: {
1284
+ baseUrl: "https://api.example.com/v1",
1285
+ apiKey: "sk-test-key",
1286
+ model: "gpt-4.1-mini",
1287
+ },
1288
+ chrome: {
1289
+ port: 9222,
1290
+ },
1291
+ runtime: {},
1292
+ },
1293
+ overrides: {
1294
+ jobSelection: "算法工程师 _ 杭州",
1295
+ startFrom: "unread",
1296
+ screeningCriteria: "有 AI Agent 经验",
1297
+ targetCount: 1,
1298
+ },
1299
+ });
1300
+ assert.equal(fromBuiltInDefault.greetingText, "Hi同学,能麻烦发下简历吗?");
1301
+ }
1190
1302
 
1191
1303
  function testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig() {
1192
1304
  const installedSpecifier = cliTestables.getDefaultMcpPackageSpecifier({
@@ -1213,18 +1325,21 @@ function testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig() {
1213
1325
  assert.equal(launchConfig.args[0], "-y");
1214
1326
  }
1215
1327
 
1216
- function testVendorBossChatCliShouldParseSharedLlmTransportArgs() {
1217
- const parsed = vendorCliTestables.parseArgs([
1218
- "start-run",
1219
- "--llm-timeout-ms",
1220
- "70000",
1221
- "--llm-max-retries",
1222
- "5",
1223
- ]);
1224
- assert.equal(parsed.command, "start-run");
1225
- assert.equal(parsed.overrides.llm.timeoutMs, 70000);
1226
- assert.equal(parsed.overrides.llm.maxRetries, 5);
1227
- }
1328
+ function testVendorBossChatCliShouldParseSharedLlmTransportArgs() {
1329
+ const parsed = vendorCliTestables.parseArgs([
1330
+ "start-run",
1331
+ "--llm-timeout-ms",
1332
+ "70000",
1333
+ "--llm-max-retries",
1334
+ "5",
1335
+ "--greeting",
1336
+ "您好,方便发下简历吗?",
1337
+ ]);
1338
+ assert.equal(parsed.command, "start-run");
1339
+ assert.equal(parsed.overrides.llm.timeoutMs, 70000);
1340
+ assert.equal(parsed.overrides.llm.maxRetries, 5);
1341
+ assert.equal(parsed.overrides.greetingText, "您好,方便发下简历吗?");
1342
+ }
1228
1343
 
1229
1344
  function testBossChatLlmParserShouldAcceptMinimalDecisionJson() {
1230
1345
  const parsed = parseLlmJson(
@@ -3049,8 +3164,9 @@ async function main() {
3049
3164
  testVendorBossChatCliShouldResolveExplicitDataDir();
3050
3165
  testVendorBossChatCliShouldUseRecommendHomeForDefaultDataDir();
3051
3166
  await testVendorBossChatCliShouldWaitForHydratedChatShell();
3052
- await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
3053
- testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
3167
+ await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
3168
+ await testVendorBossChatCliShouldUseGreetingFallbacksInPromptRunProfile();
3169
+ testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
3054
3170
  testVendorBossChatCliShouldParseSharedLlmTransportArgs();
3055
3171
  testBossChatLlmParserShouldAcceptMinimalDecisionJson();
3056
3172
  testBossChatLlmParserShouldAcceptPlainPassFailText();