@reconcrap/boss-recommend-mcp 2.0.5 → 2.0.6
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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
querySelectorAll,
|
|
9
9
|
sleep
|
|
10
10
|
} from "../../core/browser/index.js";
|
|
11
|
+
import { candidateKeyFromProfile } from "../../core/infinite-list/index.js";
|
|
11
12
|
import {
|
|
12
13
|
buildScreeningCandidateFromDetail,
|
|
13
14
|
htmlToText
|
|
@@ -22,6 +23,10 @@ import {
|
|
|
22
23
|
getRecommendRoots,
|
|
23
24
|
queryFirstAcrossRoots
|
|
24
25
|
} from "./roots.js";
|
|
26
|
+
import {
|
|
27
|
+
findRecommendCardNodeIds,
|
|
28
|
+
readRecommendCardCandidate
|
|
29
|
+
} from "./cards.js";
|
|
25
30
|
|
|
26
31
|
export function matchesRecommendDetailNetwork(url) {
|
|
27
32
|
return DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
@@ -146,14 +151,36 @@ export async function readRecommendDetailHtml(client, detailState) {
|
|
|
146
151
|
let popupHTML = "";
|
|
147
152
|
let resumeHTML = "";
|
|
148
153
|
let resumeIframeDocumentNodeId = null;
|
|
154
|
+
const errors = [];
|
|
149
155
|
|
|
150
156
|
if (detailState?.popup?.node_id) {
|
|
151
|
-
|
|
157
|
+
try {
|
|
158
|
+
popupHTML = await getOuterHTML(client, detailState.popup.node_id);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
errors.push({
|
|
161
|
+
source: "popup",
|
|
162
|
+
node_id: detailState.popup.node_id,
|
|
163
|
+
stale_node: isStaleRecommendNodeError(error),
|
|
164
|
+
error: error?.message || String(error)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
if (detailState?.resumeIframe?.node_id) {
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
try {
|
|
171
|
+
resumeIframeDocumentNodeId = await getFrameDocumentNodeId(client, detailState.resumeIframe.node_id);
|
|
172
|
+
resumeHTML = await getOuterHTML(client, resumeIframeDocumentNodeId);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
errors.push({
|
|
175
|
+
source: "resume_iframe",
|
|
176
|
+
node_id: detailState.resumeIframe.node_id,
|
|
177
|
+
document_node_id: resumeIframeDocumentNodeId,
|
|
178
|
+
stale_node: isStaleRecommendNodeError(error),
|
|
179
|
+
error: error?.message || String(error)
|
|
180
|
+
});
|
|
181
|
+
resumeIframeDocumentNodeId = null;
|
|
182
|
+
resumeHTML = "";
|
|
183
|
+
}
|
|
157
184
|
}
|
|
158
185
|
|
|
159
186
|
return {
|
|
@@ -161,7 +188,90 @@ export async function readRecommendDetailHtml(client, detailState) {
|
|
|
161
188
|
resumeHTML,
|
|
162
189
|
resumeIframeDocumentNodeId,
|
|
163
190
|
popupText: htmlToText(popupHTML),
|
|
164
|
-
resumeText: htmlToText(resumeHTML)
|
|
191
|
+
resumeText: htmlToText(resumeHTML),
|
|
192
|
+
errors
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function isStaleRecommendNodeError(error) {
|
|
197
|
+
const message = String(error?.message || error || "");
|
|
198
|
+
return /Could not find node with given id|No node with given id|Node is detached|Cannot find node/i.test(message);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function findRecommendCardNodeForCandidateKey(client, {
|
|
202
|
+
candidateKey = "",
|
|
203
|
+
rootState = null,
|
|
204
|
+
targetUrl = "",
|
|
205
|
+
source = "recommend-run-card-retry",
|
|
206
|
+
timeoutMs = 5000,
|
|
207
|
+
intervalMs = 250
|
|
208
|
+
} = {}) {
|
|
209
|
+
if (!candidateKey) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
reason: "candidate_key_required"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const started = Date.now();
|
|
217
|
+
let lastError = null;
|
|
218
|
+
let lastCardCount = 0;
|
|
219
|
+
while (Date.now() - started <= timeoutMs) {
|
|
220
|
+
const currentRootState = rootState?.iframe?.documentNodeId
|
|
221
|
+
? rootState
|
|
222
|
+
: await getRecommendRoots(client);
|
|
223
|
+
const frameNodeId = currentRootState?.iframe?.documentNodeId;
|
|
224
|
+
if (!frameNodeId) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
reason: "recommend_frame_not_found"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const nodeIds = await findRecommendCardNodeIds(client, frameNodeId);
|
|
232
|
+
lastCardCount = nodeIds.length;
|
|
233
|
+
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
234
|
+
const nodeId = nodeIds[visibleIndex];
|
|
235
|
+
try {
|
|
236
|
+
const candidate = await readRecommendCardCandidate(client, nodeId, {
|
|
237
|
+
targetUrl,
|
|
238
|
+
source,
|
|
239
|
+
metadata: {
|
|
240
|
+
visible_index: visibleIndex,
|
|
241
|
+
retry_reason: "stale_detail_node"
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
const key = candidateKeyFromProfile(candidate, {
|
|
245
|
+
nodeId,
|
|
246
|
+
visibleIndex,
|
|
247
|
+
attributes: candidate?.attributes || candidate?.metadata?.attributes || {}
|
|
248
|
+
});
|
|
249
|
+
if (key === candidateKey) {
|
|
250
|
+
return {
|
|
251
|
+
ok: true,
|
|
252
|
+
node_id: nodeId,
|
|
253
|
+
visible_index: visibleIndex,
|
|
254
|
+
candidate,
|
|
255
|
+
key,
|
|
256
|
+
root_state: currentRootState,
|
|
257
|
+
card_count: nodeIds.length
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
lastError = error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (intervalMs > 0) await sleep(intervalMs);
|
|
266
|
+
rootState = null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
reason: "candidate_key_not_mounted",
|
|
272
|
+
candidate_key: candidateKey,
|
|
273
|
+
last_card_count: lastCardCount,
|
|
274
|
+
error: lastError?.message || null
|
|
165
275
|
};
|
|
166
276
|
}
|
|
167
277
|
|
|
@@ -181,6 +291,77 @@ export async function openRecommendCardDetail(client, cardNodeId, {
|
|
|
181
291
|
};
|
|
182
292
|
}
|
|
183
293
|
|
|
294
|
+
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
295
|
+
cardNodeId,
|
|
296
|
+
candidateKey = "",
|
|
297
|
+
cardCandidate = null,
|
|
298
|
+
rootState = null,
|
|
299
|
+
targetUrl = "",
|
|
300
|
+
timeoutMs = 12000,
|
|
301
|
+
scrollIntoView = true,
|
|
302
|
+
retryTimeoutMs = 5000,
|
|
303
|
+
retryIntervalMs = 250,
|
|
304
|
+
maxAttempts = 2
|
|
305
|
+
} = {}) {
|
|
306
|
+
let currentNodeId = cardNodeId;
|
|
307
|
+
let currentCandidate = cardCandidate;
|
|
308
|
+
let currentRootState = rootState;
|
|
309
|
+
const attempts = [];
|
|
310
|
+
const limit = Math.max(1, Number(maxAttempts) || 1);
|
|
311
|
+
|
|
312
|
+
for (let attemptIndex = 0; attemptIndex < limit; attemptIndex += 1) {
|
|
313
|
+
try {
|
|
314
|
+
const opened = await openRecommendCardDetail(client, currentNodeId, {
|
|
315
|
+
timeoutMs,
|
|
316
|
+
scrollIntoView
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
...opened,
|
|
320
|
+
card_node_id: currentNodeId,
|
|
321
|
+
card_candidate: currentCandidate,
|
|
322
|
+
retry_attempts: attempts
|
|
323
|
+
};
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const stale = isStaleRecommendNodeError(error);
|
|
326
|
+
attempts.push({
|
|
327
|
+
attempt: attemptIndex + 1,
|
|
328
|
+
node_id: currentNodeId,
|
|
329
|
+
stale_node: stale,
|
|
330
|
+
error: error?.message || String(error)
|
|
331
|
+
});
|
|
332
|
+
if (!stale || attemptIndex >= limit - 1 || !candidateKey) {
|
|
333
|
+
error.recommend_detail_open_attempts = attempts;
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const resolved = await findRecommendCardNodeForCandidateKey(client, {
|
|
338
|
+
candidateKey,
|
|
339
|
+
rootState: currentRootState,
|
|
340
|
+
targetUrl,
|
|
341
|
+
timeoutMs: retryTimeoutMs,
|
|
342
|
+
intervalMs: retryIntervalMs
|
|
343
|
+
});
|
|
344
|
+
attempts[attempts.length - 1].refresh_lookup = {
|
|
345
|
+
ok: Boolean(resolved.ok),
|
|
346
|
+
node_id: resolved.node_id || null,
|
|
347
|
+
visible_index: resolved.visible_index ?? null,
|
|
348
|
+
card_count: resolved.card_count || resolved.last_card_count || 0,
|
|
349
|
+
reason: resolved.reason || null,
|
|
350
|
+
error: resolved.error || null
|
|
351
|
+
};
|
|
352
|
+
if (!resolved.ok || !resolved.node_id) {
|
|
353
|
+
error.recommend_detail_open_attempts = attempts;
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
currentNodeId = resolved.node_id;
|
|
357
|
+
currentCandidate = resolved.candidate || currentCandidate;
|
|
358
|
+
currentRootState = resolved.root_state || null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
throw new Error("Recommend detail retry exhausted");
|
|
363
|
+
}
|
|
364
|
+
|
|
184
365
|
export async function closeRecommendDetail(client, {
|
|
185
366
|
attemptsLimit = 3
|
|
186
367
|
} = {}) {
|
|
@@ -317,7 +498,8 @@ export async function extractRecommendDetailCandidate(client, {
|
|
|
317
498
|
detail_popup_root: detailState?.popup?.root || null,
|
|
318
499
|
resume_iframe_selector: detailState?.resumeIframe?.selector || null,
|
|
319
500
|
resume_iframe_root: detailState?.resumeIframe?.root || null,
|
|
320
|
-
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId
|
|
501
|
+
resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId,
|
|
502
|
+
detail_html_errors: detailHtml.errors || []
|
|
321
503
|
}
|
|
322
504
|
});
|
|
323
505
|
|
|
@@ -334,7 +516,8 @@ export async function extractRecommendDetailCandidate(client, {
|
|
|
334
516
|
popup_text: detailHtml.popupText,
|
|
335
517
|
resume_text: detailHtml.resumeText,
|
|
336
518
|
popup_html_length: detailHtml.popupHTML.length,
|
|
337
|
-
resume_html_length: detailHtml.resumeHTML.length
|
|
519
|
+
resume_html_length: detailHtml.resumeHTML.length,
|
|
520
|
+
html_errors: detailHtml.errors || []
|
|
338
521
|
},
|
|
339
522
|
close_result: closeResult
|
|
340
523
|
};
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
closeRecommendDetail,
|
|
27
27
|
createRecommendDetailNetworkRecorder,
|
|
28
28
|
extractRecommendDetailCandidate,
|
|
29
|
-
|
|
29
|
+
openRecommendCardDetailWithFreshRetry,
|
|
30
30
|
waitForRecommendDetailNetworkEvents
|
|
31
31
|
} from "./detail.js";
|
|
32
32
|
import {
|
|
@@ -596,9 +596,9 @@ export async function runRecommendWorkflow({
|
|
|
596
596
|
}
|
|
597
597
|
|
|
598
598
|
const index = results.length;
|
|
599
|
-
|
|
599
|
+
let cardNodeId = nextCandidateResult.item.node_id;
|
|
600
600
|
const candidateKey = nextCandidateResult.item.key;
|
|
601
|
-
|
|
601
|
+
let cardCandidate = nextCandidateResult.item.candidate;
|
|
602
602
|
|
|
603
603
|
let screeningCandidate = cardCandidate;
|
|
604
604
|
let detailResult = null;
|
|
@@ -608,7 +608,17 @@ export async function runRecommendWorkflow({
|
|
|
608
608
|
runControl.setPhase("recommend:detail");
|
|
609
609
|
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
610
610
|
networkRecorder.clear();
|
|
611
|
-
const openedDetail = await
|
|
611
|
+
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
612
|
+
cardNodeId,
|
|
613
|
+
candidateKey,
|
|
614
|
+
cardCandidate,
|
|
615
|
+
rootState,
|
|
616
|
+
targetUrl,
|
|
617
|
+
maxAttempts: 2
|
|
618
|
+
});
|
|
619
|
+
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
620
|
+
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
621
|
+
screeningCandidate = cardCandidate;
|
|
612
622
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
613
623
|
const networkWait = await waitForCvNetworkEvents(
|
|
614
624
|
waitForRecommendDetailNetworkEvents,
|