@reconcrap/boss-recommend-mcp 2.1.17 → 2.1.18
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/core/browser/index.js +23 -2
- package/src/domains/recruit/detail.js +114 -2
- package/src/domains/recruit/instruction-parser.js +115 -17
- package/src/domains/recruit/run-service.js +6 -2
- package/src/domains/recruit/search.js +184 -21
- package/src/index.js +18 -2
- package/src/recruit-mcp.js +18 -2
package/package.json
CHANGED
|
@@ -1971,7 +1971,18 @@ export async function getOuterHTML(client, nodeId) {
|
|
|
1971
1971
|
}
|
|
1972
1972
|
|
|
1973
1973
|
export async function getNodeBox(client, nodeId) {
|
|
1974
|
-
|
|
1974
|
+
let result;
|
|
1975
|
+
try {
|
|
1976
|
+
result = await client.DOM.getBoxModel({ nodeId });
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
const wrapped = new Error(error?.message || String(error));
|
|
1979
|
+
wrapped.name = error?.name || "Error";
|
|
1980
|
+
wrapped.node_id = nodeId;
|
|
1981
|
+
wrapped.cdp_method = "DOM.getBoxModel";
|
|
1982
|
+
wrapped.original_stack = error?.stack || "";
|
|
1983
|
+
wrapped.stack = `${new Error(`getNodeBox failed for nodeId=${nodeId}`).stack || wrapped.stack}\nCaused by: ${error?.stack || error}`;
|
|
1984
|
+
throw wrapped;
|
|
1985
|
+
}
|
|
1975
1986
|
const model = result.model;
|
|
1976
1987
|
const quad = model.border?.length ? model.border : model.content;
|
|
1977
1988
|
const xs = [quad[0], quad[2], quad[4], quad[6]];
|
|
@@ -2171,7 +2182,17 @@ export async function clickPoint(client, x, y, {
|
|
|
2171
2182
|
}
|
|
2172
2183
|
|
|
2173
2184
|
export async function scrollNodeIntoView(client, nodeId) {
|
|
2174
|
-
|
|
2185
|
+
try {
|
|
2186
|
+
await client.DOM.scrollIntoViewIfNeeded({ nodeId });
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
const wrapped = new Error(error?.message || String(error));
|
|
2189
|
+
wrapped.name = error?.name || "Error";
|
|
2190
|
+
wrapped.node_id = nodeId;
|
|
2191
|
+
wrapped.cdp_method = "DOM.scrollIntoViewIfNeeded";
|
|
2192
|
+
wrapped.original_stack = error?.stack || "";
|
|
2193
|
+
wrapped.stack = `${new Error(`scrollNodeIntoView failed for nodeId=${nodeId}`).stack || wrapped.stack}\nCaused by: ${error?.stack || error}`;
|
|
2194
|
+
throw wrapped;
|
|
2195
|
+
}
|
|
2175
2196
|
}
|
|
2176
2197
|
|
|
2177
2198
|
export async function clickNodeCenter(client, nodeId, {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getOuterHTML,
|
|
8
8
|
pressKey,
|
|
9
9
|
querySelectorAll,
|
|
10
|
+
scrollNodeIntoView,
|
|
10
11
|
sleep
|
|
11
12
|
} from "../../core/browser/index.js";
|
|
12
13
|
import {
|
|
@@ -27,6 +28,111 @@ import {
|
|
|
27
28
|
getRecruitRoots
|
|
28
29
|
} from "./roots.js";
|
|
29
30
|
|
|
31
|
+
function compactBox(box = null) {
|
|
32
|
+
if (!box) return null;
|
|
33
|
+
return {
|
|
34
|
+
center: box.center || null,
|
|
35
|
+
rect: box.rect || null
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getViewportRect(client) {
|
|
40
|
+
if (typeof client?.Page?.getLayoutMetrics !== "function") return null;
|
|
41
|
+
try {
|
|
42
|
+
const metrics = await client.Page.getLayoutMetrics();
|
|
43
|
+
const viewport = metrics?.cssVisualViewport || metrics?.visualViewport || metrics?.layoutViewport || {};
|
|
44
|
+
const width = Number(viewport.clientWidth || viewport.width || metrics?.layoutViewport?.clientWidth || 0);
|
|
45
|
+
const height = Number(viewport.clientHeight || viewport.height || metrics?.layoutViewport?.clientHeight || 0);
|
|
46
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
47
|
+
return {
|
|
48
|
+
x: 0,
|
|
49
|
+
y: 0,
|
|
50
|
+
width,
|
|
51
|
+
height
|
|
52
|
+
};
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function boxCenterIsInViewport(box, viewport, { marginPx = 16 } = {}) {
|
|
59
|
+
if (!box?.center || !viewport) return null;
|
|
60
|
+
const margin = Math.max(0, Number(marginPx) || 0);
|
|
61
|
+
return (
|
|
62
|
+
box.center.x >= viewport.x + margin
|
|
63
|
+
&& box.center.x <= viewport.x + viewport.width - margin
|
|
64
|
+
&& box.center.y >= viewport.y + margin
|
|
65
|
+
&& box.center.y <= viewport.y + viewport.height - margin
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function scrollDeltaForBox(box, viewport) {
|
|
70
|
+
if (!box?.center || !viewport) return 0;
|
|
71
|
+
const targetY = viewport.y + viewport.height * 0.48;
|
|
72
|
+
const delta = box.center.y - targetY;
|
|
73
|
+
if (Math.abs(delta) < 80) return 0;
|
|
74
|
+
return Math.max(-900, Math.min(900, delta));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function ensureRecruitCardInViewport(client, cardNodeId, {
|
|
78
|
+
maxScrollAttempts = 4,
|
|
79
|
+
marginPx = 16,
|
|
80
|
+
settleMs = 220
|
|
81
|
+
} = {}) {
|
|
82
|
+
const attempts = [];
|
|
83
|
+
await scrollNodeIntoView(client, cardNodeId);
|
|
84
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
85
|
+
|
|
86
|
+
let finalBox = null;
|
|
87
|
+
for (let attempt = 0; attempt <= maxScrollAttempts; attempt += 1) {
|
|
88
|
+
const box = await getNodeBox(client, cardNodeId);
|
|
89
|
+
finalBox = box;
|
|
90
|
+
const viewport = await getViewportRect(client);
|
|
91
|
+
const inViewport = boxCenterIsInViewport(box, viewport, { marginPx });
|
|
92
|
+
const entry = {
|
|
93
|
+
attempt,
|
|
94
|
+
in_viewport: inViewport,
|
|
95
|
+
viewport,
|
|
96
|
+
box: compactBox(box)
|
|
97
|
+
};
|
|
98
|
+
attempts.push(entry);
|
|
99
|
+
if (inViewport === true || inViewport === null) {
|
|
100
|
+
return {
|
|
101
|
+
ok: inViewport !== false,
|
|
102
|
+
verified: inViewport === true,
|
|
103
|
+
box,
|
|
104
|
+
attempts
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (attempt >= maxScrollAttempts) break;
|
|
108
|
+
|
|
109
|
+
const deltaY = scrollDeltaForBox(box, viewport);
|
|
110
|
+
if (!deltaY) break;
|
|
111
|
+
const wheelPoint = {
|
|
112
|
+
x: viewport.x + viewport.width * 0.5,
|
|
113
|
+
y: viewport.y + viewport.height * 0.5
|
|
114
|
+
};
|
|
115
|
+
await client.Input.dispatchMouseEvent({
|
|
116
|
+
type: "mouseWheel",
|
|
117
|
+
x: wheelPoint.x,
|
|
118
|
+
y: wheelPoint.y,
|
|
119
|
+
deltaX: 0,
|
|
120
|
+
deltaY
|
|
121
|
+
});
|
|
122
|
+
entry.scroll = {
|
|
123
|
+
method: "mouseWheel",
|
|
124
|
+
delta_y: deltaY,
|
|
125
|
+
point: wheelPoint
|
|
126
|
+
};
|
|
127
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const error = new Error("Recruit candidate card is not inside the visible viewport before click");
|
|
131
|
+
error.card_viewport_attempts = attempts;
|
|
132
|
+
error.card_node_id = cardNodeId;
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
|
|
30
136
|
export function matchesRecruitDetailNetwork(url) {
|
|
31
137
|
return RECRUIT_DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
32
138
|
}
|
|
@@ -256,13 +362,19 @@ export async function openRecruitCardDetail(client, cardNodeId, {
|
|
|
256
362
|
const openedStarted = Date.now();
|
|
257
363
|
const attempts = [];
|
|
258
364
|
const clickStarted = Date.now();
|
|
365
|
+
const viewportGuard = await ensureRecruitCardInViewport(client, cardNodeId);
|
|
259
366
|
const cardBox = await clickNodeCenter(client, cardNodeId, {
|
|
260
|
-
scrollIntoView:
|
|
367
|
+
scrollIntoView: false
|
|
261
368
|
});
|
|
262
369
|
let candidateClickMs = Date.now() - clickStarted;
|
|
263
370
|
attempts.push({
|
|
264
371
|
mode: "card-center",
|
|
265
|
-
center: cardBox.center
|
|
372
|
+
center: cardBox.center,
|
|
373
|
+
viewport_guard: {
|
|
374
|
+
ok: viewportGuard.ok,
|
|
375
|
+
verified: viewportGuard.verified,
|
|
376
|
+
attempts: viewportGuard.attempts
|
|
377
|
+
}
|
|
266
378
|
});
|
|
267
379
|
const detailStarted = Date.now();
|
|
268
380
|
let detailState = await waitForRecruitDetail(client, { timeoutMs });
|
|
@@ -44,7 +44,8 @@ const DEGREE_VALUES = new Set(["不限", "本科", "本科及以上", "硕士及
|
|
|
44
44
|
const CITY_STOP_PATTERN = /(?:筛选|搜索|查找|找|做过|从事过|有过|相关|的人选|的人|并且|且|学历|学校|经验|性别|年龄|目标|必须|优先|,|。|;|;|,)/;
|
|
45
45
|
const POST_ACTIONS = new Set(["none", "greet"]);
|
|
46
46
|
const CRITERIA_MARKER_PATTERN = /(?:筛选条件|筛选标准|筛选要求|筛选规则|硬性条件|硬条件|criteria)\s*[::]/i;
|
|
47
|
-
const CRITERIA_TRAILING_FIELD_PATTERN = /\n\s*(
|
|
47
|
+
const CRITERIA_TRAILING_FIELD_PATTERN = /\n\s*(?:岗位|职位|关键词|城市|地点|工作地|学历|学校类型|院校标签|经验|经验要求|工作经验|工作年限|性别|年龄|年龄要求|年龄范围|只看未查看|过滤已看|同事近期触达|近期同事触达|同事触达|同事联系|同事沟通|同事交换简历|目标筛选人数|目标人数|休息强度|后置动作|post_action|rest_level|filter_recent_colleague_contacted|recent_colleague_contacted|skip_recent_colleague_contacted)\s*[::]/i;
|
|
48
|
+
const INLINE_FIELD_BOUNDARY_PATTERN = /[;;]\s*(?:岗位|职位|关键词|城市|地点|工作地|学历|学校|学校类型|院校标签|经验|经验要求|工作经验|工作年限|性别|年龄|年龄要求|年龄范围|只看未查看|过滤已看|同事近期触达|近期同事触达|同事触达|同事联系|同事沟通|同事交换简历|目标筛选人数|目标人数|休息强度|后置动作|post_action|rest_level|filter_recent_colleague_contacted|recent_colleague_contacted|skip_recent_colleague_contacted)\s*(?:\([^)]*\))?\s*[::]/i;
|
|
48
49
|
|
|
49
50
|
function normalizeText(input) {
|
|
50
51
|
return String(input || "").replace(/\s+/g, " ").trim();
|
|
@@ -64,13 +65,15 @@ function escapeRegExp(input) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function extractFieldLineValue(rawText, labels = []) {
|
|
67
|
-
const lines = String(rawText || "").replace(/\r\n/g, "\n").split(
|
|
68
|
+
const lines = String(rawText || "").replace(/\r\n/g, "\n").split(/\n|[;;]/);
|
|
68
69
|
const labelPattern = labels.map(escapeRegExp).join("|");
|
|
69
70
|
if (!labelPattern) return null;
|
|
70
71
|
const pattern = new RegExp(`^\\s*(?:${labelPattern})(?:\\s*\\([^)]*\\))?\\s*[::]\\s*(.+?)\\s*$`, "i");
|
|
71
72
|
for (const line of lines) {
|
|
72
73
|
const match = line.match(pattern);
|
|
73
|
-
|
|
74
|
+
let value = match?.[1]?.trim();
|
|
75
|
+
const inlineBoundaryIndex = value ? value.search(INLINE_FIELD_BOUNDARY_PATTERN) : -1;
|
|
76
|
+
if (inlineBoundaryIndex >= 0) value = value.slice(0, inlineBoundaryIndex).trim();
|
|
74
77
|
if (value) return value;
|
|
75
78
|
}
|
|
76
79
|
return null;
|
|
@@ -172,6 +175,56 @@ function normalizeRecentViewedOverride(value) {
|
|
|
172
175
|
return null;
|
|
173
176
|
}
|
|
174
177
|
|
|
178
|
+
function normalizeColleagueContactedFilterOverride(value) {
|
|
179
|
+
if (typeof value === "boolean") return value;
|
|
180
|
+
if (typeof value === "number") return value !== 0;
|
|
181
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
182
|
+
const compact = normalized.replace(/\s+/g, "");
|
|
183
|
+
if (!compact) return null;
|
|
184
|
+
if ([
|
|
185
|
+
"true",
|
|
186
|
+
"yes",
|
|
187
|
+
"y",
|
|
188
|
+
"1",
|
|
189
|
+
"on",
|
|
190
|
+
"enable",
|
|
191
|
+
"enabled",
|
|
192
|
+
"需要",
|
|
193
|
+
"是",
|
|
194
|
+
"开启",
|
|
195
|
+
"过滤",
|
|
196
|
+
"需要过滤",
|
|
197
|
+
"跳过",
|
|
198
|
+
"排除",
|
|
199
|
+
"剔除",
|
|
200
|
+
"近30天未和同事交换简历",
|
|
201
|
+
"未和同事交换简历",
|
|
202
|
+
"只看未和同事交换简历"
|
|
203
|
+
].includes(compact)) return true;
|
|
204
|
+
if ([
|
|
205
|
+
"false",
|
|
206
|
+
"no",
|
|
207
|
+
"n",
|
|
208
|
+
"0",
|
|
209
|
+
"off",
|
|
210
|
+
"disable",
|
|
211
|
+
"disabled",
|
|
212
|
+
"不需要",
|
|
213
|
+
"否",
|
|
214
|
+
"关闭",
|
|
215
|
+
"不限",
|
|
216
|
+
"不过滤",
|
|
217
|
+
"不跳过",
|
|
218
|
+
"不排除",
|
|
219
|
+
"保留",
|
|
220
|
+
"none",
|
|
221
|
+
"all"
|
|
222
|
+
].includes(compact)) return false;
|
|
223
|
+
if (/(?:不|别|无需|不用|不要).{0,6}(?:过滤|排除|跳过|剔除).{0,8}(?:同事|交换简历|触达|联系|沟通)/i.test(normalized)) return false;
|
|
224
|
+
if (/(?:过滤|排除|跳过|剔除).{0,8}(?:同事|交换简历|触达|联系|沟通)/i.test(normalized)) return true;
|
|
225
|
+
return normalizeBooleanOverride(value);
|
|
226
|
+
}
|
|
227
|
+
|
|
175
228
|
function normalizeBooleanOverride(value) {
|
|
176
229
|
if (typeof value === "boolean") return value;
|
|
177
230
|
if (typeof value === "number") return value !== 0;
|
|
@@ -217,6 +270,29 @@ function extractRecentViewedExplicit(rawText) {
|
|
|
217
270
|
return value === null ? null : normalizeRecentViewedOverride(value);
|
|
218
271
|
}
|
|
219
272
|
|
|
273
|
+
function extractColleagueContactedFilterExplicit(rawText) {
|
|
274
|
+
const value = extractFieldLineValue(rawText, [
|
|
275
|
+
"同事近期触达",
|
|
276
|
+
"近期同事触达",
|
|
277
|
+
"同事触达",
|
|
278
|
+
"同事近期联系",
|
|
279
|
+
"近期同事联系",
|
|
280
|
+
"同事联系",
|
|
281
|
+
"同事近期沟通",
|
|
282
|
+
"近期同事沟通",
|
|
283
|
+
"同事沟通",
|
|
284
|
+
"同事交换简历",
|
|
285
|
+
"近期同事交换简历",
|
|
286
|
+
"近30天未和同事交换简历",
|
|
287
|
+
"filter_recent_colleague_contacted",
|
|
288
|
+
"recent_colleague_contacted",
|
|
289
|
+
"colleague_contacted_filter",
|
|
290
|
+
"colleague_contacted",
|
|
291
|
+
"skip_recent_colleague_contacted"
|
|
292
|
+
]);
|
|
293
|
+
return value === null ? null : normalizeColleagueContactedFilterOverride(value);
|
|
294
|
+
}
|
|
295
|
+
|
|
220
296
|
function normalizeDegreesOverride(value) {
|
|
221
297
|
if (Array.isArray(value)) return uniqueList(value.map(normalizeText));
|
|
222
298
|
if (typeof value === "string") return uniqueList(value.split(/[,,、|/]/).map(normalizeText));
|
|
@@ -493,15 +569,21 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
493
569
|
const rawInstruction = String(instruction || "");
|
|
494
570
|
const text = normalizeText(rawInstruction);
|
|
495
571
|
const finalConfirmed = confirmation?.final_confirmed === true;
|
|
496
|
-
const hasSkipRecentColleagueOverride =
|
|
497
|
-
|
|
498
|
-
"
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
572
|
+
const hasSkipRecentColleagueOverride = [
|
|
573
|
+
"skip_recent_colleague_contacted",
|
|
574
|
+
"filter_recent_colleague_contacted",
|
|
575
|
+
"recent_colleague_contacted",
|
|
576
|
+
"colleague_contacted_filter",
|
|
577
|
+
"colleague_contacted"
|
|
578
|
+
].some((key) => Object.prototype.hasOwnProperty.call(overrides || {}, key));
|
|
579
|
+
const confirmationSkipRecentColleagueContacted = normalizeColleagueContactedFilterOverride(
|
|
580
|
+
Object.prototype.hasOwnProperty.call(confirmation || {}, "filter_recent_colleague_contacted_value")
|
|
581
|
+
? confirmation.filter_recent_colleague_contacted_value
|
|
582
|
+
: confirmation?.skip_recent_colleague_contacted_value
|
|
502
583
|
);
|
|
503
584
|
const explicitSchools = extractSchoolFilterExplicit(rawInstruction);
|
|
504
585
|
const explicitRecentViewed = extractRecentViewedExplicit(rawInstruction);
|
|
586
|
+
const explicitColleagueContactedFilter = extractColleagueContactedFilterExplicit(rawInstruction);
|
|
505
587
|
const explicitKeyword = extractFieldLineValue(rawInstruction, ["搜索关键词", "关键词", "keyword"]);
|
|
506
588
|
const explicitJob = extractFieldLineValue(rawInstruction, ["岗位", "职位", "job"]);
|
|
507
589
|
const explicitCity = extractFieldLineValue(rawInstruction, ["城市", "地点", "工作地", "base"]);
|
|
@@ -526,7 +608,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
526
608
|
schools: explicitSchools.explicit ? explicitSchools.schools : extractSchools(text),
|
|
527
609
|
schools_explicit: explicitSchools.explicit,
|
|
528
610
|
filter_recent_viewed: explicitRecentViewed !== null ? explicitRecentViewed : extractRecentViewedFilter(text),
|
|
529
|
-
skip_recent_colleague_contacted:
|
|
611
|
+
skip_recent_colleague_contacted: explicitColleagueContactedFilter ?? confirmationSkipRecentColleagueContacted,
|
|
530
612
|
keyword_explicit: explicitKeyword || extractKeywordExplicit(text),
|
|
531
613
|
keyword_auto: extractKeywordAuto(text),
|
|
532
614
|
target_count: explicitTargetCount || extractTargetCount(text),
|
|
@@ -594,7 +676,17 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
594
676
|
? overrides.filter_recent_viewed
|
|
595
677
|
: overrides.recent_not_view
|
|
596
678
|
);
|
|
597
|
-
const overrideSkipRecentColleagueContacted =
|
|
679
|
+
const overrideSkipRecentColleagueContacted = normalizeColleagueContactedFilterOverride(
|
|
680
|
+
Object.prototype.hasOwnProperty.call(overrides, "skip_recent_colleague_contacted")
|
|
681
|
+
? overrides.skip_recent_colleague_contacted
|
|
682
|
+
: Object.prototype.hasOwnProperty.call(overrides, "filter_recent_colleague_contacted")
|
|
683
|
+
? overrides.filter_recent_colleague_contacted
|
|
684
|
+
: Object.prototype.hasOwnProperty.call(overrides, "recent_colleague_contacted")
|
|
685
|
+
? overrides.recent_colleague_contacted
|
|
686
|
+
: Object.prototype.hasOwnProperty.call(overrides, "colleague_contacted_filter")
|
|
687
|
+
? overrides.colleague_contacted_filter
|
|
688
|
+
: overrides.colleague_contacted
|
|
689
|
+
);
|
|
598
690
|
const overridePostAction = normalizePostAction(overrides.post_action);
|
|
599
691
|
if (overrideCity) parsed.city = overrideCity;
|
|
600
692
|
if (overrideDegree) parsed.degree = overrideDegree;
|
|
@@ -638,6 +730,11 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
638
730
|
const postAction = resolvePostAction(parsed, confirmation);
|
|
639
731
|
const maxGreetCount = resolveMaxGreetCount(parsed, confirmation);
|
|
640
732
|
const confirmationCriteria = normalizeStringOverride(confirmation?.criteria_value);
|
|
733
|
+
const skipRecentColleagueContacted = typeof parsed.skip_recent_colleague_contacted === "boolean"
|
|
734
|
+
? parsed.skip_recent_colleague_contacted
|
|
735
|
+
: finalConfirmed
|
|
736
|
+
? false
|
|
737
|
+
: null;
|
|
641
738
|
const baseSearchParams = {
|
|
642
739
|
job,
|
|
643
740
|
city: parsed.city,
|
|
@@ -648,7 +745,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
648
745
|
gender: parsed.gender,
|
|
649
746
|
age: parsed.age,
|
|
650
747
|
filter_recent_viewed: parsed.filter_recent_viewed,
|
|
651
|
-
skip_recent_colleague_contacted:
|
|
748
|
+
skip_recent_colleague_contacted: skipRecentColleagueContacted,
|
|
652
749
|
keyword: keywordResolution.keyword
|
|
653
750
|
};
|
|
654
751
|
const criteria = parsed.criteria_override || confirmationCriteria || parsed.criteria_explicit || null;
|
|
@@ -664,7 +761,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
664
761
|
target_count: parsed.target_count,
|
|
665
762
|
post_action: postAction,
|
|
666
763
|
max_greet_count: maxGreetCount,
|
|
667
|
-
skip_recent_colleague_contacted:
|
|
764
|
+
skip_recent_colleague_contacted: skipRecentColleagueContacted === true,
|
|
668
765
|
search_exchange_resume_filter_days: 30
|
|
669
766
|
};
|
|
670
767
|
const missingBeforeDefaults = collectMissingFields(baseSearchParams, baseScreenParams, parsed);
|
|
@@ -687,6 +784,7 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
687
784
|
&& !hasSkipRecentColleagueOverride
|
|
688
785
|
&& confirmationSkipRecentColleagueContacted === null
|
|
689
786
|
&& confirmation?.skip_recent_colleague_contacted_confirmed !== true
|
|
787
|
+
&& confirmation?.filter_recent_colleague_contacted_confirmed !== true
|
|
690
788
|
);
|
|
691
789
|
const needs_criteria_confirmation = Boolean(screenParams.criteria) && !finalConfirmed && confirmation?.criteria_confirmed !== true;
|
|
692
790
|
const pending_questions = [
|
|
@@ -703,12 +801,12 @@ export function parseRecruitInstruction({ instruction, confirmation, overrides }
|
|
|
703
801
|
: []),
|
|
704
802
|
...(needs_skip_recent_colleague_contacted_confirmation
|
|
705
803
|
? [{
|
|
706
|
-
field: "
|
|
707
|
-
question: "
|
|
804
|
+
field: "filter_recent_colleague_contacted",
|
|
805
|
+
question: "是否过滤近期已被同事触达的人选?开启后搜索页会勾选 Boss 的“近30天未和同事交换简历”。",
|
|
708
806
|
value: true,
|
|
709
807
|
options: [
|
|
710
|
-
{ label: "
|
|
711
|
-
{ label: "
|
|
808
|
+
{ label: "过滤", value: true },
|
|
809
|
+
{ label: "不过滤", value: false }
|
|
712
810
|
]
|
|
713
811
|
}]
|
|
714
812
|
: []),
|
|
@@ -109,6 +109,8 @@ function compactDetail(detailResult) {
|
|
|
109
109
|
return {
|
|
110
110
|
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
111
111
|
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
112
|
+
card_box: detailResult.card_box || null,
|
|
113
|
+
open_attempts: detailResult.open_attempts || [],
|
|
112
114
|
network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
|
|
113
115
|
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
114
116
|
cv_acquisition: detailResult.cv_acquisition || null,
|
|
@@ -593,7 +595,7 @@ export async function runRecruitWorkflow({
|
|
|
593
595
|
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
594
596
|
const postActionEnabled = normalizedPostAction !== "none";
|
|
595
597
|
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
596
|
-
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted
|
|
598
|
+
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted === true;
|
|
597
599
|
let searchExchangeResumeFilterApplied = false;
|
|
598
600
|
const limit = Math.max(1, Number(maxCandidates) || 1);
|
|
599
601
|
const detailCountLimit = detailLimit == null ? limit : Math.max(0, Number(detailLimit) || 0);
|
|
@@ -1047,6 +1049,8 @@ export async function runRecruitWorkflow({
|
|
|
1047
1049
|
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
1048
1050
|
networkParseIntervalMs: 250
|
|
1049
1051
|
});
|
|
1052
|
+
detailResult.card_box = openedDetail.card_box || null;
|
|
1053
|
+
detailResult.open_attempts = openedDetail.open_attempts || [];
|
|
1050
1054
|
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1051
1055
|
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
1052
1056
|
let source = "network";
|
|
@@ -1484,7 +1488,7 @@ export function createRecruitRunService({
|
|
|
1484
1488
|
const normalizedSearchParams = normalizeSearchParams(searchParams);
|
|
1485
1489
|
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1486
1490
|
const normalizedPostAction = normalizeRecruitPostAction(postAction);
|
|
1487
|
-
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted
|
|
1491
|
+
const searchExchangeResumeFilterRequested = normalizedSearchParams.skip_recent_colleague_contacted === true;
|
|
1488
1492
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1489
1493
|
const normalizedDetailLimit = detailLimit == null ? candidateLimit : Math.max(0, Number(detailLimit) || 0);
|
|
1490
1494
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
@@ -691,6 +691,9 @@ export function normalizeRecruitSearchParams(searchParams = {}) {
|
|
|
691
691
|
const experience = normalizeRecruitExperienceFilter(pickRecruitExperienceSource(searchParams));
|
|
692
692
|
const gender = normalizeRecruitGenderFilter(searchParams.gender);
|
|
693
693
|
const age = normalizeRecruitAgeFilter(pickRecruitAgeSource(searchParams));
|
|
694
|
+
const skipRecentColleagueContacted = typeof searchParams.skip_recent_colleague_contacted === "boolean"
|
|
695
|
+
? searchParams.skip_recent_colleague_contacted
|
|
696
|
+
: null;
|
|
694
697
|
const normalized = {
|
|
695
698
|
city: normalizeText(searchParams.city) || null,
|
|
696
699
|
degree: degrees[0] || "不限",
|
|
@@ -700,7 +703,7 @@ export function normalizeRecruitSearchParams(searchParams = {}) {
|
|
|
700
703
|
filter_recent_viewed: typeof searchParams.filter_recent_viewed === "boolean"
|
|
701
704
|
? searchParams.filter_recent_viewed
|
|
702
705
|
: null,
|
|
703
|
-
skip_recent_colleague_contacted:
|
|
706
|
+
skip_recent_colleague_contacted: skipRecentColleagueContacted
|
|
704
707
|
};
|
|
705
708
|
const job = normalizeText(searchParams.job || searchParams.job_title || searchParams.selected_job);
|
|
706
709
|
if (job) normalized.job = job;
|
|
@@ -733,6 +736,9 @@ export function hasRecruitSearchParams(searchParams = {}) {
|
|
|
733
736
|
const experience = normalizeRecruitExperienceFilter(pickRecruitExperienceSource(searchParams));
|
|
734
737
|
const gender = normalizeRecruitGenderFilter(searchParams.gender);
|
|
735
738
|
const age = normalizeRecruitAgeFilter(pickRecruitAgeSource(searchParams));
|
|
739
|
+
const skipRecentColleagueContacted = typeof searchParams.skip_recent_colleague_contacted === "boolean"
|
|
740
|
+
? searchParams.skip_recent_colleague_contacted
|
|
741
|
+
: null;
|
|
736
742
|
const normalized = {
|
|
737
743
|
city: normalizeText(searchParams.city) || null,
|
|
738
744
|
degree: degrees[0] || "不限",
|
|
@@ -742,7 +748,7 @@ export function hasRecruitSearchParams(searchParams = {}) {
|
|
|
742
748
|
filter_recent_viewed: typeof searchParams.filter_recent_viewed === "boolean"
|
|
743
749
|
? searchParams.filter_recent_viewed
|
|
744
750
|
: null,
|
|
745
|
-
skip_recent_colleague_contacted:
|
|
751
|
+
skip_recent_colleague_contacted: skipRecentColleagueContacted
|
|
746
752
|
};
|
|
747
753
|
return Boolean(
|
|
748
754
|
job
|
|
@@ -2239,20 +2245,180 @@ function parseRecruitAgeCustomHiddenValue(value) {
|
|
|
2239
2245
|
return parseAgeNumber(text, null);
|
|
2240
2246
|
}
|
|
2241
2247
|
|
|
2248
|
+
async function listRecruitAgeCustomTriggerState(client, frameNodeId) {
|
|
2249
|
+
const triggerSources = [
|
|
2250
|
+
{
|
|
2251
|
+
source: "dropdown",
|
|
2252
|
+
node_ids: uniqueNodeIds(await querySelectorAll(
|
|
2253
|
+
client,
|
|
2254
|
+
frameNodeId,
|
|
2255
|
+
RECRUIT_SEARCH_SELECTORS.ageCustomDropdown.join(", ")
|
|
2256
|
+
))
|
|
2257
|
+
},
|
|
2258
|
+
{
|
|
2259
|
+
source: "input",
|
|
2260
|
+
node_ids: uniqueNodeIds(await querySelectorAll(
|
|
2261
|
+
client,
|
|
2262
|
+
frameNodeId,
|
|
2263
|
+
RECRUIT_SEARCH_SELECTORS.ageCustomInput.join(", ")
|
|
2264
|
+
))
|
|
2265
|
+
}
|
|
2266
|
+
];
|
|
2267
|
+
const discovered = [];
|
|
2268
|
+
const seenTriggers = new Set();
|
|
2269
|
+
for (const { source, node_ids: nodeIds } of triggerSources) {
|
|
2270
|
+
for (const nodeId of nodeIds) {
|
|
2271
|
+
if (seenTriggers.has(nodeId)) continue;
|
|
2272
|
+
seenTriggers.add(nodeId);
|
|
2273
|
+
const attributes = await getAttributesMap(client, nodeId).catch(() => ({}));
|
|
2274
|
+
if (source === "input" && attributes.type === "hidden") {
|
|
2275
|
+
discovered.push({
|
|
2276
|
+
node_id: nodeId,
|
|
2277
|
+
source,
|
|
2278
|
+
visible: false,
|
|
2279
|
+
reason: "hidden_input"
|
|
2280
|
+
});
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
let box = null;
|
|
2284
|
+
try {
|
|
2285
|
+
box = await getNodeBox(client, nodeId);
|
|
2286
|
+
} catch (error) {
|
|
2287
|
+
discovered.push({
|
|
2288
|
+
node_id: nodeId,
|
|
2289
|
+
source,
|
|
2290
|
+
visible: false,
|
|
2291
|
+
error: error?.message || String(error)
|
|
2292
|
+
});
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
discovered.push({
|
|
2296
|
+
node_id: nodeId,
|
|
2297
|
+
source,
|
|
2298
|
+
visible: isVisibleBox(box),
|
|
2299
|
+
center: box.center,
|
|
2300
|
+
rect: box.rect
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
const sortTriggers = (items) => items
|
|
2305
|
+
.slice()
|
|
2306
|
+
.sort((left, right) => left.rect.x - right.rect.x || left.rect.y - right.rect.y);
|
|
2307
|
+
const visible_dropdowns = sortTriggers(discovered.filter((item) => item.visible && item.source === "dropdown"));
|
|
2308
|
+
const visible_inputs = sortTriggers(discovered.filter((item) => item.visible && item.source === "input"));
|
|
2309
|
+
return {
|
|
2310
|
+
discovered,
|
|
2311
|
+
visible_dropdowns,
|
|
2312
|
+
visible_inputs,
|
|
2313
|
+
preferred: visible_dropdowns.length >= 2 ? visible_dropdowns : visible_inputs
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
async function waitForRecruitAgeCustomTriggerState(client, frameNodeId, {
|
|
2318
|
+
timeoutMs = 1800,
|
|
2319
|
+
intervalMs = 150
|
|
2320
|
+
} = {}) {
|
|
2321
|
+
const started = Date.now();
|
|
2322
|
+
let state = null;
|
|
2323
|
+
while (Date.now() - started <= timeoutMs) {
|
|
2324
|
+
state = await listRecruitAgeCustomTriggerState(client, frameNodeId);
|
|
2325
|
+
if ((state.preferred || []).length >= 2) {
|
|
2326
|
+
return {
|
|
2327
|
+
ok: true,
|
|
2328
|
+
elapsed_ms: Date.now() - started,
|
|
2329
|
+
...state
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
await sleep(intervalMs);
|
|
2333
|
+
}
|
|
2334
|
+
return {
|
|
2335
|
+
ok: false,
|
|
2336
|
+
elapsed_ms: Date.now() - started,
|
|
2337
|
+
...(state || { discovered: [], visible_dropdowns: [], visible_inputs: [], preferred: [] })
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
async function openRecruitAgeCustomControls(client, frameNodeId) {
|
|
2342
|
+
const alreadyOpen = await waitForRecruitAgeCustomTriggerState(client, frameNodeId, {
|
|
2343
|
+
timeoutMs: 200,
|
|
2344
|
+
intervalMs: 100
|
|
2345
|
+
});
|
|
2346
|
+
if (alreadyOpen.ok) {
|
|
2347
|
+
return {
|
|
2348
|
+
opened: true,
|
|
2349
|
+
already_open: true,
|
|
2350
|
+
trigger_state: alreadyOpen
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
const attempts = [];
|
|
2355
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
2356
|
+
const candidates = await listTextCandidates(client, frameNodeId, RECRUIT_SEARCH_SELECTORS.ageCustom, {
|
|
2357
|
+
includeBox: true
|
|
2358
|
+
});
|
|
2359
|
+
const visibleCandidates = candidates.filter((item) => item.visible);
|
|
2360
|
+
const labelCandidate = chooseRecruitTextCandidate(visibleCandidates, {
|
|
2361
|
+
label: "自定义",
|
|
2362
|
+
match: "exact"
|
|
2363
|
+
});
|
|
2364
|
+
const customClick = labelCandidate
|
|
2365
|
+
? {
|
|
2366
|
+
clicked: true,
|
|
2367
|
+
selector: labelCandidate.selector,
|
|
2368
|
+
node_id: labelCandidate.node_id,
|
|
2369
|
+
box: await clickNodeCenter(client, labelCandidate.node_id, {
|
|
2370
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
2371
|
+
scrollIntoView: true
|
|
2372
|
+
})
|
|
2373
|
+
}
|
|
2374
|
+
: await clickFirstNodeBySelectors(
|
|
2375
|
+
client,
|
|
2376
|
+
frameNodeId,
|
|
2377
|
+
RECRUIT_SEARCH_SELECTORS.ageCustom,
|
|
2378
|
+
{ optional: false, scrollIntoView: true }
|
|
2379
|
+
);
|
|
2380
|
+
await sleep(500);
|
|
2381
|
+
const triggerState = await waitForRecruitAgeCustomTriggerState(client, frameNodeId);
|
|
2382
|
+
attempts.push({
|
|
2383
|
+
attempt,
|
|
2384
|
+
click: customClick,
|
|
2385
|
+
matched_label: labelCandidate ? compactRecruitTextCandidate(labelCandidate) : null,
|
|
2386
|
+
candidates: visibleCandidates.map(compactRecruitTextCandidate).slice(0, 10),
|
|
2387
|
+
trigger_state: triggerState
|
|
2388
|
+
});
|
|
2389
|
+
if (triggerState.ok) {
|
|
2390
|
+
return {
|
|
2391
|
+
opened: true,
|
|
2392
|
+
already_open: false,
|
|
2393
|
+
attempts,
|
|
2394
|
+
trigger_state: triggerState
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
const error = new Error("Recruit age custom controls did not open after clicking 自定义");
|
|
2400
|
+
error.age_custom_attempts = attempts;
|
|
2401
|
+
error.discovered_dropdowns = attempts[attempts.length - 1]?.trigger_state?.discovered || [];
|
|
2402
|
+
throw error;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2242
2405
|
async function selectRecruitAgeCustomDropdownValue(client, frameNodeId, {
|
|
2243
2406
|
dropdownIndex,
|
|
2244
2407
|
value
|
|
2245
2408
|
}) {
|
|
2246
|
-
const
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2409
|
+
const triggerState = await listRecruitAgeCustomTriggerState(client, frameNodeId);
|
|
2410
|
+
const visibleDropdownWrappers = triggerState.visible_dropdowns || [];
|
|
2411
|
+
const visibleInputTriggers = triggerState.visible_inputs || [];
|
|
2412
|
+
const visibleDropdowns = visibleDropdownWrappers.length > dropdownIndex
|
|
2413
|
+
? visibleDropdownWrappers
|
|
2414
|
+
: visibleInputTriggers;
|
|
2415
|
+
const dropdown = visibleDropdowns[dropdownIndex];
|
|
2416
|
+
if (!dropdown?.node_id) {
|
|
2417
|
+
const error = new Error(`Recruit age custom dropdown was not found: index=${dropdownIndex}`);
|
|
2418
|
+
error.discovered_dropdowns = triggerState.discovered;
|
|
2419
|
+
throw error;
|
|
2420
|
+
}
|
|
2421
|
+
const openBox = await clickNodeCenter(client, dropdown.node_id, {
|
|
2256
2422
|
...DETERMINISTIC_CLICK_OPTIONS,
|
|
2257
2423
|
scrollIntoView: true
|
|
2258
2424
|
});
|
|
@@ -2261,7 +2427,8 @@ async function selectRecruitAgeCustomDropdownValue(client, frameNodeId, {
|
|
|
2261
2427
|
const options = await listTextCandidates(client, frameNodeId, RECRUIT_SEARCH_SELECTORS.ageCustomOption, {
|
|
2262
2428
|
includeBox: true
|
|
2263
2429
|
});
|
|
2264
|
-
const
|
|
2430
|
+
const visibleOptions = options.filter((item) => item.visible);
|
|
2431
|
+
const option = chooseRecruitTextCandidate(visibleOptions, { label, match: "exact" });
|
|
2265
2432
|
if (!option) {
|
|
2266
2433
|
throw new Error(`Recruit age custom option was not found: ${label}`);
|
|
2267
2434
|
}
|
|
@@ -2274,10 +2441,11 @@ async function selectRecruitAgeCustomDropdownValue(client, frameNodeId, {
|
|
|
2274
2441
|
dropdown_index: dropdownIndex,
|
|
2275
2442
|
requested_value: value,
|
|
2276
2443
|
selected_label: option.text,
|
|
2277
|
-
dropdown_node_id:
|
|
2444
|
+
dropdown_node_id: dropdown.node_id,
|
|
2278
2445
|
option_node_id: option.node_id,
|
|
2279
2446
|
open_box: openBox,
|
|
2280
2447
|
box,
|
|
2448
|
+
discovered_dropdowns: triggerState.discovered,
|
|
2281
2449
|
discovered_options: summarizeTextCandidates(options, 40)
|
|
2282
2450
|
};
|
|
2283
2451
|
}
|
|
@@ -2320,12 +2488,7 @@ export async function setRecruitAge(client, frameNodeId, age) {
|
|
|
2320
2488
|
};
|
|
2321
2489
|
}
|
|
2322
2490
|
|
|
2323
|
-
const
|
|
2324
|
-
client,
|
|
2325
|
-
frameNodeId,
|
|
2326
|
-
RECRUIT_SEARCH_SELECTORS.ageCustom,
|
|
2327
|
-
{ optional: false, scrollIntoView: true }
|
|
2328
|
-
);
|
|
2491
|
+
const customOpen = await openRecruitAgeCustomControls(client, frameNodeId);
|
|
2329
2492
|
const before = await readRecruitAgeCustomState(client, frameNodeId);
|
|
2330
2493
|
const fixedBefore = await readRecruitAgeFixedOptionState(client, frameNodeId);
|
|
2331
2494
|
const selected = [];
|
|
@@ -2354,7 +2517,7 @@ export async function setRecruitAge(client, frameNodeId, age) {
|
|
|
2354
2517
|
min: filter.min,
|
|
2355
2518
|
max: filter.max
|
|
2356
2519
|
},
|
|
2357
|
-
|
|
2520
|
+
custom_open: customOpen,
|
|
2358
2521
|
before,
|
|
2359
2522
|
after,
|
|
2360
2523
|
fixed_options_before: fixedBefore,
|
package/src/index.js
CHANGED
|
@@ -882,7 +882,7 @@ function createRunInputSchema() {
|
|
|
882
882
|
},
|
|
883
883
|
skip_recent_colleague_contacted: {
|
|
884
884
|
type: "boolean",
|
|
885
|
-
description: "
|
|
885
|
+
description: "推荐页默认 true,用于跳过近14天同事沟通过的人选。搜索页请使用 recruit 工具的 filter_recent_colleague_contacted。"
|
|
886
886
|
},
|
|
887
887
|
criteria: { type: "string" },
|
|
888
888
|
job: { type: "string" },
|
|
@@ -1390,7 +1390,12 @@ function createCompactRunInputSchema() {
|
|
|
1390
1390
|
description: "用户完成总确认后传 true"
|
|
1391
1391
|
},
|
|
1392
1392
|
skip_recent_colleague_contacted_confirmed: { type: "boolean" },
|
|
1393
|
-
skip_recent_colleague_contacted_value: { type: "boolean" }
|
|
1393
|
+
skip_recent_colleague_contacted_value: { type: "boolean" },
|
|
1394
|
+
filter_recent_colleague_contacted_confirmed: { type: "boolean" },
|
|
1395
|
+
filter_recent_colleague_contacted_value: {
|
|
1396
|
+
type: "boolean",
|
|
1397
|
+
description: "是否过滤近期已被同事触达的人选;true 会开启搜索页“近30天未和同事交换简历”。"
|
|
1398
|
+
}
|
|
1394
1399
|
},
|
|
1395
1400
|
additionalProperties: true
|
|
1396
1401
|
},
|
|
@@ -1413,6 +1418,17 @@ function createCompactRunInputSchema() {
|
|
|
1413
1418
|
gender: { type: "string" },
|
|
1414
1419
|
recent_not_view: { type: "string" },
|
|
1415
1420
|
skip_recent_colleague_contacted: { type: "boolean" },
|
|
1421
|
+
filter_recent_colleague_contacted: {
|
|
1422
|
+
type: "boolean",
|
|
1423
|
+
description: "是否过滤近期已被同事触达的人选;true 会开启搜索页“近30天未和同事交换简历”;false 会确保该过滤取消。"
|
|
1424
|
+
},
|
|
1425
|
+
recent_colleague_contacted: {
|
|
1426
|
+
anyOf: [
|
|
1427
|
+
{ type: "boolean" },
|
|
1428
|
+
{ type: "string" }
|
|
1429
|
+
],
|
|
1430
|
+
description: "同事近期触达筛选别名;可填 不限/不过滤/过滤。"
|
|
1431
|
+
},
|
|
1416
1432
|
criteria: { type: "string" },
|
|
1417
1433
|
target_count: targetCountSchema,
|
|
1418
1434
|
post_action: { type: "string", enum: ["greet", "none"] },
|
package/src/recruit-mcp.js
CHANGED
|
@@ -712,6 +712,11 @@ export function createRecruitPipelineInputSchema() {
|
|
|
712
712
|
criteria_value: { type: "string" },
|
|
713
713
|
skip_recent_colleague_contacted_confirmed: { type: "boolean" },
|
|
714
714
|
skip_recent_colleague_contacted_value: { type: "boolean" },
|
|
715
|
+
filter_recent_colleague_contacted_confirmed: { type: "boolean" },
|
|
716
|
+
filter_recent_colleague_contacted_value: {
|
|
717
|
+
type: "boolean",
|
|
718
|
+
description: "是否过滤近期已被同事触达的人选;true 会开启搜索页“近30天未和同事交换简历”。"
|
|
719
|
+
},
|
|
715
720
|
post_action_confirmed: { type: "boolean" },
|
|
716
721
|
post_action_value: {
|
|
717
722
|
type: "string",
|
|
@@ -744,7 +749,18 @@ export function createRecruitPipelineInputSchema() {
|
|
|
744
749
|
filter_recent_viewed: { type: "boolean" },
|
|
745
750
|
skip_recent_colleague_contacted: {
|
|
746
751
|
type: "boolean",
|
|
747
|
-
description: "
|
|
752
|
+
description: "显式 true 时开启 Boss 的“近30天未和同事交换简历”过滤;false 会确保该过滤取消;未提供时不默认开启。"
|
|
753
|
+
},
|
|
754
|
+
filter_recent_colleague_contacted: {
|
|
755
|
+
type: "boolean",
|
|
756
|
+
description: "是否过滤近期已被同事触达的人选;true 会开启搜索页“近30天未和同事交换简历”;false 会确保该过滤取消。"
|
|
757
|
+
},
|
|
758
|
+
recent_colleague_contacted: {
|
|
759
|
+
anyOf: [
|
|
760
|
+
{ type: "boolean" },
|
|
761
|
+
{ type: "string" }
|
|
762
|
+
],
|
|
763
|
+
description: "同事近期触达筛选别名;可填 不限/不过滤/过滤。"
|
|
748
764
|
},
|
|
749
765
|
recent_not_view: {
|
|
750
766
|
anyOf: [
|
|
@@ -1049,7 +1065,7 @@ function buildRequiredConfirmations(parsedResult) {
|
|
|
1049
1065
|
if (parsedResult.needs_search_params_confirmation) confirmations.push("search_params");
|
|
1050
1066
|
if (parsedResult.needs_keyword_confirmation) confirmations.push("keyword");
|
|
1051
1067
|
if (parsedResult.needs_recent_viewed_filter_confirmation) confirmations.push("filter_recent_viewed");
|
|
1052
|
-
if (parsedResult.needs_skip_recent_colleague_contacted_confirmation) confirmations.push("
|
|
1068
|
+
if (parsedResult.needs_skip_recent_colleague_contacted_confirmation) confirmations.push("filter_recent_colleague_contacted");
|
|
1053
1069
|
if (parsedResult.needs_criteria_confirmation) confirmations.push("criteria");
|
|
1054
1070
|
if (parsedResult.has_unresolved_missing_fields) confirmations.push("missing_fields_or_defaults");
|
|
1055
1071
|
if ((parsedResult.suspicious_fields || []).length) confirmations.push("suspicious_fields");
|