@reconcrap/boss-recommend-mcp 2.0.14 → 2.0.16
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/domains/chat/detail.js +68 -0
- package/src/domains/chat/run-service.js +306 -98
package/package.json
CHANGED
|
@@ -39,13 +39,65 @@ import {
|
|
|
39
39
|
import {
|
|
40
40
|
assertChatShellNotResumeTopLevel,
|
|
41
41
|
getChatTopLevelState,
|
|
42
|
+
isForbiddenChatResumeTopLevelUrl,
|
|
42
43
|
makeForbiddenChatResumeNavigationError
|
|
43
44
|
} from "./page-guard.js";
|
|
44
45
|
|
|
46
|
+
export const CHAT_UNSAFE_ONLINE_RESUME_LINK_CODE = "CHAT_UNSAFE_ONLINE_RESUME_LINK";
|
|
47
|
+
|
|
45
48
|
export function matchesChatProfileNetwork(url) {
|
|
46
49
|
return CHAT_PROFILE_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
47
50
|
}
|
|
48
51
|
|
|
52
|
+
function looksLikeForbiddenChatResumePath(value = "") {
|
|
53
|
+
const normalized = String(value || "");
|
|
54
|
+
return isForbiddenChatResumeTopLevelUrl(normalized)
|
|
55
|
+
|| /(?:^|["'\s=])(?:https?:\/\/[^"'\s>]*zhipin\.com)?\/web\/frame\/c-resume(?:[/?#"' >]|$)/i
|
|
56
|
+
.test(normalized);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractFirstHtmlAttribute(html = "", names = []) {
|
|
60
|
+
const source = String(html || "");
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const escaped = String(name).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
|
+
const regex = new RegExp(`${escaped}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`, "i");
|
|
64
|
+
const match = source.match(regex);
|
|
65
|
+
if (match) return match[1] ?? match[2] ?? match[3] ?? "";
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isUnsafeChatOnlineResumeTarget(target = {}, buttonHTML = "") {
|
|
71
|
+
const attributes = target?.attributes || {};
|
|
72
|
+
const href = attributes.href
|
|
73
|
+
|| attributes["data-href"]
|
|
74
|
+
|| attributes["data-url"]
|
|
75
|
+
|| attributes.url
|
|
76
|
+
|| extractFirstHtmlAttribute(buttonHTML, ["href", "data-href", "data-url", "url"]);
|
|
77
|
+
return looksLikeForbiddenChatResumePath(href)
|
|
78
|
+
|| looksLikeForbiddenChatResumePath(buttonHTML);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function makeUnsafeChatOnlineResumeLinkError(target = {}, buttonHTML = "") {
|
|
82
|
+
const href = target?.attributes?.href
|
|
83
|
+
|| target?.attributes?.["data-href"]
|
|
84
|
+
|| target?.attributes?.["data-url"]
|
|
85
|
+
|| extractFirstHtmlAttribute(buttonHTML, ["href", "data-href", "data-url", "url"])
|
|
86
|
+
|| null;
|
|
87
|
+
const error = new Error("CHAT_UNSAFE_ONLINE_RESUME_LINK: refusing to click an online resume link that can navigate the chat tab to /web/frame/c-resume/");
|
|
88
|
+
error.code = CHAT_UNSAFE_ONLINE_RESUME_LINK_CODE;
|
|
89
|
+
error.href = href;
|
|
90
|
+
error.button_selector = target?.selector || null;
|
|
91
|
+
error.button_text = htmlToText(buttonHTML).slice(0, 120);
|
|
92
|
+
error.button_html_length = String(buttonHTML || "").length;
|
|
93
|
+
return error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isUnsafeChatOnlineResumeLinkError(error) {
|
|
97
|
+
return error?.code === CHAT_UNSAFE_ONLINE_RESUME_LINK_CODE
|
|
98
|
+
|| /CHAT_UNSAFE_ONLINE_RESUME_LINK/i.test(String(error?.message || error || ""));
|
|
99
|
+
}
|
|
100
|
+
|
|
49
101
|
export function createChatProfileNetworkRecorder(client) {
|
|
50
102
|
const events = [];
|
|
51
103
|
client.Network.responseReceived((event) => {
|
|
@@ -673,6 +725,22 @@ export async function openChatOnlineResume(client, {
|
|
|
673
725
|
buttonHTML = await getOuterHTML(client, buttonState.target.node_id);
|
|
674
726
|
} catch {}
|
|
675
727
|
|
|
728
|
+
if (isUnsafeChatOnlineResumeTarget(buttonState.target, buttonHTML)) {
|
|
729
|
+
const error = makeUnsafeChatOnlineResumeLinkError(buttonState.target, buttonHTML);
|
|
730
|
+
attempts.push({
|
|
731
|
+
attempt: index + 1,
|
|
732
|
+
ok: false,
|
|
733
|
+
error: error.code,
|
|
734
|
+
blocked_pre_click: true,
|
|
735
|
+
button_selector: buttonState.target.selector,
|
|
736
|
+
button_text: error.button_text,
|
|
737
|
+
button_href: error.href,
|
|
738
|
+
button_html_length: buttonHTML.length
|
|
739
|
+
});
|
|
740
|
+
error.attempts = attempts;
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
|
|
676
744
|
try {
|
|
677
745
|
if (buttonState.target.center) {
|
|
678
746
|
await clickPoint(client, buttonState.target.center.x, buttonState.target.center.y);
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
closeChatResumeModal,
|
|
50
50
|
createChatProfileNetworkRecorder,
|
|
51
51
|
extractChatProfileCandidate,
|
|
52
|
+
isUnsafeChatOnlineResumeLinkError,
|
|
52
53
|
openChatOnlineResume,
|
|
53
54
|
readChatConversationReadyState,
|
|
54
55
|
requestChatResumeForPassedCandidate,
|
|
@@ -203,6 +204,132 @@ function createSkippedDetailResult(cardCandidate, reason, error = null) {
|
|
|
203
204
|
};
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
|
|
208
|
+
const CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH = 180;
|
|
209
|
+
const CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH = 650;
|
|
210
|
+
const CHAT_FULL_CV_NETWORK_MIN_RICH_ITEM_COUNT = 3;
|
|
211
|
+
const CHAT_FULL_CV_SECTION_PATTERNS = Object.freeze([
|
|
212
|
+
/教育(?:经历|背景|经验)?/i,
|
|
213
|
+
/工作(?:经历|经验)?/i,
|
|
214
|
+
/项目(?:经历|经验)?/i,
|
|
215
|
+
/实习(?:经历|经验)?/i,
|
|
216
|
+
/科研(?:经历|经验)?/i,
|
|
217
|
+
/论文|会议|专利/i,
|
|
218
|
+
/个人(?:优势|总结|介绍|评价)/i,
|
|
219
|
+
/专业技能|技能(?:特长|标签)?/i,
|
|
220
|
+
/求职(?:期望|意向)/i,
|
|
221
|
+
/校园经历|在校经历|竞赛|证书/i
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
function detailTextForFullCvCheck(detailResult = {}) {
|
|
225
|
+
return [
|
|
226
|
+
detailResult?.detail?.popup_text,
|
|
227
|
+
detailResult?.detail?.content_text,
|
|
228
|
+
detailResult?.detail?.resume_iframe_text
|
|
229
|
+
].filter(Boolean).join("\n\n");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function resumeSectionMatchCount(text = "") {
|
|
233
|
+
const normalized = normalizeText(text);
|
|
234
|
+
if (!normalized) return 0;
|
|
235
|
+
return CHAT_FULL_CV_SECTION_PATTERNS
|
|
236
|
+
.filter((pattern) => pattern.test(normalized))
|
|
237
|
+
.length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function hasResumeLikeDomText(text = "") {
|
|
241
|
+
const normalized = normalizeText(text);
|
|
242
|
+
if (normalized.length >= CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH) return true;
|
|
243
|
+
return normalized.length >= CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH
|
|
244
|
+
&& resumeSectionMatchCount(normalized) >= 2;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function networkProfileTextLength(profileResult = {}) {
|
|
248
|
+
return normalizeText(profileResult?.profile?.text || "").length;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isFullCvNetworkProfile(profileResult = {}) {
|
|
252
|
+
if (!profileResult?.ok) return false;
|
|
253
|
+
const sourceKeys = profileResult.profile?.source_keys || {};
|
|
254
|
+
const textLength = networkProfileTextLength(profileResult);
|
|
255
|
+
const sectionCount = resumeSectionMatchCount(profileResult.profile?.text || "");
|
|
256
|
+
const richItemCount = [
|
|
257
|
+
"education_count",
|
|
258
|
+
"work_count",
|
|
259
|
+
"project_count",
|
|
260
|
+
"expectation_count",
|
|
261
|
+
"certification_count"
|
|
262
|
+
].reduce((sum, key) => sum + (Number(sourceKeys[key]) || 0), 0);
|
|
263
|
+
const hasResumeSections = sectionCount >= 3 || (sectionCount >= 2 && richItemCount >= 2);
|
|
264
|
+
const hasEnoughNetworkText = textLength >= CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH;
|
|
265
|
+
|
|
266
|
+
if (sourceKeys.geek_detail_info || sourceKeys.geek_detail) {
|
|
267
|
+
return hasEnoughNetworkText && (
|
|
268
|
+
hasResumeSections
|
|
269
|
+
|| richItemCount >= CHAT_FULL_CV_NETWORK_MIN_RICH_ITEM_COUNT
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (sourceKeys.network_html_text) {
|
|
273
|
+
return textLength >= CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH
|
|
274
|
+
&& sectionCount >= 2;
|
|
275
|
+
}
|
|
276
|
+
if (sourceKeys.chat_history_resume) {
|
|
277
|
+
const educationCount = Number(sourceKeys.education_count) || 0;
|
|
278
|
+
const workCount = Number(sourceKeys.work_count) || 0;
|
|
279
|
+
return (educationCount + workCount) >= 2
|
|
280
|
+
&& textLength >= CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH
|
|
281
|
+
&& sectionCount >= 2;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function hasUsableImageEvidence(imageEvidence = null) {
|
|
287
|
+
if (!imageEvidence || imageEvidence.ok === false) return false;
|
|
288
|
+
return Boolean(
|
|
289
|
+
(Array.isArray(imageEvidence.llm_file_paths) && imageEvidence.llm_file_paths.length)
|
|
290
|
+
|| (Array.isArray(imageEvidence.file_paths) && imageEvidence.file_paths.length)
|
|
291
|
+
|| Number(imageEvidence.llm_screenshot_count) > 0
|
|
292
|
+
|| Number(imageEvidence.unique_screenshot_count) > 0
|
|
293
|
+
|| Number(imageEvidence.screenshot_count) > 0
|
|
294
|
+
|| Number(imageEvidence.capture_count) > 0
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function summarizeChatFullCvEvidence({
|
|
299
|
+
detailResult = null,
|
|
300
|
+
contentWait = null,
|
|
301
|
+
imageEvidence = null
|
|
302
|
+
} = {}) {
|
|
303
|
+
const parsedProfiles = (detailResult?.parsed_network_profiles || []).filter((item) => item?.ok);
|
|
304
|
+
const fullNetworkProfiles = parsedProfiles.filter(isFullCvNetworkProfile);
|
|
305
|
+
const profileOnlyCount = Math.max(0, parsedProfiles.length - fullNetworkProfiles.length);
|
|
306
|
+
const detailText = detailTextForFullCvCheck(detailResult);
|
|
307
|
+
const domTextLength = detailText.length;
|
|
308
|
+
const domSectionCount = resumeSectionMatchCount(detailText);
|
|
309
|
+
const domFullCv = Boolean(contentWait?.ok) && hasResumeLikeDomText(detailText);
|
|
310
|
+
const imageFullCv = hasUsableImageEvidence(imageEvidence);
|
|
311
|
+
const source = fullNetworkProfiles.length
|
|
312
|
+
? "network"
|
|
313
|
+
: domFullCv
|
|
314
|
+
? "dom"
|
|
315
|
+
: imageFullCv
|
|
316
|
+
? "image"
|
|
317
|
+
: null;
|
|
318
|
+
return {
|
|
319
|
+
full_cv_acquired: Boolean(source),
|
|
320
|
+
source,
|
|
321
|
+
network_full_cv_count: fullNetworkProfiles.length,
|
|
322
|
+
network_profile_only_count: profileOnlyCount,
|
|
323
|
+
parsed_network_profile_count: parsedProfiles.length,
|
|
324
|
+
dom_full_cv: domFullCv,
|
|
325
|
+
dom_text_length: domTextLength,
|
|
326
|
+
dom_section_count: domSectionCount,
|
|
327
|
+
content_wait_ok: Boolean(contentWait?.ok),
|
|
328
|
+
image_full_cv: imageFullCv,
|
|
329
|
+
image_summary: summarizeImageEvidence(imageEvidence)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
206
333
|
async function resolveFreshChatCardNodeId(client, {
|
|
207
334
|
fallbackNodeId,
|
|
208
335
|
candidate,
|
|
@@ -717,28 +844,8 @@ export async function runChatWorkflow({
|
|
|
717
844
|
}
|
|
718
845
|
|
|
719
846
|
if (!detailResult) {
|
|
720
|
-
detailStep = "open_online_resume";
|
|
721
|
-
networkRecorder.clear();
|
|
722
|
-
const openedResume = await measureTiming(timings, "detail_open_ms", () => openChatOnlineResume(client, {
|
|
723
|
-
timeoutMs: readyTimeoutMs
|
|
724
|
-
}));
|
|
725
847
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
726
|
-
|
|
727
|
-
const networkWait = ["network", "cascade"].includes(normalizedDetailSource)
|
|
728
|
-
? await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
729
|
-
waitForChatProfileNetworkEvents,
|
|
730
|
-
networkRecorder,
|
|
731
|
-
{
|
|
732
|
-
waitPlan,
|
|
733
|
-
minCount: 1,
|
|
734
|
-
requireLoaded: true,
|
|
735
|
-
intervalMs: 200
|
|
736
|
-
}
|
|
737
|
-
))
|
|
738
|
-
: null;
|
|
739
|
-
if (networkWait?.elapsed_ms != null) {
|
|
740
|
-
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
741
|
-
}
|
|
848
|
+
let networkWait = null;
|
|
742
849
|
let contentWait = {
|
|
743
850
|
ok: false,
|
|
744
851
|
skipped: false,
|
|
@@ -746,38 +853,44 @@ export async function runChatWorkflow({
|
|
|
746
853
|
elapsed_ms: 0,
|
|
747
854
|
text_length: 0
|
|
748
855
|
};
|
|
749
|
-
let resumeState =
|
|
856
|
+
let resumeState = null;
|
|
750
857
|
let resumeHtml = null;
|
|
751
|
-
let resumeNetworkEvents =
|
|
858
|
+
let resumeNetworkEvents = [];
|
|
752
859
|
let parsedNetworkProfileCount = 0;
|
|
753
860
|
|
|
754
861
|
if (
|
|
755
862
|
["network", "cascade"].includes(normalizedDetailSource)
|
|
756
|
-
&&
|
|
863
|
+
&& selectionNetworkEvents.length > 0
|
|
757
864
|
) {
|
|
758
|
-
detailStep = "
|
|
865
|
+
detailStep = "extract_selection_network_profile";
|
|
759
866
|
detailResult = await extractChatProfileCandidate(client, {
|
|
760
867
|
cardCandidate,
|
|
761
868
|
cardNodeId: effectiveCardNodeId,
|
|
762
|
-
resumeState,
|
|
763
|
-
resumeHtml,
|
|
764
|
-
networkEvents:
|
|
765
|
-
normalizedDetailSource,
|
|
766
|
-
selectionNetworkEvents,
|
|
767
|
-
resumeNetworkEvents
|
|
768
|
-
),
|
|
869
|
+
resumeState: null,
|
|
870
|
+
resumeHtml: null,
|
|
871
|
+
networkEvents: selectionNetworkEvents,
|
|
769
872
|
targetUrl,
|
|
770
873
|
closeResume: false,
|
|
771
|
-
networkParseRetryMs: waitPlan.mode_before === "image" ?
|
|
772
|
-
networkParseIntervalMs:
|
|
874
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 250 : 900,
|
|
875
|
+
networkParseIntervalMs: 150
|
|
773
876
|
});
|
|
774
877
|
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
775
878
|
parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
776
|
-
|
|
879
|
+
const selectionNetworkEvidence = summarizeChatFullCvEvidence({ detailResult, contentWait });
|
|
880
|
+
if (selectionNetworkEvidence.network_full_cv_count > 0) {
|
|
881
|
+
networkWait = {
|
|
882
|
+
ok: true,
|
|
883
|
+
skipped: true,
|
|
884
|
+
reason: "selection_network_full_cv_before_online_resume_click",
|
|
885
|
+
elapsed_ms: detailResult.network_parse_retry_elapsed_ms,
|
|
886
|
+
count: selectionNetworkEvents.length,
|
|
887
|
+
total_event_count: selectionNetworkEvents.length,
|
|
888
|
+
wait_plan: waitPlan
|
|
889
|
+
};
|
|
777
890
|
contentWait = {
|
|
778
891
|
ok: true,
|
|
779
892
|
skipped: true,
|
|
780
|
-
reason: "
|
|
893
|
+
reason: "selection_network_full_cv_before_online_resume_click",
|
|
781
894
|
elapsed_ms: 0,
|
|
782
895
|
text_length: 0
|
|
783
896
|
};
|
|
@@ -787,40 +900,103 @@ export async function runChatWorkflow({
|
|
|
787
900
|
}
|
|
788
901
|
|
|
789
902
|
if (!detailResult) {
|
|
790
|
-
detailStep = "
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
903
|
+
detailStep = "open_online_resume";
|
|
904
|
+
networkRecorder.clear();
|
|
905
|
+
const openedResume = await measureTiming(timings, "detail_open_ms", () => openChatOnlineResume(client, {
|
|
906
|
+
timeoutMs: readyTimeoutMs
|
|
794
907
|
}));
|
|
795
|
-
resumeState =
|
|
796
|
-
|
|
908
|
+
resumeState = openedResume.resume_state;
|
|
909
|
+
detailStep = "wait_network";
|
|
910
|
+
networkWait = ["network", "cascade"].includes(normalizedDetailSource)
|
|
911
|
+
? await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
912
|
+
waitForChatProfileNetworkEvents,
|
|
913
|
+
networkRecorder,
|
|
914
|
+
{
|
|
915
|
+
waitPlan,
|
|
916
|
+
minCount: 1,
|
|
917
|
+
requireLoaded: true,
|
|
918
|
+
intervalMs: 200
|
|
919
|
+
}
|
|
920
|
+
))
|
|
921
|
+
: null;
|
|
922
|
+
if (networkWait?.elapsed_ms != null) {
|
|
923
|
+
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
924
|
+
}
|
|
797
925
|
resumeNetworkEvents = networkRecorder.events.slice();
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
926
|
+
|
|
927
|
+
if (
|
|
928
|
+
["network", "cascade"].includes(normalizedDetailSource)
|
|
929
|
+
&& networkWait?.count > 0
|
|
930
|
+
) {
|
|
931
|
+
detailStep = "extract_network_profile";
|
|
932
|
+
detailResult = await extractChatProfileCandidate(client, {
|
|
933
|
+
cardCandidate,
|
|
934
|
+
cardNodeId: effectiveCardNodeId,
|
|
935
|
+
resumeState,
|
|
936
|
+
resumeHtml,
|
|
937
|
+
networkEvents: selectedDetailNetworkEvents(
|
|
938
|
+
normalizedDetailSource,
|
|
939
|
+
selectionNetworkEvents,
|
|
940
|
+
resumeNetworkEvents
|
|
941
|
+
),
|
|
942
|
+
targetUrl,
|
|
943
|
+
closeResume: false,
|
|
944
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
945
|
+
networkParseIntervalMs: 250
|
|
946
|
+
});
|
|
947
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
948
|
+
parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
949
|
+
const networkEvidence = summarizeChatFullCvEvidence({ detailResult, contentWait });
|
|
950
|
+
if (networkEvidence.network_full_cv_count > 0) {
|
|
951
|
+
contentWait = {
|
|
952
|
+
ok: true,
|
|
953
|
+
skipped: true,
|
|
954
|
+
reason: "network_full_cv_parsed_before_dom_wait",
|
|
955
|
+
elapsed_ms: 0,
|
|
956
|
+
text_length: 0
|
|
957
|
+
};
|
|
958
|
+
} else {
|
|
959
|
+
detailResult = null;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (!detailResult) {
|
|
964
|
+
detailStep = "wait_resume_content";
|
|
965
|
+
contentWait = await measureTiming(timings, "dom_fallback_ms", () => waitForChatResumeContent(client, {
|
|
966
|
+
timeoutMs: resumeDomTimeoutMs,
|
|
967
|
+
intervalMs: 300
|
|
968
|
+
}));
|
|
969
|
+
resumeState = contentWait.resume_state || openedResume.resume_state;
|
|
970
|
+
resumeHtml = contentWait.resume_html || null;
|
|
971
|
+
resumeNetworkEvents = networkRecorder.events.slice();
|
|
972
|
+
detailStep = "extract_resume_content";
|
|
973
|
+
detailResult = await extractChatProfileCandidate(client, {
|
|
974
|
+
cardCandidate,
|
|
975
|
+
cardNodeId: effectiveCardNodeId,
|
|
976
|
+
resumeState,
|
|
977
|
+
resumeHtml,
|
|
978
|
+
networkEvents: selectedDetailNetworkEvents(
|
|
979
|
+
normalizedDetailSource,
|
|
980
|
+
selectionNetworkEvents,
|
|
981
|
+
resumeNetworkEvents
|
|
982
|
+
),
|
|
983
|
+
targetUrl,
|
|
984
|
+
closeResume: false,
|
|
985
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
986
|
+
networkParseIntervalMs: 250
|
|
987
|
+
});
|
|
988
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
989
|
+
parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
990
|
+
}
|
|
816
991
|
}
|
|
817
992
|
|
|
818
993
|
let source = normalizedDetailSource === "dom" ? "dom" : "network";
|
|
819
994
|
let imageEvidence = null;
|
|
820
995
|
let llmResult = null;
|
|
821
996
|
const captureNodeId = captureNodeIdFromResumeState(resumeState);
|
|
997
|
+
let fullCvEvidence = summarizeChatFullCvEvidence({ detailResult, contentWait });
|
|
822
998
|
const shouldCaptureImage = normalizedDetailSource === "image"
|
|
823
|
-
|| (normalizedDetailSource === "cascade" &&
|
|
999
|
+
|| (normalizedDetailSource === "cascade" && !fullCvEvidence.full_cv_acquired);
|
|
824
1000
|
if (shouldCaptureImage) {
|
|
825
1001
|
if (captureNodeId) {
|
|
826
1002
|
detailStep = "capture_image_fallback";
|
|
@@ -861,12 +1037,20 @@ export async function runChatWorkflow({
|
|
|
861
1037
|
}
|
|
862
1038
|
}));
|
|
863
1039
|
source = "image";
|
|
1040
|
+
fullCvEvidence = summarizeChatFullCvEvidence({
|
|
1041
|
+
detailResult,
|
|
1042
|
+
contentWait,
|
|
1043
|
+
imageEvidence
|
|
1044
|
+
});
|
|
864
1045
|
recordCvImageFallback(cvAcquisitionState, {
|
|
1046
|
+
reason: fullCvEvidence.network_profile_only_count > 0
|
|
1047
|
+
? "profile_only_network_image_fallback"
|
|
1048
|
+
: "network_miss_image_fallback",
|
|
865
1049
|
parsedNetworkProfileCount,
|
|
866
1050
|
waitResult: networkWait,
|
|
867
1051
|
imageEvidence
|
|
868
1052
|
});
|
|
869
|
-
if (callLlmOnImage) {
|
|
1053
|
+
if (callLlmOnImage && fullCvEvidence.full_cv_acquired) {
|
|
870
1054
|
detailStep = "llm_image_screening";
|
|
871
1055
|
if (!llmConfig) {
|
|
872
1056
|
llmResult = createMissingLlmConfigResult();
|
|
@@ -888,14 +1072,39 @@ export async function runChatWorkflow({
|
|
|
888
1072
|
}
|
|
889
1073
|
} else {
|
|
890
1074
|
source = "missing_capture_node";
|
|
1075
|
+
fullCvEvidence = summarizeChatFullCvEvidence({
|
|
1076
|
+
detailResult,
|
|
1077
|
+
contentWait,
|
|
1078
|
+
imageEvidence
|
|
1079
|
+
});
|
|
891
1080
|
recordCvNetworkMiss(cvAcquisitionState, {
|
|
892
1081
|
reason: "network_miss_no_capture_node",
|
|
893
1082
|
parsedNetworkProfileCount,
|
|
894
1083
|
waitResult: networkWait
|
|
895
1084
|
});
|
|
896
1085
|
}
|
|
897
|
-
} else if (
|
|
1086
|
+
} else if (fullCvEvidence.network_full_cv_count > 0) {
|
|
1087
|
+
source = "network";
|
|
898
1088
|
recordCvNetworkHit(cvAcquisitionState, {
|
|
1089
|
+
reason: "full_cv_network_profile",
|
|
1090
|
+
parsedNetworkProfileCount,
|
|
1091
|
+
waitResult: networkWait
|
|
1092
|
+
});
|
|
1093
|
+
} else if (fullCvEvidence.dom_full_cv) {
|
|
1094
|
+
source = "dom";
|
|
1095
|
+
if (normalizedDetailSource !== "dom") {
|
|
1096
|
+
recordCvNetworkMiss(cvAcquisitionState, {
|
|
1097
|
+
reason: parsedNetworkProfileCount > 0
|
|
1098
|
+
? "profile_only_network_dom_fallback"
|
|
1099
|
+
: "network_miss_dom_fallback",
|
|
1100
|
+
parsedNetworkProfileCount,
|
|
1101
|
+
waitResult: networkWait
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
} else if (parsedNetworkProfileCount > 0) {
|
|
1105
|
+
source = "profile_only_network";
|
|
1106
|
+
recordCvNetworkMiss(cvAcquisitionState, {
|
|
1107
|
+
reason: "profile_only_network_not_full_cv",
|
|
899
1108
|
parsedNetworkProfileCount,
|
|
900
1109
|
waitResult: networkWait
|
|
901
1110
|
});
|
|
@@ -909,25 +1118,29 @@ export async function runChatWorkflow({
|
|
|
909
1118
|
}
|
|
910
1119
|
|
|
911
1120
|
if (useLlmScreening && !llmResult) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
llmResult = createMissingLlmConfigResult();
|
|
1121
|
+
if (!fullCvEvidence.full_cv_acquired) {
|
|
1122
|
+
detailUnavailableReason = "full_cv_not_acquired";
|
|
915
1123
|
} else {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1124
|
+
detailStep = "llm_screening";
|
|
1125
|
+
if (!llmConfig) {
|
|
1126
|
+
llmResult = createMissingLlmConfigResult();
|
|
1127
|
+
} else {
|
|
1128
|
+
try {
|
|
1129
|
+
const llmTimingKey = imageEvidence?.file_paths?.length
|
|
1130
|
+
? "vision_model_ms"
|
|
1131
|
+
: "text_model_ms";
|
|
1132
|
+
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
1133
|
+
candidate: detailResult.candidate,
|
|
1134
|
+
criteria,
|
|
1135
|
+
config: llmConfig,
|
|
1136
|
+
timeoutMs: llmTimeoutMs,
|
|
1137
|
+
imageEvidence,
|
|
1138
|
+
maxImages: llmImageLimit,
|
|
1139
|
+
imageDetail: llmImageDetail
|
|
1140
|
+
}));
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
llmResult = createFailedLlmResult(error);
|
|
1143
|
+
}
|
|
931
1144
|
}
|
|
932
1145
|
}
|
|
933
1146
|
}
|
|
@@ -955,7 +1168,8 @@ export async function runChatWorkflow({
|
|
|
955
1168
|
text_length: contentWait.text_length
|
|
956
1169
|
},
|
|
957
1170
|
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
958
|
-
image_evidence: summarizeImageEvidence(imageEvidence)
|
|
1171
|
+
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1172
|
+
full_cv_evidence: fullCvEvidence
|
|
959
1173
|
};
|
|
960
1174
|
}
|
|
961
1175
|
} catch (error) {
|
|
@@ -964,6 +1178,13 @@ export async function runChatWorkflow({
|
|
|
964
1178
|
const recovery = await recoverAndReapplyChatContext(detailUnavailableReason, error);
|
|
965
1179
|
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
966
1180
|
detailResult.cv_acquisition.recovery = recovery;
|
|
1181
|
+
} else if (isUnsafeChatOnlineResumeLinkError(error)) {
|
|
1182
|
+
detailUnavailableReason = "unsafe_online_resume_navigation_link";
|
|
1183
|
+
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1184
|
+
detailResult.cv_acquisition.blocked_pre_click = true;
|
|
1185
|
+
detailResult.cv_acquisition.button_href = error.href || null;
|
|
1186
|
+
detailResult.cv_acquisition.button_selector = error.button_selector || null;
|
|
1187
|
+
detailResult.cv_acquisition.attempts = error.attempts || null;
|
|
967
1188
|
} else {
|
|
968
1189
|
if (!isRecoverableCdpNodeError(error)) throw error;
|
|
969
1190
|
detailUnavailableReason = `recoverable_cdp_node_stale:${detailStep}`;
|
|
@@ -979,22 +1200,9 @@ export async function runChatWorkflow({
|
|
|
979
1200
|
runControl.setPhase("chat:screening");
|
|
980
1201
|
let cardOnlyLlmResult = null;
|
|
981
1202
|
if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
try {
|
|
986
|
-
cardOnlyLlmResult = await measureTiming(timings, "text_model_ms", () => callScreeningLlm({
|
|
987
|
-
candidate: screeningCandidate,
|
|
988
|
-
criteria,
|
|
989
|
-
config: llmConfig,
|
|
990
|
-
timeoutMs: llmTimeoutMs,
|
|
991
|
-
maxImages: llmImageLimit,
|
|
992
|
-
imageDetail: llmImageDetail
|
|
993
|
-
}));
|
|
994
|
-
} catch (error) {
|
|
995
|
-
cardOnlyLlmResult = createFailedLlmResult(error);
|
|
996
|
-
}
|
|
997
|
-
}
|
|
1203
|
+
detailUnavailableReason = detailResult
|
|
1204
|
+
? "full_cv_not_acquired"
|
|
1205
|
+
: "detail_not_opened_full_cv_required";
|
|
998
1206
|
}
|
|
999
1207
|
const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
|
|
1000
1208
|
const screening = detailUnavailableReason
|