@reconcrap/boss-recommend-mcp 2.0.17 → 2.0.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/chat-mcp.js +13 -1
- package/src/core/capture/index.js +264 -12
- package/src/core/cv-acquisition/index.js +4 -0
- package/src/domains/chat/jobs.js +170 -7
- package/src/domains/chat/run-service.js +198 -20
package/package.json
CHANGED
package/src/chat-mcp.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import {
|
|
35
35
|
CHAT_TARGET_URL,
|
|
36
36
|
closeChatResumeModal,
|
|
37
|
+
closeChatJobDropdown,
|
|
37
38
|
createChatRunService,
|
|
38
39
|
getChatRoots,
|
|
39
40
|
isForbiddenChatResumeTopLevelUrl,
|
|
@@ -833,7 +834,18 @@ async function connectChatChromeSession({
|
|
|
833
834
|
|
|
834
835
|
async function readChatJobOptionsFromSession(session) {
|
|
835
836
|
const roots = await getChatRoots(session.client);
|
|
836
|
-
|
|
837
|
+
const result = await readChatJobOptions(session.client, roots.rootNodes.top);
|
|
838
|
+
try {
|
|
839
|
+
result.menu_close = await closeChatJobDropdown(session.client, roots.rootNodes.top);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
result.menu_close = {
|
|
842
|
+
ok: false,
|
|
843
|
+
closed: false,
|
|
844
|
+
reason: "close_failed",
|
|
845
|
+
error: error?.message || String(error)
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
return result;
|
|
837
849
|
}
|
|
838
850
|
|
|
839
851
|
function normalizeChatStartInput(args = {}, configResolution = null) {
|
|
@@ -246,6 +246,190 @@ function pickEvenly(items = [], limit = 1) {
|
|
|
246
246
|
return Array.from(new Map(picked.map((item) => [item.node_id, item])).values());
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
function patternLabel(pattern) {
|
|
250
|
+
if (pattern instanceof RegExp) return pattern.source;
|
|
251
|
+
return normalizeText(pattern);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function stopBoundaryPatterns(patterns = []) {
|
|
255
|
+
return (Array.isArray(patterns) ? patterns : [patterns])
|
|
256
|
+
.filter(Boolean)
|
|
257
|
+
.map((pattern) => {
|
|
258
|
+
if (pattern instanceof RegExp) {
|
|
259
|
+
return {
|
|
260
|
+
raw: pattern,
|
|
261
|
+
label: pattern.source,
|
|
262
|
+
matches: (text) => pattern.test(text)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const normalized = normalizeText(pattern);
|
|
266
|
+
return {
|
|
267
|
+
raw: pattern,
|
|
268
|
+
label: normalized,
|
|
269
|
+
matches: (text) => normalized && text.includes(normalized)
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function collectStopBoundaryNodes(client, rootNodeId, {
|
|
275
|
+
selector = "",
|
|
276
|
+
textPatterns = [],
|
|
277
|
+
maxProbeNodes = 180,
|
|
278
|
+
maxTextLength = 700,
|
|
279
|
+
stepTimeoutMs = 45000
|
|
280
|
+
} = {}) {
|
|
281
|
+
const patterns = stopBoundaryPatterns(textPatterns);
|
|
282
|
+
const normalizedSelector = normalizeText(selector);
|
|
283
|
+
if (!normalizedSelector && !patterns.length) {
|
|
284
|
+
return {
|
|
285
|
+
enabled: false,
|
|
286
|
+
ok: false,
|
|
287
|
+
reason: "not_configured",
|
|
288
|
+
nodes: []
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const started = Date.now();
|
|
292
|
+
let nodeIds = [];
|
|
293
|
+
try {
|
|
294
|
+
nodeIds = uniqueNumbers(await querySelectorAll(
|
|
295
|
+
client,
|
|
296
|
+
rootNodeId,
|
|
297
|
+
normalizedSelector || DEFAULT_SCROLL_ANCHOR_SELECTOR
|
|
298
|
+
));
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return {
|
|
301
|
+
enabled: true,
|
|
302
|
+
ok: false,
|
|
303
|
+
reason: "query_selector_all_failed",
|
|
304
|
+
selector: normalizedSelector || DEFAULT_SCROLL_ANCHOR_SELECTOR,
|
|
305
|
+
error: error?.message || String(error),
|
|
306
|
+
nodes: []
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const probeLimit = Math.max(1, Number(maxProbeNodes) || 180);
|
|
311
|
+
const maxStopTextLength = Math.max(40, Number(maxTextLength) || 700);
|
|
312
|
+
const perNodeTimeoutMs = Math.min(1000, Math.max(200, Math.floor((Number(stepTimeoutMs) || 45000) / 40)));
|
|
313
|
+
const nodes = [];
|
|
314
|
+
for (const nodeId of nodeIds.slice(0, probeLimit)) {
|
|
315
|
+
try {
|
|
316
|
+
let text = "";
|
|
317
|
+
let matchedPattern = null;
|
|
318
|
+
if (patterns.length) {
|
|
319
|
+
const outerHTML = await withCaptureTimeout(getOuterHTML(client, nodeId), {
|
|
320
|
+
label: `stop_boundary_html_${nodeId}`,
|
|
321
|
+
timeoutMs: perNodeTimeoutMs
|
|
322
|
+
});
|
|
323
|
+
text = normalizeText(htmlToText(outerHTML));
|
|
324
|
+
if (!text || text.length > maxStopTextLength) continue;
|
|
325
|
+
matchedPattern = patterns.find((pattern) => pattern.matches(text));
|
|
326
|
+
if (!matchedPattern) continue;
|
|
327
|
+
}
|
|
328
|
+
nodes.push({
|
|
329
|
+
node_id: nodeId,
|
|
330
|
+
text_preview: text.slice(0, 120),
|
|
331
|
+
matched_pattern: matchedPattern ? patternLabel(matchedPattern.raw) : null
|
|
332
|
+
});
|
|
333
|
+
} catch {}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
enabled: true,
|
|
338
|
+
ok: nodes.length > 0,
|
|
339
|
+
reason: nodes.length ? null : "no_matching_stop_boundary_nodes",
|
|
340
|
+
selector: normalizedSelector || DEFAULT_SCROLL_ANCHOR_SELECTOR,
|
|
341
|
+
elapsed_ms: Date.now() - started,
|
|
342
|
+
discovered_node_count: nodeIds.length,
|
|
343
|
+
probed_node_count: Math.min(nodeIds.length, probeLimit),
|
|
344
|
+
match_count: nodes.length,
|
|
345
|
+
pattern_labels: patterns.map((pattern) => pattern.label),
|
|
346
|
+
nodes
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function resolveVisibleStopBoundary(client, stopBoundaryPlan, clip, {
|
|
351
|
+
topPadding = 8,
|
|
352
|
+
minCaptureHeight = 180,
|
|
353
|
+
stepTimeoutMs = 45000
|
|
354
|
+
} = {}) {
|
|
355
|
+
if (!stopBoundaryPlan?.nodes?.length || !clip) return null;
|
|
356
|
+
const clipTop = Number(clip.y) || 0;
|
|
357
|
+
const clipBottom = clipTop + (Number(clip.height) || 0);
|
|
358
|
+
const safePadding = Math.max(0, Number(topPadding) || 0);
|
|
359
|
+
const safeMinHeight = Math.max(1, Number(minCaptureHeight) || 180);
|
|
360
|
+
const perNodeTimeoutMs = Math.min(900, Math.max(180, Math.floor((Number(stepTimeoutMs) || 45000) / 50)));
|
|
361
|
+
const visible = [];
|
|
362
|
+
|
|
363
|
+
for (const node of stopBoundaryPlan.nodes) {
|
|
364
|
+
try {
|
|
365
|
+
const box = await withCaptureTimeout(getNodeBox(client, node.node_id), {
|
|
366
|
+
label: `stop_boundary_box_${node.node_id}`,
|
|
367
|
+
timeoutMs: perNodeTimeoutMs
|
|
368
|
+
});
|
|
369
|
+
const rect = box?.rect || {};
|
|
370
|
+
const width = Number(rect.width) || 0;
|
|
371
|
+
const height = Number(rect.height) || 0;
|
|
372
|
+
if (width < 40 || height < 6) continue;
|
|
373
|
+
const top = Number(rect.y) || 0;
|
|
374
|
+
const bottom = top + height;
|
|
375
|
+
if (bottom <= clipTop + 1) {
|
|
376
|
+
return {
|
|
377
|
+
action: "stop_before_capture",
|
|
378
|
+
reason: "stop_boundary_above_clip",
|
|
379
|
+
node_id: node.node_id,
|
|
380
|
+
matched_pattern: node.matched_pattern,
|
|
381
|
+
text_preview: node.text_preview,
|
|
382
|
+
rect,
|
|
383
|
+
clip
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (top < clipBottom && bottom > clipTop) {
|
|
387
|
+
visible.push({
|
|
388
|
+
...node,
|
|
389
|
+
rect,
|
|
390
|
+
top,
|
|
391
|
+
bottom
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
} catch {}
|
|
395
|
+
}
|
|
396
|
+
if (!visible.length) return null;
|
|
397
|
+
|
|
398
|
+
visible.sort((a, b) => a.top - b.top);
|
|
399
|
+
const boundary = visible[0];
|
|
400
|
+
const boundaryY = Math.max(clipTop, boundary.top - safePadding);
|
|
401
|
+
const adjustedHeight = Math.max(0, boundaryY - clipTop);
|
|
402
|
+
if (adjustedHeight < safeMinHeight) {
|
|
403
|
+
return {
|
|
404
|
+
action: "stop_before_capture",
|
|
405
|
+
reason: "stop_boundary_near_clip_top",
|
|
406
|
+
node_id: boundary.node_id,
|
|
407
|
+
matched_pattern: boundary.matched_pattern,
|
|
408
|
+
text_preview: boundary.text_preview,
|
|
409
|
+
rect: boundary.rect,
|
|
410
|
+
clip,
|
|
411
|
+
adjusted_height: adjustedHeight,
|
|
412
|
+
min_capture_height: safeMinHeight
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
action: "capture_then_stop",
|
|
418
|
+
reason: "stop_boundary_visible",
|
|
419
|
+
node_id: boundary.node_id,
|
|
420
|
+
matched_pattern: boundary.matched_pattern,
|
|
421
|
+
text_preview: boundary.text_preview,
|
|
422
|
+
rect: boundary.rect,
|
|
423
|
+
clip,
|
|
424
|
+
adjusted_clip: {
|
|
425
|
+
...clip,
|
|
426
|
+
height: adjustedHeight
|
|
427
|
+
},
|
|
428
|
+
adjusted_height: adjustedHeight,
|
|
429
|
+
min_capture_height: safeMinHeight
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
249
433
|
async function collectDomScrollAnchors(client, rootNodeId, {
|
|
250
434
|
selector = DEFAULT_SCROLL_ANCHOR_SELECTOR,
|
|
251
435
|
maxScreenshots = 6,
|
|
@@ -525,6 +709,12 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
525
709
|
scrollAnchorSelector = DEFAULT_SCROLL_ANCHOR_SELECTOR,
|
|
526
710
|
scrollAnchorMaxProbeNodes = 260,
|
|
527
711
|
scrollAnchorMinGap = 180,
|
|
712
|
+
stopBoundarySelector = "",
|
|
713
|
+
stopBoundaryTextPatterns = [],
|
|
714
|
+
stopBoundaryMaxProbeNodes = 180,
|
|
715
|
+
stopBoundaryMaxTextLength = 700,
|
|
716
|
+
stopBoundaryTopPadding = 8,
|
|
717
|
+
stopBoundaryMinCaptureHeight = 180,
|
|
528
718
|
metadata = {}
|
|
529
719
|
} = {}) {
|
|
530
720
|
if (!nodeId) throw new Error("captureScrolledNodeScreenshots requires nodeId");
|
|
@@ -540,11 +730,26 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
540
730
|
stepTimeoutMs
|
|
541
731
|
})
|
|
542
732
|
: null;
|
|
733
|
+
const stopBoundaryEnabled = Boolean(
|
|
734
|
+
normalizeText(stopBoundarySelector)
|
|
735
|
+
|| (Array.isArray(stopBoundaryTextPatterns)
|
|
736
|
+
? stopBoundaryTextPatterns.length
|
|
737
|
+
: stopBoundaryTextPatterns)
|
|
738
|
+
);
|
|
739
|
+
let stopBoundaryPlan = {
|
|
740
|
+
enabled: false,
|
|
741
|
+
ok: false,
|
|
742
|
+
reason: "not_configured",
|
|
743
|
+
nodes: []
|
|
744
|
+
};
|
|
745
|
+
const stopBoundaryChecks = [];
|
|
543
746
|
const screenshots = [];
|
|
544
747
|
let consecutiveDuplicates = 0;
|
|
545
748
|
let previousHash = "";
|
|
546
749
|
let captureCount = 0;
|
|
547
750
|
let droppedDuplicateCount = 0;
|
|
751
|
+
let forceInputScrollAfterDuplicate = false;
|
|
752
|
+
let stopBoundaryResult = null;
|
|
548
753
|
let currentScrollMetadata = {
|
|
549
754
|
before_capture: "initial",
|
|
550
755
|
method: normalizedScrollMethod,
|
|
@@ -597,7 +802,37 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
597
802
|
timeoutMs: stepTimeoutMs
|
|
598
803
|
});
|
|
599
804
|
const clip = withPadding(box.rect, padding);
|
|
600
|
-
|
|
805
|
+
let visibleStopBoundary = null;
|
|
806
|
+
if (stopBoundaryEnabled) {
|
|
807
|
+
stopBoundaryPlan = await collectStopBoundaryNodes(client, nodeId, {
|
|
808
|
+
selector: stopBoundarySelector,
|
|
809
|
+
textPatterns: stopBoundaryTextPatterns,
|
|
810
|
+
maxProbeNodes: stopBoundaryMaxProbeNodes,
|
|
811
|
+
maxTextLength: stopBoundaryMaxTextLength,
|
|
812
|
+
stepTimeoutMs
|
|
813
|
+
});
|
|
814
|
+
stopBoundaryChecks.push({
|
|
815
|
+
capture_index: index,
|
|
816
|
+
ok: Boolean(stopBoundaryPlan.ok),
|
|
817
|
+
reason: stopBoundaryPlan.reason || null,
|
|
818
|
+
discovered_node_count: stopBoundaryPlan.discovered_node_count || 0,
|
|
819
|
+
probed_node_count: stopBoundaryPlan.probed_node_count || 0,
|
|
820
|
+
match_count: stopBoundaryPlan.match_count || 0,
|
|
821
|
+
elapsed_ms: stopBoundaryPlan.elapsed_ms || 0
|
|
822
|
+
});
|
|
823
|
+
visibleStopBoundary = await resolveVisibleStopBoundary(client, stopBoundaryPlan, clip, {
|
|
824
|
+
topPadding: stopBoundaryTopPadding,
|
|
825
|
+
minCaptureHeight: stopBoundaryMinCaptureHeight,
|
|
826
|
+
stepTimeoutMs
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
if (visibleStopBoundary?.action === "stop_before_capture") {
|
|
830
|
+
stopBoundaryResult = visibleStopBoundary;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
const effectiveClip = visibleStopBoundary?.adjusted_clip || clip;
|
|
834
|
+
const effectiveCaptureViewport = Boolean(captureViewport && !visibleStopBoundary?.adjusted_clip);
|
|
835
|
+
const captureOptions = effectiveCaptureViewport ? {
|
|
601
836
|
format,
|
|
602
837
|
fromSurface,
|
|
603
838
|
captureBeyondViewport: false
|
|
@@ -605,7 +840,7 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
605
840
|
format,
|
|
606
841
|
fromSurface,
|
|
607
842
|
captureBeyondViewport,
|
|
608
|
-
clip
|
|
843
|
+
clip: effectiveClip
|
|
609
844
|
};
|
|
610
845
|
if (quality != null) {
|
|
611
846
|
captureOptions.quality = quality;
|
|
@@ -658,16 +893,30 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
658
893
|
file_path: outputPath,
|
|
659
894
|
sha256: hash,
|
|
660
895
|
duplicate_of_previous: Boolean(duplicateOfPrevious),
|
|
661
|
-
clip,
|
|
662
|
-
capture_viewport:
|
|
896
|
+
clip: effectiveClip,
|
|
897
|
+
capture_viewport: effectiveCaptureViewport,
|
|
663
898
|
node_rect: box.rect,
|
|
664
899
|
scroll: currentScrollMetadata,
|
|
900
|
+
stop_boundary: visibleStopBoundary || null,
|
|
665
901
|
metadata
|
|
666
902
|
});
|
|
667
903
|
}
|
|
668
904
|
|
|
905
|
+
if (visibleStopBoundary?.action === "capture_then_stop") {
|
|
906
|
+
stopBoundaryResult = visibleStopBoundary;
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
|
|
669
910
|
previousHash = hash;
|
|
670
|
-
|
|
911
|
+
forceInputScrollAfterDuplicate = Boolean(
|
|
912
|
+
duplicateOfPrevious
|
|
913
|
+
&& normalizedScrollMethod === "dom-anchor-fallback-input"
|
|
914
|
+
&& currentScrollMetadata?.method === "DOM.scrollIntoViewIfNeeded"
|
|
915
|
+
);
|
|
916
|
+
if (
|
|
917
|
+
consecutiveDuplicates >= Math.max(1, Number(duplicateStopCount) || 1)
|
|
918
|
+
&& !forceInputScrollAfterDuplicate
|
|
919
|
+
) {
|
|
671
920
|
break;
|
|
672
921
|
}
|
|
673
922
|
|
|
@@ -675,7 +924,7 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
675
924
|
assertCaptureTotalBudget(sequenceStarted, totalTimeoutMs, `scroll_after_page_${index + 1}`);
|
|
676
925
|
let scrolledByDomAnchor = false;
|
|
677
926
|
const nextAnchor = anchorPlan?.anchors?.[index + 1] || null;
|
|
678
|
-
if (nextAnchor?.node_id && normalizedScrollMethod !== "input") {
|
|
927
|
+
if (nextAnchor?.node_id && normalizedScrollMethod !== "input" && !forceInputScrollAfterDuplicate) {
|
|
679
928
|
try {
|
|
680
929
|
await scrollDomAnchorIntoView(client, nextAnchor.node_id, {
|
|
681
930
|
label: `scroll_dom_anchor_${index + 1}`,
|
|
@@ -782,12 +1031,15 @@ export async function captureScrolledNodeScreenshots(client, nodeId, {
|
|
|
782
1031
|
step_timeout_ms: Math.max(0, Number(stepTimeoutMs) || 0),
|
|
783
1032
|
total_timeout_ms: Math.max(0, Number(totalTimeoutMs) || 0),
|
|
784
1033
|
scroll_method: normalizedScrollMethod,
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1034
|
+
scroll_anchor_selector: scrollAnchorSelector,
|
|
1035
|
+
scroll_anchor_max_probe_nodes: Math.max(1, Number(scrollAnchorMaxProbeNodes) || 260),
|
|
1036
|
+
scroll_anchor_min_gap: Math.max(0, Number(scrollAnchorMinGap) || 0)
|
|
1037
|
+
},
|
|
1038
|
+
scroll_anchor_plan: anchorPlan,
|
|
1039
|
+
stop_boundary_plan: stopBoundaryPlan,
|
|
1040
|
+
stop_boundary_checks: stopBoundaryChecks,
|
|
1041
|
+
stop_boundary_result: stopBoundaryResult,
|
|
1042
|
+
file_paths: screenshots.map((item) => item.file_path).filter(Boolean),
|
|
791
1043
|
screenshots,
|
|
792
1044
|
metadata
|
|
793
1045
|
};
|
|
@@ -138,6 +138,10 @@ export function summarizeImageEvidence(imageEvidence = null) {
|
|
|
138
138
|
llm_original_total_byte_length: imageEvidence.llm_original_total_byte_length || 0,
|
|
139
139
|
llm_composition_error: imageEvidence.llm_composition_error || null,
|
|
140
140
|
optimization: imageEvidence.optimization || null,
|
|
141
|
+
scroll_anchor_plan: imageEvidence.scroll_anchor_plan || null,
|
|
142
|
+
stop_boundary_plan: imageEvidence.stop_boundary_plan || null,
|
|
143
|
+
stop_boundary_checks: imageEvidence.stop_boundary_checks || [],
|
|
144
|
+
stop_boundary_result: imageEvidence.stop_boundary_result || null,
|
|
141
145
|
error_code: imageEvidence.error_code || imageEvidence.code || null,
|
|
142
146
|
error: imageEvidence.error || null,
|
|
143
147
|
file_paths: imageEvidence.file_paths || [],
|
package/src/domains/chat/jobs.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getAttributesMap,
|
|
5
5
|
getNodeBox,
|
|
6
6
|
getOuterHTML,
|
|
7
|
+
pressKey,
|
|
7
8
|
querySelector,
|
|
8
9
|
querySelectorAll,
|
|
9
10
|
sleep
|
|
@@ -256,6 +257,80 @@ async function clickFirstVisible(client, rootNodeId, selectors = []) {
|
|
|
256
257
|
};
|
|
257
258
|
}
|
|
258
259
|
|
|
260
|
+
async function openChatJobDropdown(client, rootNodeId, {
|
|
261
|
+
timeoutMs = 12000,
|
|
262
|
+
intervalMs = 300,
|
|
263
|
+
settleMs = 800
|
|
264
|
+
} = {}) {
|
|
265
|
+
const started = Date.now();
|
|
266
|
+
const triedPoints = new Set();
|
|
267
|
+
const attempts = [];
|
|
268
|
+
for (const selector of CHAT_JOB_TRIGGER_SELECTORS) {
|
|
269
|
+
const currentRootNodeId = await freshTopRootNodeId(client, rootNodeId);
|
|
270
|
+
const nodeIds = await safeQuerySelectorAll(client, currentRootNodeId, selector);
|
|
271
|
+
for (const nodeId of nodeIds) {
|
|
272
|
+
try {
|
|
273
|
+
const box = await getNodeBox(client, nodeId);
|
|
274
|
+
if (box.rect.width <= 2 || box.rect.height <= 2) continue;
|
|
275
|
+
const y = box.center.y;
|
|
276
|
+
const xCandidates = [
|
|
277
|
+
["center", box.center.x],
|
|
278
|
+
["right_12", box.rect.x + box.rect.width - 12],
|
|
279
|
+
["right_44", box.rect.x + box.rect.width - 44],
|
|
280
|
+
["right_64", box.rect.x + box.rect.width - 64]
|
|
281
|
+
].filter(([, x]) => x > box.rect.x + 4 && x < box.rect.x + box.rect.width - 4);
|
|
282
|
+
for (const [pointName, x] of xCandidates) {
|
|
283
|
+
const pointKey = `${nodeId}:${Math.round(x)}:${Math.round(y)}`;
|
|
284
|
+
if (triedPoints.has(pointKey)) continue;
|
|
285
|
+
triedPoints.add(pointKey);
|
|
286
|
+
await clickPoint(client, x, y);
|
|
287
|
+
if (settleMs > 0) await sleep(Math.min(settleMs, 800));
|
|
288
|
+
const remaining = Math.max(300, timeoutMs - (Date.now() - started));
|
|
289
|
+
const optionsResult = await waitForChatJobOptions(client, currentRootNodeId, {
|
|
290
|
+
timeoutMs: Math.min(remaining, 1800),
|
|
291
|
+
intervalMs,
|
|
292
|
+
requireVisible: true
|
|
293
|
+
});
|
|
294
|
+
const visibleCount = (optionsResult.job_options || []).filter((option) => option.visible).length;
|
|
295
|
+
const attempt = {
|
|
296
|
+
clicked: true,
|
|
297
|
+
selector,
|
|
298
|
+
node_id: nodeId,
|
|
299
|
+
point: pointName,
|
|
300
|
+
center: { x, y },
|
|
301
|
+
visible_option_count: visibleCount
|
|
302
|
+
};
|
|
303
|
+
attempts.push(attempt);
|
|
304
|
+
if (visibleCount > 0) {
|
|
305
|
+
return {
|
|
306
|
+
...attempt,
|
|
307
|
+
attempts,
|
|
308
|
+
options_result: optionsResult
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (Date.now() - started > timeoutMs) break;
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
attempts.push({
|
|
315
|
+
clicked: false,
|
|
316
|
+
selector,
|
|
317
|
+
node_id: nodeId,
|
|
318
|
+
error: error?.message || String(error)
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (Date.now() - started > timeoutMs) break;
|
|
322
|
+
}
|
|
323
|
+
if (Date.now() - started > timeoutMs) break;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
clicked: attempts.some((attempt) => attempt.clicked),
|
|
327
|
+
selector: attempts.find((attempt) => attempt.clicked)?.selector || "",
|
|
328
|
+
node_id: attempts.find((attempt) => attempt.clicked)?.node_id || 0,
|
|
329
|
+
attempts,
|
|
330
|
+
options_result: null
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
259
334
|
async function waitForChatJobOptions(client, rootNodeId, {
|
|
260
335
|
timeoutMs = 12000,
|
|
261
336
|
intervalMs = 300,
|
|
@@ -312,6 +387,83 @@ async function waitForSelectedChatJob(client, rootNodeId, jobLabel = "", {
|
|
|
312
387
|
};
|
|
313
388
|
}
|
|
314
389
|
|
|
390
|
+
async function visibleChatJobOptions(client, rootNodeId) {
|
|
391
|
+
const currentRootNodeId = await freshTopRootNodeId(client, rootNodeId);
|
|
392
|
+
const visible = [];
|
|
393
|
+
for (const selector of CHAT_JOB_OPTION_SELECTORS) {
|
|
394
|
+
const nodeIds = await safeQuerySelectorAll(client, currentRootNodeId, selector);
|
|
395
|
+
for (const nodeId of nodeIds) {
|
|
396
|
+
try {
|
|
397
|
+
const box = await getNodeBox(client, nodeId);
|
|
398
|
+
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
399
|
+
visible.push({
|
|
400
|
+
selector,
|
|
401
|
+
node_id: nodeId,
|
|
402
|
+
center: box.center,
|
|
403
|
+
rect: box.rect
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
// Hidden job options are normal when the dropdown is closed.
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return visible;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export async function closeChatJobDropdown(client, rootNodeId, {
|
|
415
|
+
settleMs = 180
|
|
416
|
+
} = {}) {
|
|
417
|
+
const before = await visibleChatJobOptions(client, rootNodeId);
|
|
418
|
+
if (!before.length) {
|
|
419
|
+
return {
|
|
420
|
+
ok: true,
|
|
421
|
+
closed: false,
|
|
422
|
+
reason: "already_closed",
|
|
423
|
+
visible_before_count: 0,
|
|
424
|
+
visible_after_count: 0
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (typeof client?.Input?.dispatchKeyEvent !== "function") {
|
|
428
|
+
return {
|
|
429
|
+
ok: false,
|
|
430
|
+
closed: false,
|
|
431
|
+
reason: "dispatch_key_unavailable",
|
|
432
|
+
visible_before_count: before.length,
|
|
433
|
+
visible_after_count: before.length
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
await pressKey(client, "Escape", {
|
|
437
|
+
code: "Escape",
|
|
438
|
+
windowsVirtualKeyCode: 27,
|
|
439
|
+
nativeVirtualKeyCode: 27
|
|
440
|
+
});
|
|
441
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
442
|
+
const after = await visibleChatJobOptions(client, rootNodeId);
|
|
443
|
+
return {
|
|
444
|
+
ok: after.length === 0,
|
|
445
|
+
closed: after.length === 0,
|
|
446
|
+
reason: after.length ? "still_visible_after_escape" : "escape",
|
|
447
|
+
visible_before_count: before.length,
|
|
448
|
+
visible_after_count: after.length,
|
|
449
|
+
first_visible_before: before[0] || null,
|
|
450
|
+
first_visible_after: after[0] || null
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function closeChatJobDropdownQuietly(client, rootNodeId, settleMs = 180) {
|
|
455
|
+
try {
|
|
456
|
+
return await closeChatJobDropdown(client, rootNodeId, { settleMs });
|
|
457
|
+
} catch (error) {
|
|
458
|
+
return {
|
|
459
|
+
ok: false,
|
|
460
|
+
closed: false,
|
|
461
|
+
reason: "close_failed",
|
|
462
|
+
error: error?.message || String(error)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
315
467
|
export async function selectChatJob(client, rootNodeId, {
|
|
316
468
|
jobLabel = "",
|
|
317
469
|
timeoutMs = 12000,
|
|
@@ -340,6 +492,7 @@ export async function selectChatJob(client, rootNodeId, {
|
|
|
340
492
|
|| selectedLabelMatches(optionsResult.selected_label, requested)
|
|
341
493
|
)
|
|
342
494
|
) {
|
|
495
|
+
const menuClose = await closeChatJobDropdownQuietly(client, currentRootNodeId, Math.min(settleMs, 300));
|
|
343
496
|
return {
|
|
344
497
|
selected: true,
|
|
345
498
|
verified: true,
|
|
@@ -347,34 +500,41 @@ export async function selectChatJob(client, rootNodeId, {
|
|
|
347
500
|
requested,
|
|
348
501
|
selected_option: matched,
|
|
349
502
|
options: optionsResult.job_options || [],
|
|
350
|
-
selected_label: optionsResult.selected_label || matched.label
|
|
503
|
+
selected_label: optionsResult.selected_label || matched.label,
|
|
504
|
+
menu_close: menuClose
|
|
351
505
|
};
|
|
352
506
|
}
|
|
353
507
|
|
|
354
508
|
if (!matched || !matched.visible) {
|
|
355
509
|
const triggerRootNodeId = await freshTopRootNodeId(client, currentRootNodeId);
|
|
356
|
-
const trigger = await
|
|
357
|
-
|
|
510
|
+
const trigger = await openChatJobDropdown(client, triggerRootNodeId, {
|
|
511
|
+
timeoutMs,
|
|
512
|
+
intervalMs,
|
|
513
|
+
settleMs
|
|
514
|
+
});
|
|
358
515
|
currentRootNodeId = await freshTopRootNodeId(client, triggerRootNodeId);
|
|
359
|
-
optionsResult = await waitForChatJobOptions(client, currentRootNodeId, {
|
|
516
|
+
optionsResult = trigger.options_result || await waitForChatJobOptions(client, currentRootNodeId, {
|
|
360
517
|
timeoutMs,
|
|
361
518
|
intervalMs,
|
|
362
519
|
requireVisible: true
|
|
363
520
|
});
|
|
364
521
|
matched = (optionsResult.job_options || []).find((option) => matchJobOption(option, requested)) || null;
|
|
365
522
|
if (!matched || !matched.visible) {
|
|
523
|
+
const menuClose = await closeChatJobDropdownQuietly(client, currentRootNodeId, Math.min(settleMs, 300));
|
|
366
524
|
return {
|
|
367
525
|
selected: false,
|
|
368
526
|
reason: matched ? "job_option_not_visible" : "job_option_not_found",
|
|
369
527
|
requested,
|
|
370
528
|
trigger,
|
|
371
529
|
options: optionsResult.job_options || [],
|
|
372
|
-
selected_label_before: optionsResult.selected_label || ""
|
|
530
|
+
selected_label_before: optionsResult.selected_label || "",
|
|
531
|
+
menu_close: menuClose
|
|
373
532
|
};
|
|
374
533
|
}
|
|
375
534
|
}
|
|
376
535
|
|
|
377
536
|
if (matched.active || normalizeJobText(optionsResult.selected_label).toLowerCase() === normalizeJobText(matched.label).toLowerCase()) {
|
|
537
|
+
const menuClose = await closeChatJobDropdownQuietly(client, currentRootNodeId, Math.min(settleMs, 300));
|
|
378
538
|
return {
|
|
379
539
|
selected: true,
|
|
380
540
|
verified: true,
|
|
@@ -382,7 +542,8 @@ export async function selectChatJob(client, rootNodeId, {
|
|
|
382
542
|
requested,
|
|
383
543
|
selected_option: matched,
|
|
384
544
|
options: optionsResult.job_options || [],
|
|
385
|
-
selected_label: optionsResult.selected_label || matched.label
|
|
545
|
+
selected_label: optionsResult.selected_label || matched.label,
|
|
546
|
+
menu_close: menuClose
|
|
386
547
|
};
|
|
387
548
|
}
|
|
388
549
|
|
|
@@ -408,6 +569,7 @@ export async function selectChatJob(client, rootNodeId, {
|
|
|
408
569
|
const selectedLabel = normalizeJobText(after.selected_label || "");
|
|
409
570
|
const activeMatch = activeMatchingJobOption(after.job_options || [], matched.label);
|
|
410
571
|
const verified = Boolean(verification.verified || selectedLabelMatches(selectedLabel, matched.label) || activeMatch);
|
|
572
|
+
const menuClose = await closeChatJobDropdownQuietly(client, afterRootNodeId, Math.min(settleMs, 300));
|
|
411
573
|
|
|
412
574
|
return {
|
|
413
575
|
selected: verified,
|
|
@@ -420,6 +582,7 @@ export async function selectChatJob(client, rootNodeId, {
|
|
|
420
582
|
options: after.job_options || optionsResult.job_options || [],
|
|
421
583
|
selected_label: selectedLabel,
|
|
422
584
|
before: optionsResult,
|
|
423
|
-
after
|
|
585
|
+
after,
|
|
586
|
+
menu_close: menuClose
|
|
424
587
|
};
|
|
425
588
|
}
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
createInfiniteListState,
|
|
22
22
|
detectInfiniteListBottomMarker,
|
|
23
23
|
getNextInfiniteListCandidate,
|
|
24
|
-
markInfiniteListCandidateProcessed
|
|
24
|
+
markInfiniteListCandidateProcessed,
|
|
25
|
+
resetInfiniteListForRefreshRound
|
|
25
26
|
} from "../../core/infinite-list/index.js";
|
|
26
27
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
27
28
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
@@ -117,6 +118,22 @@ function compactCandidate(candidate) {
|
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
function compactChatJobGuard(result = null) {
|
|
122
|
+
if (!result || typeof result !== "object") return null;
|
|
123
|
+
return {
|
|
124
|
+
selected: Boolean(result.selected),
|
|
125
|
+
verified: Boolean(result.verified),
|
|
126
|
+
already_current: Boolean(result.already_current),
|
|
127
|
+
requested: result.requested || null,
|
|
128
|
+
reason: result.reason || null,
|
|
129
|
+
selected_label: result.selected_label || result.selected_option?.label || null,
|
|
130
|
+
selected_value: result.selected_option?.value || result.active_option?.value || null,
|
|
131
|
+
active_label: result.active_option?.label || null,
|
|
132
|
+
active_value: result.active_option?.value || null,
|
|
133
|
+
menu_close: result.menu_close || null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
function compactDetail(detailResult) {
|
|
121
138
|
if (!detailResult) return null;
|
|
122
139
|
return {
|
|
@@ -146,13 +163,71 @@ function llmToScreening(llmResult, candidate) {
|
|
|
146
163
|
};
|
|
147
164
|
}
|
|
148
165
|
|
|
149
|
-
function captureNodeIdFromResumeState(resumeState) {
|
|
150
|
-
return resumeState?.
|
|
151
|
-
|| resumeState?.
|
|
166
|
+
export function captureNodeIdFromResumeState(resumeState) {
|
|
167
|
+
return resumeState?.popup?.node_id
|
|
168
|
+
|| resumeState?.content?.node_id
|
|
152
169
|
|| resumeState?.resumeIframe?.node_id
|
|
153
170
|
|| null;
|
|
154
171
|
}
|
|
155
172
|
|
|
173
|
+
export function resolveChatDomFallbackWait({
|
|
174
|
+
normalizedDetailSource = "cascade",
|
|
175
|
+
parsedNetworkProfileCount = 0,
|
|
176
|
+
waitPlan = null,
|
|
177
|
+
resumeDomTimeoutMs = 60000
|
|
178
|
+
} = {}) {
|
|
179
|
+
const detailSource = normalizeDetailSource(normalizedDetailSource);
|
|
180
|
+
const configuredTimeoutMs = Math.max(0, Number(resumeDomTimeoutMs) || 0);
|
|
181
|
+
if (detailSource === "image") {
|
|
182
|
+
return {
|
|
183
|
+
skipped: false,
|
|
184
|
+
timeout_ms: Math.min(configuredTimeoutMs, 3500),
|
|
185
|
+
configured_timeout_ms: configuredTimeoutMs,
|
|
186
|
+
short_probe: true,
|
|
187
|
+
reason: "forced_image_modal_probe"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (detailSource === "dom") {
|
|
191
|
+
return {
|
|
192
|
+
skipped: false,
|
|
193
|
+
timeout_ms: configuredTimeoutMs,
|
|
194
|
+
configured_timeout_ms: configuredTimeoutMs,
|
|
195
|
+
short_probe: false,
|
|
196
|
+
reason: "dom_source_full_wait"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const profileCount = Math.max(0, Number(parsedNetworkProfileCount) || 0);
|
|
201
|
+
const previousImageMode = waitPlan?.mode_before === "image";
|
|
202
|
+
if (profileCount > 0) {
|
|
203
|
+
return {
|
|
204
|
+
skipped: false,
|
|
205
|
+
timeout_ms: Math.min(configuredTimeoutMs, previousImageMode ? 1500 : 3500),
|
|
206
|
+
configured_timeout_ms: configuredTimeoutMs,
|
|
207
|
+
short_probe: true,
|
|
208
|
+
reason: previousImageMode
|
|
209
|
+
? "previous_image_mode_profile_only_network_short_dom_probe"
|
|
210
|
+
: "profile_only_network_short_dom_probe"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (previousImageMode) {
|
|
214
|
+
return {
|
|
215
|
+
skipped: false,
|
|
216
|
+
timeout_ms: Math.min(configuredTimeoutMs, 2500),
|
|
217
|
+
configured_timeout_ms: configuredTimeoutMs,
|
|
218
|
+
short_probe: true,
|
|
219
|
+
reason: "previous_image_mode_network_miss_short_dom_probe"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
skipped: false,
|
|
224
|
+
timeout_ms: configuredTimeoutMs,
|
|
225
|
+
configured_timeout_ms: configuredTimeoutMs,
|
|
226
|
+
short_probe: false,
|
|
227
|
+
reason: "cascade_full_dom_wait"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
156
231
|
function isRecoverableCdpNodeError(error) {
|
|
157
232
|
return /(?:Could not find node|No node with given id|Cannot find node|Could not compute box model)/i
|
|
158
233
|
.test(String(error?.message || error || ""));
|
|
@@ -208,6 +283,30 @@ const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
|
|
|
208
283
|
const CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH = 180;
|
|
209
284
|
const CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH = 650;
|
|
210
285
|
const CHAT_FULL_CV_NETWORK_MIN_RICH_ITEM_COUNT = 3;
|
|
286
|
+
const CHAT_RESUME_IMAGE_STOP_BOUNDARY_SELECTOR = [
|
|
287
|
+
"h1",
|
|
288
|
+
"h2",
|
|
289
|
+
"h3",
|
|
290
|
+
"h4",
|
|
291
|
+
"h5",
|
|
292
|
+
"p",
|
|
293
|
+
"span",
|
|
294
|
+
"section",
|
|
295
|
+
"article",
|
|
296
|
+
"div",
|
|
297
|
+
"[class*='privacy']",
|
|
298
|
+
"[class*='recommend']",
|
|
299
|
+
"[class*='similar']"
|
|
300
|
+
].join(",");
|
|
301
|
+
const CHAT_RESUME_IMAGE_STOP_BOUNDARY_TEXT = Object.freeze([
|
|
302
|
+
/其他名企大厂/,
|
|
303
|
+
/其他.*牛人/,
|
|
304
|
+
/毕业的牛人/,
|
|
305
|
+
/经历牛人/,
|
|
306
|
+
/为妥善保护/,
|
|
307
|
+
/查看全部.*项分析/,
|
|
308
|
+
/牛人分析器/
|
|
309
|
+
]);
|
|
211
310
|
const CHAT_FULL_CV_SECTION_PATTERNS = Object.freeze([
|
|
212
311
|
/教育(?:经历|背景|经验)?/i,
|
|
213
312
|
/工作(?:经历|经验)?/i,
|
|
@@ -738,6 +837,44 @@ export async function runChatWorkflow({
|
|
|
738
837
|
});
|
|
739
838
|
continue;
|
|
740
839
|
}
|
|
840
|
+
if (normalizeText(job)) {
|
|
841
|
+
const jobGuard = await selectChatJob(client, rootState.rootNodes.top, {
|
|
842
|
+
jobLabel: job,
|
|
843
|
+
timeoutMs: Math.min(readyTimeoutMs, 12000),
|
|
844
|
+
settleMs: Math.min(listSettleMs, 800)
|
|
845
|
+
});
|
|
846
|
+
if (!jobGuard.selected || jobGuard.verified !== true) {
|
|
847
|
+
const error = new Error(`CHAT_JOB_GUARD_FAILED: requested=${job}; selected=${jobGuard.selected_label || "unknown"}; reason=${jobGuard.reason || "unknown"}`);
|
|
848
|
+
error.code = "CHAT_JOB_GUARD_FAILED";
|
|
849
|
+
error.chat_job_guard = compactChatJobGuard(jobGuard);
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
if (!jobGuard.already_current) {
|
|
853
|
+
runControl.checkpoint({
|
|
854
|
+
chat_context_step: "job_guard_reselected",
|
|
855
|
+
job_guard: compactChatJobGuard(jobGuard),
|
|
856
|
+
candidate_list: resetInfiniteListForRefreshRound(listState, {
|
|
857
|
+
reason: "chat_job_drift_repaired",
|
|
858
|
+
round: listState.ledger?.length || 0,
|
|
859
|
+
method: "selectChatJob",
|
|
860
|
+
metadata: {
|
|
861
|
+
requested_job: job,
|
|
862
|
+
selected_label: jobGuard.selected_label || "",
|
|
863
|
+
selected_value: jobGuard.selected_option?.value || ""
|
|
864
|
+
}
|
|
865
|
+
})
|
|
866
|
+
});
|
|
867
|
+
rootState = await ensureChatViewport(await getChatRoots(client), "candidate_job_guard_reselected");
|
|
868
|
+
await sleep(Math.min(listSettleMs, 1200));
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (jobGuard.menu_close?.closed) {
|
|
872
|
+
runControl.checkpoint({
|
|
873
|
+
chat_context_step: "job_guard_closed_dropdown",
|
|
874
|
+
job_guard: compactChatJobGuard(jobGuard)
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
}
|
|
741
878
|
|
|
742
879
|
const nextCandidateResult = await measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
743
880
|
client,
|
|
@@ -962,10 +1099,40 @@ export async function runChatWorkflow({
|
|
|
962
1099
|
|
|
963
1100
|
if (!detailResult) {
|
|
964
1101
|
detailStep = "wait_resume_content";
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1102
|
+
const domFallbackPlan = resolveChatDomFallbackWait({
|
|
1103
|
+
normalizedDetailSource,
|
|
1104
|
+
parsedNetworkProfileCount,
|
|
1105
|
+
waitPlan,
|
|
1106
|
+
resumeDomTimeoutMs
|
|
1107
|
+
});
|
|
1108
|
+
if (domFallbackPlan.skipped || domFallbackPlan.timeout_ms <= 0) {
|
|
1109
|
+
contentWait = {
|
|
1110
|
+
ok: false,
|
|
1111
|
+
skipped: true,
|
|
1112
|
+
reason: domFallbackPlan.reason,
|
|
1113
|
+
elapsed_ms: 0,
|
|
1114
|
+
text_length: 0,
|
|
1115
|
+
resume_state: openedResume.resume_state,
|
|
1116
|
+
resume_html: null,
|
|
1117
|
+
dom_fallback_plan: domFallbackPlan,
|
|
1118
|
+
configured_timeout_ms: domFallbackPlan.configured_timeout_ms,
|
|
1119
|
+
timeout_ms: domFallbackPlan.timeout_ms,
|
|
1120
|
+
short_probe: Boolean(domFallbackPlan.short_probe)
|
|
1121
|
+
};
|
|
1122
|
+
addTiming(timings, "dom_fallback_ms", 0);
|
|
1123
|
+
} else {
|
|
1124
|
+
contentWait = await measureTiming(timings, "dom_fallback_ms", () => waitForChatResumeContent(client, {
|
|
1125
|
+
timeoutMs: domFallbackPlan.timeout_ms,
|
|
1126
|
+
intervalMs: 300
|
|
1127
|
+
}));
|
|
1128
|
+
contentWait.dom_fallback_plan = domFallbackPlan;
|
|
1129
|
+
contentWait.configured_timeout_ms = domFallbackPlan.configured_timeout_ms;
|
|
1130
|
+
contentWait.timeout_ms = domFallbackPlan.timeout_ms;
|
|
1131
|
+
contentWait.short_probe = Boolean(domFallbackPlan.short_probe);
|
|
1132
|
+
if (domFallbackPlan.short_probe && !contentWait.ok) {
|
|
1133
|
+
contentWait.reason = contentWait.reason || domFallbackPlan.reason;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
969
1136
|
resumeState = contentWait.resume_state || openedResume.resume_state;
|
|
970
1137
|
resumeHtml = contentWait.resume_html || null;
|
|
971
1138
|
resumeNetworkEvents = networkRecorder.events.slice();
|
|
@@ -1012,23 +1179,29 @@ export async function runChatWorkflow({
|
|
|
1012
1179
|
quality: 72,
|
|
1013
1180
|
optimize: true,
|
|
1014
1181
|
resizeMaxWidth: 1100,
|
|
1015
|
-
captureViewport:
|
|
1016
|
-
padding:
|
|
1182
|
+
captureViewport: false,
|
|
1183
|
+
padding: 0,
|
|
1017
1184
|
maxScreenshots: maxImagePages,
|
|
1018
1185
|
wheelDeltaY: imageWheelDeltaY,
|
|
1019
1186
|
settleMs: 350,
|
|
1020
1187
|
scrollMethod: "dom-anchor-fallback-input",
|
|
1021
1188
|
stepTimeoutMs: 45000,
|
|
1022
1189
|
totalTimeoutMs: 90000,
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1190
|
+
duplicateStopCount: 1,
|
|
1191
|
+
skipDuplicateScreenshots: true,
|
|
1192
|
+
composeForLlm: true,
|
|
1193
|
+
llmPagesPerImage: 3,
|
|
1194
|
+
llmResizeMaxWidth: 1100,
|
|
1195
|
+
llmQuality: 72,
|
|
1196
|
+
stopBoundarySelector: CHAT_RESUME_IMAGE_STOP_BOUNDARY_SELECTOR,
|
|
1197
|
+
stopBoundaryTextPatterns: CHAT_RESUME_IMAGE_STOP_BOUNDARY_TEXT,
|
|
1198
|
+
stopBoundaryMaxProbeNodes: 360,
|
|
1199
|
+
stopBoundaryTopPadding: 10,
|
|
1200
|
+
stopBoundaryMinCaptureHeight: 180,
|
|
1201
|
+
metadata: {
|
|
1202
|
+
domain: "chat",
|
|
1203
|
+
capture_mode: "scroll_sequence",
|
|
1204
|
+
capture_scope: "resume_modal_clip",
|
|
1032
1205
|
acquisition_reason: normalizedDetailSource === "image"
|
|
1033
1206
|
? "forced_image"
|
|
1034
1207
|
: "network_miss_image_fallback",
|
|
@@ -1165,7 +1338,12 @@ export async function runChatWorkflow({
|
|
|
1165
1338
|
skipped: Boolean(contentWait.skipped),
|
|
1166
1339
|
reason: contentWait.reason || null,
|
|
1167
1340
|
elapsed_ms: contentWait.elapsed_ms,
|
|
1168
|
-
text_length: contentWait.text_length
|
|
1341
|
+
text_length: contentWait.text_length,
|
|
1342
|
+
timeout_ms: contentWait.timeout_ms ?? contentWait.dom_fallback_plan?.timeout_ms ?? null,
|
|
1343
|
+
configured_timeout_ms: contentWait.configured_timeout_ms
|
|
1344
|
+
?? contentWait.dom_fallback_plan?.configured_timeout_ms
|
|
1345
|
+
?? null,
|
|
1346
|
+
short_probe: Boolean(contentWait.short_probe)
|
|
1169
1347
|
},
|
|
1170
1348
|
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
1171
1349
|
image_evidence: summarizeImageEvidence(imageEvidence),
|