@reconcrap/boss-recommend-mcp 2.0.11 → 2.0.13
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 +256 -23
- package/src/cli.js +2 -8
- package/src/core/browser/index.js +35 -8
- package/src/core/capture/index.js +323 -20
- package/src/core/cv-acquisition/index.js +3 -0
- package/src/core/infinite-list/index.js +354 -1
- package/src/domains/chat/constants.js +11 -0
- package/src/domains/chat/run-service.js +21 -5
- package/src/domains/recommend/constants.js +11 -0
- package/src/domains/recommend/run-service.js +57 -16
- package/src/domains/recruit/constants.js +23 -0
- package/src/domains/recruit/run-service.js +19 -4
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getAttributesMap,
|
|
7
7
|
getNodeBox,
|
|
8
8
|
getOuterHTML,
|
|
9
|
+
querySelectorAll,
|
|
9
10
|
sleep
|
|
10
11
|
} from "../browser/index.js";
|
|
11
12
|
import {
|
|
@@ -158,6 +159,185 @@ function screenshotHash(buffer) {
|
|
|
158
159
|
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
function createCaptureTimeoutError(label, timeoutMs) {
|
|
163
|
+
const error = new Error(`Image fallback capture timed out during ${label} after ${timeoutMs}ms`);
|
|
164
|
+
error.code = "IMAGE_CAPTURE_TIMEOUT";
|
|
165
|
+
error.capture_step = label;
|
|
166
|
+
error.timeout_ms = timeoutMs;
|
|
167
|
+
return error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function withCaptureTimeout(promise, {
|
|
171
|
+
label = "capture_step",
|
|
172
|
+
timeoutMs = 0
|
|
173
|
+
} = {}) {
|
|
174
|
+
const safeTimeout = Math.max(0, Number(timeoutMs) || 0);
|
|
175
|
+
if (!safeTimeout) return promise;
|
|
176
|
+
let timer = null;
|
|
177
|
+
try {
|
|
178
|
+
return await Promise.race([
|
|
179
|
+
promise,
|
|
180
|
+
new Promise((_, reject) => {
|
|
181
|
+
timer = setTimeout(() => reject(createCaptureTimeoutError(label, safeTimeout)), safeTimeout);
|
|
182
|
+
})
|
|
183
|
+
]);
|
|
184
|
+
} finally {
|
|
185
|
+
if (timer) clearTimeout(timer);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function assertCaptureTotalBudget(started, totalTimeoutMs, label) {
|
|
190
|
+
const safeTimeout = Math.max(0, Number(totalTimeoutMs) || 0);
|
|
191
|
+
if (!safeTimeout) return;
|
|
192
|
+
const elapsed = Date.now() - started;
|
|
193
|
+
if (elapsed <= safeTimeout) return;
|
|
194
|
+
const error = createCaptureTimeoutError(label, safeTimeout);
|
|
195
|
+
error.elapsed_ms = elapsed;
|
|
196
|
+
error.code = "IMAGE_CAPTURE_TOTAL_TIMEOUT";
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const DEFAULT_SCROLL_ANCHOR_SELECTOR = [
|
|
201
|
+
"h1",
|
|
202
|
+
"h2",
|
|
203
|
+
"h3",
|
|
204
|
+
"h4",
|
|
205
|
+
"h5",
|
|
206
|
+
"p",
|
|
207
|
+
"li",
|
|
208
|
+
"section",
|
|
209
|
+
"article",
|
|
210
|
+
"table",
|
|
211
|
+
"tr",
|
|
212
|
+
"dl",
|
|
213
|
+
"dt",
|
|
214
|
+
"dd",
|
|
215
|
+
"[class*='resume']",
|
|
216
|
+
"[class*='work']",
|
|
217
|
+
"[class*='project']",
|
|
218
|
+
"[class*='education']",
|
|
219
|
+
"[class*='experience']",
|
|
220
|
+
"[class*='item']",
|
|
221
|
+
"div"
|
|
222
|
+
].join(",");
|
|
223
|
+
|
|
224
|
+
function normalizeScrollMethod(value = "dom-anchor-fallback-input") {
|
|
225
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
226
|
+
if (["dom", "dom-anchor", "dom_anchor", "anchor"].includes(normalized)) return "dom-anchor";
|
|
227
|
+
if (["dom-anchor-fallback-input", "dom_anchor_fallback_input", "dom-fallback-input"].includes(normalized)) {
|
|
228
|
+
return "dom-anchor-fallback-input";
|
|
229
|
+
}
|
|
230
|
+
return "input";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function uniqueNumbers(values = []) {
|
|
234
|
+
return Array.from(new Set(values.map((value) => Number(value) || 0).filter(Boolean)));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function pickEvenly(items = [], limit = 1) {
|
|
238
|
+
const safeLimit = Math.max(1, Number(limit) || 1);
|
|
239
|
+
if (items.length <= safeLimit) return items;
|
|
240
|
+
const picked = [];
|
|
241
|
+
const last = items.length - 1;
|
|
242
|
+
for (let index = 0; index < safeLimit; index += 1) {
|
|
243
|
+
const sourceIndex = Math.round((index * last) / Math.max(1, safeLimit - 1));
|
|
244
|
+
picked.push(items[sourceIndex]);
|
|
245
|
+
}
|
|
246
|
+
return Array.from(new Map(picked.map((item) => [item.node_id, item])).values());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function collectDomScrollAnchors(client, rootNodeId, {
|
|
250
|
+
selector = DEFAULT_SCROLL_ANCHOR_SELECTOR,
|
|
251
|
+
maxScreenshots = 6,
|
|
252
|
+
maxProbeNodes = 260,
|
|
253
|
+
minAnchorGap = 180,
|
|
254
|
+
stepTimeoutMs = 45000
|
|
255
|
+
} = {}) {
|
|
256
|
+
const started = Date.now();
|
|
257
|
+
let nodeIds = [];
|
|
258
|
+
try {
|
|
259
|
+
nodeIds = uniqueNumbers(await querySelectorAll(client, rootNodeId, selector));
|
|
260
|
+
} catch (error) {
|
|
261
|
+
return {
|
|
262
|
+
ok: false,
|
|
263
|
+
method: "dom-anchor",
|
|
264
|
+
reason: "query_selector_all_failed",
|
|
265
|
+
error: error?.message || String(error)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (!nodeIds.length) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
method: "dom-anchor",
|
|
272
|
+
reason: "no_anchor_nodes"
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const probeLimit = Math.max(1, Number(maxProbeNodes) || 260);
|
|
277
|
+
const perNodeTimeoutMs = Math.min(1200, Math.max(250, Math.floor((Number(stepTimeoutMs) || 45000) / 30)));
|
|
278
|
+
const measured = [];
|
|
279
|
+
for (const nodeId of nodeIds.slice(0, probeLimit)) {
|
|
280
|
+
try {
|
|
281
|
+
const box = await withCaptureTimeout(getNodeBox(client, nodeId), {
|
|
282
|
+
label: `anchor_box_${nodeId}`,
|
|
283
|
+
timeoutMs: perNodeTimeoutMs
|
|
284
|
+
});
|
|
285
|
+
const rect = box?.rect || {};
|
|
286
|
+
if ((Number(rect.width) || 0) < 80 || (Number(rect.height) || 0) < 8) continue;
|
|
287
|
+
measured.push({
|
|
288
|
+
node_id: nodeId,
|
|
289
|
+
y: Math.round(Number(rect.y) || 0),
|
|
290
|
+
height: Math.round(Number(rect.height) || 0)
|
|
291
|
+
});
|
|
292
|
+
} catch {}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let anchors = [];
|
|
296
|
+
if (measured.length) {
|
|
297
|
+
const sorted = measured.sort((a, b) => a.y - b.y);
|
|
298
|
+
for (const item of sorted) {
|
|
299
|
+
const last = anchors[anchors.length - 1];
|
|
300
|
+
if (!last || Math.abs(item.y - last.y) >= Math.max(40, Number(minAnchorGap) || 180)) {
|
|
301
|
+
anchors.push(item);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (anchors.length < 2) {
|
|
307
|
+
anchors = nodeIds.slice(0, probeLimit).map((nodeId, index) => ({
|
|
308
|
+
node_id: nodeId,
|
|
309
|
+
y: null,
|
|
310
|
+
height: null,
|
|
311
|
+
document_order: index
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
anchors = pickEvenly(anchors, Math.max(1, Number(maxScreenshots) || 1));
|
|
316
|
+
return {
|
|
317
|
+
ok: anchors.length > 0,
|
|
318
|
+
method: "dom-anchor",
|
|
319
|
+
elapsed_ms: Date.now() - started,
|
|
320
|
+
selector,
|
|
321
|
+
discovered_node_count: nodeIds.length,
|
|
322
|
+
measured_node_count: measured.length,
|
|
323
|
+
anchor_count: anchors.length,
|
|
324
|
+
anchors
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function scrollDomAnchorIntoView(client, nodeId, {
|
|
329
|
+
timeoutMs = 10000,
|
|
330
|
+
label = "dom_scroll_anchor"
|
|
331
|
+
} = {}) {
|
|
332
|
+
if (client.DOM && typeof client.DOM.scrollIntoViewIfNeeded === "function") {
|
|
333
|
+
return withCaptureTimeout(client.DOM.scrollIntoViewIfNeeded({ nodeId }), { label, timeoutMs });
|
|
334
|
+
}
|
|
335
|
+
if (typeof client.send === "function") {
|
|
336
|
+
return withCaptureTimeout(client.send("DOM.scrollIntoViewIfNeeded", { nodeId }), { label, timeoutMs });
|
|
337
|
+
}
|
|
338
|
+
throw new Error("CDP client does not expose DOM.scrollIntoViewIfNeeded");
|
|
339
|
+
}
|
|
340
|
+
|
|
161
341
|
async function optimizeScreenshotBuffer(buffer, {
|
|
162
342
|
enabled = false,
|
|
163
343
|
format = "png",
|
|
@@ -339,20 +519,83 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
339
519
|
llmPagesPerImage = 3,
|
|
340
520
|
llmResizeMaxWidth = 1100,
|
|
341
521
|
llmQuality = 72,
|
|
522
|
+
stepTimeoutMs = 45000,
|
|
523
|
+
totalTimeoutMs = 90000,
|
|
524
|
+
scrollMethod = "dom-anchor-fallback-input",
|
|
525
|
+
scrollAnchorSelector = DEFAULT_SCROLL_ANCHOR_SELECTOR,
|
|
526
|
+
scrollAnchorMaxProbeNodes = 260,
|
|
527
|
+
scrollAnchorMinGap = 180,
|
|
342
528
|
metadata = {}
|
|
343
529
|
} = {}) {
|
|
344
530
|
if (!nodeId) throw new Error("captureScrolledNodeScreenshots requires nodeId");
|
|
345
531
|
const sequenceStarted = Date.now();
|
|
532
|
+
const normalizedScrollMethod = normalizeScrollMethod(scrollMethod);
|
|
533
|
+
const maxScreenshotCount = Math.max(1, Number(maxScreenshots) || 1);
|
|
534
|
+
const anchorPlan = normalizedScrollMethod !== "input"
|
|
535
|
+
? await collectDomScrollAnchors(client, nodeId, {
|
|
536
|
+
selector: scrollAnchorSelector,
|
|
537
|
+
maxScreenshots: maxScreenshotCount,
|
|
538
|
+
maxProbeNodes: scrollAnchorMaxProbeNodes,
|
|
539
|
+
minAnchorGap: scrollAnchorMinGap,
|
|
540
|
+
stepTimeoutMs
|
|
541
|
+
})
|
|
542
|
+
: null;
|
|
346
543
|
const screenshots = [];
|
|
347
544
|
let consecutiveDuplicates = 0;
|
|
348
545
|
let previousHash = "";
|
|
349
546
|
let captureCount = 0;
|
|
350
547
|
let droppedDuplicateCount = 0;
|
|
548
|
+
let currentScrollMetadata = {
|
|
549
|
+
before_capture: "initial",
|
|
550
|
+
method: normalizedScrollMethod,
|
|
551
|
+
anchor_plan: anchorPlan
|
|
552
|
+
? {
|
|
553
|
+
ok: Boolean(anchorPlan.ok),
|
|
554
|
+
reason: anchorPlan.reason || null,
|
|
555
|
+
discovered_node_count: anchorPlan.discovered_node_count || 0,
|
|
556
|
+
measured_node_count: anchorPlan.measured_node_count || 0,
|
|
557
|
+
anchor_count: anchorPlan.anchor_count || 0,
|
|
558
|
+
elapsed_ms: anchorPlan.elapsed_ms || 0
|
|
559
|
+
}
|
|
560
|
+
: null
|
|
561
|
+
};
|
|
351
562
|
|
|
352
|
-
|
|
563
|
+
if (anchorPlan?.anchors?.[0]?.node_id && normalizedScrollMethod !== "input") {
|
|
564
|
+
try {
|
|
565
|
+
await scrollDomAnchorIntoView(client, anchorPlan.anchors[0].node_id, {
|
|
566
|
+
label: "scroll_dom_anchor_initial",
|
|
567
|
+
timeoutMs: Math.min(Math.max(3000, Number(stepTimeoutMs) || 45000), 10000)
|
|
568
|
+
});
|
|
569
|
+
currentScrollMetadata = {
|
|
570
|
+
before_capture: "dom_anchor_initial",
|
|
571
|
+
method: "DOM.scrollIntoViewIfNeeded",
|
|
572
|
+
anchor_node_id: anchorPlan.anchors[0].node_id,
|
|
573
|
+
anchor_y: anchorPlan.anchors[0].y,
|
|
574
|
+
anchor_height: anchorPlan.anchors[0].height,
|
|
575
|
+
anchor_plan: currentScrollMetadata.anchor_plan
|
|
576
|
+
};
|
|
577
|
+
} catch (error) {
|
|
578
|
+
if (normalizedScrollMethod === "dom-anchor") {
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
currentScrollMetadata = {
|
|
582
|
+
before_capture: "dom_anchor_initial_failed",
|
|
583
|
+
method: "DOM.scrollIntoViewIfNeeded",
|
|
584
|
+
anchor_node_id: anchorPlan.anchors[0].node_id,
|
|
585
|
+
error: error?.message || String(error),
|
|
586
|
+
anchor_plan: currentScrollMetadata.anchor_plan
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
for (let index = 0; index < maxScreenshotCount; index += 1) {
|
|
592
|
+
assertCaptureTotalBudget(sequenceStarted, totalTimeoutMs, `capture_page_${index + 1}`);
|
|
353
593
|
captureCount += 1;
|
|
354
594
|
const captureStarted = Date.now();
|
|
355
|
-
const box = await getNodeBox(client, nodeId)
|
|
595
|
+
const box = await withCaptureTimeout(getNodeBox(client, nodeId), {
|
|
596
|
+
label: `get_box_${index + 1}`,
|
|
597
|
+
timeoutMs: stepTimeoutMs
|
|
598
|
+
});
|
|
356
599
|
const clip = withPadding(box.rect, padding);
|
|
357
600
|
const captureOptions = captureViewport ? {
|
|
358
601
|
format,
|
|
@@ -367,13 +610,19 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
367
610
|
if (quality != null) {
|
|
368
611
|
captureOptions.quality = quality;
|
|
369
612
|
}
|
|
370
|
-
const screenshot = await client.Page.captureScreenshot(captureOptions)
|
|
613
|
+
const screenshot = await withCaptureTimeout(client.Page.captureScreenshot(captureOptions), {
|
|
614
|
+
label: `capture_screenshot_${index + 1}`,
|
|
615
|
+
timeoutMs: stepTimeoutMs
|
|
616
|
+
});
|
|
371
617
|
const originalBuffer = Buffer.from(screenshot.data || "", "base64");
|
|
372
|
-
const optimized = await optimizeScreenshotBuffer(originalBuffer, {
|
|
618
|
+
const optimized = await withCaptureTimeout(optimizeScreenshotBuffer(originalBuffer, {
|
|
373
619
|
enabled: optimize,
|
|
374
620
|
format,
|
|
375
621
|
quality,
|
|
376
622
|
resizeMaxWidth
|
|
623
|
+
}), {
|
|
624
|
+
label: `optimize_screenshot_${index + 1}`,
|
|
625
|
+
timeoutMs: stepTimeoutMs
|
|
377
626
|
});
|
|
378
627
|
const buffer = optimized.buffer;
|
|
379
628
|
const hash = screenshotHash(buffer);
|
|
@@ -412,9 +661,7 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
412
661
|
clip,
|
|
413
662
|
capture_viewport: Boolean(captureViewport),
|
|
414
663
|
node_rect: box.rect,
|
|
415
|
-
scroll:
|
|
416
|
-
? { before_capture: "initial" }
|
|
417
|
-
: { before_capture: `wheel_down_${index}` },
|
|
664
|
+
scroll: currentScrollMetadata,
|
|
418
665
|
metadata
|
|
419
666
|
});
|
|
420
667
|
}
|
|
@@ -424,27 +671,75 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
424
671
|
break;
|
|
425
672
|
}
|
|
426
673
|
|
|
427
|
-
if (index <
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
674
|
+
if (index < maxScreenshotCount - 1) {
|
|
675
|
+
assertCaptureTotalBudget(sequenceStarted, totalTimeoutMs, `scroll_after_page_${index + 1}`);
|
|
676
|
+
let scrolledByDomAnchor = false;
|
|
677
|
+
const nextAnchor = anchorPlan?.anchors?.[index + 1] || null;
|
|
678
|
+
if (nextAnchor?.node_id && normalizedScrollMethod !== "input") {
|
|
679
|
+
try {
|
|
680
|
+
await scrollDomAnchorIntoView(client, nextAnchor.node_id, {
|
|
681
|
+
label: `scroll_dom_anchor_${index + 1}`,
|
|
682
|
+
timeoutMs: Math.min(Math.max(3000, Number(stepTimeoutMs) || 45000), 10000)
|
|
683
|
+
});
|
|
684
|
+
scrolledByDomAnchor = true;
|
|
685
|
+
currentScrollMetadata = {
|
|
686
|
+
before_capture: `dom_anchor_${index + 1}`,
|
|
687
|
+
method: "DOM.scrollIntoViewIfNeeded",
|
|
688
|
+
anchor_node_id: nextAnchor.node_id,
|
|
689
|
+
anchor_y: nextAnchor.y,
|
|
690
|
+
anchor_height: nextAnchor.height
|
|
691
|
+
};
|
|
692
|
+
} catch (error) {
|
|
693
|
+
if (normalizedScrollMethod === "dom-anchor") {
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
696
|
+
currentScrollMetadata = {
|
|
697
|
+
before_capture: `dom_anchor_${index + 1}_failed`,
|
|
698
|
+
method: "DOM.scrollIntoViewIfNeeded",
|
|
699
|
+
anchor_node_id: nextAnchor.node_id,
|
|
700
|
+
error: error?.message || String(error)
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
} else if (normalizedScrollMethod === "dom-anchor") {
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (!scrolledByDomAnchor && normalizedScrollMethod !== "dom-anchor") {
|
|
708
|
+
const x = box.center.x;
|
|
709
|
+
const y = box.center.y;
|
|
710
|
+
await withCaptureTimeout(client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" }), {
|
|
711
|
+
label: `scroll_mouse_move_${index + 1}`,
|
|
712
|
+
timeoutMs: Math.min(Math.max(3000, Number(stepTimeoutMs) || 45000), 10000)
|
|
713
|
+
});
|
|
714
|
+
await withCaptureTimeout(client.Input.dispatchMouseEvent({
|
|
715
|
+
type: "mouseWheel",
|
|
716
|
+
x,
|
|
717
|
+
y,
|
|
718
|
+
deltaX: 0,
|
|
719
|
+
deltaY: Math.max(1, Number(wheelDeltaY) || 650)
|
|
720
|
+
}), {
|
|
721
|
+
label: `scroll_wheel_${index + 1}`,
|
|
722
|
+
timeoutMs: Math.min(Math.max(3000, Number(stepTimeoutMs) || 45000), 10000)
|
|
723
|
+
});
|
|
724
|
+
currentScrollMetadata = {
|
|
725
|
+
before_capture: `wheel_down_${index + 1}`,
|
|
726
|
+
method: "Input.dispatchMouseEvent",
|
|
727
|
+
fallback_from_dom_anchor: Boolean(anchorPlan && normalizedScrollMethod === "dom-anchor-fallback-input")
|
|
728
|
+
};
|
|
729
|
+
}
|
|
438
730
|
if (settleMs > 0) await sleep(settleMs);
|
|
439
731
|
}
|
|
440
732
|
}
|
|
441
733
|
|
|
442
734
|
const llmComposition = composeForLlm
|
|
443
|
-
? await composeScreenshotsForLlm(screenshots, {
|
|
735
|
+
? await withCaptureTimeout(composeScreenshotsForLlm(screenshots, {
|
|
444
736
|
basePath: filePath,
|
|
445
737
|
pagesPerImage: llmPagesPerImage,
|
|
446
738
|
resizeMaxWidth: llmResizeMaxWidth,
|
|
447
739
|
quality: llmQuality
|
|
740
|
+
}), {
|
|
741
|
+
label: "compose_llm_screenshots",
|
|
742
|
+
timeoutMs: stepTimeoutMs
|
|
448
743
|
})
|
|
449
744
|
: {
|
|
450
745
|
llm_file_paths: screenshots.map((item) => item.file_path).filter(Boolean),
|
|
@@ -456,6 +751,7 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
456
751
|
|
|
457
752
|
return {
|
|
458
753
|
schema_version: 1,
|
|
754
|
+
ok: true,
|
|
459
755
|
source: "image-scroll-sequence",
|
|
460
756
|
captured_at: nowIso(),
|
|
461
757
|
node_id: nodeId,
|
|
@@ -482,8 +778,15 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
482
778
|
llm_compose_enabled: Boolean(composeForLlm),
|
|
483
779
|
llm_pages_per_image: Math.max(1, Math.min(5, Number(llmPagesPerImage) || 3)),
|
|
484
780
|
llm_resize_max_width: Math.max(0, Number(llmResizeMaxWidth) || 0),
|
|
485
|
-
llm_quality: llmQuality ?? null
|
|
781
|
+
llm_quality: llmQuality ?? null,
|
|
782
|
+
step_timeout_ms: Math.max(0, Number(stepTimeoutMs) || 0),
|
|
783
|
+
total_timeout_ms: Math.max(0, Number(totalTimeoutMs) || 0),
|
|
784
|
+
scroll_method: normalizedScrollMethod,
|
|
785
|
+
scroll_anchor_selector: scrollAnchorSelector,
|
|
786
|
+
scroll_anchor_max_probe_nodes: Math.max(1, Number(scrollAnchorMaxProbeNodes) || 260),
|
|
787
|
+
scroll_anchor_min_gap: Math.max(0, Number(scrollAnchorMinGap) || 0)
|
|
486
788
|
},
|
|
789
|
+
scroll_anchor_plan: anchorPlan,
|
|
487
790
|
file_paths: screenshots.map((item) => item.file_path).filter(Boolean),
|
|
488
791
|
screenshots,
|
|
489
792
|
metadata
|
|
@@ -124,6 +124,7 @@ export function hasParsedNetworkProfile(detailResult = {}) {
|
|
|
124
124
|
export function summarizeImageEvidence(imageEvidence = null) {
|
|
125
125
|
if (!imageEvidence) return null;
|
|
126
126
|
return {
|
|
127
|
+
ok: imageEvidence.ok !== false,
|
|
127
128
|
source: imageEvidence.source || "",
|
|
128
129
|
elapsed_ms: imageEvidence.elapsed_ms || 0,
|
|
129
130
|
capture_count: imageEvidence.capture_count || imageEvidence.screenshot_count || 0,
|
|
@@ -137,6 +138,8 @@ export function summarizeImageEvidence(imageEvidence = null) {
|
|
|
137
138
|
llm_original_total_byte_length: imageEvidence.llm_original_total_byte_length || 0,
|
|
138
139
|
llm_composition_error: imageEvidence.llm_composition_error || null,
|
|
139
140
|
optimization: imageEvidence.optimization || null,
|
|
141
|
+
error_code: imageEvidence.error_code || imageEvidence.code || null,
|
|
142
|
+
error: imageEvidence.error || null,
|
|
140
143
|
file_paths: imageEvidence.file_paths || [],
|
|
141
144
|
llm_file_paths: imageEvidence.llm_file_paths || [],
|
|
142
145
|
first_clip: imageEvidence.screenshots?.[0]?.clip || imageEvidence.clip || null
|