@reconcrap/boss-recommend-mcp 1.3.4 → 1.3.6

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
@@ -215,6 +215,14 @@ chat-only 交互建议:
215
215
 
216
216
  - 先调用一次 `start_boss_chat_run`(可不带参数),服务会先导航到 `https://www.zhipin.com/web/chat/index` 并返回 `NEED_INPUT`,其中包含岗位 `job_options` 与待补字段。
217
217
  - 然后基于 `job_options` 让用户选择 `job`,并补齐 `start_from`、`target_count`、`criteria` 后再次调用 `start_boss_chat_run` 启动任务。
218
+ - `target_count` 支持正整数;若用户给出 `全部候选人` / `所有候选人`,会自动按不限(扫到底)处理。
219
+
220
+ Trae-CN / 长对话防循环建议:
221
+
222
+ - 固定流程:`boss_chat_health_check` -> `start_boss_chat_run(空参可)` -> 一次性补齐 `job/start_from/target_count/criteria` -> 再次 `start_boss_chat_run`。
223
+ - `start_boss_chat_run` 返回 `ACCEPTED` 后直接结束当前回合,不要自动轮询。
224
+ - 缺参或校验失败时,一次性列出全部缺失/错误项,避免重复同一句提示触发宿主“陷入循环”保护。
225
+ - 仅当用户明确要求“查进度”时再调用 `get_boss_chat_run`。
218
226
 
219
227
  ## 长流程 Agent 兼容模式
220
228
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -3,3 +3,29 @@
3
3
  Bundled chat-page automation skill shipped with `boss-recommend-mcp`.
4
4
 
5
5
  Use this skill when the user wants a chat-only Boss workflow without installing `boss-chat` separately.
6
+
7
+ ## Stable Prompt Template (Trae-CN)
8
+
9
+ Use the following prompt when host agents are prone to loop detection in long conversations:
10
+
11
+ ```text
12
+ Please run a Boss chat-only task (do not switch to recommend flow).
13
+
14
+ Execution order:
15
+ 1) Call boss_chat_health_check.
16
+ 2) Call start_boss_chat_run once (empty params allowed) to fetch job_options and missing fields.
17
+ 3) Ask for these required fields in one shot: job, start_from (unread/all), target_count, criteria.
18
+ 4) After user reply, call start_boss_chat_run exactly once to start the run.
19
+ 5) If ACCEPTED, reply only with run_id and "task started"; no auto polling.
20
+
21
+ Anti-loop rules:
22
+ - Do not repeat the same sentence across turns.
23
+ - On validation errors, list all missing/invalid fields once.
24
+ - Do not call start_boss_chat_run repeatedly in one turn.
25
+ - Do not call get_boss_chat_run unless user explicitly asks for progress.
26
+
27
+ target_count mapping:
28
+ - Positive integer means explicit cap (for example 20).
29
+ - `all` / `unlimited` / `全部` / `不限` / `扫到底` / `全量` means unlimited.
30
+ - `全部候选人` / `所有候选人` must also be treated as unlimited.
31
+ ```
@@ -37,6 +37,12 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
37
37
  - `safe_pacing`
38
38
  - `batch_rest_enabled`
39
39
 
40
+ `target_count` 填写规则(关键):
41
+
42
+ - 正整数:如 `20`
43
+ - 扫到底:`all` / `unlimited` / `全部` / `不限` / `扫到底` / `全量`
44
+ - 同义短语也可直接用:`全部候选人` / `所有候选人`(等价于扫到底)
45
+
40
46
  ## Hard Rules
41
47
 
42
48
  - LLM 配置必须复用 `boss-recommend-mcp` 的 `screening-config.json`;不要再向用户单独要 `baseUrl/apiKey/model`。
@@ -46,6 +52,7 @@ description: "Use when users want Boss chat-page screening/outreach via the bund
46
52
  - 不得在 recommend 任务尚未完成时并行启动独立 chat run。
47
53
  - `job` / `start_from` / `criteria` 缺一不可;缺参时只补缺口。
48
54
  - `target_count` 在 chat-only 启动前也是必填项,不能默认省略。
55
+ - 当用户说“全部候选人/所有候选人”时,必须按“扫到底(unlimited)”处理,不要再追问正整数。
49
56
  - 禁止 agent 自行补全 `job/start_from/criteria` 并直接执行,必须由用户明确给出或确认。
50
57
  - chat-only 启动流程必须先进入聊天页并拉取岗位列表,再让用户从列表中选择 `job`。
51
58
  - 允许先用空参调用 `start_boss_chat_run` 触发 `NEED_INPUT`;若返回了 `job_options`,必须完整展示所有岗位选项给用户确认。
package/src/boss-chat.js CHANGED
@@ -33,19 +33,29 @@ function parsePositiveInteger(value, fallback = null) {
33
33
  function isUnlimitedTargetCountToken(value) {
34
34
  const token = normalizeText(value).toLowerCase();
35
35
  if (!token) return false;
36
- return [
36
+ const compact = token.replace(/\s+/g, "");
37
+ const knownTokens = new Set([
37
38
  "all",
38
39
  "unlimited",
39
40
  "infinity",
40
41
  "inf",
41
42
  "max",
42
43
  "full",
44
+ "allcandidates",
43
45
  "全部",
44
46
  "全量",
45
47
  "不限",
46
48
  "扫到底",
49
+ "全部候选人",
50
+ "所有候选人",
51
+ "全部人选",
52
+ "所有人选",
47
53
  "直到完成所有人选"
48
- ].includes(token);
54
+ ]);
55
+ if (knownTokens.has(token) || knownTokens.has(compact)) return true;
56
+ if (/^(?:all|unlimited|infinity|inf|max|full)(?:candidate|candidates)?$/i.test(compact)) return true;
57
+ if (/^(?:全部|所有|全量|不限)(?:候选人|人选|牛人|人才|人员)?$/u.test(compact)) return true;
58
+ return false;
49
59
  }
50
60
 
51
61
  function parseBossChatTargetCount(value) {
package/src/cli.js CHANGED
@@ -195,6 +195,14 @@ function parsePositivePort(raw) {
195
195
  return Number.isFinite(port) && port > 0 ? port : null;
196
196
  }
197
197
 
198
+ function parseBossChatTargetCountOption(raw) {
199
+ if (raw === undefined || raw === null) return undefined;
200
+ const text = String(raw).trim();
201
+ if (!text) return undefined;
202
+ const parsed = parsePositivePort(text);
203
+ return parsed ?? text;
204
+ }
205
+
198
206
  function parseBooleanOption(raw, fallback = undefined) {
199
207
  if (raw === undefined || raw === null || raw === "") return fallback;
200
208
  if (raw === true) return true;
@@ -1327,7 +1335,7 @@ function buildBossChatCliInput(options = {}) {
1327
1335
  job: typeof options.job === "string" ? options.job.trim() : undefined,
1328
1336
  start_from: String(options["start-from"] || options.start_from || "").trim().toLowerCase() || undefined,
1329
1337
  criteria: typeof options.criteria === "string" ? options.criteria.trim() : undefined,
1330
- target_count: parsePositivePort(options.targetCount || options["target-count"] || options.target_count),
1338
+ target_count: parseBossChatTargetCountOption(options.targetCount || options["target-count"] || options.target_count),
1331
1339
  port: parsePositivePort(options.port),
1332
1340
  dry_run: options["dry-run"] === true || options.dryRun === true,
1333
1341
  no_state: options["no-state"] === true || options.noState === true,
package/src/index.js CHANGED
@@ -77,19 +77,29 @@ function normalizeText(value) {
77
77
  function isUnlimitedTargetCountToken(value) {
78
78
  const token = normalizeText(value).toLowerCase();
79
79
  if (!token) return false;
80
- return [
80
+ const compact = token.replace(/\s+/g, "");
81
+ const knownTokens = new Set([
81
82
  "all",
82
83
  "unlimited",
83
84
  "infinity",
84
85
  "inf",
85
86
  "max",
86
87
  "full",
88
+ "allcandidates",
87
89
  "全部",
88
90
  "全量",
89
91
  "不限",
90
92
  "扫到底",
93
+ "全部候选人",
94
+ "所有候选人",
95
+ "全部人选",
96
+ "所有人选",
91
97
  "直到完成所有人选"
92
- ].includes(token);
98
+ ]);
99
+ if (knownTokens.has(token) || knownTokens.has(compact)) return true;
100
+ if (/^(?:all|unlimited|infinity|inf|max|full)(?:candidate|candidates)?$/i.test(compact)) return true;
101
+ if (/^(?:全部|所有|全量|不限)(?:候选人|人选|牛人|人才|人员)?$/u.test(compact)) return true;
102
+ return false;
93
103
  }
94
104
 
95
105
  function parsePositiveInteger(raw, fallback) {
@@ -383,7 +393,7 @@ function createRunInputSchema() {
383
393
  },
384
394
  {
385
395
  type: "string",
386
- enum: ["all", "unlimited", "全部", "不限", "扫到底", "全量"]
396
+ enum: ["all", "unlimited", "全部", "不限", "扫到底", "全量", "全部候选人", "所有候选人"]
387
397
  }
388
398
  ]
389
399
  },
@@ -432,10 +442,10 @@ function createBossChatStartInputSchema() {
432
442
  },
433
443
  {
434
444
  type: "string",
435
- enum: ["all", "unlimited", "全部", "不限", "扫到底", "全量"]
445
+ enum: ["all", "unlimited", "全部", "不限", "扫到底", "全量", "全部候选人", "所有候选人"]
436
446
  }
437
447
  ],
438
- description: "本次处理人数上限;支持正整数或 all/不限(扫到底)"
448
+ description: "本次处理人数上限;支持正整数或 all/不限/全部候选人(扫到底)"
439
449
  },
440
450
  port: {
441
451
  type: "integer",
@@ -713,7 +723,7 @@ function validateBossChatStartArgs(args) {
713
723
  typeof rawTargetCount === "string" && isUnlimitedTargetCountToken(rawTargetCount);
714
724
  const numericUnlimited = Number.isFinite(targetCount) && targetCount === -1;
715
725
  if ((!Number.isFinite(targetCount) || targetCount <= 0) && !tokenAllowed && !numericUnlimited) {
716
- return "target_count must be a positive integer or one of: all, unlimited, 全部, 不限, 扫到底, 全量";
726
+ return "target_count must be a positive integer or one of: all, unlimited, 全部, 不限, 扫到底, 全量, 全部候选人, 所有候选人";
717
727
  }
718
728
  }
719
729
  if (Object.prototype.hasOwnProperty.call(args, "port")) {
@@ -242,6 +242,20 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
242
242
  assert.equal(stateAfterStart.last_start_args.model, "gpt-4.1-mini");
243
243
  assert.equal(stateAfterStart.last_start_args.port, "9666");
244
244
 
245
+ const startedAll = await startBossChatRun({
246
+ workspaceRoot,
247
+ input: {
248
+ profile: "default",
249
+ job: "算法工程师",
250
+ start_from: "all",
251
+ criteria: "全部候选人都过一遍",
252
+ target_count: "全部候选人"
253
+ }
254
+ });
255
+ assert.equal(startedAll.status, "ACCEPTED");
256
+ const stateAfterStartAll = readStubState(workspaceRoot);
257
+ assert.equal(stateAfterStartAll.last_start_args.targetCount, "-1");
258
+
245
259
  const running = await getBossChatRun({
246
260
  workspaceRoot,
247
261
  input: {
@@ -313,6 +327,16 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
313
327
  }, 14);
314
328
  assert.equal(started.status, "ACCEPTED");
315
329
 
330
+ const startedAll = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
331
+ job: "算法工程师",
332
+ start_from: "all",
333
+ criteria: "全部候选人都过一遍",
334
+ target_count: "全部候选人"
335
+ }, 140);
336
+ assert.equal(startedAll.status, "ACCEPTED");
337
+ const stateAfterStartAll = readStubState(workspaceRoot);
338
+ assert.equal(stateAfterStartAll.last_start_args.targetCount, "-1");
339
+
316
340
  const running = await callTool(workspaceRoot, TOOL_BOSS_CHAT_GET_RUN, {
317
341
  run_id: started.run_id,
318
342
  profile: "default"
@@ -386,6 +410,18 @@ async function testBossChatCliShouldSupportRunAndFollowUpParsing() {
386
410
  assert.equal(typeof payload.run_id, "string");
387
411
  const state = readStubState(workspaceRoot);
388
412
  assert.equal(state.get_calls[payload.run_id] || 0, 0);
413
+
414
+ await captureConsoleLogs(async () => {
415
+ await cliTestables.runBossChatCliCommand("run", {
416
+ "workspace-root": workspaceRoot,
417
+ job: "算法工程师",
418
+ "start-from": "all",
419
+ criteria: "全部候选人都过一遍",
420
+ targetCount: "全部候选人"
421
+ });
422
+ });
423
+ const allState = readStubState(workspaceRoot);
424
+ assert.equal(allState.last_start_args.targetCount, "-1");
389
425
  });
390
426
  }
391
427