@reconcrap/boss-recommend-mcp 2.0.15 → 2.0.17
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
CHANGED
|
@@ -10,6 +10,7 @@ const SCREEN_CONFIG_TEMPLATE_DEFAULTS = Object.freeze({
|
|
|
10
10
|
apiKey: "replace-with-your-api-key",
|
|
11
11
|
model: "gpt-4.1-mini"
|
|
12
12
|
});
|
|
13
|
+
const LLM_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "auto", "current"]);
|
|
13
14
|
|
|
14
15
|
export const TARGET_COUNT_CANONICAL_ALL = "all";
|
|
15
16
|
export const TARGET_COUNT_ACCEPTED_EXAMPLES = [TARGET_COUNT_CANONICAL_ALL, -1, 20, "全部候选人"];
|
|
@@ -223,6 +224,12 @@ function parsePositiveInteger(raw, fallback = null) {
|
|
|
223
224
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
function parseConfigNumber(raw, fallback = null) {
|
|
228
|
+
if (raw === undefined || raw === null || raw === "") return fallback;
|
|
229
|
+
const parsed = Number(raw);
|
|
230
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
231
|
+
}
|
|
232
|
+
|
|
226
233
|
function parseConfigBoolean(raw, fallback = false) {
|
|
227
234
|
if (typeof raw === "boolean") return raw;
|
|
228
235
|
const normalized = normalizeText(raw).toLowerCase();
|
|
@@ -231,6 +238,11 @@ function parseConfigBoolean(raw, fallback = false) {
|
|
|
231
238
|
return fallback;
|
|
232
239
|
}
|
|
233
240
|
|
|
241
|
+
function normalizeLlmThinkingLevel(raw, fallback = "low") {
|
|
242
|
+
const normalized = normalizeText(raw).toLowerCase();
|
|
243
|
+
return LLM_THINKING_LEVELS.has(normalized) ? normalized : fallback;
|
|
244
|
+
}
|
|
245
|
+
|
|
234
246
|
function resolveConfigPathValue(raw, configDir) {
|
|
235
247
|
const normalized = normalizeText(raw);
|
|
236
248
|
if (!normalized) return "";
|
|
@@ -387,8 +399,18 @@ export function resolveBossScreeningConfig(workspaceRoot) {
|
|
|
387
399
|
baseUrl: normalizeText(parsed.baseUrl).replace(/\/+$/, ""),
|
|
388
400
|
apiKey: normalizeText(parsed.apiKey),
|
|
389
401
|
model: normalizeText(parsed.model),
|
|
402
|
+
openaiOrganization: normalizeText(parsed.openaiOrganization || parsed.organization),
|
|
403
|
+
openaiProject: normalizeText(parsed.openaiProject || parsed.project),
|
|
390
404
|
debugPort: parsePositiveInteger(parsed.debugPort, 9222),
|
|
391
|
-
llmThinkingLevel:
|
|
405
|
+
llmThinkingLevel: normalizeLlmThinkingLevel(parsed.llmThinkingLevel || parsed.thinkingLevel || parsed.reasoningEffort, "low"),
|
|
406
|
+
llmTimeoutMs: parsePositiveInteger(parsed.llmTimeoutMs || parsed.timeoutMs, null),
|
|
407
|
+
llmMaxRetries: parsePositiveInteger(parsed.llmMaxRetries || parsed.maxRetries, null),
|
|
408
|
+
llmMaxTokens: parsePositiveInteger(parsed.llmMaxTokens || parsed.maxTokens, null),
|
|
409
|
+
llmMaxCompletionTokens: parsePositiveInteger(parsed.llmMaxCompletionTokens || parsed.maxCompletionTokens, null),
|
|
410
|
+
llmImageLimit: parsePositiveInteger(parsed.llmImageLimit || parsed.imageLimit, null),
|
|
411
|
+
llmImageDetail: normalizeText(parsed.llmImageDetail || parsed.imageDetail),
|
|
412
|
+
temperature: parseConfigNumber(parsed.temperature, null),
|
|
413
|
+
topP: parseConfigNumber(parsed.topP || parsed.top_p, null),
|
|
392
414
|
outputDir: resolveConfigPathValue(parsed.outputDir, configDir),
|
|
393
415
|
humanRestEnabled: parseConfigBoolean(parsed.humanRestEnabled, false)
|
|
394
416
|
},
|
package/src/cli.js
CHANGED
|
@@ -55,6 +55,10 @@ const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
|
|
|
55
55
|
const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
|
|
56
56
|
const installConfigDefaults = Object.freeze({
|
|
57
57
|
llmThinkingLevel: "low",
|
|
58
|
+
llmMaxTokens: 512,
|
|
59
|
+
llmMaxRetries: 3,
|
|
60
|
+
llmImageLimit: 8,
|
|
61
|
+
llmImageDetail: "low",
|
|
58
62
|
humanRestEnabled: false
|
|
59
63
|
});
|
|
60
64
|
const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
|
|
@@ -77,6 +77,31 @@ function applyChatCompletionThinking(payload, { baseUrl = "", model = "", thinki
|
|
|
77
77
|
return payload;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function parsePositiveNumber(value, fallback = null) {
|
|
81
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
82
|
+
const parsed = Number(value);
|
|
83
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseFiniteNumber(value, fallback = null) {
|
|
87
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
88
|
+
const parsed = Number(value);
|
|
89
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveLlmOutputTokenBudget(config = {}, thinkingLevel = "") {
|
|
93
|
+
const explicit = parsePositiveNumber(
|
|
94
|
+
config.llmMaxCompletionTokens
|
|
95
|
+
?? config.maxCompletionTokens
|
|
96
|
+
?? config.llmMaxTokens
|
|
97
|
+
?? config.maxTokens,
|
|
98
|
+
null
|
|
99
|
+
);
|
|
100
|
+
if (explicit) return Math.max(1, Math.floor(explicit));
|
|
101
|
+
const normalizedThinking = normalizeLlmThinkingLevel(thinkingLevel || "low") || "low";
|
|
102
|
+
return normalizedThinking === "off" || normalizedThinking === "minimal" ? 64 : 512;
|
|
103
|
+
}
|
|
104
|
+
|
|
80
105
|
export function normalizeText(input) {
|
|
81
106
|
return String(input || "").replace(/\s+/g, " ").trim();
|
|
82
107
|
}
|
|
@@ -1436,20 +1461,31 @@ export async function callScreeningLlm({
|
|
|
1436
1461
|
throw new Error("Candidate text and image evidence are empty");
|
|
1437
1462
|
}
|
|
1438
1463
|
|
|
1464
|
+
const thinkingLevel = config.llmThinkingLevel || config.thinkingLevel || config.reasoningEffort || "low";
|
|
1465
|
+
const outputTokenBudget = resolveLlmOutputTokenBudget(config, thinkingLevel);
|
|
1439
1466
|
const payload = {
|
|
1440
1467
|
model,
|
|
1441
|
-
temperature: 0.1,
|
|
1442
|
-
max_tokens:
|
|
1468
|
+
temperature: parseFiniteNumber(config.temperature, 0.1),
|
|
1469
|
+
max_tokens: outputTokenBudget,
|
|
1443
1470
|
messages: buildScreeningLlmMessages({
|
|
1444
1471
|
candidate,
|
|
1445
1472
|
criteria,
|
|
1446
1473
|
imageInputs
|
|
1447
1474
|
})
|
|
1448
1475
|
};
|
|
1476
|
+
const topP = parseFiniteNumber(config.topP ?? config.top_p, null);
|
|
1477
|
+
if (topP !== null) payload.top_p = topP;
|
|
1478
|
+
const maxCompletionTokens = parsePositiveNumber(
|
|
1479
|
+
config.llmMaxCompletionTokens ?? config.maxCompletionTokens,
|
|
1480
|
+
null
|
|
1481
|
+
);
|
|
1482
|
+
if (maxCompletionTokens !== null) {
|
|
1483
|
+
payload.max_completion_tokens = Math.max(1, Math.floor(maxCompletionTokens));
|
|
1484
|
+
}
|
|
1449
1485
|
applyChatCompletionThinking(payload, {
|
|
1450
1486
|
baseUrl,
|
|
1451
1487
|
model,
|
|
1452
|
-
thinkingLevel
|
|
1488
|
+
thinkingLevel
|
|
1453
1489
|
});
|
|
1454
1490
|
|
|
1455
1491
|
const maxRetries = normalizeLlmMaxRetries(config.llmMaxRetries ?? config.maxRetries);
|
|
@@ -1504,7 +1540,12 @@ export async function callScreeningLlm({
|
|
|
1504
1540
|
ok: true,
|
|
1505
1541
|
provider: {
|
|
1506
1542
|
baseUrl: baseUrl.replace(/\/\/[^/]+/, "//[redacted-host]"),
|
|
1507
|
-
model
|
|
1543
|
+
model,
|
|
1544
|
+
thinking_level: normalizeLlmThinkingLevel(thinkingLevel) || "low",
|
|
1545
|
+
thinking: payload.thinking || null,
|
|
1546
|
+
reasoning_effort: payload.reasoning_effort || null,
|
|
1547
|
+
max_tokens: payload.max_tokens,
|
|
1548
|
+
max_completion_tokens: payload.max_completion_tokens || null
|
|
1508
1549
|
},
|
|
1509
1550
|
passed,
|
|
1510
1551
|
reason: "",
|
|
@@ -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,
|
|
@@ -205,6 +206,8 @@ function createSkippedDetailResult(cardCandidate, reason, error = null) {
|
|
|
205
206
|
|
|
206
207
|
const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
|
|
207
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;
|
|
208
211
|
const CHAT_FULL_CV_SECTION_PATTERNS = Object.freeze([
|
|
209
212
|
/教育(?:经历|背景|经验)?/i,
|
|
210
213
|
/工作(?:经历|经验)?/i,
|
|
@@ -250,17 +253,32 @@ function isFullCvNetworkProfile(profileResult = {}) {
|
|
|
250
253
|
const sourceKeys = profileResult.profile?.source_keys || {};
|
|
251
254
|
const textLength = networkProfileTextLength(profileResult);
|
|
252
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;
|
|
253
265
|
|
|
254
|
-
if (sourceKeys.geek_detail_info || sourceKeys.geek_detail)
|
|
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
|
+
}
|
|
255
272
|
if (sourceKeys.network_html_text) {
|
|
256
|
-
return textLength >=
|
|
257
|
-
|
|
273
|
+
return textLength >= CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH
|
|
274
|
+
&& sectionCount >= 2;
|
|
258
275
|
}
|
|
259
276
|
if (sourceKeys.chat_history_resume) {
|
|
260
277
|
const educationCount = Number(sourceKeys.education_count) || 0;
|
|
261
278
|
const workCount = Number(sourceKeys.work_count) || 0;
|
|
262
|
-
return (educationCount + workCount)
|
|
263
|
-
&&
|
|
279
|
+
return (educationCount + workCount) >= 2
|
|
280
|
+
&& textLength >= CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH
|
|
281
|
+
&& sectionCount >= 2;
|
|
264
282
|
}
|
|
265
283
|
return false;
|
|
266
284
|
}
|
|
@@ -826,28 +844,8 @@ export async function runChatWorkflow({
|
|
|
826
844
|
}
|
|
827
845
|
|
|
828
846
|
if (!detailResult) {
|
|
829
|
-
detailStep = "open_online_resume";
|
|
830
|
-
networkRecorder.clear();
|
|
831
|
-
const openedResume = await measureTiming(timings, "detail_open_ms", () => openChatOnlineResume(client, {
|
|
832
|
-
timeoutMs: readyTimeoutMs
|
|
833
|
-
}));
|
|
834
847
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
835
|
-
|
|
836
|
-
const networkWait = ["network", "cascade"].includes(normalizedDetailSource)
|
|
837
|
-
? await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
838
|
-
waitForChatProfileNetworkEvents,
|
|
839
|
-
networkRecorder,
|
|
840
|
-
{
|
|
841
|
-
waitPlan,
|
|
842
|
-
minCount: 1,
|
|
843
|
-
requireLoaded: true,
|
|
844
|
-
intervalMs: 200
|
|
845
|
-
}
|
|
846
|
-
))
|
|
847
|
-
: null;
|
|
848
|
-
if (networkWait?.elapsed_ms != null) {
|
|
849
|
-
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
850
|
-
}
|
|
848
|
+
let networkWait = null;
|
|
851
849
|
let contentWait = {
|
|
852
850
|
ok: false,
|
|
853
851
|
skipped: false,
|
|
@@ -855,39 +853,44 @@ export async function runChatWorkflow({
|
|
|
855
853
|
elapsed_ms: 0,
|
|
856
854
|
text_length: 0
|
|
857
855
|
};
|
|
858
|
-
let resumeState =
|
|
856
|
+
let resumeState = null;
|
|
859
857
|
let resumeHtml = null;
|
|
860
|
-
let resumeNetworkEvents =
|
|
858
|
+
let resumeNetworkEvents = [];
|
|
861
859
|
let parsedNetworkProfileCount = 0;
|
|
862
860
|
|
|
863
861
|
if (
|
|
864
862
|
["network", "cascade"].includes(normalizedDetailSource)
|
|
865
|
-
&&
|
|
863
|
+
&& selectionNetworkEvents.length > 0
|
|
866
864
|
) {
|
|
867
|
-
detailStep = "
|
|
865
|
+
detailStep = "extract_selection_network_profile";
|
|
868
866
|
detailResult = await extractChatProfileCandidate(client, {
|
|
869
867
|
cardCandidate,
|
|
870
868
|
cardNodeId: effectiveCardNodeId,
|
|
871
|
-
resumeState,
|
|
872
|
-
resumeHtml,
|
|
873
|
-
networkEvents:
|
|
874
|
-
normalizedDetailSource,
|
|
875
|
-
selectionNetworkEvents,
|
|
876
|
-
resumeNetworkEvents
|
|
877
|
-
),
|
|
869
|
+
resumeState: null,
|
|
870
|
+
resumeHtml: null,
|
|
871
|
+
networkEvents: selectionNetworkEvents,
|
|
878
872
|
targetUrl,
|
|
879
873
|
closeResume: false,
|
|
880
|
-
networkParseRetryMs: waitPlan.mode_before === "image" ?
|
|
881
|
-
networkParseIntervalMs:
|
|
874
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 250 : 900,
|
|
875
|
+
networkParseIntervalMs: 150
|
|
882
876
|
});
|
|
883
877
|
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
884
878
|
parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
885
|
-
const
|
|
886
|
-
if (
|
|
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
|
+
};
|
|
887
890
|
contentWait = {
|
|
888
891
|
ok: true,
|
|
889
892
|
skipped: true,
|
|
890
|
-
reason: "
|
|
893
|
+
reason: "selection_network_full_cv_before_online_resume_click",
|
|
891
894
|
elapsed_ms: 0,
|
|
892
895
|
text_length: 0
|
|
893
896
|
};
|
|
@@ -897,32 +900,94 @@ export async function runChatWorkflow({
|
|
|
897
900
|
}
|
|
898
901
|
|
|
899
902
|
if (!detailResult) {
|
|
900
|
-
detailStep = "
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
903
|
+
detailStep = "open_online_resume";
|
|
904
|
+
networkRecorder.clear();
|
|
905
|
+
const openedResume = await measureTiming(timings, "detail_open_ms", () => openChatOnlineResume(client, {
|
|
906
|
+
timeoutMs: readyTimeoutMs
|
|
904
907
|
}));
|
|
905
|
-
resumeState =
|
|
906
|
-
|
|
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
|
+
}
|
|
907
925
|
resumeNetworkEvents = networkRecorder.events.slice();
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
+
}
|
|
926
991
|
}
|
|
927
992
|
|
|
928
993
|
let source = normalizedDetailSource === "dom" ? "dom" : "network";
|
|
@@ -1113,6 +1178,13 @@ export async function runChatWorkflow({
|
|
|
1113
1178
|
const recovery = await recoverAndReapplyChatContext(detailUnavailableReason, error);
|
|
1114
1179
|
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1115
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;
|
|
1116
1188
|
} else {
|
|
1117
1189
|
if (!isRecoverableCdpNodeError(error)) throw error;
|
|
1118
1190
|
detailUnavailableReason = `recoverable_cdp_node_stale:${detailStep}`;
|