@reconcrap/boss-recommend-mcp 1.1.1 → 1.1.3
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/package.json +1 -1
- package/src/adapters.js +108 -22
- package/src/index.js +2 -2
- package/src/parser.js +23 -29
- package/src/pipeline.js +8 -6
- package/src/test-adapters-runtime.js +54 -1
- package/src/test-parser.js +67 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +11 -4
package/package.json
CHANGED
package/src/adapters.js
CHANGED
|
@@ -16,6 +16,7 @@ const screenConfigTemplateDefaults = {
|
|
|
16
16
|
apiKey: "replace-with-openai-api-key",
|
|
17
17
|
model: "gpt-4.1-mini"
|
|
18
18
|
};
|
|
19
|
+
const DEFAULT_RECOMMEND_SCREEN_TIMEOUT_MS = 24 * 60 * 60 * 1000;
|
|
19
20
|
|
|
20
21
|
function getCodexHome() {
|
|
21
22
|
return process.env.CODEX_HOME
|
|
@@ -830,7 +831,39 @@ function parseJsonOutput(text) {
|
|
|
830
831
|
return null;
|
|
831
832
|
}
|
|
832
833
|
|
|
833
|
-
function
|
|
834
|
+
function createScreenProgressTracker(currentTracker = {}) {
|
|
835
|
+
const outcome = String(currentTracker.outcome || "").trim();
|
|
836
|
+
return {
|
|
837
|
+
candidate_index: Number.isInteger(currentTracker.candidate_index) ? currentTracker.candidate_index : null,
|
|
838
|
+
outcome: outcome === "pass" || outcome === "skip" ? outcome : null,
|
|
839
|
+
action_failed: currentTracker.action_failed === true
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function finalizeCandidateProgress(progress, tracker) {
|
|
844
|
+
if (!Number.isInteger(tracker.candidate_index)) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
let changed = false;
|
|
849
|
+
if (tracker.action_failed === true) {
|
|
850
|
+
progress.skipped += 1;
|
|
851
|
+
changed = true;
|
|
852
|
+
} else if (tracker.outcome === "pass") {
|
|
853
|
+
progress.passed += 1;
|
|
854
|
+
changed = true;
|
|
855
|
+
} else if (tracker.outcome === "skip") {
|
|
856
|
+
progress.skipped += 1;
|
|
857
|
+
changed = true;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
tracker.candidate_index = null;
|
|
861
|
+
tracker.outcome = null;
|
|
862
|
+
tracker.action_failed = false;
|
|
863
|
+
return changed;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function parseScreenProgressLine(line, currentProgress = {}, currentTracker = {}) {
|
|
834
867
|
const normalizedLine = String(line || "").replace(/\s+/g, " ").trim();
|
|
835
868
|
if (!normalizedLine) return null;
|
|
836
869
|
|
|
@@ -840,23 +873,56 @@ function parseScreenProgressLine(line, currentProgress = {}) {
|
|
|
840
873
|
skipped: Number.isInteger(currentProgress.skipped) ? currentProgress.skipped : 0,
|
|
841
874
|
greet_count: Number.isInteger(currentProgress.greet_count) ? currentProgress.greet_count : 0
|
|
842
875
|
};
|
|
876
|
+
const nextTracker = createScreenProgressTracker(currentTracker);
|
|
843
877
|
|
|
844
878
|
let changed = false;
|
|
845
879
|
const processedMatch = normalizedLine.match(/处理第\s*(\d+)\s*位候选人/u);
|
|
846
880
|
if (processedMatch) {
|
|
881
|
+
if (finalizeCandidateProgress(nextProgress, nextTracker)) {
|
|
882
|
+
changed = true;
|
|
883
|
+
}
|
|
847
884
|
const processed = Number.parseInt(processedMatch[1], 10);
|
|
848
885
|
if (Number.isInteger(processed) && processed >= 0 && processed !== nextProgress.processed) {
|
|
849
886
|
nextProgress.processed = processed;
|
|
850
887
|
changed = true;
|
|
851
888
|
}
|
|
889
|
+
nextTracker.candidate_index = processed;
|
|
890
|
+
nextTracker.outcome = null;
|
|
891
|
+
nextTracker.action_failed = false;
|
|
852
892
|
}
|
|
853
893
|
|
|
854
894
|
if (/筛选结果:\s*通过/u.test(normalizedLine)) {
|
|
855
|
-
|
|
856
|
-
|
|
895
|
+
if (nextTracker.outcome !== "pass" || nextTracker.action_failed) {
|
|
896
|
+
changed = true;
|
|
897
|
+
}
|
|
898
|
+
nextTracker.outcome = "pass";
|
|
899
|
+
nextTracker.action_failed = false;
|
|
857
900
|
} else if (/筛选结果:\s*不通过/u.test(normalizedLine)) {
|
|
858
|
-
|
|
859
|
-
|
|
901
|
+
if (nextTracker.outcome !== "skip" || nextTracker.action_failed) {
|
|
902
|
+
changed = true;
|
|
903
|
+
}
|
|
904
|
+
nextTracker.outcome = "skip";
|
|
905
|
+
nextTracker.action_failed = false;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (/候选人处理失败\s*:/u.test(normalizedLine)) {
|
|
909
|
+
if (!nextTracker.action_failed) {
|
|
910
|
+
changed = true;
|
|
911
|
+
}
|
|
912
|
+
nextTracker.action_failed = true;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (/^\[关闭详情\].*成功/u.test(normalizedLine)) {
|
|
916
|
+
if (finalizeCandidateProgress(nextProgress, nextTracker)) {
|
|
917
|
+
changed = true;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const finalStateLine = /Process timed out after|status"\s*:\s*"(?:COMPLETED|PAUSED|FAILED)"/iu.test(normalizedLine);
|
|
922
|
+
if (finalStateLine) {
|
|
923
|
+
if (finalizeCandidateProgress(nextProgress, nextTracker)) {
|
|
924
|
+
changed = true;
|
|
925
|
+
}
|
|
860
926
|
}
|
|
861
927
|
|
|
862
928
|
const greetMatch = normalizedLine.match(/greet[_\s-]*count\s*[:=]\s*(\d+)/iu);
|
|
@@ -871,7 +937,34 @@ function parseScreenProgressLine(line, currentProgress = {}) {
|
|
|
871
937
|
if (!changed) return null;
|
|
872
938
|
return {
|
|
873
939
|
line: normalizedLine,
|
|
874
|
-
progress: nextProgress
|
|
940
|
+
progress: nextProgress,
|
|
941
|
+
tracker: nextTracker
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function resolveRecommendScreenTimeoutMs(runtime = null) {
|
|
946
|
+
const runtimeTimeoutMs = parsePositiveInteger(runtime?.timeoutMs);
|
|
947
|
+
const envTimeoutMs = parsePositiveInteger(process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS);
|
|
948
|
+
return runtimeTimeoutMs || envTimeoutMs || DEFAULT_RECOMMEND_SCREEN_TIMEOUT_MS;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function buildRecommendScreenProcessError(result, screenTimeoutMs) {
|
|
952
|
+
if (result.code === 0) return null;
|
|
953
|
+
if (result.error_code === "TIMEOUT") {
|
|
954
|
+
return {
|
|
955
|
+
code: "TIMEOUT",
|
|
956
|
+
message: `推荐页筛选命令执行超时(${screenTimeoutMs}ms)。`
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
if (result.error_code === "ABORTED") {
|
|
960
|
+
return {
|
|
961
|
+
code: "PROCESS_ABORTED",
|
|
962
|
+
message: "推荐页筛选命令已取消。"
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
return {
|
|
966
|
+
code: "RECOMMEND_SCREEN_FAILED",
|
|
967
|
+
message: "推荐页筛选命令执行失败。"
|
|
875
968
|
};
|
|
876
969
|
}
|
|
877
970
|
|
|
@@ -1683,21 +1776,24 @@ export async function runRecommendScreenCli({ workspaceRoot, screenParams, resum
|
|
|
1683
1776
|
skipped: 0,
|
|
1684
1777
|
greet_count: 0
|
|
1685
1778
|
};
|
|
1779
|
+
let inferredTracker = createScreenProgressTracker();
|
|
1780
|
+
const screenTimeoutMs = resolveRecommendScreenTimeoutMs(runtime);
|
|
1686
1781
|
|
|
1687
1782
|
const result = await runProcess({
|
|
1688
1783
|
command: "node",
|
|
1689
1784
|
args,
|
|
1690
1785
|
cwd: screenDir,
|
|
1691
|
-
timeoutMs:
|
|
1786
|
+
timeoutMs: screenTimeoutMs,
|
|
1692
1787
|
heartbeatIntervalMs: runtime?.heartbeatIntervalMs,
|
|
1693
1788
|
signal: runtime?.signal,
|
|
1694
1789
|
onOutput: (event) => {
|
|
1695
1790
|
safeInvokeCallback(runtime?.onOutput, event);
|
|
1696
1791
|
},
|
|
1697
1792
|
onLine: (event) => {
|
|
1698
|
-
const parsed = parseScreenProgressLine(event?.line, inferredProgress);
|
|
1793
|
+
const parsed = parseScreenProgressLine(event?.line, inferredProgress, inferredTracker);
|
|
1699
1794
|
if (!parsed) return;
|
|
1700
1795
|
inferredProgress = parsed.progress;
|
|
1796
|
+
inferredTracker = parsed.tracker;
|
|
1701
1797
|
safeInvokeCallback(runtime?.onProgress, {
|
|
1702
1798
|
...inferredProgress,
|
|
1703
1799
|
line: parsed.line
|
|
@@ -1731,24 +1827,14 @@ export async function runRecommendScreenCli({ workspaceRoot, screenParams, resum
|
|
|
1731
1827
|
stderr: result.stderr,
|
|
1732
1828
|
structured,
|
|
1733
1829
|
summary,
|
|
1734
|
-
error: structured?.error || missingOutputError || (
|
|
1735
|
-
result.code === 0
|
|
1736
|
-
? null
|
|
1737
|
-
: result.error_code === "ABORTED"
|
|
1738
|
-
? {
|
|
1739
|
-
code: "PROCESS_ABORTED",
|
|
1740
|
-
message: "推荐页筛选命令已取消。"
|
|
1741
|
-
}
|
|
1742
|
-
: {
|
|
1743
|
-
code: "RECOMMEND_SCREEN_FAILED",
|
|
1744
|
-
message: "推荐页筛选命令执行失败。"
|
|
1745
|
-
}
|
|
1746
|
-
)
|
|
1830
|
+
error: structured?.error || missingOutputError || buildRecommendScreenProcessError(result, screenTimeoutMs)
|
|
1747
1831
|
};
|
|
1748
1832
|
}
|
|
1749
1833
|
|
|
1750
1834
|
export const __testables = {
|
|
1751
1835
|
runProcess,
|
|
1752
1836
|
parseJsonOutput,
|
|
1753
|
-
parseScreenProgressLine
|
|
1837
|
+
parseScreenProgressLine,
|
|
1838
|
+
resolveRecommendScreenTimeoutMs,
|
|
1839
|
+
buildRecommendScreenProcessError
|
|
1754
1840
|
};
|
package/src/index.js
CHANGED
|
@@ -203,7 +203,7 @@ function createRunInputSchema() {
|
|
|
203
203
|
post_action_confirmed: { type: "boolean" },
|
|
204
204
|
post_action_value: {
|
|
205
205
|
type: "string",
|
|
206
|
-
enum: ["favorite", "greet"]
|
|
206
|
+
enum: ["favorite", "greet", "none"]
|
|
207
207
|
},
|
|
208
208
|
final_confirmed: { type: "boolean" },
|
|
209
209
|
job_confirmed: { type: "boolean" },
|
|
@@ -267,7 +267,7 @@ function createRunInputSchema() {
|
|
|
267
267
|
max_greet_count: { type: "integer", minimum: 1 },
|
|
268
268
|
post_action: {
|
|
269
269
|
type: "string",
|
|
270
|
-
enum: ["favorite", "greet"]
|
|
270
|
+
enum: ["favorite", "greet", "none"]
|
|
271
271
|
}
|
|
272
272
|
},
|
|
273
273
|
additionalProperties: false
|
package/src/parser.js
CHANGED
|
@@ -28,10 +28,11 @@ const DEGREE_ORDER = [
|
|
|
28
28
|
];
|
|
29
29
|
const GENDER_OPTIONS = ["不限", "男", "女"];
|
|
30
30
|
const RECENT_NOT_VIEW_OPTIONS = ["不限", "近14天没有"];
|
|
31
|
-
const POST_ACTION_OPTIONS = ["favorite", "greet"];
|
|
31
|
+
const POST_ACTION_OPTIONS = ["favorite", "greet", "none"];
|
|
32
32
|
const POST_ACTION_LABELS = {
|
|
33
33
|
favorite: "收藏",
|
|
34
|
-
greet: "直接沟通"
|
|
34
|
+
greet: "直接沟通",
|
|
35
|
+
none: "什么也不做"
|
|
35
36
|
};
|
|
36
37
|
const LEADING_NOISE_PATTERNS = [
|
|
37
38
|
/^使用boss-recommend-pipeline skills/i,
|
|
@@ -90,7 +91,7 @@ const FILTER_CLAUSE_PATTERNS = [
|
|
|
90
91
|
/近?14天(?:内)?没有|近?14天(?:内)?没看过|近?14天(?:内)?未查看|过滤[^。;;\n]{0,12}14天|排除[^。;;\n]{0,12}14天/i,
|
|
91
92
|
/目标(?:处理|筛选)?(?:人数|数量)?|至少(?:处理|筛选)|(?:处理|筛选)\s*\d+\s*(?:位|人)/i,
|
|
92
93
|
/最多(?:打招呼|沟通|联系)|(?:打招呼|沟通|联系)(?:上限|最多|不超过|至多)/i,
|
|
93
|
-
|
|
94
|
+
/收藏|打招呼|直接沟通|什么也不做|不做任何操作|不操作|仅筛选|只筛选/i
|
|
94
95
|
];
|
|
95
96
|
const META_CLAUSE_PATTERNS = [
|
|
96
97
|
/推荐页|推荐页面|boss推荐/i,
|
|
@@ -192,7 +193,10 @@ function expandDegreeAtOrAbove(value) {
|
|
|
192
193
|
function parseDegreeSelectionsFromText(text) {
|
|
193
194
|
const normalizedText = normalizeText(text);
|
|
194
195
|
if (!normalizedText) return [];
|
|
195
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
/(?:学历|学位|教育)(?:要求)?\s*(?:[::]\s*)?(?:不限|不限制|无要求)|(?:不限|不限制)\s*(?:[::]\s*)?(?:学历|学位|教育)(?:要求)?/i
|
|
198
|
+
.test(normalizedText)
|
|
199
|
+
) {
|
|
196
200
|
return ["不限"];
|
|
197
201
|
}
|
|
198
202
|
|
|
@@ -256,6 +260,9 @@ function normalizePostAction(value) {
|
|
|
256
260
|
if (!normalized) return null;
|
|
257
261
|
if (["favorite", "fav", "收藏"].includes(normalized)) return "favorite";
|
|
258
262
|
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
263
|
+
if (["none", "noop", "no-op", "什么也不做", "不做任何操作", "不操作", "仅筛选", "只筛选"].includes(normalized)) {
|
|
264
|
+
return "none";
|
|
265
|
+
}
|
|
259
266
|
return null;
|
|
260
267
|
}
|
|
261
268
|
|
|
@@ -374,6 +381,8 @@ function resolvePostAction({ instruction, confirmation, overrides }) {
|
|
|
374
381
|
? "favorite"
|
|
375
382
|
: /打招呼|直接沟通|沟通/.test(instruction)
|
|
376
383
|
? "greet"
|
|
384
|
+
: /什么也不做|不做任何操作|不操作|仅筛选|只筛选/.test(instruction)
|
|
385
|
+
? "none"
|
|
377
386
|
: null;
|
|
378
387
|
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
379
388
|
|
|
@@ -531,41 +540,25 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
531
540
|
invalidOverrideSchoolTags: schoolTagAudit.invalid,
|
|
532
541
|
maxGreetCountResolution
|
|
533
542
|
});
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
);
|
|
539
|
-
const hasDegreeSignal = Boolean(
|
|
540
|
-
(Array.isArray(overrideDegrees) && overrideDegrees.length > 0)
|
|
541
|
-
|| (Array.isArray(confirmationDegrees) && confirmationDegrees.length > 0)
|
|
542
|
-
|| (Array.isArray(detectedDegrees) && detectedDegrees.length > 0)
|
|
543
|
-
);
|
|
544
|
-
const hasGenderSignal = Boolean(
|
|
545
|
-
overrideGender
|
|
546
|
-
|| confirmationGender
|
|
547
|
-
|| extractGender(text)
|
|
548
|
-
);
|
|
549
|
-
const hasRecentNotViewSignal = Boolean(
|
|
550
|
-
overrideRecentNotView
|
|
551
|
-
|| confirmationRecentNotView
|
|
552
|
-
|| extractRecentNotView(text)
|
|
553
|
-
);
|
|
543
|
+
const hasConfirmedSchoolTagValue = Array.isArray(confirmationSchoolTag) && confirmationSchoolTag.length > 0;
|
|
544
|
+
const hasConfirmedDegreeValue = Array.isArray(confirmationDegrees) && confirmationDegrees.length > 0;
|
|
545
|
+
const hasConfirmedGenderValue = Boolean(confirmationGender);
|
|
546
|
+
const hasConfirmedRecentNotViewValue = Boolean(confirmationRecentNotView);
|
|
554
547
|
const needs_school_tag_confirmation = (
|
|
555
548
|
confirmation?.school_tag_confirmed !== true
|
|
556
|
-
|| !
|
|
549
|
+
|| !hasConfirmedSchoolTagValue
|
|
557
550
|
);
|
|
558
551
|
const needs_degree_confirmation = (
|
|
559
552
|
confirmation?.degree_confirmed !== true
|
|
560
|
-
|| !
|
|
553
|
+
|| !hasConfirmedDegreeValue
|
|
561
554
|
);
|
|
562
555
|
const needs_gender_confirmation = (
|
|
563
556
|
confirmation?.gender_confirmed !== true
|
|
564
|
-
|| !
|
|
557
|
+
|| !hasConfirmedGenderValue
|
|
565
558
|
);
|
|
566
559
|
const needs_recent_not_view_confirmation = (
|
|
567
560
|
confirmation?.recent_not_view_confirmed !== true
|
|
568
|
-
|| !
|
|
561
|
+
|| !hasConfirmedRecentNotViewValue
|
|
569
562
|
);
|
|
570
563
|
const needs_filters_confirmation = (
|
|
571
564
|
confirmation?.filters_confirmed !== true
|
|
@@ -656,7 +649,8 @@ export function parseRecommendInstruction({ instruction, confirmation, overrides
|
|
|
656
649
|
value: postActionResolution.proposed_post_action,
|
|
657
650
|
options: [
|
|
658
651
|
{ label: POST_ACTION_LABELS.favorite, value: "favorite" },
|
|
659
|
-
{ label: POST_ACTION_LABELS.greet, value: "greet" }
|
|
652
|
+
{ label: POST_ACTION_LABELS.greet, value: "greet" },
|
|
653
|
+
{ label: POST_ACTION_LABELS.none, value: "none" }
|
|
660
654
|
]
|
|
661
655
|
});
|
|
662
656
|
}
|
package/src/pipeline.js
CHANGED
|
@@ -959,7 +959,7 @@ export async function runRecommendPipeline(
|
|
|
959
959
|
});
|
|
960
960
|
|
|
961
961
|
return {
|
|
962
|
-
status: "COMPLETED",
|
|
962
|
+
status: "COMPLETED",
|
|
963
963
|
search_params: parsed.searchParams,
|
|
964
964
|
screen_params: parsed.screenParams,
|
|
965
965
|
result: {
|
|
@@ -976,8 +976,10 @@ export async function runRecommendPipeline(
|
|
|
976
976
|
post_action: parsed.screenParams.post_action,
|
|
977
977
|
max_greet_count: parsed.screenParams.max_greet_count,
|
|
978
978
|
greet_count: screenSummary.greet_count ?? 0,
|
|
979
|
-
greet_limit_fallback_count: screenSummary.greet_limit_fallback_count ?? 0
|
|
980
|
-
},
|
|
981
|
-
message:
|
|
982
|
-
|
|
983
|
-
|
|
979
|
+
greet_limit_fallback_count: screenSummary.greet_limit_fallback_count ?? 0
|
|
980
|
+
},
|
|
981
|
+
message: parsed.screenParams.post_action === "none"
|
|
982
|
+
? "Recommend 流水线已完成。本次 post_action=none:符合条件的人选仅记录到 CSV,不执行收藏或打招呼。"
|
|
983
|
+
: "Recommend 流水线已完成。post_action 在运行开始时已一次性确认;若选择打招呼并设置上限,超出上限后会自动改为收藏。"
|
|
984
|
+
};
|
|
985
|
+
}
|
|
@@ -4,7 +4,13 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { runRecommendScreenCli, __testables as adapterTestables } from "./adapters.js";
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
runProcess,
|
|
9
|
+
parseJsonOutput,
|
|
10
|
+
parseScreenProgressLine,
|
|
11
|
+
resolveRecommendScreenTimeoutMs,
|
|
12
|
+
buildRecommendScreenProcessError
|
|
13
|
+
} = adapterTestables;
|
|
8
14
|
|
|
9
15
|
async function testRunProcessHeartbeatAndOutput() {
|
|
10
16
|
const heartbeats = [];
|
|
@@ -57,6 +63,50 @@ function testParsePausedStructuredOutput() {
|
|
|
57
63
|
assert.equal(parsed?.result?.processed_count, 3);
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
function testParseScreenProgressLineShouldCountFavoriteFailureAsSkipped() {
|
|
67
|
+
let progress = { processed: 0, passed: 0, skipped: 0, greet_count: 0 };
|
|
68
|
+
let tracker = {};
|
|
69
|
+
const feed = (line) => {
|
|
70
|
+
const parsed = parseScreenProgressLine(line, progress, tracker);
|
|
71
|
+
if (!parsed) return;
|
|
72
|
+
progress = parsed.progress;
|
|
73
|
+
tracker = parsed.tracker;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
feed("处理第 1 位候选人: 甲");
|
|
77
|
+
feed("筛选结果: 通过");
|
|
78
|
+
feed("[关闭详情] 成功: no popup or detail signal visible");
|
|
79
|
+
feed("处理第 2 位候选人: 乙");
|
|
80
|
+
feed("筛选结果: 通过");
|
|
81
|
+
feed("候选人处理失败: FAVORITE_BUTTON_FAILED");
|
|
82
|
+
feed("[关闭详情] 成功: no popup or detail signal visible");
|
|
83
|
+
|
|
84
|
+
assert.equal(progress.processed, 2);
|
|
85
|
+
assert.equal(progress.passed, 1);
|
|
86
|
+
assert.equal(progress.skipped, 1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function testResolveScreenTimeoutDefaultsTo24Hours() {
|
|
90
|
+
const previous = process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
|
|
91
|
+
delete process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
|
|
92
|
+
try {
|
|
93
|
+
assert.equal(resolveRecommendScreenTimeoutMs(null), 24 * 60 * 60 * 1000);
|
|
94
|
+
assert.equal(resolveRecommendScreenTimeoutMs({ timeoutMs: 1234 }), 1234);
|
|
95
|
+
} finally {
|
|
96
|
+
if (previous === undefined) {
|
|
97
|
+
delete process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
|
|
98
|
+
} else {
|
|
99
|
+
process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS = previous;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function testBuildRecommendScreenProcessErrorMapsTimeout() {
|
|
105
|
+
const error = buildRecommendScreenProcessError({ code: -1, error_code: "TIMEOUT" }, 86400000);
|
|
106
|
+
assert.equal(error?.code, "TIMEOUT");
|
|
107
|
+
assert.equal(String(error?.message || "").includes("86400000"), true);
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
async function testResumeRequiresCheckpointFile() {
|
|
61
111
|
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
62
112
|
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-resume-"));
|
|
@@ -103,6 +153,9 @@ async function main() {
|
|
|
103
153
|
await testRunProcessHeartbeatAndOutput();
|
|
104
154
|
await testRunProcessAbortSignal();
|
|
105
155
|
testParsePausedStructuredOutput();
|
|
156
|
+
testParseScreenProgressLineShouldCountFavoriteFailureAsSkipped();
|
|
157
|
+
testResolveScreenTimeoutDefaultsTo24Hours();
|
|
158
|
+
testBuildRecommendScreenProcessErrorMapsTimeout();
|
|
106
159
|
await testResumeRequiresCheckpointFile();
|
|
107
160
|
console.log("adapters runtime tests passed");
|
|
108
161
|
}
|
package/src/test-parser.js
CHANGED
|
@@ -29,9 +29,13 @@ function testConfirmedPostActionAndOverrides() {
|
|
|
29
29
|
confirmation: {
|
|
30
30
|
filters_confirmed: true,
|
|
31
31
|
school_tag_confirmed: true,
|
|
32
|
+
school_tag_value: ["211"],
|
|
32
33
|
degree_confirmed: true,
|
|
34
|
+
degree_value: ["本科"],
|
|
33
35
|
gender_confirmed: true,
|
|
36
|
+
gender_value: "女",
|
|
34
37
|
recent_not_view_confirmed: true,
|
|
38
|
+
recent_not_view_value: "近14天没有",
|
|
35
39
|
criteria_confirmed: true,
|
|
36
40
|
target_count_confirmed: true,
|
|
37
41
|
target_count_value: 12,
|
|
@@ -67,6 +71,30 @@ function testConfirmedPostActionAndOverrides() {
|
|
|
67
71
|
assert.equal(result.needs_max_greet_count_confirmation, false);
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
function testMissingRecentNotViewValueShouldRequireReconfirmation() {
|
|
75
|
+
const result = parseRecommendInstruction({
|
|
76
|
+
instruction: "推荐页筛选985男生,近14天没有,有销售经验,符合标准收藏",
|
|
77
|
+
confirmation: {
|
|
78
|
+
filters_confirmed: true,
|
|
79
|
+
school_tag_confirmed: true,
|
|
80
|
+
school_tag_value: ["985"],
|
|
81
|
+
degree_confirmed: true,
|
|
82
|
+
degree_value: ["本科"],
|
|
83
|
+
gender_confirmed: true,
|
|
84
|
+
gender_value: "男",
|
|
85
|
+
recent_not_view_confirmed: true,
|
|
86
|
+
criteria_confirmed: true,
|
|
87
|
+
target_count_confirmed: true,
|
|
88
|
+
post_action_confirmed: true,
|
|
89
|
+
post_action_value: "favorite"
|
|
90
|
+
},
|
|
91
|
+
overrides: null
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
assert.equal(result.needs_recent_not_view_confirmation, true);
|
|
95
|
+
assert.equal(result.pending_questions.some((q) => q.field === "recent_not_view"), true);
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
function testFilterConfirmedWithoutExplicitValuesShouldRequireReconfirmation() {
|
|
71
99
|
const result = parseRecommendInstruction({
|
|
72
100
|
instruction: "通过boss推荐skill帮我找人",
|
|
@@ -172,6 +200,17 @@ function testDegreeAtOrAboveExpansion() {
|
|
|
172
200
|
assert.deepEqual(result.searchParams.degree, ["大专", "本科", "硕士", "博士"]);
|
|
173
201
|
}
|
|
174
202
|
|
|
203
|
+
function testDegreeShouldNotBeOverwrittenBySchoolTagUnlimitedClause() {
|
|
204
|
+
const result = parseRecommendInstruction({
|
|
205
|
+
instruction: "学校标签不限,学历要求大专及以上,性别不限,过滤近14天已看",
|
|
206
|
+
confirmation: null,
|
|
207
|
+
overrides: null
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
assert.deepEqual(result.searchParams.school_tag, ["不限"]);
|
|
211
|
+
assert.deepEqual(result.searchParams.degree, ["大专", "本科", "硕士", "博士"]);
|
|
212
|
+
}
|
|
213
|
+
|
|
175
214
|
function testDegreeExplicitListOnly() {
|
|
176
215
|
const result = parseRecommendInstruction({
|
|
177
216
|
instruction: "推荐页筛选大专、本科,近14天没有,有Agent经验",
|
|
@@ -390,6 +429,31 @@ function testTargetCountCanBeSkippedAfterConfirmation() {
|
|
|
390
429
|
assert.equal(result.screenParams.target_count, null);
|
|
391
430
|
}
|
|
392
431
|
|
|
432
|
+
function testPostActionNoneCanBeConfirmed() {
|
|
433
|
+
const result = parseRecommendInstruction({
|
|
434
|
+
instruction: "推荐页筛选211女生,近14天没有,有AI经验,符合标准什么也不做",
|
|
435
|
+
confirmation: {
|
|
436
|
+
filters_confirmed: true,
|
|
437
|
+
school_tag_confirmed: true,
|
|
438
|
+
school_tag_value: ["211"],
|
|
439
|
+
degree_confirmed: true,
|
|
440
|
+
degree_value: ["本科"],
|
|
441
|
+
gender_confirmed: true,
|
|
442
|
+
gender_value: "女",
|
|
443
|
+
recent_not_view_confirmed: true,
|
|
444
|
+
recent_not_view_value: "近14天没有",
|
|
445
|
+
criteria_confirmed: true,
|
|
446
|
+
target_count_confirmed: true,
|
|
447
|
+
post_action_confirmed: true,
|
|
448
|
+
post_action_value: "none"
|
|
449
|
+
},
|
|
450
|
+
overrides: null
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
assert.equal(result.screenParams.post_action, "none");
|
|
454
|
+
assert.equal(result.needs_post_action_confirmation, false);
|
|
455
|
+
}
|
|
456
|
+
|
|
393
457
|
function testJobSelectionHintCanComeFromOverrides() {
|
|
394
458
|
const result = parseRecommendInstruction({
|
|
395
459
|
instruction: "推荐页筛选211女生,有算法经验,符合标准收藏",
|
|
@@ -415,11 +479,13 @@ function testMcpMentionShouldStayInCriteria() {
|
|
|
415
479
|
function main() {
|
|
416
480
|
testNeedConfirmationIncludesPostAction();
|
|
417
481
|
testConfirmedPostActionAndOverrides();
|
|
482
|
+
testMissingRecentNotViewValueShouldRequireReconfirmation();
|
|
418
483
|
testFilterConfirmedWithoutExplicitValuesShouldRequireReconfirmation();
|
|
419
484
|
testFilterConfirmedWithExplicitConfirmationValuesShouldNotFallbackToUnlimited();
|
|
420
485
|
testMultipleSchoolTagsMarkedSuspicious();
|
|
421
486
|
testDegreeCanBeExtracted();
|
|
422
487
|
testDegreeAtOrAboveExpansion();
|
|
488
|
+
testDegreeShouldNotBeOverwrittenBySchoolTagUnlimitedClause();
|
|
423
489
|
testDegreeExplicitListOnly();
|
|
424
490
|
testDegreeOverrideCanBeArray();
|
|
425
491
|
testSchoolTagOverrideCanBeArray();
|
|
@@ -433,6 +499,7 @@ function main() {
|
|
|
433
499
|
testGreetAutoFilledMaxGreetCountShouldRequireReconfirmation();
|
|
434
500
|
testTargetCountNeedsConfirmationEvenWhenOptional();
|
|
435
501
|
testTargetCountCanBeSkippedAfterConfirmation();
|
|
502
|
+
testPostActionNoneCanBeConfirmed();
|
|
436
503
|
testJobSelectionHintCanComeFromOverrides();
|
|
437
504
|
console.log("parser tests passed");
|
|
438
505
|
}
|
|
@@ -31,6 +31,9 @@ function normalizePostAction(value) {
|
|
|
31
31
|
if (!normalized) return null;
|
|
32
32
|
if (["favorite", "fav", "收藏"].includes(normalized)) return "favorite";
|
|
33
33
|
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
34
|
+
if (["none", "noop", "no-op", "什么也不做", "不做任何操作", "不操作", "仅筛选", "只筛选"].includes(normalized)) {
|
|
35
|
+
return "none";
|
|
36
|
+
}
|
|
34
37
|
return null;
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -210,10 +213,11 @@ async function promptMissingInputs(args) {
|
|
|
210
213
|
if (!(args.postActionConfirmed === true && args.postAction)) {
|
|
211
214
|
args.postAction = await askWithValidation(
|
|
212
215
|
ask,
|
|
213
|
-
"本次通过人选统一执行什么动作?请输入 1(收藏)
|
|
216
|
+
"本次通过人选统一执行什么动作?请输入 1(收藏) / 2(直接沟通) / 3(什么也不做): ",
|
|
214
217
|
(value) => {
|
|
215
218
|
if (value === "1") return "favorite";
|
|
216
219
|
if (value === "2") return "greet";
|
|
220
|
+
if (value === "3") return "none";
|
|
217
221
|
return null;
|
|
218
222
|
}
|
|
219
223
|
);
|
|
@@ -289,9 +293,10 @@ async function promptPostAction() {
|
|
|
289
293
|
});
|
|
290
294
|
const ask = (question) => new Promise((resolve) => rl.question(question, resolve));
|
|
291
295
|
try {
|
|
292
|
-
const answer = normalizeText(await ask("本次通过人选统一执行什么动作?请输入 1(收藏)
|
|
296
|
+
const answer = normalizeText(await ask("本次通过人选统一执行什么动作?请输入 1(收藏) / 2(直接沟通) / 3(什么也不做): "));
|
|
293
297
|
if (answer === "1") return "favorite";
|
|
294
298
|
if (answer === "2") return "greet";
|
|
299
|
+
if (answer === "3") return "none";
|
|
295
300
|
throw new Error("INVALID_POST_ACTION_CONFIRMATION");
|
|
296
301
|
} finally {
|
|
297
302
|
rl.close();
|
|
@@ -1955,7 +1960,9 @@ class RecommendScreenCli {
|
|
|
1955
1960
|
}
|
|
1956
1961
|
const actionResult = effectiveAction === "favorite"
|
|
1957
1962
|
? await this.favoriteCandidate()
|
|
1958
|
-
:
|
|
1963
|
+
: effectiveAction === "greet"
|
|
1964
|
+
? await this.greetCandidate()
|
|
1965
|
+
: { actionTaken: "none" };
|
|
1959
1966
|
if (actionResult.actionTaken === "greet") {
|
|
1960
1967
|
this.greetCount += 1;
|
|
1961
1968
|
}
|
|
@@ -2050,7 +2057,7 @@ async function main() {
|
|
|
2050
2057
|
console.log(JSON.stringify({
|
|
2051
2058
|
status: "COMPLETED",
|
|
2052
2059
|
result: {
|
|
2053
|
-
usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action greet --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --port 9222 --output <csv-path> --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
|
|
2060
|
+
usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --port 9222 --output <csv-path> --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
|
|
2054
2061
|
}
|
|
2055
2062
|
}));
|
|
2056
2063
|
return;
|