@reconcrap/boss-recommend-mcp 2.0.8 → 2.0.9
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/chat-mcp.js +11 -3
- package/src/core/capture/index.js +123 -25
- package/src/core/cv-acquisition/index.js +5 -0
- package/src/core/screening/index.js +263 -22
- package/src/domains/chat/detail.js +29 -18
- package/src/domains/chat/run-service.js +18 -4
- package/src/domains/recommend/detail.js +29 -18
- package/src/domains/recommend/run-service.js +14 -3
- package/src/domains/recruit/detail.js +29 -18
- package/src/domains/recruit/run-service.js +14 -3
- package/src/index.js +2 -2
- package/src/recommend-mcp.js +11 -3
- package/src/recruit-mcp.js +12 -4
package/package.json
CHANGED
package/src/chat-mcp.js
CHANGED
|
@@ -863,9 +863,17 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "", configRe
|
|
|
863
863
|
llmConfig: resolvedConfig.ok ? {
|
|
864
864
|
...resolvedConfig.config
|
|
865
865
|
} : null,
|
|
866
|
-
llmTimeoutMs: parsePositiveInteger(
|
|
867
|
-
|
|
868
|
-
|
|
866
|
+
llmTimeoutMs: parsePositiveInteger(
|
|
867
|
+
args.llm_timeout_ms,
|
|
868
|
+
parsePositiveInteger(resolvedConfig.config?.llmTimeoutMs || resolvedConfig.config?.timeoutMs, slowLive ? 180000 : 120000)
|
|
869
|
+
),
|
|
870
|
+
llmImageLimit: parsePositiveInteger(
|
|
871
|
+
args.llm_image_limit,
|
|
872
|
+
parsePositiveInteger(resolvedConfig.config?.llmImageLimit || resolvedConfig.config?.imageLimit, 8)
|
|
873
|
+
),
|
|
874
|
+
llmImageDetail: normalizeText(
|
|
875
|
+
args.llm_image_detail || resolvedConfig.config?.llmImageDetail || resolvedConfig.config?.imageDetail
|
|
876
|
+
) || "low",
|
|
869
877
|
screeningMode: normalizeScreeningModeArg(args),
|
|
870
878
|
listMaxScrolls: parsePositiveInteger(args.list_max_scrolls, 200),
|
|
871
879
|
listStableSignatureLimit: parsePositiveInteger(args.list_stable_signature_limit, 2),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import sharp from "sharp";
|
|
4
5
|
import {
|
|
5
6
|
getAttributesMap,
|
|
6
7
|
getNodeBox,
|
|
@@ -149,6 +150,63 @@ function screenshotHash(buffer) {
|
|
|
149
150
|
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
async function optimizeScreenshotBuffer(buffer, {
|
|
154
|
+
enabled = false,
|
|
155
|
+
format = "png",
|
|
156
|
+
quality,
|
|
157
|
+
resizeMaxWidth = 0
|
|
158
|
+
} = {}) {
|
|
159
|
+
if (!enabled && !resizeMaxWidth) {
|
|
160
|
+
return {
|
|
161
|
+
buffer,
|
|
162
|
+
optimized: false,
|
|
163
|
+
optimization_error: null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const normalizedFormat = format === "jpg" ? "jpeg" : format;
|
|
168
|
+
let pipeline = sharp(buffer, { failOn: "none" });
|
|
169
|
+
const metadata = await pipeline.metadata();
|
|
170
|
+
const width = Number(metadata.width) || 0;
|
|
171
|
+
const safeMaxWidth = Math.max(0, Number(resizeMaxWidth) || 0);
|
|
172
|
+
if (safeMaxWidth > 0 && width > safeMaxWidth) {
|
|
173
|
+
pipeline = pipeline.resize({
|
|
174
|
+
width: safeMaxWidth,
|
|
175
|
+
withoutEnlargement: true
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (normalizedFormat === "jpeg") {
|
|
179
|
+
pipeline = pipeline.jpeg({
|
|
180
|
+
quality: quality == null ? 72 : Math.max(35, Math.min(95, Number(quality) || 72)),
|
|
181
|
+
mozjpeg: true
|
|
182
|
+
});
|
|
183
|
+
} else if (normalizedFormat === "webp") {
|
|
184
|
+
pipeline = pipeline.webp({
|
|
185
|
+
quality: quality == null ? 76 : Math.max(35, Math.min(95, Number(quality) || 76))
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
pipeline = pipeline.png({
|
|
189
|
+
compressionLevel: 9,
|
|
190
|
+
adaptiveFiltering: true
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const optimizedBuffer = await pipeline.toBuffer();
|
|
194
|
+
return {
|
|
195
|
+
buffer: optimizedBuffer,
|
|
196
|
+
optimized: true,
|
|
197
|
+
original_byte_length: buffer.length,
|
|
198
|
+
optimization_error: null
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
buffer,
|
|
203
|
+
optimized: false,
|
|
204
|
+
original_byte_length: buffer.length,
|
|
205
|
+
optimization_error: error?.message || String(error)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
152
210
|
export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
153
211
|
filePath,
|
|
154
212
|
format = "png",
|
|
@@ -156,10 +214,14 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
156
214
|
padding = 0,
|
|
157
215
|
captureBeyondViewport = true,
|
|
158
216
|
fromSurface = true,
|
|
217
|
+
captureViewport = false,
|
|
159
218
|
maxScreenshots = 6,
|
|
160
219
|
wheelDeltaY = 650,
|
|
161
220
|
settleMs = 900,
|
|
162
221
|
duplicateStopCount = 2,
|
|
222
|
+
skipDuplicateScreenshots = false,
|
|
223
|
+
optimize = false,
|
|
224
|
+
resizeMaxWidth = 0,
|
|
163
225
|
metadata = {}
|
|
164
226
|
} = {}) {
|
|
165
227
|
if (!nodeId) throw new Error("captureScrolledNodeScreenshots requires nodeId");
|
|
@@ -167,12 +229,19 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
167
229
|
const screenshots = [];
|
|
168
230
|
let consecutiveDuplicates = 0;
|
|
169
231
|
let previousHash = "";
|
|
232
|
+
let captureCount = 0;
|
|
233
|
+
let droppedDuplicateCount = 0;
|
|
170
234
|
|
|
171
235
|
for (let index = 0; index < Math.max(1, Number(maxScreenshots) || 1); index += 1) {
|
|
236
|
+
captureCount += 1;
|
|
172
237
|
const captureStarted = Date.now();
|
|
173
238
|
const box = await getNodeBox(client, nodeId);
|
|
174
239
|
const clip = withPadding(box.rect, padding);
|
|
175
|
-
const captureOptions = {
|
|
240
|
+
const captureOptions = captureViewport ? {
|
|
241
|
+
format,
|
|
242
|
+
fromSurface,
|
|
243
|
+
captureBeyondViewport: false
|
|
244
|
+
} : {
|
|
176
245
|
format,
|
|
177
246
|
fromSurface,
|
|
178
247
|
captureBeyondViewport,
|
|
@@ -182,7 +251,14 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
182
251
|
captureOptions.quality = quality;
|
|
183
252
|
}
|
|
184
253
|
const screenshot = await client.Page.captureScreenshot(captureOptions);
|
|
185
|
-
const
|
|
254
|
+
const originalBuffer = Buffer.from(screenshot.data || "", "base64");
|
|
255
|
+
const optimized = await optimizeScreenshotBuffer(originalBuffer, {
|
|
256
|
+
enabled: optimize,
|
|
257
|
+
format,
|
|
258
|
+
quality,
|
|
259
|
+
resizeMaxWidth
|
|
260
|
+
});
|
|
261
|
+
const buffer = optimized.buffer;
|
|
186
262
|
const hash = screenshotHash(buffer);
|
|
187
263
|
const duplicateOfPrevious = previousHash && previousHash === hash;
|
|
188
264
|
if (duplicateOfPrevious) {
|
|
@@ -191,30 +267,40 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
191
267
|
consecutiveDuplicates = 0;
|
|
192
268
|
}
|
|
193
269
|
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
}
|
|
270
|
+
let outputPath = null;
|
|
271
|
+
if (duplicateOfPrevious && skipDuplicateScreenshots) {
|
|
272
|
+
droppedDuplicateCount += 1;
|
|
273
|
+
} else {
|
|
274
|
+
outputPath = filePath ? filePathForSequence(filePath, screenshots.length, format) : null;
|
|
275
|
+
if (outputPath) {
|
|
276
|
+
fs.writeFileSync(outputPath, buffer);
|
|
277
|
+
}
|
|
198
278
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
279
|
+
screenshots.push({
|
|
280
|
+
index: screenshots.length,
|
|
281
|
+
capture_index: index,
|
|
282
|
+
source: "image",
|
|
283
|
+
captured_at: nowIso(),
|
|
284
|
+
node_id: nodeId,
|
|
285
|
+
format,
|
|
286
|
+
mime_type: `image/${format === "jpeg" ? "jpeg" : "png"}`,
|
|
287
|
+
byte_length: buffer.length,
|
|
288
|
+
original_byte_length: optimized.original_byte_length || originalBuffer.length,
|
|
289
|
+
optimized: Boolean(optimized.optimized),
|
|
290
|
+
optimization_error: optimized.optimization_error || null,
|
|
291
|
+
elapsed_ms: Date.now() - captureStarted,
|
|
292
|
+
file_path: outputPath,
|
|
293
|
+
sha256: hash,
|
|
294
|
+
duplicate_of_previous: Boolean(duplicateOfPrevious),
|
|
295
|
+
clip,
|
|
296
|
+
capture_viewport: Boolean(captureViewport),
|
|
297
|
+
node_rect: box.rect,
|
|
298
|
+
scroll: index === 0
|
|
299
|
+
? { before_capture: "initial" }
|
|
300
|
+
: { before_capture: `wheel_down_${index}` },
|
|
301
|
+
metadata
|
|
302
|
+
});
|
|
303
|
+
}
|
|
218
304
|
|
|
219
305
|
previousHash = hash;
|
|
220
306
|
if (consecutiveDuplicates >= Math.max(1, Number(duplicateStopCount) || 1)) {
|
|
@@ -242,8 +328,20 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
242
328
|
captured_at: nowIso(),
|
|
243
329
|
node_id: nodeId,
|
|
244
330
|
elapsed_ms: Date.now() - sequenceStarted,
|
|
331
|
+
capture_count: captureCount,
|
|
245
332
|
screenshot_count: screenshots.length,
|
|
246
333
|
unique_screenshot_count: new Set(screenshots.map((item) => item.sha256)).size,
|
|
334
|
+
duplicate_screenshot_count: captureCount - new Set(screenshots.map((item) => item.sha256)).size,
|
|
335
|
+
dropped_duplicate_count: droppedDuplicateCount,
|
|
336
|
+
total_byte_length: screenshots.reduce((sum, item) => sum + (Number(item.byte_length) || 0), 0),
|
|
337
|
+
original_total_byte_length: screenshots.reduce((sum, item) => sum + (Number(item.original_byte_length) || 0), 0),
|
|
338
|
+
optimization: {
|
|
339
|
+
enabled: Boolean(optimize),
|
|
340
|
+
resize_max_width: Math.max(0, Number(resizeMaxWidth) || 0),
|
|
341
|
+
capture_viewport: Boolean(captureViewport),
|
|
342
|
+
format,
|
|
343
|
+
quality: quality ?? null
|
|
344
|
+
},
|
|
247
345
|
file_paths: screenshots.map((item) => item.file_path).filter(Boolean),
|
|
248
346
|
screenshots,
|
|
249
347
|
metadata
|
|
@@ -126,8 +126,13 @@ export function summarizeImageEvidence(imageEvidence = null) {
|
|
|
126
126
|
return {
|
|
127
127
|
source: imageEvidence.source || "",
|
|
128
128
|
elapsed_ms: imageEvidence.elapsed_ms || 0,
|
|
129
|
+
capture_count: imageEvidence.capture_count || imageEvidence.screenshot_count || 0,
|
|
129
130
|
screenshot_count: imageEvidence.screenshot_count || 0,
|
|
130
131
|
unique_screenshot_count: imageEvidence.unique_screenshot_count || 0,
|
|
132
|
+
dropped_duplicate_count: imageEvidence.dropped_duplicate_count || 0,
|
|
133
|
+
total_byte_length: imageEvidence.total_byte_length || 0,
|
|
134
|
+
original_total_byte_length: imageEvidence.original_total_byte_length || 0,
|
|
135
|
+
optimization: imageEvidence.optimization || null,
|
|
131
136
|
file_paths: imageEvidence.file_paths || [],
|
|
132
137
|
first_clip: imageEvidence.screenshots?.[0]?.clip || imageEvidence.clip || null
|
|
133
138
|
};
|
|
@@ -289,6 +289,72 @@ function tryExtractJsonObject(text) {
|
|
|
289
289
|
return null;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
function extractBalancedJsonAt(text = "", startIndex = 0) {
|
|
293
|
+
const source = String(text || "");
|
|
294
|
+
const start = source.indexOf("{", Math.max(0, Number(startIndex) || 0));
|
|
295
|
+
if (start < 0) return "";
|
|
296
|
+
let depth = 0;
|
|
297
|
+
let inString = false;
|
|
298
|
+
let quote = "";
|
|
299
|
+
let escaped = false;
|
|
300
|
+
for (let index = start; index < source.length; index += 1) {
|
|
301
|
+
const char = source[index];
|
|
302
|
+
if (inString) {
|
|
303
|
+
if (escaped) {
|
|
304
|
+
escaped = false;
|
|
305
|
+
} else if (char === "\\") {
|
|
306
|
+
escaped = true;
|
|
307
|
+
} else if (char === quote) {
|
|
308
|
+
inString = false;
|
|
309
|
+
quote = "";
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (char === "\"" || char === "'") {
|
|
314
|
+
inString = true;
|
|
315
|
+
quote = char;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (char === "{") depth += 1;
|
|
319
|
+
if (char === "}") {
|
|
320
|
+
depth -= 1;
|
|
321
|
+
if (depth === 0) return source.slice(start, index + 1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return "";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function tryParseEmbeddedJsonObjects(text = "") {
|
|
328
|
+
const source = decodeHtmlEntities(String(text || ""));
|
|
329
|
+
const objects = [];
|
|
330
|
+
const anchors = [
|
|
331
|
+
"__INITIAL_STATE__",
|
|
332
|
+
"__NEXT_DATA__",
|
|
333
|
+
"geekDetailInfo",
|
|
334
|
+
"geekDetail",
|
|
335
|
+
"geekBaseInfo",
|
|
336
|
+
"geekEduExpList",
|
|
337
|
+
"geekWorkExpList",
|
|
338
|
+
"resume"
|
|
339
|
+
];
|
|
340
|
+
for (const anchor of anchors) {
|
|
341
|
+
let searchIndex = 0;
|
|
342
|
+
while (searchIndex >= 0 && searchIndex < source.length) {
|
|
343
|
+
const anchorIndex = source.indexOf(anchor, searchIndex);
|
|
344
|
+
if (anchorIndex < 0) break;
|
|
345
|
+
const windowStart = Math.max(0, anchorIndex - 4000);
|
|
346
|
+
const braceIndex = source.lastIndexOf("{", anchorIndex);
|
|
347
|
+
if (braceIndex >= windowStart) {
|
|
348
|
+
const jsonText = extractBalancedJsonAt(source, braceIndex);
|
|
349
|
+
const parsed = tryParseJson(jsonText);
|
|
350
|
+
if (parsed && typeof parsed === "object") objects.push(parsed);
|
|
351
|
+
}
|
|
352
|
+
searchIndex = anchorIndex + anchor.length;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return objects;
|
|
356
|
+
}
|
|
357
|
+
|
|
292
358
|
function flattenChatMessageContent(content) {
|
|
293
359
|
if (typeof content === "string") return content;
|
|
294
360
|
if (Array.isArray(content)) {
|
|
@@ -392,6 +458,65 @@ function pickFirst(...values) {
|
|
|
392
458
|
return "";
|
|
393
459
|
}
|
|
394
460
|
|
|
461
|
+
function isPlainObject(value) {
|
|
462
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function isBossGeekDetailShape(value) {
|
|
466
|
+
if (!isPlainObject(value)) return false;
|
|
467
|
+
return Boolean(
|
|
468
|
+
isPlainObject(value.geekBaseInfo)
|
|
469
|
+
|| value.geekName
|
|
470
|
+
|| value.geekAdvantage
|
|
471
|
+
|| Array.isArray(value.geekEduExpList)
|
|
472
|
+
|| Array.isArray(value.geekEducationList)
|
|
473
|
+
|| Array.isArray(value.geekWorkExpList)
|
|
474
|
+
|| Array.isArray(value.geekProjExpList)
|
|
475
|
+
|| Array.isArray(value.geekCertificationList)
|
|
476
|
+
|| Array.isArray(value.geekSkillList)
|
|
477
|
+
|| isPlainObject(value.highestEduExp)
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function isBossChatProfileShape(value) {
|
|
482
|
+
if (!isPlainObject(value)) return false;
|
|
483
|
+
return Boolean(
|
|
484
|
+
(value.name || value.encryptGeekId || value.uid)
|
|
485
|
+
&& (
|
|
486
|
+
Array.isArray(value.eduExpList)
|
|
487
|
+
|| Array.isArray(value.workExpList)
|
|
488
|
+
|| value.school
|
|
489
|
+
|| value.major
|
|
490
|
+
|| value.lastCompany
|
|
491
|
+
|| value.positionName
|
|
492
|
+
)
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function collectObjects(root, {
|
|
497
|
+
maxObjects = 500,
|
|
498
|
+
maxDepth = 8
|
|
499
|
+
} = {}) {
|
|
500
|
+
if (!root || typeof root !== "object") return [];
|
|
501
|
+
const queue = [{ value: root, depth: 0 }];
|
|
502
|
+
const seen = new WeakSet();
|
|
503
|
+
const objects = [];
|
|
504
|
+
while (queue.length && objects.length < maxObjects) {
|
|
505
|
+
const { value, depth } = queue.shift();
|
|
506
|
+
if (!value || typeof value !== "object" || seen.has(value)) continue;
|
|
507
|
+
seen.add(value);
|
|
508
|
+
if (isPlainObject(value)) objects.push(value);
|
|
509
|
+
if (depth >= maxDepth) continue;
|
|
510
|
+
const children = Array.isArray(value) ? value : Object.values(value);
|
|
511
|
+
for (const child of children) {
|
|
512
|
+
if (child && typeof child === "object") {
|
|
513
|
+
queue.push({ value: child, depth: depth + 1 });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return objects;
|
|
518
|
+
}
|
|
519
|
+
|
|
395
520
|
function joinRange(start, end, fallback = "") {
|
|
396
521
|
const left = parseDateLike(start);
|
|
397
522
|
const right = parseDateLike(end);
|
|
@@ -461,8 +586,8 @@ function formatEducation(item = {}, index = 0) {
|
|
|
461
586
|
];
|
|
462
587
|
return [
|
|
463
588
|
`${index + 1}. ${[
|
|
464
|
-
pickFirst(item.school),
|
|
465
|
-
pickFirst(item.major),
|
|
589
|
+
pickFirst(item.school, item.schoolName),
|
|
590
|
+
pickFirst(item.major, item.majorName),
|
|
466
591
|
pickFirst(item.degreeName, item.degree),
|
|
467
592
|
period
|
|
468
593
|
].filter(Boolean).join(" / ")}`,
|
|
@@ -507,16 +632,28 @@ function resolveBossGeekDetail(payload = {}) {
|
|
|
507
632
|
const candidates = [
|
|
508
633
|
{ sourceKey: "geekDetailInfo", detail: payload?.zpData?.geekDetailInfo },
|
|
509
634
|
{ sourceKey: "geekDetail", detail: payload?.zpData?.geekDetail },
|
|
635
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.zpData?.data?.geekDetailInfo },
|
|
636
|
+
{ sourceKey: "geekDetail", detail: payload?.zpData?.data?.geekDetail },
|
|
637
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.zpData?.data?.detailInfo },
|
|
638
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.zpData?.data?.resumeDetail },
|
|
639
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.zpData?.data },
|
|
640
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.data?.geekDetailInfo },
|
|
641
|
+
{ sourceKey: "geekDetail", detail: payload?.data?.geekDetail },
|
|
642
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.data?.detailInfo },
|
|
643
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.data?.resumeDetail },
|
|
644
|
+
{ sourceKey: "geekDetailInfo", detail: payload?.data },
|
|
510
645
|
{ sourceKey: "geekDetailInfo", detail: payload?.geekDetailInfo },
|
|
511
|
-
{ sourceKey: "geekDetail", detail: payload?.geekDetail }
|
|
646
|
+
{ sourceKey: "geekDetail", detail: payload?.geekDetail },
|
|
647
|
+
{ sourceKey: "geekDetailInfo", detail: payload }
|
|
512
648
|
];
|
|
513
|
-
const found = candidates.find((item) => item.detail
|
|
649
|
+
const found = candidates.find((item) => isBossGeekDetailShape(item.detail));
|
|
514
650
|
return found || { sourceKey: "", detail: null };
|
|
515
651
|
}
|
|
516
652
|
|
|
517
653
|
function extractBossChatGeekInfo(payload = {}) {
|
|
518
|
-
const data = payload?.zpData?.data;
|
|
654
|
+
const data = payload?.zpData?.data || payload?.data || payload?.zpData?.geekInfo || payload?.geekInfo;
|
|
519
655
|
if (!data || typeof data !== "object") return null;
|
|
656
|
+
if (!isBossChatProfileShape(data)) return null;
|
|
520
657
|
const educationList = normalizeList(data.eduExpList);
|
|
521
658
|
const workList = normalizeList(data.workExpList);
|
|
522
659
|
const firstEducation = educationList[0] || {};
|
|
@@ -585,7 +722,13 @@ function extractBossChatGeekInfo(payload = {}) {
|
|
|
585
722
|
}
|
|
586
723
|
|
|
587
724
|
function extractBossChatHistoryResume(payload = {}) {
|
|
588
|
-
const messages = normalizeList(payload?.zpData?.messages)
|
|
725
|
+
const messages = normalizeList(payload?.zpData?.messages).length
|
|
726
|
+
? normalizeList(payload?.zpData?.messages)
|
|
727
|
+
: normalizeList(payload?.messages).length
|
|
728
|
+
? normalizeList(payload?.messages)
|
|
729
|
+
: normalizeList(payload?.data?.messages).length
|
|
730
|
+
? normalizeList(payload?.data?.messages)
|
|
731
|
+
: normalizeList(payload?.zpData?.data?.messages);
|
|
589
732
|
const resumes = messages
|
|
590
733
|
.map((message) => message?.body?.resume)
|
|
591
734
|
.filter((resume) => resume && typeof resume === "object");
|
|
@@ -647,12 +790,56 @@ function extractBossChatHistoryResume(payload = {}) {
|
|
|
647
790
|
};
|
|
648
791
|
}
|
|
649
792
|
|
|
793
|
+
function extractBossProfileRecursively(payload = {}) {
|
|
794
|
+
for (const object of collectObjects(payload)) {
|
|
795
|
+
if (isBossGeekDetailShape(object)) {
|
|
796
|
+
const profile = extractBossGeekDetailInfo({ geekDetailInfo: object });
|
|
797
|
+
if (profile?.text || profile?.identity?.name) {
|
|
798
|
+
return {
|
|
799
|
+
...profile,
|
|
800
|
+
source_keys: {
|
|
801
|
+
...(profile.source_keys || {}),
|
|
802
|
+
recursive_profile_match: true
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (isBossChatProfileShape(object)) {
|
|
808
|
+
const profile = extractBossChatGeekInfo({ zpData: { data: object } });
|
|
809
|
+
if (profile?.text || profile?.identity?.name) {
|
|
810
|
+
return {
|
|
811
|
+
...profile,
|
|
812
|
+
source_keys: {
|
|
813
|
+
...(profile.source_keys || {}),
|
|
814
|
+
recursive_profile_match: true
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (isPlainObject(object.resume)) {
|
|
820
|
+
const profile = extractBossChatHistoryResume({ zpData: { messages: [{ body: { resume: object.resume } }] } });
|
|
821
|
+
if (profile?.text || profile?.identity?.name) {
|
|
822
|
+
return {
|
|
823
|
+
...profile,
|
|
824
|
+
source_keys: {
|
|
825
|
+
...(profile.source_keys || {}),
|
|
826
|
+
recursive_profile_match: true
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
|
|
650
835
|
function extractBossGeekDetailInfo(payload = {}) {
|
|
651
836
|
const { sourceKey, detail } = resolveBossGeekDetail(payload);
|
|
652
837
|
if (!detail || typeof detail !== "object") return null;
|
|
653
838
|
|
|
654
|
-
const base = detail.geekBaseInfo || {};
|
|
655
|
-
const educationList = normalizeList(detail.geekEduExpList)
|
|
839
|
+
const base = detail.geekBaseInfo || detail.baseInfo || detail.base || {};
|
|
840
|
+
const educationList = normalizeList(detail.geekEduExpList).length
|
|
841
|
+
? normalizeList(detail.geekEduExpList)
|
|
842
|
+
: normalizeList(detail.geekEducationList);
|
|
656
843
|
const firstEducation = educationList[0] || detail.highestEduExp || {};
|
|
657
844
|
const workList = normalizeList(detail.geekWorkExpList);
|
|
658
845
|
const firstWork = workList[0] || {};
|
|
@@ -666,6 +853,8 @@ function extractBossGeekDetailInfo(payload = {}) {
|
|
|
666
853
|
const normalizedExpectationList = expectationList.length ? expectationList : expectationFallback;
|
|
667
854
|
const certifications = normalizeList(detail.geekCertificationList);
|
|
668
855
|
const skillTags = [
|
|
856
|
+
...normalizeTagList(detail.geekSkillList),
|
|
857
|
+
...normalizeTagList(detail.skillList),
|
|
669
858
|
...normalizeTagList(detail.blueGeekSkills),
|
|
670
859
|
...normalizeTagList(base.userHighlightList),
|
|
671
860
|
...normalizeTagList(base.userDescHighlightList),
|
|
@@ -674,6 +863,7 @@ function extractBossGeekDetailInfo(payload = {}) {
|
|
|
674
863
|
...normalizeTagList(detail.professionalSkill)
|
|
675
864
|
];
|
|
676
865
|
const summaryParts = [
|
|
866
|
+
pickFirst(detail.geekAdvantage),
|
|
677
867
|
pickFirst(base.userDescription),
|
|
678
868
|
pickFirst(base.userDesc),
|
|
679
869
|
pickFirst(base.workEduDesc),
|
|
@@ -681,7 +871,7 @@ function extractBossGeekDetailInfo(payload = {}) {
|
|
|
681
871
|
].filter(Boolean);
|
|
682
872
|
const sections = {
|
|
683
873
|
base: [
|
|
684
|
-
base.name ? `姓名:${
|
|
874
|
+
pickFirst(base.name, detail.geekName, detail.name) ? `姓名:${pickFirst(base.name, detail.geekName, detail.name)}` : "",
|
|
685
875
|
normalizeGenderValue(base.gender) ? `性别:${normalizeGenderValue(base.gender)}` : "",
|
|
686
876
|
pickFirst(base.ageDesc, base.age) ? `年龄:${pickFirst(base.ageDesc, base.age)}` : "",
|
|
687
877
|
pickFirst(base.degreeCategory) ? `最高学历:${pickFirst(base.degreeCategory)}` : "",
|
|
@@ -710,11 +900,11 @@ function extractBossGeekDetailInfo(payload = {}) {
|
|
|
710
900
|
|
|
711
901
|
return {
|
|
712
902
|
identity: {
|
|
713
|
-
name: pickFirst(base.name),
|
|
903
|
+
name: pickFirst(base.name, detail.geekName, detail.name),
|
|
714
904
|
current_position: pickFirst(firstWork.positionName, firstWork.positionTitle, firstWork.position),
|
|
715
|
-
current_company: pickFirst(firstWork.formattedCompany, firstWork.company),
|
|
716
|
-
school: pickFirst(firstEducation.school),
|
|
717
|
-
major: pickFirst(firstEducation.major),
|
|
905
|
+
current_company: pickFirst(firstWork.formattedCompany, firstWork.company, firstWork.brandName),
|
|
906
|
+
school: pickFirst(firstEducation.school, firstEducation.schoolName),
|
|
907
|
+
major: pickFirst(firstEducation.major, firstEducation.majorName),
|
|
718
908
|
degree: pickFirst(base.degreeCategory, firstEducation.degreeName, firstEducation.degree),
|
|
719
909
|
years_experience: parseYearsExperience(pickFirst(base.workYearDesc, base.workYearsDesc)) ?? null,
|
|
720
910
|
age: parseAge(pickFirst(base.ageDesc, base.age)) ?? null,
|
|
@@ -744,24 +934,68 @@ function extractBossGeekDetailInfo(payload = {}) {
|
|
|
744
934
|
|
|
745
935
|
export function extractBossProfileFromNetworkBody(networkBody = {}) {
|
|
746
936
|
const text = parseNetworkBodyText(networkBody);
|
|
747
|
-
const
|
|
748
|
-
|
|
937
|
+
const parsedObjects = [
|
|
938
|
+
tryParseJson(text),
|
|
939
|
+
...tryParseEmbeddedJsonObjects(text)
|
|
940
|
+
].filter((item) => item && typeof item === "object");
|
|
941
|
+
if (!parsedObjects.length) {
|
|
942
|
+
const htmlText = /<html|<body|<div|<section|<script/i.test(text) ? htmlToText(text) : "";
|
|
943
|
+
if (htmlText && htmlText.length > 80) {
|
|
944
|
+
const candidate = normalizeCandidateProfile({
|
|
945
|
+
domain: "recommend",
|
|
946
|
+
source: "network-html-fallback",
|
|
947
|
+
text: htmlText
|
|
948
|
+
});
|
|
949
|
+
return {
|
|
950
|
+
ok: true,
|
|
951
|
+
url: networkBody.url || null,
|
|
952
|
+
status: networkBody.status ?? null,
|
|
953
|
+
mimeType: networkBody.mimeType || null,
|
|
954
|
+
text_length: text.length,
|
|
955
|
+
profile: {
|
|
956
|
+
identity: candidate.identity,
|
|
957
|
+
tags: candidate.tags,
|
|
958
|
+
sections: { html_text: [htmlText] },
|
|
959
|
+
text: htmlText,
|
|
960
|
+
source_keys: {
|
|
961
|
+
network_html_text: true,
|
|
962
|
+
html_text_length: htmlText.length
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
749
967
|
return {
|
|
750
968
|
ok: false,
|
|
751
969
|
error: "NETWORK_BODY_NOT_JSON",
|
|
752
970
|
text_length: text.length
|
|
753
971
|
};
|
|
754
972
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
973
|
+
let profile = null;
|
|
974
|
+
let parsed = parsedObjects[0];
|
|
975
|
+
for (const candidateObject of parsedObjects) {
|
|
976
|
+
profile = extractBossGeekDetailInfo(candidateObject)
|
|
977
|
+
|| extractBossChatGeekInfo(candidateObject)
|
|
978
|
+
|| extractBossChatHistoryResume(candidateObject)
|
|
979
|
+
|| extractBossProfileRecursively(candidateObject);
|
|
980
|
+
if (profile) {
|
|
981
|
+
parsed = candidateObject;
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
758
985
|
if (!profile) {
|
|
986
|
+
const encryptedPayload = parsedObjects.find((item) => (
|
|
987
|
+
normalizeText(item?.zpData?.encryptGeekDetailInfo || item?.encryptGeekDetailInfo || "")
|
|
988
|
+
));
|
|
759
989
|
return {
|
|
760
990
|
ok: false,
|
|
761
|
-
error: "BOSS_GEEK_DETAIL_INFO_NOT_FOUND",
|
|
991
|
+
error: encryptedPayload ? "BOSS_GEEK_DETAIL_INFO_ENCRYPTED" : "BOSS_GEEK_DETAIL_INFO_NOT_FOUND",
|
|
762
992
|
text_length: text.length,
|
|
763
|
-
|
|
764
|
-
|
|
993
|
+
parsed_object_count: parsedObjects.length,
|
|
994
|
+
top_level_keys: Object.keys(parsed || {}).slice(0, 30),
|
|
995
|
+
zpData_keys: Object.keys(parsed?.zpData || {}).slice(0, 50),
|
|
996
|
+
data_keys: Object.keys(parsed?.data || parsed?.zpData?.data || {}).slice(0, 50),
|
|
997
|
+
encrypted_resume: Boolean(encryptedPayload),
|
|
998
|
+
encrypted_resume_length: normalizeText(encryptedPayload?.zpData?.encryptGeekDetailInfo || encryptedPayload?.encryptGeekDetailInfo || "").length
|
|
765
999
|
};
|
|
766
1000
|
}
|
|
767
1001
|
return {
|
|
@@ -1088,6 +1322,8 @@ export function createFailedLlmScreeningResult(error) {
|
|
|
1088
1322
|
decision_cot: "",
|
|
1089
1323
|
reasoning_content: "",
|
|
1090
1324
|
raw_model_output: "",
|
|
1325
|
+
image_input_count: Number(error?.image_input_count) || 0,
|
|
1326
|
+
image_inputs: Array.isArray(error?.image_inputs) ? error.image_inputs : [],
|
|
1091
1327
|
error: error?.message || String(error || "unknown"),
|
|
1092
1328
|
screened_at: nowIso()
|
|
1093
1329
|
};
|
|
@@ -1176,6 +1412,7 @@ export async function callScreeningLlm({
|
|
|
1176
1412
|
const payload = {
|
|
1177
1413
|
model,
|
|
1178
1414
|
temperature: 0.1,
|
|
1415
|
+
max_tokens: Math.max(1, Number(config.maxTokens || config.llmMaxTokens) || 64),
|
|
1179
1416
|
messages: buildScreeningLlmMessages({
|
|
1180
1417
|
candidate,
|
|
1181
1418
|
criteria,
|
|
@@ -1185,7 +1422,7 @@ export async function callScreeningLlm({
|
|
|
1185
1422
|
applyChatCompletionThinking(payload, {
|
|
1186
1423
|
baseUrl,
|
|
1187
1424
|
model,
|
|
1188
|
-
thinkingLevel: config.llmThinkingLevel || config.thinkingLevel || config.reasoningEffort
|
|
1425
|
+
thinkingLevel: config.llmThinkingLevel || config.thinkingLevel || config.reasoningEffort || "off"
|
|
1189
1426
|
});
|
|
1190
1427
|
|
|
1191
1428
|
const controller = new AbortController();
|
|
@@ -1250,6 +1487,10 @@ export async function callScreeningLlm({
|
|
|
1250
1487
|
image_inputs: summarizeLlmImageInputs(imageInputs),
|
|
1251
1488
|
screened_at: nowIso()
|
|
1252
1489
|
};
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
error.image_input_count = imageInputs.length;
|
|
1492
|
+
error.image_inputs = summarizeLlmImageInputs(imageInputs);
|
|
1493
|
+
throw error;
|
|
1253
1494
|
} finally {
|
|
1254
1495
|
clearTimeout(timer);
|
|
1255
1496
|
}
|
|
@@ -1273,10 +1273,10 @@ export async function extractChatProfileCandidate(client, {
|
|
|
1273
1273
|
resumeHtml: providedResumeHtml = null,
|
|
1274
1274
|
networkEvents = [],
|
|
1275
1275
|
targetUrl = "",
|
|
1276
|
-
closeResume = true
|
|
1276
|
+
closeResume = true,
|
|
1277
|
+
networkParseRetryMs = 1800,
|
|
1278
|
+
networkParseIntervalMs = 250
|
|
1277
1279
|
} = {}) {
|
|
1278
|
-
await sleep(1000);
|
|
1279
|
-
const networkBodies = await readChatProfileNetworkBodies(client, networkEvents);
|
|
1280
1280
|
let resumeHtml = providedResumeHtml || null;
|
|
1281
1281
|
if (!resumeHtml) {
|
|
1282
1282
|
try {
|
|
@@ -1292,21 +1292,30 @@ export async function extractChatProfileCandidate(client, {
|
|
|
1292
1292
|
resumeHtml.resumeIframeText
|
|
1293
1293
|
].filter(Boolean).join("\n\n");
|
|
1294
1294
|
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1295
|
+
const parseStarted = Date.now();
|
|
1296
|
+
let networkBodies = [];
|
|
1297
|
+
let detailCandidateResult = null;
|
|
1298
|
+
do {
|
|
1299
|
+
networkBodies = await readChatProfileNetworkBodies(client, networkEvents);
|
|
1300
|
+
detailCandidateResult = buildScreeningCandidateFromDetail({
|
|
1301
|
+
domain: "chat",
|
|
1302
|
+
source: "chat-live-cdp-profile",
|
|
1303
|
+
cardCandidate,
|
|
1304
|
+
detailText,
|
|
1305
|
+
networkBodies,
|
|
1306
|
+
metadata: {
|
|
1307
|
+
target_url: targetUrl,
|
|
1308
|
+
card_node_id: cardNodeId,
|
|
1309
|
+
resume_popup_selector: resumeState?.popup?.selector || null,
|
|
1310
|
+
resume_content_selector: resumeState?.content?.selector || null,
|
|
1311
|
+
resume_iframe_selector: resumeState?.resumeIframe?.selector || null,
|
|
1312
|
+
resume_iframe_document_node_id: resumeHtml.resumeIframeDocumentNodeId
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
if (detailCandidateResult.parsed_network_profiles.some((item) => item.ok)) break;
|
|
1316
|
+
if (Date.now() - parseStarted >= Math.max(0, Number(networkParseRetryMs) || 0)) break;
|
|
1317
|
+
await sleep(Math.max(50, Number(networkParseIntervalMs) || 250));
|
|
1318
|
+
} while (true);
|
|
1310
1319
|
|
|
1311
1320
|
let closeResult = null;
|
|
1312
1321
|
if (closeResume) {
|
|
@@ -1317,6 +1326,8 @@ export async function extractChatProfileCandidate(client, {
|
|
|
1317
1326
|
candidate: detailCandidateResult.candidate,
|
|
1318
1327
|
parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
|
|
1319
1328
|
network_bodies: networkBodies,
|
|
1329
|
+
network_parse_retry_elapsed_ms: Date.now() - parseStarted,
|
|
1330
|
+
network_event_count: networkEvents.length,
|
|
1320
1331
|
detail: {
|
|
1321
1332
|
popup_text: resumeHtml.popupText,
|
|
1322
1333
|
content_text: resumeHtml.contentText,
|
|
@@ -752,8 +752,11 @@ export async function runChatWorkflow({
|
|
|
752
752
|
resumeNetworkEvents
|
|
753
753
|
),
|
|
754
754
|
targetUrl,
|
|
755
|
-
closeResume: false
|
|
755
|
+
closeResume: false,
|
|
756
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
757
|
+
networkParseIntervalMs: 250
|
|
756
758
|
});
|
|
759
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
757
760
|
parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
758
761
|
if (parsedNetworkProfileCount > 0) {
|
|
759
762
|
contentWait = {
|
|
@@ -789,8 +792,11 @@ export async function runChatWorkflow({
|
|
|
789
792
|
resumeNetworkEvents
|
|
790
793
|
),
|
|
791
794
|
targetUrl,
|
|
792
|
-
closeResume: false
|
|
795
|
+
closeResume: false,
|
|
796
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
797
|
+
networkParseIntervalMs: 250
|
|
793
798
|
});
|
|
799
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
794
800
|
parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
795
801
|
}
|
|
796
802
|
|
|
@@ -808,12 +814,20 @@ export async function runChatWorkflow({
|
|
|
808
814
|
imageOutputDir,
|
|
809
815
|
domain: "chat",
|
|
810
816
|
runId: runControl?.runId,
|
|
811
|
-
index
|
|
817
|
+
index,
|
|
818
|
+
extension: "jpg"
|
|
812
819
|
}),
|
|
820
|
+
format: "jpeg",
|
|
821
|
+
quality: 72,
|
|
822
|
+
optimize: true,
|
|
823
|
+
resizeMaxWidth: 1100,
|
|
824
|
+
captureViewport: true,
|
|
813
825
|
padding: 8,
|
|
814
826
|
maxScreenshots: maxImagePages,
|
|
815
827
|
wheelDeltaY: imageWheelDeltaY,
|
|
816
|
-
settleMs:
|
|
828
|
+
settleMs: 350,
|
|
829
|
+
duplicateStopCount: 1,
|
|
830
|
+
skipDuplicateScreenshots: true,
|
|
817
831
|
metadata: {
|
|
818
832
|
domain: "chat",
|
|
819
833
|
capture_mode: "scroll_sequence",
|
|
@@ -487,31 +487,40 @@ export async function extractRecommendDetailCandidate(client, {
|
|
|
487
487
|
detailState,
|
|
488
488
|
networkEvents = [],
|
|
489
489
|
targetUrl = "",
|
|
490
|
-
closeDetail = true
|
|
490
|
+
closeDetail = true,
|
|
491
|
+
networkParseRetryMs = 1800,
|
|
492
|
+
networkParseIntervalMs = 250
|
|
491
493
|
} = {}) {
|
|
492
|
-
await sleep(1000);
|
|
493
|
-
const networkBodies = await readRecommendDetailNetworkBodies(client, networkEvents);
|
|
494
494
|
const detailHtml = await readRecommendDetailHtml(client, detailState);
|
|
495
495
|
const detailText = [
|
|
496
496
|
detailHtml.popupText,
|
|
497
497
|
detailHtml.resumeText
|
|
498
498
|
].filter(Boolean).join("\n\n");
|
|
499
499
|
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
500
|
+
const parseStarted = Date.now();
|
|
501
|
+
let networkBodies = [];
|
|
502
|
+
let detailCandidateResult = null;
|
|
503
|
+
do {
|
|
504
|
+
networkBodies = await readRecommendDetailNetworkBodies(client, networkEvents);
|
|
505
|
+
detailCandidateResult = buildScreeningCandidateFromDetail({
|
|
506
|
+
cardCandidate,
|
|
507
|
+
detailText,
|
|
508
|
+
networkBodies,
|
|
509
|
+
metadata: {
|
|
510
|
+
target_url: targetUrl,
|
|
511
|
+
card_node_id: cardNodeId,
|
|
512
|
+
detail_popup_selector: detailState?.popup?.selector || null,
|
|
513
|
+
detail_popup_root: detailState?.popup?.root || null,
|
|
514
|
+
resume_iframe_selector: detailState?.resumeIframe?.selector || null,
|
|
515
|
+
resume_iframe_root: detailState?.resumeIframe?.root || null,
|
|
516
|
+
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId,
|
|
517
|
+
detail_html_errors: detailHtml.errors || []
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
if (detailCandidateResult.parsed_network_profiles.some((item) => item.ok)) break;
|
|
521
|
+
if (Date.now() - parseStarted >= Math.max(0, Number(networkParseRetryMs) || 0)) break;
|
|
522
|
+
await sleep(Math.max(50, Number(networkParseIntervalMs) || 250));
|
|
523
|
+
} while (true);
|
|
515
524
|
|
|
516
525
|
let closeResult = null;
|
|
517
526
|
if (closeDetail) {
|
|
@@ -522,6 +531,8 @@ export async function extractRecommendDetailCandidate(client, {
|
|
|
522
531
|
candidate: detailCandidateResult.candidate,
|
|
523
532
|
parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
|
|
524
533
|
network_bodies: networkBodies,
|
|
534
|
+
network_parse_retry_elapsed_ms: Date.now() - parseStarted,
|
|
535
|
+
network_event_count: networkEvents.length,
|
|
525
536
|
detail: {
|
|
526
537
|
popup_text: detailHtml.popupText,
|
|
527
538
|
resume_text: detailHtml.resumeText,
|
|
@@ -677,8 +677,11 @@ export async function runRecommendWorkflow({
|
|
|
677
677
|
detailState: openedDetail.detail_state,
|
|
678
678
|
networkEvents: networkRecorder.events,
|
|
679
679
|
targetUrl,
|
|
680
|
-
closeDetail: false
|
|
680
|
+
closeDetail: false,
|
|
681
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
682
|
+
networkParseIntervalMs: 250
|
|
681
683
|
});
|
|
684
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
682
685
|
|
|
683
686
|
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
684
687
|
let source = "network";
|
|
@@ -698,12 +701,20 @@ export async function runRecommendWorkflow({
|
|
|
698
701
|
imageOutputDir,
|
|
699
702
|
domain: "recommend",
|
|
700
703
|
runId: runControl?.runId,
|
|
701
|
-
index
|
|
704
|
+
index,
|
|
705
|
+
extension: "jpg"
|
|
702
706
|
}),
|
|
707
|
+
format: "jpeg",
|
|
708
|
+
quality: 72,
|
|
709
|
+
optimize: true,
|
|
710
|
+
resizeMaxWidth: 1100,
|
|
711
|
+
captureViewport: true,
|
|
703
712
|
padding: 4,
|
|
704
713
|
maxScreenshots: maxImagePages,
|
|
705
714
|
wheelDeltaY: imageWheelDeltaY,
|
|
706
|
-
settleMs:
|
|
715
|
+
settleMs: 350,
|
|
716
|
+
duplicateStopCount: 1,
|
|
717
|
+
skipDuplicateScreenshots: true,
|
|
707
718
|
metadata: {
|
|
708
719
|
domain: "recommend",
|
|
709
720
|
capture_mode: "scroll_sequence",
|
|
@@ -379,31 +379,40 @@ export async function extractRecruitDetailCandidate(client, {
|
|
|
379
379
|
detailHtml: providedDetailHtml = null,
|
|
380
380
|
networkEvents = [],
|
|
381
381
|
targetUrl = "",
|
|
382
|
-
closeDetail = true
|
|
382
|
+
closeDetail = true,
|
|
383
|
+
networkParseRetryMs = 1800,
|
|
384
|
+
networkParseIntervalMs = 250
|
|
383
385
|
} = {}) {
|
|
384
|
-
await sleep(1000);
|
|
385
|
-
const networkBodies = await readRecruitDetailNetworkBodies(client, networkEvents);
|
|
386
386
|
const detailHtml = providedDetailHtml || await readRecruitDetailHtml(client, detailState);
|
|
387
387
|
const detailText = [
|
|
388
388
|
detailHtml.popupText,
|
|
389
389
|
detailHtml.resumeText
|
|
390
390
|
].filter(Boolean).join("\n\n");
|
|
391
391
|
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
networkBodies,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
392
|
+
const parseStarted = Date.now();
|
|
393
|
+
let networkBodies = [];
|
|
394
|
+
let detailCandidateResult = null;
|
|
395
|
+
do {
|
|
396
|
+
networkBodies = await readRecruitDetailNetworkBodies(client, networkEvents);
|
|
397
|
+
detailCandidateResult = buildScreeningCandidateFromDetail({
|
|
398
|
+
domain: "recruit",
|
|
399
|
+
cardCandidate,
|
|
400
|
+
detailText,
|
|
401
|
+
networkBodies,
|
|
402
|
+
metadata: {
|
|
403
|
+
target_url: targetUrl,
|
|
404
|
+
card_node_id: cardNodeId,
|
|
405
|
+
detail_popup_selector: detailState?.popup?.selector || null,
|
|
406
|
+
detail_popup_root: detailState?.popup?.root || null,
|
|
407
|
+
resume_iframe_selector: detailState?.resumeIframe?.selector || null,
|
|
408
|
+
resume_iframe_root: detailState?.resumeIframe?.root || null,
|
|
409
|
+
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
if (detailCandidateResult.parsed_network_profiles.some((item) => item.ok)) break;
|
|
413
|
+
if (Date.now() - parseStarted >= Math.max(0, Number(networkParseRetryMs) || 0)) break;
|
|
414
|
+
await sleep(Math.max(50, Number(networkParseIntervalMs) || 250));
|
|
415
|
+
} while (true);
|
|
407
416
|
|
|
408
417
|
let closeResult = null;
|
|
409
418
|
if (closeDetail) {
|
|
@@ -414,6 +423,8 @@ export async function extractRecruitDetailCandidate(client, {
|
|
|
414
423
|
candidate: detailCandidateResult.candidate,
|
|
415
424
|
parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
|
|
416
425
|
network_bodies: networkBodies,
|
|
426
|
+
network_parse_retry_elapsed_ms: Date.now() - parseStarted,
|
|
427
|
+
network_event_count: networkEvents.length,
|
|
417
428
|
detail: {
|
|
418
429
|
popup_text: detailHtml.popupText,
|
|
419
430
|
resume_text: detailHtml.resumeText,
|
|
@@ -403,8 +403,11 @@ export async function runRecruitWorkflow({
|
|
|
403
403
|
detailState: openedDetail.detail_state,
|
|
404
404
|
networkEvents: networkRecorder.events,
|
|
405
405
|
targetUrl,
|
|
406
|
-
closeDetail: false
|
|
406
|
+
closeDetail: false,
|
|
407
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
408
|
+
networkParseIntervalMs: 250
|
|
407
409
|
});
|
|
410
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
408
411
|
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
409
412
|
let source = "network";
|
|
410
413
|
let imageEvidence = null;
|
|
@@ -423,12 +426,20 @@ export async function runRecruitWorkflow({
|
|
|
423
426
|
imageOutputDir,
|
|
424
427
|
domain: "recruit",
|
|
425
428
|
runId: runControl?.runId,
|
|
426
|
-
index
|
|
429
|
+
index,
|
|
430
|
+
extension: "jpg"
|
|
427
431
|
}),
|
|
432
|
+
format: "jpeg",
|
|
433
|
+
quality: 72,
|
|
434
|
+
optimize: true,
|
|
435
|
+
resizeMaxWidth: 1100,
|
|
436
|
+
captureViewport: true,
|
|
428
437
|
padding: 4,
|
|
429
438
|
maxScreenshots: maxImagePages,
|
|
430
439
|
wheelDeltaY: imageWheelDeltaY,
|
|
431
|
-
settleMs:
|
|
440
|
+
settleMs: 350,
|
|
441
|
+
duplicateStopCount: 1,
|
|
442
|
+
skipDuplicateScreenshots: true,
|
|
432
443
|
metadata: {
|
|
433
444
|
domain: "recruit",
|
|
434
445
|
capture_mode: "scroll_sequence",
|
package/src/index.js
CHANGED
|
@@ -572,7 +572,7 @@ function createRunInputSchema() {
|
|
|
572
572
|
},
|
|
573
573
|
llm_image_detail: {
|
|
574
574
|
type: "string",
|
|
575
|
-
description: "可选,图片输入 detail,默认
|
|
575
|
+
description: "可选,图片输入 detail,默认 low"
|
|
576
576
|
},
|
|
577
577
|
delay_ms: {
|
|
578
578
|
type: "integer",
|
|
@@ -746,7 +746,7 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
|
|
|
746
746
|
},
|
|
747
747
|
llm_image_detail: {
|
|
748
748
|
type: "string",
|
|
749
|
-
description: "可选,图片输入 detail,默认
|
|
749
|
+
description: "可选,图片输入 detail,默认 low"
|
|
750
750
|
},
|
|
751
751
|
online_resume_button_timeout_ms: {
|
|
752
752
|
type: "integer",
|
package/src/recommend-mcp.js
CHANGED
|
@@ -1045,9 +1045,17 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1045
1045
|
llmConfig: normalized.screeningMode === "llm" && configResolution?.ok ? {
|
|
1046
1046
|
...configResolution.config
|
|
1047
1047
|
} : null,
|
|
1048
|
-
llmTimeoutMs: parsePositiveInteger(
|
|
1049
|
-
|
|
1050
|
-
|
|
1048
|
+
llmTimeoutMs: parsePositiveInteger(
|
|
1049
|
+
args.llm_timeout_ms,
|
|
1050
|
+
parsePositiveInteger(configResolution?.config?.llmTimeoutMs || configResolution?.config?.timeoutMs, slowLive ? 180000 : 120000)
|
|
1051
|
+
),
|
|
1052
|
+
llmImageLimit: parsePositiveInteger(
|
|
1053
|
+
args.llm_image_limit,
|
|
1054
|
+
parsePositiveInteger(configResolution?.config?.llmImageLimit || configResolution?.config?.imageLimit, 8)
|
|
1055
|
+
),
|
|
1056
|
+
llmImageDetail: normalizeText(
|
|
1057
|
+
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
1058
|
+
) || "low",
|
|
1051
1059
|
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1052
1060
|
name: "mcp-recommend-pipeline-run",
|
|
1053
1061
|
parsed
|
package/src/recruit-mcp.js
CHANGED
|
@@ -449,7 +449,7 @@ export function createRecruitPipelineInputSchema() {
|
|
|
449
449
|
},
|
|
450
450
|
llm_image_detail: {
|
|
451
451
|
type: "string",
|
|
452
|
-
description: "可选,图片输入 detail,默认
|
|
452
|
+
description: "可选,图片输入 detail,默认 low"
|
|
453
453
|
},
|
|
454
454
|
delay_ms: {
|
|
455
455
|
type: "integer",
|
|
@@ -834,9 +834,17 @@ function getRunOptions(args, parsed, session, configResolution = null) {
|
|
|
834
834
|
llmConfig: screeningMode === "llm" && configResolution?.ok ? {
|
|
835
835
|
...configResolution.config
|
|
836
836
|
} : null,
|
|
837
|
-
llmTimeoutMs: parsePositiveInteger(
|
|
838
|
-
|
|
839
|
-
|
|
837
|
+
llmTimeoutMs: parsePositiveInteger(
|
|
838
|
+
args.llm_timeout_ms,
|
|
839
|
+
parsePositiveInteger(configResolution?.config?.llmTimeoutMs || configResolution?.config?.timeoutMs, slowLive ? 180000 : 120000)
|
|
840
|
+
),
|
|
841
|
+
llmImageLimit: parsePositiveInteger(
|
|
842
|
+
args.llm_image_limit,
|
|
843
|
+
parsePositiveInteger(configResolution?.config?.llmImageLimit || configResolution?.config?.imageLimit, 8)
|
|
844
|
+
),
|
|
845
|
+
llmImageDetail: normalizeText(
|
|
846
|
+
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
847
|
+
) || "low",
|
|
840
848
|
imageOutputDir: resolveBossConfiguredOutputDir("", getRecruitRunsDir()),
|
|
841
849
|
name: "mcp-recruit-pipeline-run"
|
|
842
850
|
};
|