@reconcrap/boss-recommend-mcp 2.0.43 → 2.0.44

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/index.js CHANGED
@@ -265,6 +265,27 @@ function createTargetCountInputSchema(description) {
265
265
  };
266
266
  }
267
267
 
268
+ function createHumanBehaviorInputSchema(description = "可选,启用可靠性实验用的人类节奏配置;默认关闭") {
269
+ return {
270
+ type: "object",
271
+ properties: {
272
+ enabled: { type: "boolean" },
273
+ profile: {
274
+ type: "string",
275
+ enum: ["baseline", "paced", "paced_with_rests"]
276
+ },
277
+ clickMovement: { type: "boolean" },
278
+ textEntry: { type: "boolean" },
279
+ listScrollJitter: { type: "boolean" },
280
+ shortRest: { type: "boolean" },
281
+ batchRest: { type: "boolean" },
282
+ actionCooldown: { type: "boolean" }
283
+ },
284
+ additionalProperties: false,
285
+ description
286
+ };
287
+ }
288
+
268
289
  function getRecommendedPollAfterSec(args = {}) {
269
290
  return hasFollowUpChatRequest(args)
270
291
  ? getLongRunPollAfterSec()
@@ -609,6 +630,13 @@ function createRunInputSchema() {
609
630
  target_count: createTargetCountInputSchema("boss-chat follow-up 本次处理人数上限;支持正整数、all 或 -1(扫到底)"),
610
631
  dry_run: { type: "boolean" },
611
632
  no_state: { type: "boolean" },
633
+ human_behavior: createHumanBehaviorInputSchema("可选,follow-up chat 节奏配置;默认 baseline/off"),
634
+ humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
635
+ human_behavior_enabled: { type: "boolean" },
636
+ human_behavior_profile: {
637
+ type: "string",
638
+ enum: ["baseline", "paced", "paced_with_rests"]
639
+ },
612
640
  safe_pacing: { type: "boolean" },
613
641
  batch_rest_enabled: { type: "boolean" }
614
642
  },
@@ -638,6 +666,25 @@ function createRunInputSchema() {
638
666
  type: "boolean",
639
667
  description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
640
668
  },
669
+ human_behavior: createHumanBehaviorInputSchema("可选,recommend 可靠性实验用节奏配置;默认 baseline/off"),
670
+ humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
671
+ human_behavior_enabled: {
672
+ type: "boolean",
673
+ description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
674
+ },
675
+ human_behavior_profile: {
676
+ type: "string",
677
+ enum: ["baseline", "paced", "paced_with_rests"],
678
+ description: "可选实验 profile:baseline/paced/paced_with_rests"
679
+ },
680
+ safe_pacing: {
681
+ type: "boolean",
682
+ description: "兼容字段;true 启用 paced,false 关闭"
683
+ },
684
+ batch_rest_enabled: {
685
+ type: "boolean",
686
+ description: "兼容字段;true 启用 paced_with_rests 的候选人短休/批次休息"
687
+ },
641
688
  max_candidates: createTargetCountInputSchema("本次最多处理候选人数;默认使用确认后的 target_count,未设置时为 5"),
642
689
  detail_limit: {
643
690
  type: "integer",
@@ -866,6 +913,17 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
866
913
  },
867
914
  dry_run: { type: "boolean" },
868
915
  no_state: { type: "boolean" },
916
+ human_behavior: createHumanBehaviorInputSchema("可选,chat 可靠性实验用节奏配置;默认 baseline/off"),
917
+ humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
918
+ human_behavior_enabled: {
919
+ type: "boolean",
920
+ description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
921
+ },
922
+ human_behavior_profile: {
923
+ type: "string",
924
+ enum: ["baseline", "paced", "paced_with_rests"],
925
+ description: "可选实验 profile:baseline/paced/paced_with_rests"
926
+ },
869
927
  safe_pacing: { type: "boolean" },
870
928
  batch_rest_enabled: { type: "boolean" }
871
929
  },
@@ -44,10 +44,11 @@ import {
44
44
  parseRecommendInstruction
45
45
  } from "./parser.js";
46
46
  import { getRunsDir } from "./run-state.js";
47
- import {
48
- resolveBossConfiguredOutputDir,
49
- resolveBossScreeningConfig
50
- } from "./chat-runtime-config.js";
47
+ import {
48
+ resolveBossConfiguredOutputDir,
49
+ resolveHumanBehaviorForRun,
50
+ resolveBossScreeningConfig
51
+ } from "./chat-runtime-config.js";
51
52
  import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
52
53
 
53
54
  const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
@@ -1216,12 +1217,13 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
1216
1217
  };
1217
1218
  }
1218
1219
 
1219
- function getRunOptions(args, parsed, normalized, session, configResolution = null) {
1220
- const slowLive = args.slow_live === true;
1221
- const executePostAction = args.dry_run_post_action === true
1222
- ? false
1223
- : args.execute_post_action !== false;
1224
- return {
1220
+ function getRunOptions(args, parsed, normalized, session, configResolution = null) {
1221
+ const slowLive = args.slow_live === true;
1222
+ const executePostAction = args.dry_run_post_action === true
1223
+ ? false
1224
+ : args.execute_post_action !== false;
1225
+ const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
1226
+ return {
1225
1227
  client: session.client,
1226
1228
  targetUrl: RECOMMEND_TARGET_URL,
1227
1229
  criteria: normalized.criteria,
@@ -1264,14 +1266,16 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
1264
1266
  args.llm_image_limit,
1265
1267
  parsePositiveInteger(configResolution?.config?.llmImageLimit || configResolution?.config?.imageLimit, 8)
1266
1268
  ),
1267
- llmImageDetail: normalizeText(
1268
- args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
1269
- ) || "low",
1270
- imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
1271
- name: "mcp-recommend-pipeline-run",
1272
- parsed
1273
- };
1274
- }
1269
+ llmImageDetail: normalizeText(
1270
+ args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
1271
+ ) || "low",
1272
+ imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
1273
+ humanRestEnabled: humanBehavior.restEnabled,
1274
+ humanBehavior,
1275
+ name: "mcp-recommend-pipeline-run",
1276
+ parsed
1277
+ };
1278
+ }
1275
1279
 
1276
1280
  function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
1277
1281
  const parsed = parseRecommendPipelineRequest(args);
@@ -34,6 +34,7 @@ import {
34
34
  } from "./domains/recruit/index.js";
35
35
  import {
36
36
  resolveBossConfiguredOutputDir,
37
+ resolveHumanBehaviorForRun,
37
38
  resolveBossScreeningConfig
38
39
  } from "./chat-runtime-config.js";
39
40
  import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
@@ -364,6 +365,27 @@ function createTargetCountSchema(description) {
364
365
  };
365
366
  }
366
367
 
368
+ function createHumanBehaviorInputSchema(description = "可选,search/recruit 可靠性实验用节奏配置;默认关闭") {
369
+ return {
370
+ type: "object",
371
+ properties: {
372
+ enabled: { type: "boolean" },
373
+ profile: {
374
+ type: "string",
375
+ enum: ["baseline", "paced", "paced_with_rests"]
376
+ },
377
+ clickMovement: { type: "boolean" },
378
+ textEntry: { type: "boolean" },
379
+ listScrollJitter: { type: "boolean" },
380
+ shortRest: { type: "boolean" },
381
+ batchRest: { type: "boolean" },
382
+ actionCooldown: { type: "boolean" }
383
+ },
384
+ additionalProperties: false,
385
+ description
386
+ };
387
+ }
388
+
367
389
  export function createRecruitPipelineInputSchema() {
368
390
  return {
369
391
  type: "object",
@@ -431,6 +453,25 @@ export function createRecruitPipelineInputSchema() {
431
453
  type: "boolean",
432
454
  description: "VPN/慢页面模式:放宽 live DOM 等待时间"
433
455
  },
456
+ human_behavior: createHumanBehaviorInputSchema("可选,search/recruit 可靠性实验用节奏配置;默认 baseline/off"),
457
+ humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
458
+ human_behavior_enabled: {
459
+ type: "boolean",
460
+ description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
461
+ },
462
+ human_behavior_profile: {
463
+ type: "string",
464
+ enum: ["baseline", "paced", "paced_with_rests"],
465
+ description: "可选实验 profile:baseline/paced/paced_with_rests"
466
+ },
467
+ safe_pacing: {
468
+ type: "boolean",
469
+ description: "兼容字段;true 启用 paced,false 关闭"
470
+ },
471
+ batch_rest_enabled: {
472
+ type: "boolean",
473
+ description: "兼容字段;true 启用 paced_with_rests 的候选人短休/批次休息"
474
+ },
434
475
  max_candidates: createTargetCountSchema("本次最多处理候选人数;默认使用解析出的 target_count"),
435
476
  detail_limit: {
436
477
  type: "integer",
@@ -843,6 +884,7 @@ function getRunOptions(args, parsed, session, configResolution = null) {
843
884
  const slowLive = args.slow_live === true;
844
885
  const targetCount = parsePositiveInteger(args.max_candidates, parsed.screenParams.target_count || 10);
845
886
  const screeningMode = normalizeScreeningModeArg(args);
887
+ const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
846
888
  return {
847
889
  client: session.client,
848
890
  targetUrl: RECRUIT_TARGET_URL,
@@ -873,6 +915,8 @@ function getRunOptions(args, parsed, session, configResolution = null) {
873
915
  args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
874
916
  ) || "low",
875
917
  imageOutputDir: resolveBossConfiguredOutputDir("", getRecruitRunsDir()),
918
+ humanRestEnabled: humanBehavior.restEnabled,
919
+ humanBehavior,
876
920
  name: "mcp-recruit-pipeline-run"
877
921
  };
878
922
  }
@@ -1,305 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import process from "node:process";
5
- import {
6
- assertNoForbiddenCdpCalls,
7
- assertRuntimeEvaluateBlocked,
8
- bringPageToFront,
9
- connectToChromeTarget,
10
- enableDomains,
11
- getAttributesMap,
12
- getOuterHTML,
13
- querySelector,
14
- sleep
15
- } from "../src/core/browser/index.js";
16
- import {
17
- htmlToText,
18
- normalizeText
19
- } from "../src/core/screening/index.js";
20
- import {
21
- findRecommendJobTrigger,
22
- getRecommendRoots,
23
- refreshRecommendListAtEnd,
24
- RECOMMEND_TARGET_URL,
25
- waitForRecommendCardNodeIds
26
- } from "../src/domains/recommend/index.js";
27
-
28
- function parseArgs(argv) {
29
- const options = {
30
- host: "127.0.0.1",
31
- port: 9222,
32
- targetUrl: RECOMMEND_TARGET_URL,
33
- targetUrlIncludes: RECOMMEND_TARGET_URL,
34
- jobLabel: "",
35
- pageScope: "recommend",
36
- fallbackPageScope: "recommend",
37
- forceNavigate: true,
38
- forceRecentNotView: true,
39
- reloadSettleMs: 12000,
40
- cardTimeoutMs: 60000,
41
- saveReport: ".live-artifacts/recommend-recovery-smoke.json",
42
- filterGroups: [
43
- {
44
- group: "degree",
45
- labels: ["本科", "硕士", "博士"],
46
- selectAllLabels: true
47
- },
48
- {
49
- group: "school",
50
- labels: ["985", "211", "双一流院校", "国内外名校"],
51
- selectAllLabels: true
52
- }
53
- ]
54
- };
55
-
56
- for (let index = 0; index < argv.length; index += 1) {
57
- const arg = argv[index];
58
- if (arg === "--host") options.host = argv[++index];
59
- if (arg === "--port") options.port = Number(argv[++index]);
60
- if (arg === "--target-url") options.targetUrl = argv[++index];
61
- if (arg === "--target-url-includes") options.targetUrlIncludes = argv[++index];
62
- if (arg === "--job") options.jobLabel = argv[++index];
63
- if (arg === "--page-scope") options.pageScope = argv[++index];
64
- if (arg === "--fallback-page-scope") options.fallbackPageScope = argv[++index];
65
- if (arg === "--no-force-navigate") options.forceNavigate = false;
66
- if (arg === "--no-recent-not-view") options.forceRecentNotView = false;
67
- if (arg === "--reload-settle-ms") options.reloadSettleMs = Number(argv[++index]);
68
- if (arg === "--card-timeout-ms") options.cardTimeoutMs = Number(argv[++index]);
69
- if (arg === "--save-report") options.saveReport = argv[++index];
70
- if (arg === "--no-save-report") options.saveReport = "";
71
- if (arg === "--no-default-filter") options.filterGroups = [];
72
- if (arg === "--filter") {
73
- const raw = String(argv[++index] || "");
74
- const [group, labelsRaw = ""] = raw.split(/[:=]/);
75
- options.filterGroups.push({
76
- group: group.trim(),
77
- labels: labelsRaw.split(/[,,、|/]/).map((item) => item.trim()).filter(Boolean),
78
- selectAllLabels: true
79
- });
80
- }
81
- }
82
-
83
- return options;
84
- }
85
-
86
- function methodSummary(methodLog) {
87
- const summary = {};
88
- for (const entry of methodLog) {
89
- summary[entry.method] = (summary[entry.method] || 0) + 1;
90
- }
91
- return summary;
92
- }
93
-
94
- function writeJsonFile(filePath, payload) {
95
- const resolved = path.resolve(filePath);
96
- fs.mkdirSync(path.dirname(resolved), { recursive: true });
97
- fs.writeFileSync(resolved, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
98
- return resolved;
99
- }
100
-
101
- async function connectToRecommendSession(options) {
102
- try {
103
- return await connectToChromeTarget({
104
- host: options.host,
105
- port: options.port,
106
- targetUrlIncludes: options.targetUrlIncludes
107
- });
108
- } catch (error) {
109
- return connectToChromeTarget({
110
- host: options.host,
111
- port: options.port,
112
- targetPredicate: (target) => (
113
- target?.type === "page"
114
- && String(target?.url || "").includes("zhipin.com/web/chat")
115
- )
116
- });
117
- }
118
- }
119
-
120
- async function readCurrentJobLabel(client, frameDocumentNodeId) {
121
- const labelNodeId = await querySelector(
122
- client,
123
- frameDocumentNodeId,
124
- ".job-selecter-wrap .ui-dropmenu-label, .ui-dropmenu-label"
125
- );
126
- if (!labelNodeId) return "";
127
- const html = await getOuterHTML(client, labelNodeId);
128
- return normalizeText(htmlToText(html));
129
- }
130
-
131
- async function summarizeRecommendState(client) {
132
- const roots = await getRecommendRoots(client);
133
- const iframeAttributes = await getAttributesMap(client, roots.iframe.nodeId).catch(() => ({}));
134
- const cardNodeIds = await waitForRecommendCardNodeIds(client, roots.iframe.documentNodeId, {
135
- timeoutMs: 1200,
136
- intervalMs: 200
137
- }).catch(() => []);
138
- const trigger = await findRecommendJobTrigger(client, roots.iframe.documentNodeId).catch(() => null);
139
- const currentJobLabel = trigger
140
- ? await readCurrentJobLabel(client, roots.iframe.documentNodeId).catch(() => "")
141
- : "";
142
-
143
- return {
144
- roots,
145
- summary: {
146
- iframe_selector: roots.iframe.selector || "",
147
- iframe_node_id: roots.iframe.nodeId,
148
- iframe_document_node_id: roots.iframe.documentNodeId,
149
- iframe_src: iframeAttributes.src || "",
150
- current_job_label: currentJobLabel,
151
- job_trigger_found: Boolean(trigger),
152
- job_trigger_rect: trigger?.rect || null,
153
- card_count: cardNodeIds.length
154
- }
155
- };
156
- }
157
-
158
- function compactRefreshResult(refreshResult = {}) {
159
- return {
160
- ok: Boolean(refreshResult.ok),
161
- method: refreshResult.method || "",
162
- reason: refreshResult.reason || null,
163
- error: refreshResult.error || null,
164
- forced_recent_not_view: Boolean(refreshResult.forced_recent_not_view),
165
- target_url: refreshResult.target_url || null,
166
- card_count: refreshResult.card_count || 0,
167
- elapsed_ms: refreshResult.elapsed_ms || 0,
168
- attempts: (refreshResult.attempts || []).map((attempt) => ({
169
- ok: Boolean(attempt.ok),
170
- method: attempt.method || "",
171
- reason: attempt.reason || null,
172
- error: attempt.error || null,
173
- card_count: attempt.card_count || 0,
174
- elapsed_ms: attempt.elapsed_ms || 0,
175
- job_selection_attempts: attempt.job_selection_attempts || []
176
- })),
177
- job_selection: refreshResult.job_selection
178
- ? {
179
- requested: refreshResult.job_selection.requested,
180
- selected: Boolean(refreshResult.job_selection.selected),
181
- already_current: Boolean(refreshResult.job_selection.already_current),
182
- reason: refreshResult.job_selection.reason || null,
183
- selected_option: refreshResult.job_selection.selected_option || null,
184
- refresh_attempts: refreshResult.job_selection.refresh_attempts || []
185
- }
186
- : null,
187
- job_selection_attempts: refreshResult.job_selection_attempts || [],
188
- page_scope: refreshResult.page_scope
189
- ? {
190
- requested_scope: refreshResult.page_scope.requested_scope,
191
- effective_scope: refreshResult.page_scope.effective_scope,
192
- selected: Boolean(refreshResult.page_scope.selected),
193
- fallback_applied: Boolean(refreshResult.page_scope.fallback_applied),
194
- reason: refreshResult.page_scope.reason || null,
195
- card_count: refreshResult.page_scope.card_count || refreshResult.page_scope.after?.card_count || 0
196
- }
197
- : null,
198
- filter: refreshResult.filter
199
- ? {
200
- confirmed: Boolean(refreshResult.filter.confirmed),
201
- selected_option: refreshResult.filter.selected_option || null,
202
- selected_options: refreshResult.filter.selected_options || []
203
- }
204
- : null,
205
- filter_reapply_attempts: refreshResult.filter_reapply_attempts || []
206
- };
207
- }
208
-
209
- async function run() {
210
- const options = parseArgs(process.argv.slice(2));
211
- let session;
212
- const result = {
213
- status: "UNKNOWN",
214
- generated_at: new Date().toISOString(),
215
- chrome: {
216
- host: options.host,
217
- port: options.port,
218
- target_url_includes: options.targetUrlIncludes
219
- },
220
- input: {
221
- target_url: options.targetUrl,
222
- job_label: options.jobLabel,
223
- page_scope: options.pageScope,
224
- fallback_page_scope: options.fallbackPageScope,
225
- force_navigate: options.forceNavigate,
226
- force_recent_not_view: options.forceRecentNotView,
227
- reload_settle_ms: options.reloadSettleMs,
228
- card_timeout_ms: options.cardTimeoutMs,
229
- filter_groups: options.filterGroups
230
- },
231
- before: null,
232
- refresh: null,
233
- after: null
234
- };
235
-
236
- try {
237
- session = await connectToRecommendSession(options);
238
- const { client, methodLog, target } = session;
239
- result.chrome.target = {
240
- id: target.id,
241
- type: target.type,
242
- url: target.url,
243
- title: target.title
244
- };
245
- result.runtime_guard_probe = await assertRuntimeEvaluateBlocked(client);
246
- await enableDomains(client, ["Page", "DOM", "Input", "Accessibility"]);
247
- await bringPageToFront(client);
248
-
249
- const beforeState = await summarizeRecommendState(client);
250
- result.before = beforeState.summary;
251
- const jobLabel = options.jobLabel || beforeState.summary.current_job_label;
252
- if (!jobLabel) {
253
- throw new Error("No recommend job label was provided or detectable; pass --job");
254
- }
255
- result.input.job_label = jobLabel;
256
-
257
- const refreshResult = await refreshRecommendListAtEnd(client, {
258
- rootState: beforeState.roots,
259
- jobLabel,
260
- pageScope: options.pageScope,
261
- fallbackPageScope: options.fallbackPageScope,
262
- filter: { filterGroups: options.filterGroups },
263
- preferEndRefreshButton: false,
264
- forceNavigate: options.forceNavigate,
265
- targetUrl: options.targetUrl,
266
- forceRecentNotView: options.forceRecentNotView,
267
- cardTimeoutMs: options.cardTimeoutMs,
268
- reloadSettleMs: options.reloadSettleMs
269
- });
270
- result.refresh = compactRefreshResult(refreshResult);
271
-
272
- await sleep(1000);
273
- const afterState = await summarizeRecommendState(client);
274
- result.after = afterState.summary;
275
- result.method_summary = methodSummary(methodLog);
276
- assertNoForbiddenCdpCalls(methodLog);
277
-
278
- if (!refreshResult.ok) {
279
- throw new Error(`Recommend recovery refresh failed: ${refreshResult.reason || refreshResult.error || "unknown"}`);
280
- }
281
- if (!refreshResult.job_selection?.selected) {
282
- throw new Error("Recommend recovery smoke did not select the requested job");
283
- }
284
- if (!refreshResult.card_count) {
285
- throw new Error("Recommend recovery smoke found no cards after refresh");
286
- }
287
-
288
- result.status = "PASS";
289
- } catch (error) {
290
- result.status = "FAIL";
291
- result.error = {
292
- message: error?.message || String(error),
293
- stack: error?.stack || ""
294
- };
295
- process.exitCode = 1;
296
- } finally {
297
- if (session) await session.close().catch(() => null);
298
- if (options.saveReport) {
299
- result.report_path = writeJsonFile(options.saveReport, result);
300
- }
301
- console.log(JSON.stringify(result, null, 2));
302
- }
303
- }
304
-
305
- await run();