@reconcrap/boss-recommend-mcp 0.1.1 → 0.1.2
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 +13 -1
- package/package.json +1 -1
- package/skills/boss-recommend-pipeline/SKILL.md +10 -2
- package/src/cli.js +185 -1
- package/src/index.js +4 -0
- package/src/parser.js +53 -3
- package/src/pipeline.js +8 -0
- package/src/test-parser.js +40 -0
- package/src/test-pipeline.js +34 -0
package/README.md
CHANGED
|
@@ -19,9 +19,10 @@ MCP 工具名:`run_recommend_pipeline`
|
|
|
19
19
|
## 设计特点
|
|
20
20
|
|
|
21
21
|
- 页面目标固定为 `https://www.zhipin.com/web/chat/recommend`
|
|
22
|
-
- 支持推荐页原生筛选:学校标签 / 性别 / 近14天没有
|
|
23
22
|
- 支持推荐页原生筛选:学校标签 / 学历 / 性别 / 近14天没有
|
|
24
23
|
- 学历支持单选与多选语义:如“本科及以上”会展开为 `本科/硕士/博士`;如“大专、本科”只勾选这两项
|
|
24
|
+
- 执行前会逐项确认筛选参数:学校标签 / 学历 / 性别 / 是否过滤近14天已看
|
|
25
|
+
- npm 全局安装后会自动执行 install:生成 Codex skill、导出 MCP 模板,并自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
|
|
25
26
|
- `post_action` 必须在每次完整运行开始时确认一次
|
|
26
27
|
- `target_count` 会在每次运行开始时询问一次(可留空,不设上限)
|
|
27
28
|
- 当 `post_action=greet` 时,必须在运行开始时确认 `max_greet_count`
|
|
@@ -43,6 +44,13 @@ npm install
|
|
|
43
44
|
node src/cli.js start
|
|
44
45
|
```
|
|
45
46
|
|
|
47
|
+
可选环境变量(用于跨 agent 自动配置):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
BOSS_RECOMMEND_MCP_CONFIG_TARGETS # JSON 数组或系统 path 分隔路径列表,指定额外 mcp.json 目标文件
|
|
51
|
+
BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS # JSON 数组或系统 path 分隔路径列表,指定额外 skills 根目录
|
|
52
|
+
```
|
|
53
|
+
|
|
46
54
|
或使用 CLI fallback:
|
|
47
55
|
|
|
48
56
|
```bash
|
|
@@ -94,6 +102,10 @@ node src/cli.js run --instruction-file request.txt --confirmation-file confirmat
|
|
|
94
102
|
"instruction": "推荐页筛选211女生,近14天没有,有 AI Agent 经验,符合标准的直接沟通",
|
|
95
103
|
"confirmation": {
|
|
96
104
|
"filters_confirmed": true,
|
|
105
|
+
"school_tag_confirmed": true,
|
|
106
|
+
"degree_confirmed": true,
|
|
107
|
+
"gender_confirmed": true,
|
|
108
|
+
"recent_not_view_confirmed": true,
|
|
97
109
|
"criteria_confirmed": true,
|
|
98
110
|
"target_count_confirmed": true,
|
|
99
111
|
"target_count_value": 20,
|
package/package.json
CHANGED
|
@@ -22,7 +22,10 @@ description: "Use when users ask to run Boss recommend-page filtering and screen
|
|
|
22
22
|
|
|
23
23
|
在真正执行前,必须先确认:
|
|
24
24
|
|
|
25
|
-
-
|
|
25
|
+
- 学校标签(`school_tag`)
|
|
26
|
+
- 学历(`degree`)
|
|
27
|
+
- 性别(`gender`)
|
|
28
|
+
- 是否过滤近14天已看(`recent_not_view`)
|
|
26
29
|
- screening criteria 是否正确
|
|
27
30
|
- `target_count`(目标筛选人数)是否需要设置(可不设上限)
|
|
28
31
|
- `post_action` 是否确定为 `favorite` 或 `greet`
|
|
@@ -41,6 +44,10 @@ description: "Use when users ask to run Boss recommend-page filtering and screen
|
|
|
41
44
|
- `instruction` (required)
|
|
42
45
|
- `confirmation`
|
|
43
46
|
- `filters_confirmed`
|
|
47
|
+
- `school_tag_confirmed`
|
|
48
|
+
- `degree_confirmed`
|
|
49
|
+
- `gender_confirmed`
|
|
50
|
+
- `recent_not_view_confirmed`
|
|
44
51
|
- `criteria_confirmed`
|
|
45
52
|
- `target_count_confirmed`
|
|
46
53
|
- `target_count_value` (integer, optional)
|
|
@@ -91,5 +98,6 @@ CLI fallback 的状态机与 MCP 保持一致:
|
|
|
91
98
|
|
|
92
99
|
- 用结构化中文输出
|
|
93
100
|
- 先给用户确认卡片,再正式执行
|
|
101
|
+
- 对 `school_tag/degree/gender/recent_not_view` 必须逐项提问并逐项确认,不可合并成一句“filters已确认”
|
|
94
102
|
- 不要跳过 `post_action` 的首轮确认
|
|
95
|
-
- 不要把 recommend 流程说成 search 流程
|
|
103
|
+
- 不要把 recommend 流程说成 search 流程
|
package/src/cli.js
CHANGED
|
@@ -27,6 +27,8 @@ const defaultMcpServerName = "boss-recommend";
|
|
|
27
27
|
const defaultMcpCommand = "npx";
|
|
28
28
|
const defaultMcpArgs = ["-y", "@reconcrap/boss-recommend-mcp@latest", "start"];
|
|
29
29
|
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h"]);
|
|
30
|
+
const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
|
|
31
|
+
const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
|
|
30
32
|
|
|
31
33
|
function getPackageVersion() {
|
|
32
34
|
try {
|
|
@@ -49,6 +51,39 @@ function ensureDir(targetPath) {
|
|
|
49
51
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
function pathExists(targetPath) {
|
|
55
|
+
try {
|
|
56
|
+
return fs.existsSync(targetPath);
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readJsonObjectFileSafe(filePath) {
|
|
63
|
+
if (!pathExists(filePath)) return {};
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
66
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Fallback below.
|
|
71
|
+
}
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function dedupePaths(items) {
|
|
76
|
+
const result = [];
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (const item of items || []) {
|
|
79
|
+
const resolved = path.resolve(String(item || ""));
|
|
80
|
+
if (!resolved || seen.has(resolved)) continue;
|
|
81
|
+
seen.add(resolved);
|
|
82
|
+
result.push(resolved);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
52
87
|
function getDesktopDir() {
|
|
53
88
|
return path.join(os.homedir(), "Desktop");
|
|
54
89
|
}
|
|
@@ -154,6 +189,7 @@ function normalizeMcpClientName(value) {
|
|
|
154
189
|
const raw = String(value || "").trim().toLowerCase();
|
|
155
190
|
if (!raw) return "";
|
|
156
191
|
if (raw === "claude-code") return "claudecode";
|
|
192
|
+
if (raw === "trae-cn") return "trae";
|
|
157
193
|
return raw;
|
|
158
194
|
}
|
|
159
195
|
|
|
@@ -219,6 +255,135 @@ function writeMcpConfigFiles(options = {}) {
|
|
|
219
255
|
return { outputDir, files };
|
|
220
256
|
}
|
|
221
257
|
|
|
258
|
+
function parsePathListFromEnv(raw) {
|
|
259
|
+
if (!raw) return [];
|
|
260
|
+
const text = String(raw).trim();
|
|
261
|
+
if (!text) return [];
|
|
262
|
+
try {
|
|
263
|
+
const parsed = JSON.parse(text);
|
|
264
|
+
if (Array.isArray(parsed)) {
|
|
265
|
+
return dedupePaths(parsed.filter(Boolean));
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// Fallback to delimiter split.
|
|
269
|
+
}
|
|
270
|
+
return dedupePaths(text.split(path.delimiter).map((item) => item.trim()).filter(Boolean));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getKnownExternalMcpConfigPaths() {
|
|
274
|
+
const home = os.homedir();
|
|
275
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
276
|
+
return dedupePaths([
|
|
277
|
+
path.join(appData, "Cursor", "User", "mcp.json"),
|
|
278
|
+
path.join(appData, "Trae", "User", "mcp.json"),
|
|
279
|
+
path.join(appData, "Trae CN", "User", "mcp.json"),
|
|
280
|
+
path.join(home, ".trae", "mcp.json"),
|
|
281
|
+
path.join(home, ".trae-cn", "mcp.json"),
|
|
282
|
+
path.join(home, ".claude", "mcp.json"),
|
|
283
|
+
path.join(home, ".openclaw", "mcp.json")
|
|
284
|
+
]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function resolveExternalMcpConfigTargets() {
|
|
288
|
+
const fromEnv = parsePathListFromEnv(process.env[externalMcpTargetsEnv]);
|
|
289
|
+
const known = getKnownExternalMcpConfigPaths().filter((filePath) => {
|
|
290
|
+
if (pathExists(filePath)) return true;
|
|
291
|
+
return pathExists(path.dirname(filePath));
|
|
292
|
+
});
|
|
293
|
+
return dedupePaths([...fromEnv, ...known]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function mergeMcpServerConfigFile(filePath, options = {}) {
|
|
297
|
+
const nextConfig = buildMcpConfigFileContent(options);
|
|
298
|
+
const serverName = Object.keys(nextConfig.mcpServers || {})[0] || defaultMcpServerName;
|
|
299
|
+
const launchConfig = nextConfig.mcpServers?.[serverName] || buildMcpLaunchConfig(options);
|
|
300
|
+
const current = readJsonObjectFileSafe(filePath);
|
|
301
|
+
const existingServers =
|
|
302
|
+
current?.mcpServers && typeof current.mcpServers === "object" && !Array.isArray(current.mcpServers)
|
|
303
|
+
? current.mcpServers
|
|
304
|
+
: {};
|
|
305
|
+
const existingEntry = existingServers[serverName];
|
|
306
|
+
const merged = {
|
|
307
|
+
...current,
|
|
308
|
+
mcpServers: {
|
|
309
|
+
...existingServers,
|
|
310
|
+
[serverName]: launchConfig
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
ensureDir(path.dirname(filePath));
|
|
315
|
+
fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
|
|
316
|
+
const updated = JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
|
|
317
|
+
return {
|
|
318
|
+
file: filePath,
|
|
319
|
+
server: serverName,
|
|
320
|
+
updated
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function installExternalMcpConfigs(options = {}) {
|
|
325
|
+
const targets = resolveExternalMcpConfigTargets();
|
|
326
|
+
const applied = [];
|
|
327
|
+
const skipped = [];
|
|
328
|
+
for (const target of targets) {
|
|
329
|
+
try {
|
|
330
|
+
const existed = pathExists(target);
|
|
331
|
+
const merged = mergeMcpServerConfigFile(target, options);
|
|
332
|
+
applied.push({
|
|
333
|
+
file: target,
|
|
334
|
+
server: merged.server,
|
|
335
|
+
created: !existed,
|
|
336
|
+
updated: merged.updated
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
skipped.push({
|
|
340
|
+
file: target,
|
|
341
|
+
reason: error.message
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { targets, applied, skipped };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getKnownExternalSkillBaseDirs() {
|
|
349
|
+
const home = os.homedir();
|
|
350
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
351
|
+
return dedupePaths([
|
|
352
|
+
path.join(home, ".cursor", "skills"),
|
|
353
|
+
path.join(home, ".trae", "skills"),
|
|
354
|
+
path.join(home, ".trae-cn", "skills"),
|
|
355
|
+
path.join(home, ".claude", "skills"),
|
|
356
|
+
path.join(home, ".openclaw", "skills"),
|
|
357
|
+
path.join(appData, "Cursor", "User", "skills"),
|
|
358
|
+
path.join(appData, "Trae", "User", "skills"),
|
|
359
|
+
path.join(appData, "Trae CN", "User", "skills"),
|
|
360
|
+
path.join(appData, "OpenClaw", "User", "skills")
|
|
361
|
+
]);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function resolveExternalSkillBaseDirs() {
|
|
365
|
+
const fromEnv = parsePathListFromEnv(process.env[externalSkillDirsEnv]);
|
|
366
|
+
const known = getKnownExternalSkillBaseDirs().filter((dirPath) => pathExists(dirPath));
|
|
367
|
+
return dedupePaths([...fromEnv, ...known]);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function mirrorSkillToExternalDirs() {
|
|
371
|
+
const baseDirs = resolveExternalSkillBaseDirs();
|
|
372
|
+
const mirrored = [];
|
|
373
|
+
const skipped = [];
|
|
374
|
+
for (const baseDir of baseDirs) {
|
|
375
|
+
try {
|
|
376
|
+
const targetDir = path.join(baseDir, skillName);
|
|
377
|
+
ensureDir(path.dirname(targetDir));
|
|
378
|
+
fs.cpSync(skillSourceDir, targetDir, { recursive: true, force: true });
|
|
379
|
+
mirrored.push({ base_dir: baseDir, target_dir: targetDir });
|
|
380
|
+
} catch (error) {
|
|
381
|
+
skipped.push({ base_dir: baseDir, reason: error.message });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return { baseDirs, mirrored, skipped };
|
|
385
|
+
}
|
|
386
|
+
|
|
222
387
|
function syncSkillAssets(options = {}) {
|
|
223
388
|
const force = options.force === true;
|
|
224
389
|
const targetDir = getSkillTargetDir();
|
|
@@ -421,7 +586,7 @@ function printHelp() {
|
|
|
421
586
|
console.log(" boss-recommend-mcp install-skill Install only the Codex skill");
|
|
422
587
|
console.log(" boss-recommend-mcp init-config Create ~/.codex/boss-recommend-mcp/screening-config.json if missing");
|
|
423
588
|
console.log(" boss-recommend-mcp set-port Persist preferred Chrome debug port to screening-config.json");
|
|
424
|
-
console.log(" boss-recommend-mcp mcp-config Generate MCP config JSON for Cursor/Trae/Claude Code/OpenClaw");
|
|
589
|
+
console.log(" boss-recommend-mcp mcp-config Generate MCP config JSON for Cursor/Trae(含 trae-cn)/Claude Code/OpenClaw");
|
|
425
590
|
console.log(" boss-recommend-mcp doctor Check config and runtime prerequisites");
|
|
426
591
|
console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
|
|
427
592
|
console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
|
|
@@ -447,12 +612,31 @@ function installAll() {
|
|
|
447
612
|
const skillTarget = installSkill();
|
|
448
613
|
const configResult = ensureUserConfig();
|
|
449
614
|
const mcpTemplateResult = writeMcpConfigFiles({ client: "all" });
|
|
615
|
+
const externalMcpResult = installExternalMcpConfigs({});
|
|
616
|
+
const externalSkillResult = mirrorSkillToExternalDirs();
|
|
450
617
|
console.log(`Skill installed to: ${skillTarget}`);
|
|
451
618
|
console.log(configResult.created ? `Config template created at: ${configResult.path}` : `Config already exists at: ${configResult.path}`);
|
|
452
619
|
console.log(`MCP config templates exported to: ${mcpTemplateResult.outputDir}`);
|
|
453
620
|
for (const item of mcpTemplateResult.files) {
|
|
454
621
|
console.log(`- ${item.client}: ${item.file}`);
|
|
455
622
|
}
|
|
623
|
+
if (externalMcpResult.targets.length > 0) {
|
|
624
|
+
console.log(`Auto-configured external MCP files: ${externalMcpResult.applied.length}`);
|
|
625
|
+
for (const item of externalMcpResult.applied) {
|
|
626
|
+
const action = item.created ? "created" : item.updated ? "updated" : "unchanged";
|
|
627
|
+
console.log(`- ${item.file} (${action})`);
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
console.log("No external MCP config target detected. Set BOSS_RECOMMEND_MCP_CONFIG_TARGETS to auto-configure custom agents.");
|
|
631
|
+
}
|
|
632
|
+
if (externalSkillResult.baseDirs.length > 0) {
|
|
633
|
+
console.log(`Mirrored skill to external dirs: ${externalSkillResult.mirrored.length}`);
|
|
634
|
+
for (const item of externalSkillResult.mirrored) {
|
|
635
|
+
console.log(`- ${item.target_dir}`);
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
console.log("No external skill dir detected. Set BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS to mirror skill for non-Codex agents.");
|
|
639
|
+
}
|
|
456
640
|
}
|
|
457
641
|
|
|
458
642
|
async function runPipelineOnce(options) {
|
package/src/index.js
CHANGED
|
@@ -38,6 +38,10 @@ function createToolSchema() {
|
|
|
38
38
|
type: "object",
|
|
39
39
|
properties: {
|
|
40
40
|
filters_confirmed: { type: "boolean" },
|
|
41
|
+
school_tag_confirmed: { type: "boolean" },
|
|
42
|
+
degree_confirmed: { type: "boolean" },
|
|
43
|
+
gender_confirmed: { type: "boolean" },
|
|
44
|
+
recent_not_view_confirmed: { type: "boolean" },
|
|
41
45
|
criteria_confirmed: { type: "boolean" },
|
|
42
46
|
target_count_confirmed: { type: "boolean" },
|
|
43
47
|
target_count_value: {
|
package/src/parser.js
CHANGED
|
@@ -443,17 +443,63 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
const suspicious_fields = collectSuspiciousFields({ detectedSchoolTags });
|
|
446
|
-
const
|
|
446
|
+
const needs_school_tag_confirmation = confirmation?.school_tag_confirmed !== true;
|
|
447
|
+
const needs_degree_confirmation = confirmation?.degree_confirmed !== true;
|
|
448
|
+
const needs_gender_confirmation = confirmation?.gender_confirmed !== true;
|
|
449
|
+
const needs_recent_not_view_confirmation = confirmation?.recent_not_view_confirmed !== true;
|
|
450
|
+
const needs_filters_confirmation = (
|
|
451
|
+
confirmation?.filters_confirmed !== true
|
|
452
|
+
|| needs_school_tag_confirmation
|
|
453
|
+
|| needs_degree_confirmation
|
|
454
|
+
|| needs_gender_confirmation
|
|
455
|
+
|| needs_recent_not_view_confirmation
|
|
456
|
+
);
|
|
447
457
|
const needs_criteria_confirmation = confirmation?.criteria_confirmed !== true;
|
|
448
458
|
const needs_target_count_confirmation = targetCountResolution.needs_target_count_confirmation;
|
|
449
459
|
const needs_post_action_confirmation = postActionResolution.needs_post_action_confirmation;
|
|
450
460
|
const needs_max_greet_count_confirmation = maxGreetCountResolution.needs_max_greet_count_confirmation;
|
|
451
461
|
const pending_questions = [];
|
|
452
462
|
|
|
453
|
-
if (
|
|
463
|
+
if (needs_school_tag_confirmation) {
|
|
464
|
+
pending_questions.push({
|
|
465
|
+
field: "school_tag",
|
|
466
|
+
question: "请确认学校标签筛选。",
|
|
467
|
+
value: searchParams.school_tag,
|
|
468
|
+
options: SCHOOL_TAG_OPTIONS
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (needs_degree_confirmation) {
|
|
473
|
+
pending_questions.push({
|
|
474
|
+
field: "degree",
|
|
475
|
+
question: "请确认学历筛选(可多选)。",
|
|
476
|
+
value: searchParams.degree,
|
|
477
|
+
options: DEGREE_OPTIONS
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (needs_gender_confirmation) {
|
|
482
|
+
pending_questions.push({
|
|
483
|
+
field: "gender",
|
|
484
|
+
question: "请确认性别筛选。",
|
|
485
|
+
value: searchParams.gender,
|
|
486
|
+
options: GENDER_OPTIONS
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (needs_recent_not_view_confirmation) {
|
|
491
|
+
pending_questions.push({
|
|
492
|
+
field: "recent_not_view",
|
|
493
|
+
question: "请确认是否过滤近14天内已看过的人选。",
|
|
494
|
+
value: searchParams.recent_not_view,
|
|
495
|
+
options: RECENT_NOT_VIEW_OPTIONS
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (needs_filters_confirmation && pending_questions.every((item) => item.field !== "filters")) {
|
|
454
500
|
pending_questions.push({
|
|
455
501
|
field: "filters",
|
|
456
|
-
question: "
|
|
502
|
+
question: "请确认以上推荐页筛选项整体无误。",
|
|
457
503
|
value: searchParams
|
|
458
504
|
});
|
|
459
505
|
}
|
|
@@ -500,6 +546,10 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
500
546
|
missing_fields,
|
|
501
547
|
suspicious_fields,
|
|
502
548
|
needs_filters_confirmation,
|
|
549
|
+
needs_school_tag_confirmation,
|
|
550
|
+
needs_degree_confirmation,
|
|
551
|
+
needs_gender_confirmation,
|
|
552
|
+
needs_recent_not_view_confirmation,
|
|
503
553
|
needs_criteria_confirmation,
|
|
504
554
|
needs_target_count_confirmation,
|
|
505
555
|
needs_post_action_confirmation,
|
package/src/pipeline.js
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
function buildRequiredConfirmations(parsedResult) {
|
|
10
10
|
const confirmations = [];
|
|
11
11
|
if (parsedResult.needs_filters_confirmation) confirmations.push("filters");
|
|
12
|
+
if (parsedResult.needs_school_tag_confirmation) confirmations.push("school_tag");
|
|
13
|
+
if (parsedResult.needs_degree_confirmation) confirmations.push("degree");
|
|
14
|
+
if (parsedResult.needs_gender_confirmation) confirmations.push("gender");
|
|
15
|
+
if (parsedResult.needs_recent_not_view_confirmation) confirmations.push("recent_not_view");
|
|
12
16
|
if (parsedResult.needs_criteria_confirmation) confirmations.push("criteria");
|
|
13
17
|
if (parsedResult.needs_target_count_confirmation) confirmations.push("target_count");
|
|
14
18
|
if (parsedResult.needs_post_action_confirmation) confirmations.push("post_action");
|
|
@@ -89,6 +93,10 @@ export async function runRecommendPipeline(
|
|
|
89
93
|
|
|
90
94
|
if (
|
|
91
95
|
parsed.needs_filters_confirmation
|
|
96
|
+
|| parsed.needs_school_tag_confirmation
|
|
97
|
+
|| parsed.needs_degree_confirmation
|
|
98
|
+
|| parsed.needs_gender_confirmation
|
|
99
|
+
|| parsed.needs_recent_not_view_confirmation
|
|
92
100
|
|| parsed.needs_criteria_confirmation
|
|
93
101
|
|| parsed.needs_target_count_confirmation
|
|
94
102
|
|| parsed.needs_post_action_confirmation
|
package/src/test-parser.js
CHANGED
|
@@ -15,6 +15,10 @@ function testNeedConfirmationIncludesPostAction() {
|
|
|
15
15
|
assert.equal(result.screenParams.criteria, "有大模型平台经验");
|
|
16
16
|
assert.equal(result.proposed_post_action, "favorite");
|
|
17
17
|
assert.equal(result.needs_filters_confirmation, true);
|
|
18
|
+
assert.equal(result.needs_school_tag_confirmation, true);
|
|
19
|
+
assert.equal(result.needs_degree_confirmation, true);
|
|
20
|
+
assert.equal(result.needs_gender_confirmation, true);
|
|
21
|
+
assert.equal(result.needs_recent_not_view_confirmation, true);
|
|
18
22
|
assert.equal(result.needs_criteria_confirmation, true);
|
|
19
23
|
assert.equal(result.needs_post_action_confirmation, true);
|
|
20
24
|
}
|
|
@@ -24,6 +28,10 @@ function testConfirmedPostActionAndOverrides() {
|
|
|
24
28
|
instruction: "推荐页筛选女生,有多模态经历",
|
|
25
29
|
confirmation: {
|
|
26
30
|
filters_confirmed: true,
|
|
31
|
+
school_tag_confirmed: true,
|
|
32
|
+
degree_confirmed: true,
|
|
33
|
+
gender_confirmed: true,
|
|
34
|
+
recent_not_view_confirmed: true,
|
|
27
35
|
criteria_confirmed: true,
|
|
28
36
|
target_count_confirmed: true,
|
|
29
37
|
target_count_value: 12,
|
|
@@ -49,6 +57,10 @@ function testConfirmedPostActionAndOverrides() {
|
|
|
49
57
|
assert.equal(result.screenParams.post_action, "greet");
|
|
50
58
|
assert.equal(result.screenParams.max_greet_count, 8);
|
|
51
59
|
assert.equal(result.needs_filters_confirmation, false);
|
|
60
|
+
assert.equal(result.needs_school_tag_confirmation, false);
|
|
61
|
+
assert.equal(result.needs_degree_confirmation, false);
|
|
62
|
+
assert.equal(result.needs_gender_confirmation, false);
|
|
63
|
+
assert.equal(result.needs_recent_not_view_confirmation, false);
|
|
52
64
|
assert.equal(result.needs_criteria_confirmation, false);
|
|
53
65
|
assert.equal(result.needs_target_count_confirmation, false);
|
|
54
66
|
assert.equal(result.needs_post_action_confirmation, false);
|
|
@@ -60,6 +72,10 @@ function testMultipleSchoolTagsMarkedSuspicious() {
|
|
|
60
72
|
instruction: "推荐页筛选985和211,有推荐系统经验",
|
|
61
73
|
confirmation: {
|
|
62
74
|
filters_confirmed: true,
|
|
75
|
+
school_tag_confirmed: true,
|
|
76
|
+
degree_confirmed: true,
|
|
77
|
+
gender_confirmed: true,
|
|
78
|
+
recent_not_view_confirmed: true,
|
|
63
79
|
criteria_confirmed: true,
|
|
64
80
|
post_action_confirmed: true,
|
|
65
81
|
post_action_value: "favorite"
|
|
@@ -120,6 +136,10 @@ function testCriteriaCanBeProvidedViaOverrides() {
|
|
|
120
136
|
instruction: "推荐页筛选211女生",
|
|
121
137
|
confirmation: {
|
|
122
138
|
filters_confirmed: true,
|
|
139
|
+
school_tag_confirmed: true,
|
|
140
|
+
degree_confirmed: true,
|
|
141
|
+
gender_confirmed: true,
|
|
142
|
+
recent_not_view_confirmed: true,
|
|
123
143
|
criteria_confirmed: true,
|
|
124
144
|
target_count_confirmed: true,
|
|
125
145
|
post_action_confirmed: true,
|
|
@@ -139,6 +159,10 @@ function testMissingCriteriaTriggersNeedInput() {
|
|
|
139
159
|
instruction: "推荐页筛选985男生",
|
|
140
160
|
confirmation: {
|
|
141
161
|
filters_confirmed: true,
|
|
162
|
+
school_tag_confirmed: true,
|
|
163
|
+
degree_confirmed: true,
|
|
164
|
+
gender_confirmed: true,
|
|
165
|
+
recent_not_view_confirmed: true,
|
|
142
166
|
criteria_confirmed: true,
|
|
143
167
|
target_count_confirmed: true,
|
|
144
168
|
post_action_confirmed: true,
|
|
@@ -155,6 +179,10 @@ function testGreetNeedsMaxGreetCountConfirmation() {
|
|
|
155
179
|
instruction: "推荐页筛选985男生,有大模型工程经验,符合标准直接沟通",
|
|
156
180
|
confirmation: {
|
|
157
181
|
filters_confirmed: true,
|
|
182
|
+
school_tag_confirmed: true,
|
|
183
|
+
degree_confirmed: true,
|
|
184
|
+
gender_confirmed: true,
|
|
185
|
+
recent_not_view_confirmed: true,
|
|
158
186
|
criteria_confirmed: true,
|
|
159
187
|
target_count_confirmed: true,
|
|
160
188
|
post_action_confirmed: true,
|
|
@@ -174,6 +202,10 @@ function testGreetMaxGreetCountCanComeFromOverrides() {
|
|
|
174
202
|
instruction: "推荐页筛选985男生,有大模型工程经验,符合标准直接沟通",
|
|
175
203
|
confirmation: {
|
|
176
204
|
filters_confirmed: true,
|
|
205
|
+
school_tag_confirmed: true,
|
|
206
|
+
degree_confirmed: true,
|
|
207
|
+
gender_confirmed: true,
|
|
208
|
+
recent_not_view_confirmed: true,
|
|
177
209
|
criteria_confirmed: true,
|
|
178
210
|
target_count_confirmed: true,
|
|
179
211
|
post_action_confirmed: true,
|
|
@@ -194,6 +226,10 @@ function testTargetCountNeedsConfirmationEvenWhenOptional() {
|
|
|
194
226
|
instruction: "推荐页筛选985男生,有大模型平台经验,符合标准收藏",
|
|
195
227
|
confirmation: {
|
|
196
228
|
filters_confirmed: true,
|
|
229
|
+
school_tag_confirmed: true,
|
|
230
|
+
degree_confirmed: true,
|
|
231
|
+
gender_confirmed: true,
|
|
232
|
+
recent_not_view_confirmed: true,
|
|
197
233
|
criteria_confirmed: true,
|
|
198
234
|
post_action_confirmed: true,
|
|
199
235
|
post_action_value: "favorite"
|
|
@@ -210,6 +246,10 @@ function testTargetCountCanBeSkippedAfterConfirmation() {
|
|
|
210
246
|
instruction: "推荐页筛选985男生,有大模型平台经验,符合标准收藏",
|
|
211
247
|
confirmation: {
|
|
212
248
|
filters_confirmed: true,
|
|
249
|
+
school_tag_confirmed: true,
|
|
250
|
+
degree_confirmed: true,
|
|
251
|
+
gender_confirmed: true,
|
|
252
|
+
recent_not_view_confirmed: true,
|
|
213
253
|
criteria_confirmed: true,
|
|
214
254
|
target_count_confirmed: true,
|
|
215
255
|
post_action_confirmed: true,
|
package/src/test-pipeline.js
CHANGED
|
@@ -18,6 +18,10 @@ function createParsed(overrides = {}) {
|
|
|
18
18
|
missing_fields: [],
|
|
19
19
|
suspicious_fields: [],
|
|
20
20
|
needs_filters_confirmation: false,
|
|
21
|
+
needs_school_tag_confirmation: false,
|
|
22
|
+
needs_degree_confirmation: false,
|
|
23
|
+
needs_gender_confirmation: false,
|
|
24
|
+
needs_recent_not_view_confirmation: false,
|
|
21
25
|
needs_criteria_confirmation: false,
|
|
22
26
|
needs_target_count_confirmation: false,
|
|
23
27
|
needs_post_action_confirmation: false,
|
|
@@ -84,6 +88,35 @@ async function testNeedTargetCountConfirmationGate() {
|
|
|
84
88
|
assert.equal(preflightCalled, false);
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
async function testNeedSchoolTagConfirmationGate() {
|
|
92
|
+
let preflightCalled = false;
|
|
93
|
+
const result = await runRecommendPipeline(
|
|
94
|
+
{
|
|
95
|
+
workspaceRoot: process.cwd(),
|
|
96
|
+
instruction: "test",
|
|
97
|
+
confirmation: {},
|
|
98
|
+
overrides: {}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
parseRecommendInstruction: () => createParsed({
|
|
102
|
+
needs_school_tag_confirmation: true,
|
|
103
|
+
pending_questions: [{ field: "school_tag" }]
|
|
104
|
+
}),
|
|
105
|
+
runPipelinePreflight: () => {
|
|
106
|
+
preflightCalled = true;
|
|
107
|
+
return { ok: true, checks: [], debug_port: 9222 };
|
|
108
|
+
},
|
|
109
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
110
|
+
runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
|
|
111
|
+
runRecommendScreenCli: async () => ({ ok: true, summary: {} })
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
assert.equal(result.status, "NEED_CONFIRMATION");
|
|
116
|
+
assert.equal(result.required_confirmations.includes("school_tag"), true);
|
|
117
|
+
assert.equal(preflightCalled, false);
|
|
118
|
+
}
|
|
119
|
+
|
|
87
120
|
async function testNeedMaxGreetCountConfirmationGate() {
|
|
88
121
|
const result = await runRecommendPipeline(
|
|
89
122
|
{
|
|
@@ -218,6 +251,7 @@ async function testSearchFailure() {
|
|
|
218
251
|
|
|
219
252
|
async function main() {
|
|
220
253
|
await testNeedConfirmationGate();
|
|
254
|
+
await testNeedSchoolTagConfirmationGate();
|
|
221
255
|
await testNeedTargetCountConfirmationGate();
|
|
222
256
|
await testNeedMaxGreetCountConfirmationGate();
|
|
223
257
|
await testNeedInputGate();
|