@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.1

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.
Files changed (88) hide show
  1. package/README.md +86 -33
  2. package/package.json +62 -9
  3. package/skills/boss-chat/SKILL.md +5 -4
  4. package/skills/boss-recommend-pipeline/SKILL.md +21 -31
  5. package/skills/boss-recruit-pipeline/README.md +17 -0
  6. package/skills/boss-recruit-pipeline/SKILL.md +55 -0
  7. package/src/chat-mcp.js +1333 -0
  8. package/src/chat-runtime-config.js +559 -0
  9. package/src/cli.js +1254 -225
  10. package/src/core/browser/index.js +378 -0
  11. package/src/core/capture/index.js +298 -0
  12. package/src/core/cv-acquisition/index.js +219 -0
  13. package/src/core/greet-quota/index.js +54 -0
  14. package/src/core/infinite-list/index.js +459 -0
  15. package/src/core/reporting/legacy-csv.js +332 -0
  16. package/src/core/run/index.js +286 -0
  17. package/src/core/screening/index.js +1166 -0
  18. package/src/core/self-heal/index.js +848 -0
  19. package/src/domains/chat/cards.js +129 -0
  20. package/src/domains/chat/constants.js +183 -0
  21. package/src/domains/chat/detail.js +1369 -0
  22. package/src/domains/chat/index.js +7 -0
  23. package/src/domains/chat/jobs.js +334 -0
  24. package/src/domains/chat/page-guard.js +88 -0
  25. package/src/domains/chat/roots.js +56 -0
  26. package/src/domains/chat/run-service.js +1101 -0
  27. package/src/domains/recommend/actions.js +457 -0
  28. package/src/domains/recommend/cards.js +228 -0
  29. package/src/domains/recommend/constants.js +141 -0
  30. package/src/domains/recommend/detail.js +341 -0
  31. package/src/domains/recommend/filters.js +581 -0
  32. package/src/domains/recommend/index.js +10 -0
  33. package/src/domains/recommend/jobs.js +232 -0
  34. package/src/domains/recommend/refresh.js +204 -0
  35. package/src/domains/recommend/roots.js +78 -0
  36. package/src/domains/recommend/run-service.js +903 -0
  37. package/src/domains/recommend/scopes.js +245 -0
  38. package/src/domains/recruit/actions.js +277 -0
  39. package/src/domains/recruit/cards.js +66 -0
  40. package/src/domains/recruit/constants.js +130 -0
  41. package/src/domains/recruit/detail.js +414 -0
  42. package/src/domains/recruit/index.js +9 -0
  43. package/src/domains/recruit/instruction-parser.js +451 -0
  44. package/src/domains/recruit/refresh.js +40 -0
  45. package/src/domains/recruit/roots.js +67 -0
  46. package/src/domains/recruit/run-service.js +580 -0
  47. package/src/domains/recruit/search.js +1149 -0
  48. package/src/index.js +578 -419
  49. package/src/recommend-mcp.js +1257 -0
  50. package/src/recruit-mcp.js +1035 -0
  51. package/src/adapters.js +0 -3079
  52. package/src/boss-chat.js +0 -1037
  53. package/src/pipeline.js +0 -2249
  54. package/src/recommend-healing-config.js +0 -131
  55. package/src/recommend-healing-rules.json +0 -261
  56. package/src/self-heal.js +0 -2237
  57. package/src/test-adapters-runtime.js +0 -628
  58. package/src/test-boss-chat.js +0 -3196
  59. package/src/test-index-async.js +0 -498
  60. package/src/test-parser.js +0 -742
  61. package/src/test-pipeline.js +0 -2703
  62. package/src/test-run-state.js +0 -152
  63. package/src/test-self-heal.js +0 -224
  64. package/vendor/boss-chat-cli/README.md +0 -134
  65. package/vendor/boss-chat-cli/package.json +0 -53
  66. package/vendor/boss-chat-cli/src/app.js +0 -1501
  67. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  68. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  69. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  70. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  71. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  72. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  73. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  74. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  75. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  76. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  77. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  78. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  79. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  80. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  81. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  82. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  83. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
  84. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  85. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  86. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
  87. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  88. package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
@@ -0,0 +1,559 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+
6
+ const BOSS_CHAT_RUNTIME_SUBDIR = "boss-chat";
7
+ const TARGET_COUNT_WRAPPER_KEYS = ["target_count", "targetCount", "value", "count", "limit"];
8
+ const SCREEN_CONFIG_TEMPLATE_DEFAULTS = Object.freeze({
9
+ baseUrl: "https://api.openai.com/v1",
10
+ apiKey: "replace-with-your-api-key",
11
+ model: "gpt-4.1-mini"
12
+ });
13
+
14
+ export const TARGET_COUNT_CANONICAL_ALL = "all";
15
+ export const TARGET_COUNT_ACCEPTED_EXAMPLES = [TARGET_COUNT_CANONICAL_ALL, -1, 20, "全部候选人"];
16
+
17
+ function normalizeText(value) {
18
+ return String(value || "").replace(/\s+/g, " ").trim();
19
+ }
20
+
21
+ function getStateHome() {
22
+ return process.env.BOSS_RECOMMEND_HOME
23
+ ? path.resolve(process.env.BOSS_RECOMMEND_HOME)
24
+ : path.join(os.homedir(), ".boss-recommend-mcp");
25
+ }
26
+
27
+ function getCodexHome() {
28
+ return process.env.CODEX_HOME
29
+ ? path.resolve(process.env.CODEX_HOME)
30
+ : path.join(os.homedir(), ".codex");
31
+ }
32
+
33
+ function pathExists(targetPath) {
34
+ try {
35
+ return Boolean(targetPath) && fs.existsSync(targetPath);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ function isRootDirectory(workspaceRoot) {
42
+ const root = path.resolve(String(workspaceRoot || ""));
43
+ return path.parse(root).root.toLowerCase() === root.toLowerCase();
44
+ }
45
+
46
+ function isEphemeralNpxWorkspaceRoot(workspaceRoot) {
47
+ const root = path.resolve(String(workspaceRoot || ""));
48
+ const normalized = root.replace(/\\/g, "/").toLowerCase();
49
+ return (
50
+ normalized.includes("/appdata/local/npm-cache/_npx/")
51
+ || normalized.includes("/node_modules/@reconcrap/boss-recommend-mcp")
52
+ );
53
+ }
54
+
55
+ function isSystemDirectoryWorkspaceRoot(workspaceRoot) {
56
+ const root = path.resolve(String(workspaceRoot || ""));
57
+ const normalized = root.replace(/\\/g, "/").toLowerCase();
58
+ if (process.platform === "win32") {
59
+ return (
60
+ normalized.endsWith("/windows")
61
+ || normalized.endsWith("/windows/system32")
62
+ || normalized.endsWith("/windows/syswow64")
63
+ || normalized.endsWith("/program files")
64
+ || normalized.endsWith("/program files (x86)")
65
+ );
66
+ }
67
+ return (
68
+ normalized === "/system"
69
+ || normalized.startsWith("/system/")
70
+ || normalized === "/usr"
71
+ || normalized.startsWith("/usr/")
72
+ || normalized === "/bin"
73
+ || normalized.startsWith("/bin/")
74
+ || normalized === "/sbin"
75
+ || normalized.startsWith("/sbin/")
76
+ );
77
+ }
78
+
79
+ function shouldIgnoreWorkspaceConfigRoot(workspaceRoot) {
80
+ const root = path.resolve(String(workspaceRoot || process.cwd()));
81
+ const home = path.resolve(os.homedir());
82
+ return (
83
+ isEphemeralNpxWorkspaceRoot(root)
84
+ || isRootDirectory(root)
85
+ || root.toLowerCase() === home.toLowerCase()
86
+ || isSystemDirectoryWorkspaceRoot(root)
87
+ );
88
+ }
89
+
90
+ function resolveWorkspaceConfigCandidates(workspaceRoot) {
91
+ const root = path.resolve(String(workspaceRoot || process.cwd()));
92
+ if (shouldIgnoreWorkspaceConfigRoot(root)) return [];
93
+ const directPath = path.join(root, "config", "screening-config.json");
94
+ const nestedPath = path.join(root, "boss-recommend-mcp", "config", "screening-config.json");
95
+ const candidates = [directPath];
96
+ if (path.basename(root).toLowerCase() !== "boss-recommend-mcp") {
97
+ candidates.push(nestedPath);
98
+ }
99
+ return Array.from(new Set(candidates));
100
+ }
101
+
102
+ function getUserConfigPath() {
103
+ return path.join(getStateHome(), "screening-config.json");
104
+ }
105
+
106
+ function getLegacyUserConfigPath() {
107
+ return path.join(getCodexHome(), "boss-recommend-mcp", "screening-config.json");
108
+ }
109
+
110
+ function getUserCalibrationPath() {
111
+ return path.join(getCodexHome(), "boss-recommend-mcp", "favorite-calibration.json");
112
+ }
113
+
114
+ function buildScreenConfigCandidateMap(workspaceRoot) {
115
+ return {
116
+ env_path: process.env.BOSS_RECOMMEND_SCREEN_CONFIG
117
+ ? path.resolve(process.env.BOSS_RECOMMEND_SCREEN_CONFIG)
118
+ : null,
119
+ workspace_paths: resolveWorkspaceConfigCandidates(workspaceRoot),
120
+ user_path: getUserConfigPath(),
121
+ legacy_path: getLegacyUserConfigPath()
122
+ };
123
+ }
124
+
125
+ function resolveScreenConfigCandidates(workspaceRoot) {
126
+ const candidateMap = buildScreenConfigCandidateMap(workspaceRoot);
127
+ return [
128
+ candidateMap.env_path,
129
+ candidateMap.user_path,
130
+ ...candidateMap.workspace_paths,
131
+ candidateMap.legacy_path
132
+ ].filter(Boolean);
133
+ }
134
+
135
+ function canWriteDirectory(targetDir) {
136
+ try {
137
+ fs.mkdirSync(targetDir, { recursive: true });
138
+ fs.accessSync(targetDir, fs.constants.W_OK);
139
+ return true;
140
+ } catch {
141
+ return false;
142
+ }
143
+ }
144
+
145
+ function resolveWritableScreenConfigPath(workspaceRoot) {
146
+ const candidateMap = buildScreenConfigCandidateMap(workspaceRoot);
147
+ const workspacePreferred = candidateMap.workspace_paths?.[0] || null;
148
+ if (candidateMap.env_path) return candidateMap.env_path;
149
+ if (candidateMap.user_path && canWriteDirectory(path.dirname(candidateMap.user_path))) {
150
+ return candidateMap.user_path;
151
+ }
152
+ if (workspacePreferred && canWriteDirectory(path.dirname(workspacePreferred))) {
153
+ return workspacePreferred;
154
+ }
155
+ if (workspacePreferred) return workspacePreferred;
156
+ return candidateMap.user_path || candidateMap.legacy_path;
157
+ }
158
+
159
+ function resolveScreenConfigPath(workspaceRoot) {
160
+ const candidateMap = buildScreenConfigCandidateMap(workspaceRoot);
161
+ if (candidateMap.env_path) return candidateMap.env_path;
162
+ if (candidateMap.user_path && pathExists(candidateMap.user_path)) return candidateMap.user_path;
163
+ const existingWorkspacePath = candidateMap.workspace_paths.find((item) => pathExists(item));
164
+ if (existingWorkspacePath) return existingWorkspacePath;
165
+ return resolveWritableScreenConfigPath(workspaceRoot) || candidateMap.legacy_path;
166
+ }
167
+
168
+ function readJsonFile(filePath) {
169
+ if (!filePath || !pathExists(filePath)) return null;
170
+ try {
171
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+
177
+ function isUsableFeaturedCalibrationFile(filePath) {
178
+ const parsed = readJsonFile(filePath);
179
+ return Boolean(
180
+ parsed
181
+ && typeof parsed === "object"
182
+ && !Array.isArray(parsed)
183
+ && parsed.favoritePosition
184
+ && Number.isFinite(parsed.favoritePosition.pageX)
185
+ && Number.isFinite(parsed.favoritePosition.pageY)
186
+ );
187
+ }
188
+
189
+ function resolveFeaturedCalibrationPath(workspaceRoot) {
190
+ const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_CALIBRATION_FILE || "");
191
+ if (fromEnv) return path.resolve(fromEnv);
192
+
193
+ const configResolution = resolveBossScreeningConfig(workspaceRoot);
194
+ const configPath = configResolution.config_path || resolveScreenConfigPath(workspaceRoot) || getUserConfigPath();
195
+ const config = readJsonFile(configPath);
196
+ const calibrationFile = normalizeText(config?.calibrationFile || "");
197
+ if (calibrationFile && configPath) {
198
+ return path.resolve(path.dirname(configPath), calibrationFile);
199
+ }
200
+
201
+ return getUserCalibrationPath();
202
+ }
203
+
204
+ function resolveRecruitCalibrationScriptPath(workspaceRoot) {
205
+ const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_RECRUIT_CALIBRATION_SCRIPT || "");
206
+ const workspaceResolved = path.resolve(String(workspaceRoot || process.cwd()));
207
+ const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
208
+ const candidates = [
209
+ fromEnv,
210
+ path.join(workspaceResolved, "..", "..", "boss recruit pipeline", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
211
+ path.join(workspaceResolved, "..", "boss recruit pipeline", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
212
+ path.join(appData, "npm", "node_modules", "@reconcrap", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs")
213
+ ].filter(Boolean).map((item) => path.resolve(item));
214
+
215
+ for (const candidate of new Set(candidates)) {
216
+ if (pathExists(candidate)) return candidate;
217
+ }
218
+ return null;
219
+ }
220
+
221
+ function parsePositiveInteger(raw, fallback = null) {
222
+ const parsed = Number.parseInt(String(raw || ""), 10);
223
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
224
+ }
225
+
226
+ function validateScreeningConfig(config) {
227
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
228
+ return {
229
+ ok: false,
230
+ reason: "INVALID_OR_MISSING_CONFIG",
231
+ message: "screening-config.json 缺失或格式无效。请填写 baseUrl、apiKey、model。"
232
+ };
233
+ }
234
+ const baseUrl = normalizeText(config.baseUrl).replace(/\/+$/, "");
235
+ const apiKey = normalizeText(config.apiKey);
236
+ const model = normalizeText(config.model);
237
+ const missing = [];
238
+ if (!baseUrl) missing.push("baseUrl");
239
+ if (!apiKey) missing.push("apiKey");
240
+ if (!model) missing.push("model");
241
+ if (missing.length > 0) {
242
+ return {
243
+ ok: false,
244
+ reason: "MISSING_REQUIRED_FIELDS",
245
+ message: `screening-config.json 缺少必填字段:${missing.join(", ")}。`
246
+ };
247
+ }
248
+ if (/^replace-with/i.test(apiKey) || apiKey === SCREEN_CONFIG_TEMPLATE_DEFAULTS.apiKey) {
249
+ return {
250
+ ok: false,
251
+ reason: "PLACEHOLDER_API_KEY",
252
+ message: "screening-config.json 的 apiKey 仍是模板占位符,请填写真实 API Key。"
253
+ };
254
+ }
255
+ if (
256
+ baseUrl === SCREEN_CONFIG_TEMPLATE_DEFAULTS.baseUrl
257
+ && apiKey === SCREEN_CONFIG_TEMPLATE_DEFAULTS.apiKey
258
+ && model === SCREEN_CONFIG_TEMPLATE_DEFAULTS.model
259
+ ) {
260
+ return {
261
+ ok: false,
262
+ reason: "PLACEHOLDER_TEMPLATE_VALUES",
263
+ message: "screening-config.json 仍是默认模板值,请填写 baseUrl、apiKey、model。"
264
+ };
265
+ }
266
+ return { ok: true, reason: "OK", message: "screening-config.json 校验通过。" };
267
+ }
268
+
269
+ export function resolveBossChatDataDir() {
270
+ if (process.env.BOSS_CHAT_HOME) {
271
+ return {
272
+ data_dir: path.resolve(process.env.BOSS_CHAT_HOME),
273
+ data_dir_source: "env:BOSS_CHAT_HOME"
274
+ };
275
+ }
276
+ const stateHome = getStateHome();
277
+ const source = process.env.BOSS_RECOMMEND_HOME
278
+ ? "default:env:BOSS_RECOMMEND_HOME"
279
+ : "default:user_home";
280
+ return {
281
+ data_dir: path.join(stateHome, BOSS_CHAT_RUNTIME_SUBDIR),
282
+ data_dir_source: source
283
+ };
284
+ }
285
+
286
+ export function getBossChatDataDir() {
287
+ return resolveBossChatDataDir().data_dir;
288
+ }
289
+
290
+ export function getLegacyBossChatWorkspaceDataDir(workspaceRoot) {
291
+ const root = path.resolve(String(workspaceRoot || ""));
292
+ if (!root || isRootDirectory(root) || isSystemDirectoryWorkspaceRoot(root)) return null;
293
+ return path.join(root, ".boss-chat");
294
+ }
295
+
296
+ export function resolveBossChatRuntimeLayout(workspaceRoot) {
297
+ const resolvedDataDir = resolveBossChatDataDir();
298
+ const legacyWorkspaceDir = getLegacyBossChatWorkspaceDataDir(workspaceRoot);
299
+ const migrationSourceDir = legacyWorkspaceDir && pathExists(legacyWorkspaceDir) && !pathExists(resolvedDataDir.data_dir)
300
+ ? legacyWorkspaceDir
301
+ : null;
302
+ return {
303
+ workspace_root: workspaceRoot ? path.resolve(String(workspaceRoot)) : null,
304
+ data_dir: resolvedDataDir.data_dir,
305
+ data_dir_source: resolvedDataDir.data_dir_source,
306
+ legacy_workspace_dir: legacyWorkspaceDir,
307
+ migration_source_dir: migrationSourceDir,
308
+ migration_pending: Boolean(migrationSourceDir)
309
+ };
310
+ }
311
+
312
+ export function getBossScreenConfigResolution(workspaceRoot) {
313
+ const candidateMap = buildScreenConfigCandidateMap(workspaceRoot);
314
+ const workspaceRootResolved = path.resolve(String(workspaceRoot || process.cwd()));
315
+ return {
316
+ resolved_path: resolveScreenConfigPath(workspaceRoot) || null,
317
+ candidate_paths: resolveScreenConfigCandidates(workspaceRoot),
318
+ workspace_root: workspaceRootResolved,
319
+ workspace_ephemeral: isEphemeralNpxWorkspaceRoot(workspaceRootResolved),
320
+ workspace_ignored_for_config: shouldIgnoreWorkspaceConfigRoot(workspaceRootResolved),
321
+ writable_path: resolveWritableScreenConfigPath(workspaceRoot),
322
+ legacy_path: candidateMap.legacy_path
323
+ };
324
+ }
325
+
326
+ export function getFeaturedCalibrationResolution(workspaceRoot) {
327
+ const calibrationPath = resolveFeaturedCalibrationPath(workspaceRoot);
328
+ return {
329
+ calibration_path: calibrationPath,
330
+ calibration_exists: pathExists(calibrationPath),
331
+ calibration_usable: isUsableFeaturedCalibrationFile(calibrationPath),
332
+ calibration_script_path: resolveRecruitCalibrationScriptPath(workspaceRoot)
333
+ };
334
+ }
335
+
336
+ export function resolveBossScreeningConfig(workspaceRoot) {
337
+ const candidatePaths = resolveScreenConfigCandidates(workspaceRoot);
338
+ const configPath = resolveScreenConfigPath(workspaceRoot) || null;
339
+ const configDir = configPath ? path.dirname(configPath) : null;
340
+ if (!configPath || !pathExists(configPath)) {
341
+ return {
342
+ ok: false,
343
+ error: {
344
+ code: "SCREEN_CONFIG_ERROR",
345
+ message: `screening-config.json 不存在。请先完成 recommend 配置。${configPath ? ` (path: ${configPath})` : ""}`,
346
+ retryable: true
347
+ },
348
+ config_path: configPath,
349
+ config_dir: configDir,
350
+ candidate_paths: candidatePaths
351
+ };
352
+ }
353
+ const parsed = readJsonFile(configPath);
354
+ const validation = validateScreeningConfig(parsed);
355
+ if (!validation.ok) {
356
+ return {
357
+ ok: false,
358
+ error: {
359
+ code: "SCREEN_CONFIG_ERROR",
360
+ message: `${validation.message} (path: ${configPath})`,
361
+ retryable: true
362
+ },
363
+ config_path: configPath,
364
+ config_dir: configDir,
365
+ candidate_paths: candidatePaths
366
+ };
367
+ }
368
+ return {
369
+ ok: true,
370
+ config: {
371
+ baseUrl: normalizeText(parsed.baseUrl).replace(/\/+$/, ""),
372
+ model: normalizeText(parsed.model),
373
+ debugPort: parsePositiveInteger(parsed.debugPort, 9222)
374
+ },
375
+ config_path: configPath,
376
+ config_dir: configDir,
377
+ candidate_paths: candidatePaths
378
+ };
379
+ }
380
+
381
+ function isUnlimitedTargetCountToken(value) {
382
+ const token = normalizeText(value).toLowerCase();
383
+ if (!token) return false;
384
+ const compact = token.replace(/\s+/g, "");
385
+ const withoutAnnotation = compact.replace(/[((【[].*?[))】\]]/gu, "");
386
+ const knownTokens = new Set([
387
+ "all",
388
+ "unlimited",
389
+ "infinity",
390
+ "inf",
391
+ "max",
392
+ "full",
393
+ "allcandidates",
394
+ "全部",
395
+ "全量",
396
+ "不限",
397
+ "扫到底",
398
+ "全部候选人",
399
+ "所有候选人",
400
+ "全部人选",
401
+ "所有人选",
402
+ "直到完成所有人选"
403
+ ]);
404
+ if (knownTokens.has(token) || knownTokens.has(compact) || knownTokens.has(withoutAnnotation)) return true;
405
+ if (/^(?:all|unlimited|infinity|inf|max|full)(?:candidate|candidates)?$/i.test(compact)) return true;
406
+ if (/^(?:all|unlimited|infinity|inf|max|full)(?:候选人|人选|牛人|人才|人员)?$/iu.test(withoutAnnotation)) return true;
407
+ if (/^(?:全部|所有|全量|不限)(?:候选人|人选|牛人|人才|人员)?$/u.test(compact)) return true;
408
+ if (!/\d/.test(compact) && /(?:扫到底|全部候选人|所有候选人|全部人选|所有人选)/u.test(compact)) return true;
409
+ return false;
410
+ }
411
+
412
+ function getWrappedTargetCountValue(value) {
413
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
414
+ for (const key of TARGET_COUNT_WRAPPER_KEYS) {
415
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
416
+ return value[key];
417
+ }
418
+ }
419
+ return value;
420
+ }
421
+
422
+ export function getBossChatTargetCountValue(input = {}) {
423
+ if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
424
+ if (Object.prototype.hasOwnProperty.call(input, "target_count") && input.target_count !== undefined && input.target_count !== null) {
425
+ return input.target_count;
426
+ }
427
+ if (Object.prototype.hasOwnProperty.call(input, "targetCount") && input.targetCount !== undefined && input.targetCount !== null) {
428
+ return input.targetCount;
429
+ }
430
+ if (Object.prototype.hasOwnProperty.call(input, "target_count")) return input.target_count;
431
+ if (Object.prototype.hasOwnProperty.call(input, "targetCount")) return input.targetCount;
432
+ return undefined;
433
+ }
434
+
435
+ function cloneForDiagnostics(value) {
436
+ if (value === undefined) return undefined;
437
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) return value;
438
+ try {
439
+ return JSON.parse(JSON.stringify(value));
440
+ } catch {
441
+ return String(value);
442
+ }
443
+ }
444
+
445
+ export function buildTargetCountCompatibilityHints({
446
+ argumentName = "target_count",
447
+ recommendedArgumentPatch = { target_count: TARGET_COUNT_CANONICAL_ALL },
448
+ includeOptions = true
449
+ } = {}) {
450
+ const normalizedArgumentName = normalizeText(argumentName) || "target_count";
451
+ const clonedRecommendedPatch = cloneForDiagnostics(recommendedArgumentPatch)
452
+ || { target_count: TARGET_COUNT_CANONICAL_ALL };
453
+ const literal = `${normalizedArgumentName}="${TARGET_COUNT_CANONICAL_ALL}"`;
454
+ const base = {
455
+ argument_name: normalizedArgumentName,
456
+ answer_format: `${normalizedArgumentName} = 正整数 | "${TARGET_COUNT_CANONICAL_ALL}"`,
457
+ canonical_unlimited_value: TARGET_COUNT_CANONICAL_ALL,
458
+ recommended_value: TARGET_COUNT_CANONICAL_ALL,
459
+ recommended_argument_patch: clonedRecommendedPatch,
460
+ accepted_examples: TARGET_COUNT_ACCEPTED_EXAMPLES.slice()
461
+ };
462
+ if (!includeOptions) return base;
463
+ return {
464
+ ...base,
465
+ options: [
466
+ {
467
+ label: `扫到底(必须传 ${literal},推荐)`,
468
+ value: TARGET_COUNT_CANONICAL_ALL,
469
+ canonical_value: TARGET_COUNT_CANONICAL_ALL,
470
+ argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
471
+ },
472
+ {
473
+ label: `不限(等价于 ${literal})`,
474
+ value: "unlimited",
475
+ canonical_value: TARGET_COUNT_CANONICAL_ALL,
476
+ argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
477
+ },
478
+ {
479
+ label: `全部候选人(等价于 ${literal})`,
480
+ value: "全部候选人",
481
+ canonical_value: TARGET_COUNT_CANONICAL_ALL,
482
+ argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
483
+ },
484
+ {
485
+ label: `所有候选人(等价于 ${literal})`,
486
+ value: "所有候选人",
487
+ canonical_value: TARGET_COUNT_CANONICAL_ALL,
488
+ argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
489
+ }
490
+ ]
491
+ };
492
+ }
493
+
494
+ export function normalizeTargetCountInput(value) {
495
+ if (value === undefined || value === null) {
496
+ return {
497
+ provided: false,
498
+ targetCount: null,
499
+ cliArg: null,
500
+ publicValue: null,
501
+ rawValue: value,
502
+ parseError: null
503
+ };
504
+ }
505
+ const unwrapped = getWrappedTargetCountValue(value);
506
+ if (unwrapped !== value) {
507
+ return normalizeTargetCountInput(unwrapped);
508
+ }
509
+ const raw = normalizeText(unwrapped);
510
+ if (!raw) {
511
+ return {
512
+ provided: false,
513
+ targetCount: null,
514
+ cliArg: null,
515
+ publicValue: null,
516
+ rawValue: value,
517
+ parseError: null
518
+ };
519
+ }
520
+ if (isUnlimitedTargetCountToken(raw)) {
521
+ return {
522
+ provided: true,
523
+ targetCount: null,
524
+ cliArg: "-1",
525
+ publicValue: TARGET_COUNT_CANONICAL_ALL,
526
+ rawValue: cloneForDiagnostics(value),
527
+ parseError: null
528
+ };
529
+ }
530
+ const parsed = Number.parseInt(String(raw), 10);
531
+ if (Number.isFinite(parsed) && parsed === -1) {
532
+ return {
533
+ provided: true,
534
+ targetCount: null,
535
+ cliArg: "-1",
536
+ publicValue: TARGET_COUNT_CANONICAL_ALL,
537
+ rawValue: cloneForDiagnostics(value),
538
+ parseError: null
539
+ };
540
+ }
541
+ if (Number.isFinite(parsed) && parsed > 0) {
542
+ return {
543
+ provided: true,
544
+ targetCount: parsed,
545
+ cliArg: String(parsed),
546
+ publicValue: parsed,
547
+ rawValue: cloneForDiagnostics(value),
548
+ parseError: null
549
+ };
550
+ }
551
+ return {
552
+ provided: false,
553
+ targetCount: null,
554
+ cliArg: null,
555
+ publicValue: null,
556
+ rawValue: cloneForDiagnostics(value),
557
+ parseError: "target_count must be a positive integer, -1, or one of: all, unlimited, 全部, 不限, 扫到底, 全量, 全部候选人, 所有候选人"
558
+ };
559
+ }