@reconcrap/boss-recommend-mcp 2.0.33 → 2.0.35
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/page-guard.js +15 -5
- package/src/domains/chat/run-service.js +117 -20
- package/src/domains/recommend/refresh.js +19 -5
- package/src/domains/recommend/run-service.js +211 -67
- package/src/domains/recruit/refresh.js +10 -6
- package/src/domains/recruit/run-service.js +449 -164
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getMainFrameUrl,
|
|
3
|
+
sleep,
|
|
3
4
|
waitForMainFrameUrl
|
|
4
5
|
} from "../../core/browser/index.js";
|
|
5
6
|
import { CHAT_TARGET_URL } from "./constants.js";
|
|
@@ -60,19 +61,25 @@ export async function assertChatShellNotResumeTopLevel(client, {
|
|
|
60
61
|
export async function recoverChatShell(client, {
|
|
61
62
|
targetUrl = CHAT_TARGET_URL,
|
|
62
63
|
timeoutMs = 60000,
|
|
63
|
-
intervalMs = 500
|
|
64
|
+
intervalMs = 500,
|
|
65
|
+
forceNavigate = false,
|
|
66
|
+
settleMs = 1200
|
|
64
67
|
} = {}) {
|
|
65
68
|
const before = await getChatTopLevelState(client);
|
|
66
|
-
if (before.is_chat_shell) {
|
|
69
|
+
if (before.is_chat_shell && !forceNavigate) {
|
|
67
70
|
return {
|
|
68
71
|
recovered: false,
|
|
69
72
|
before,
|
|
70
73
|
after: before,
|
|
71
|
-
navigate_url: null
|
|
74
|
+
navigate_url: null,
|
|
75
|
+
force_navigate: false
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
await client.Page.navigate({ url: targetUrl });
|
|
79
|
+
const navigateResult = await client.Page.navigate({ url: targetUrl });
|
|
80
|
+
if (forceNavigate && settleMs > 0) {
|
|
81
|
+
await sleep(settleMs);
|
|
82
|
+
}
|
|
76
83
|
const waited = await waitForMainFrameUrl(client, isChatShellUrl, {
|
|
77
84
|
timeoutMs,
|
|
78
85
|
intervalMs
|
|
@@ -80,9 +87,12 @@ export async function recoverChatShell(client, {
|
|
|
80
87
|
const after = await getChatTopLevelState(client);
|
|
81
88
|
return {
|
|
82
89
|
recovered: waited.ok && after.is_chat_shell,
|
|
90
|
+
refreshed: Boolean(forceNavigate && before.is_chat_shell && after.is_chat_shell),
|
|
83
91
|
before,
|
|
84
92
|
after,
|
|
85
93
|
wait: waited,
|
|
86
|
-
|
|
94
|
+
navigate_result: navigateResult || null,
|
|
95
|
+
navigate_url: targetUrl,
|
|
96
|
+
force_navigate: Boolean(forceNavigate)
|
|
87
97
|
};
|
|
88
98
|
}
|
|
@@ -161,6 +161,17 @@ function resultOpenedDetail(result) {
|
|
|
161
161
|
return Boolean(result?.detail && !result.detail?.cv_acquisition?.skipped);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
export function countChatResultStatuses(results = []) {
|
|
165
|
+
return {
|
|
166
|
+
processed: results.length,
|
|
167
|
+
screened: results.length,
|
|
168
|
+
detail_opened: results.filter(resultOpenedDetail).length,
|
|
169
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
170
|
+
passed: results.filter((item) => item.screening?.passed).length,
|
|
171
|
+
skipped: results.filter((item) => item.screening?.status === "skip").length
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
164
175
|
export function chatDetailSkipReasonFromReadyState(state = {}) {
|
|
165
176
|
if (state?.attachment_resume_enabled) return "attachment_resume_already_available";
|
|
166
177
|
return "";
|
|
@@ -173,6 +184,11 @@ export function makeChatResumeModalOpenBeforeCandidateClickError(closeResult = n
|
|
|
173
184
|
return error;
|
|
174
185
|
}
|
|
175
186
|
|
|
187
|
+
export function isChatResumeModalCloseFailureError(error) {
|
|
188
|
+
return error?.code === "CHAT_RESUME_MODAL_OPEN_BEFORE_CANDIDATE_CLICK"
|
|
189
|
+
|| /CHAT_RESUME_MODAL_OPEN_BEFORE_CANDIDATE_CLICK/i.test(String(error?.message || error || ""));
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
export async function ensureNoOpenChatResumeModalBeforeCandidateClick(client, {
|
|
177
193
|
closeAttempts = 3
|
|
178
194
|
} = {}) {
|
|
@@ -319,12 +335,24 @@ function createSkippedDetailResult(cardCandidate, reason, error = null) {
|
|
|
319
335
|
cv_acquisition: {
|
|
320
336
|
source: reason,
|
|
321
337
|
skipped: true,
|
|
322
|
-
error: error?.message || null
|
|
338
|
+
error: error?.message || null,
|
|
339
|
+
error_code: error?.code || null
|
|
323
340
|
},
|
|
324
341
|
close_result: null
|
|
325
342
|
};
|
|
326
343
|
}
|
|
327
344
|
|
|
345
|
+
function compactChatRuntimeError(error) {
|
|
346
|
+
if (!error) return null;
|
|
347
|
+
return {
|
|
348
|
+
name: error.name || "Error",
|
|
349
|
+
code: error.code || null,
|
|
350
|
+
message: error.message || String(error),
|
|
351
|
+
close_result: error.close_result || null,
|
|
352
|
+
page_state: error.page_state || null
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
328
356
|
const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
|
|
329
357
|
const CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH = 180;
|
|
330
358
|
const CHAT_FULL_CV_NETWORK_MIN_TEXT_LENGTH = 650;
|
|
@@ -745,21 +773,25 @@ export async function runChatWorkflow({
|
|
|
745
773
|
chat_context: contextSetup
|
|
746
774
|
});
|
|
747
775
|
|
|
748
|
-
async function recoverAndReapplyChatContext(reason, error = null
|
|
776
|
+
async function recoverAndReapplyChatContext(reason, error = null, {
|
|
777
|
+
forceRefresh = false
|
|
778
|
+
} = {}) {
|
|
749
779
|
runControl.setPhase("chat:recover_shell");
|
|
750
|
-
const
|
|
780
|
+
const shellRecovery = await recoverChatShell(client, {
|
|
751
781
|
targetUrl,
|
|
752
|
-
timeoutMs: readyTimeoutMs
|
|
782
|
+
timeoutMs: readyTimeoutMs,
|
|
783
|
+
forceNavigate: forceRefresh
|
|
753
784
|
});
|
|
754
785
|
runControl.checkpoint({
|
|
755
786
|
chat_shell_recovery: {
|
|
756
787
|
reason,
|
|
757
788
|
error: error?.message || null,
|
|
758
|
-
|
|
789
|
+
total_refresh: Boolean(forceRefresh),
|
|
790
|
+
...shellRecovery
|
|
759
791
|
}
|
|
760
792
|
});
|
|
761
|
-
if (!
|
|
762
|
-
throw new Error(`Chat shell recovery failed after ${reason}: ${
|
|
793
|
+
if (!shellRecovery.recovered && !shellRecovery.after?.is_chat_shell) {
|
|
794
|
+
throw new Error(`Chat shell recovery failed after ${reason}: ${shellRecovery.after?.url || shellRecovery.before?.url || "unknown"}`);
|
|
763
795
|
}
|
|
764
796
|
await closeChatResumeModal(client, { attemptsLimit: 2 });
|
|
765
797
|
const recoveredSetup = await setupChatRunContext(client, {
|
|
@@ -771,6 +803,24 @@ export async function runChatWorkflow({
|
|
|
771
803
|
ensureViewport: ensureChatViewport
|
|
772
804
|
});
|
|
773
805
|
rootState = recoveredSetup.rootState;
|
|
806
|
+
const counters = countChatResultStatuses(results);
|
|
807
|
+
const candidateList = resetInfiniteListForRefreshRound(listState, {
|
|
808
|
+
reason,
|
|
809
|
+
round: listState.ledger?.length || 0,
|
|
810
|
+
method: forceRefresh ? "total_refresh_reapply_chat_context" : "reapply_chat_context",
|
|
811
|
+
metadata: {
|
|
812
|
+
processed: counters.processed,
|
|
813
|
+
passed: counters.passed,
|
|
814
|
+
skipped: counters.skipped
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
const recovery = {
|
|
818
|
+
reason,
|
|
819
|
+
total_refresh: Boolean(forceRefresh),
|
|
820
|
+
shell: shellRecovery,
|
|
821
|
+
candidate_list: candidateList,
|
|
822
|
+
counters
|
|
823
|
+
};
|
|
774
824
|
contextSetup = {
|
|
775
825
|
...recoveredSetup.contextSetup,
|
|
776
826
|
recovered_from: reason,
|
|
@@ -778,7 +828,8 @@ export async function runChatWorkflow({
|
|
|
778
828
|
previous_context: contextSetup
|
|
779
829
|
};
|
|
780
830
|
runControl.checkpoint({
|
|
781
|
-
chat_context: contextSetup
|
|
831
|
+
chat_context: contextSetup,
|
|
832
|
+
candidate_list: candidateList
|
|
782
833
|
});
|
|
783
834
|
return recovery;
|
|
784
835
|
}
|
|
@@ -811,6 +862,7 @@ export async function runChatWorkflow({
|
|
|
811
862
|
detail_opened: 0,
|
|
812
863
|
llm_screened: 0,
|
|
813
864
|
passed: 0,
|
|
865
|
+
skipped: 0,
|
|
814
866
|
requested: 0,
|
|
815
867
|
request_satisfied: 0,
|
|
816
868
|
request_skipped: 0,
|
|
@@ -846,6 +898,7 @@ export async function runChatWorkflow({
|
|
|
846
898
|
detail_opened: 0,
|
|
847
899
|
llm_screened: 0,
|
|
848
900
|
passed: 0,
|
|
901
|
+
skipped: 0,
|
|
849
902
|
requested: requestedCount,
|
|
850
903
|
request_satisfied: requestSatisfiedCount,
|
|
851
904
|
request_skipped: requestSkippedCount,
|
|
@@ -863,6 +916,7 @@ export async function runChatWorkflow({
|
|
|
863
916
|
detail_opened: 0,
|
|
864
917
|
llm_screened: 0,
|
|
865
918
|
passed: 0,
|
|
919
|
+
skipped: 0,
|
|
866
920
|
requested: 0,
|
|
867
921
|
request_satisfied: 0,
|
|
868
922
|
request_skipped: 0,
|
|
@@ -1000,11 +1054,23 @@ export async function runChatWorkflow({
|
|
|
1000
1054
|
let detailUnavailableReason = "";
|
|
1001
1055
|
if (index < detailCountLimit) {
|
|
1002
1056
|
let detailStep = "start";
|
|
1057
|
+
const checkpointInProgressCandidate = (patch = {}) => runControl.checkpoint({
|
|
1058
|
+
in_progress_candidate: {
|
|
1059
|
+
index,
|
|
1060
|
+
key: candidateKey,
|
|
1061
|
+
card_node_id: effectiveCardNodeId || cardNodeId,
|
|
1062
|
+
candidate: compactCandidate(cardCandidate),
|
|
1063
|
+
detail_step: detailStep,
|
|
1064
|
+
counters: countChatResultStatuses(results),
|
|
1065
|
+
...patch
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1003
1068
|
try {
|
|
1004
1069
|
await runControl.waitIfPaused();
|
|
1005
1070
|
runControl.throwIfCanceled();
|
|
1006
1071
|
runControl.setPhase("chat:detail");
|
|
1007
1072
|
rootState = await ensureChatViewport(rootState, "detail");
|
|
1073
|
+
checkpointInProgressCandidate({ event: "detail_start" });
|
|
1008
1074
|
|
|
1009
1075
|
detailStep = "select_candidate";
|
|
1010
1076
|
networkRecorder.clear();
|
|
@@ -1402,11 +1468,23 @@ export async function runChatWorkflow({
|
|
|
1402
1468
|
}
|
|
1403
1469
|
|
|
1404
1470
|
let closeResult = null;
|
|
1471
|
+
let closeRecovery = null;
|
|
1405
1472
|
if (closeResume) {
|
|
1406
1473
|
detailStep = "close_resume_modal";
|
|
1474
|
+
checkpointInProgressCandidate({
|
|
1475
|
+
event: "before_close_resume_modal",
|
|
1476
|
+
source,
|
|
1477
|
+
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1478
|
+
llm_screening: compactLlmResult(llmResult),
|
|
1479
|
+
full_cv_evidence: fullCvEvidence
|
|
1480
|
+
});
|
|
1407
1481
|
closeResult = await measureTiming(timings, "close_detail_ms", () => closeChatResumeModal(client));
|
|
1408
1482
|
if (!closeResult?.closed) {
|
|
1409
|
-
|
|
1483
|
+
closeRecovery = await recoverAndReapplyChatContext(
|
|
1484
|
+
"resume_modal_close_failed:close_resume_modal",
|
|
1485
|
+
makeChatResumeModalOpenBeforeCandidateClickError(closeResult),
|
|
1486
|
+
{ forceRefresh: true }
|
|
1487
|
+
);
|
|
1410
1488
|
}
|
|
1411
1489
|
}
|
|
1412
1490
|
detailResult.close_result = closeResult;
|
|
@@ -1435,15 +1513,28 @@ export async function runChatWorkflow({
|
|
|
1435
1513
|
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1436
1514
|
capture_target: captureTarget || null,
|
|
1437
1515
|
capture_target_wait: captureTargetWait,
|
|
1438
|
-
full_cv_evidence: fullCvEvidence
|
|
1516
|
+
full_cv_evidence: fullCvEvidence,
|
|
1517
|
+
close_recovery: closeRecovery
|
|
1439
1518
|
};
|
|
1440
1519
|
}
|
|
1441
1520
|
} catch (error) {
|
|
1521
|
+
checkpointInProgressCandidate({
|
|
1522
|
+
event: "detail_error",
|
|
1523
|
+
error: compactChatRuntimeError(error)
|
|
1524
|
+
});
|
|
1442
1525
|
if (isForbiddenChatResumeNavigationError(error)) {
|
|
1443
1526
|
detailUnavailableReason = "forbidden_top_level_resume_navigation";
|
|
1444
1527
|
const recovery = await recoverAndReapplyChatContext(detailUnavailableReason, error);
|
|
1445
1528
|
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
1446
1529
|
detailResult.cv_acquisition.recovery = recovery;
|
|
1530
|
+
} else if (isChatResumeModalCloseFailureError(error)) {
|
|
1531
|
+
const recoveryReason = `resume_modal_close_failed:${detailStep}`;
|
|
1532
|
+
const recovery = await recoverAndReapplyChatContext(recoveryReason, error, { forceRefresh: true });
|
|
1533
|
+
checkpointInProgressCandidate({
|
|
1534
|
+
event: "retry_after_modal_recovery",
|
|
1535
|
+
recovery
|
|
1536
|
+
});
|
|
1537
|
+
continue;
|
|
1447
1538
|
} else if (isUnsafeChatOnlineResumeLinkError(error)) {
|
|
1448
1539
|
detailUnavailableReason = "unsafe_online_resume_navigation_link";
|
|
1449
1540
|
detailResult = createSkippedDetailResult(cardCandidate, detailUnavailableReason, error);
|
|
@@ -1516,16 +1607,18 @@ export async function runChatWorkflow({
|
|
|
1516
1607
|
}
|
|
1517
1608
|
});
|
|
1518
1609
|
|
|
1610
|
+
const counters = countChatResultStatuses(results);
|
|
1519
1611
|
runControl.updateProgress({
|
|
1520
1612
|
card_count: cardNodeIds.length,
|
|
1521
1613
|
target_count: passTarget || (processUntilListEnd ? "all" : processedLimit),
|
|
1522
1614
|
target_pass_count: passTarget,
|
|
1523
1615
|
processed_limit: processedLimit,
|
|
1524
|
-
processed:
|
|
1525
|
-
screened:
|
|
1526
|
-
detail_opened:
|
|
1527
|
-
llm_screened:
|
|
1528
|
-
passed:
|
|
1616
|
+
processed: counters.processed,
|
|
1617
|
+
screened: counters.screened,
|
|
1618
|
+
detail_opened: counters.detail_opened,
|
|
1619
|
+
llm_screened: counters.llm_screened,
|
|
1620
|
+
passed: counters.passed,
|
|
1621
|
+
skipped: counters.skipped,
|
|
1529
1622
|
requested: requestedCount,
|
|
1530
1623
|
request_satisfied: requestSatisfiedCount,
|
|
1531
1624
|
request_skipped: requestSkippedCount,
|
|
@@ -1541,6 +1634,7 @@ export async function runChatWorkflow({
|
|
|
1541
1634
|
const checkpointStarted = Date.now();
|
|
1542
1635
|
runControl.checkpoint({
|
|
1543
1636
|
results,
|
|
1637
|
+
in_progress_candidate: null,
|
|
1544
1638
|
last_candidate: {
|
|
1545
1639
|
id: screeningCandidate.id || null,
|
|
1546
1640
|
key: candidateKey,
|
|
@@ -1564,6 +1658,7 @@ export async function runChatWorkflow({
|
|
|
1564
1658
|
}
|
|
1565
1659
|
|
|
1566
1660
|
runControl.setPhase("chat:done");
|
|
1661
|
+
const finalCounters = countChatResultStatuses(results);
|
|
1567
1662
|
return {
|
|
1568
1663
|
domain: "chat",
|
|
1569
1664
|
target_url: targetUrl,
|
|
@@ -1579,11 +1674,12 @@ export async function runChatWorkflow({
|
|
|
1579
1674
|
process_until_list_end: Boolean(processUntilListEnd),
|
|
1580
1675
|
processed_limit: processedLimit,
|
|
1581
1676
|
detail_source: normalizedDetailSource,
|
|
1582
|
-
processed:
|
|
1583
|
-
screened:
|
|
1584
|
-
detail_opened:
|
|
1585
|
-
llm_screened:
|
|
1586
|
-
passed:
|
|
1677
|
+
processed: finalCounters.processed,
|
|
1678
|
+
screened: finalCounters.screened,
|
|
1679
|
+
detail_opened: finalCounters.detail_opened,
|
|
1680
|
+
llm_screened: finalCounters.llm_screened,
|
|
1681
|
+
passed: finalCounters.passed,
|
|
1682
|
+
skipped: finalCounters.skipped,
|
|
1587
1683
|
requested: requestedCount,
|
|
1588
1684
|
request_satisfied: requestSatisfiedCount,
|
|
1589
1685
|
request_skipped: requestSkippedCount,
|
|
@@ -1685,6 +1781,7 @@ export function createChatRunService({
|
|
|
1685
1781
|
detail_opened: 0,
|
|
1686
1782
|
llm_screened: 0,
|
|
1687
1783
|
passed: 0,
|
|
1784
|
+
skipped: 0,
|
|
1688
1785
|
requested: 0,
|
|
1689
1786
|
request_satisfied: 0,
|
|
1690
1787
|
request_skipped: 0
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
waitForRecommendCardNodeIds
|
|
5
5
|
} from "./cards.js";
|
|
6
6
|
import {
|
|
7
|
-
RECOMMEND_RECENT_NOT_VIEW_LABEL
|
|
7
|
+
RECOMMEND_RECENT_NOT_VIEW_LABEL,
|
|
8
|
+
RECOMMEND_TARGET_URL
|
|
8
9
|
} from "./constants.js";
|
|
9
10
|
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
10
11
|
import { selectRecommendJob } from "./jobs.js";
|
|
@@ -94,6 +95,8 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
94
95
|
fallbackPageScope = "recommend",
|
|
95
96
|
filter = {},
|
|
96
97
|
preferEndRefreshButton = true,
|
|
98
|
+
forceNavigate = false,
|
|
99
|
+
targetUrl = RECOMMEND_TARGET_URL,
|
|
97
100
|
forceRecentNotView = true,
|
|
98
101
|
cardTimeoutMs = 30000,
|
|
99
102
|
buttonSettleMs = 8000,
|
|
@@ -156,8 +159,17 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
|
|
162
|
+
let fallbackMethod = "page_reload";
|
|
159
163
|
try {
|
|
160
|
-
|
|
164
|
+
let method = "page_reload";
|
|
165
|
+
if (forceNavigate && typeof client?.Page?.navigate === "function") {
|
|
166
|
+
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
167
|
+
method = "page_navigate";
|
|
168
|
+
fallbackMethod = method;
|
|
169
|
+
} else {
|
|
170
|
+
await client.Page.reload({ ignoreCache: true });
|
|
171
|
+
fallbackMethod = method;
|
|
172
|
+
}
|
|
161
173
|
if (reloadSettleMs > 0) await sleep(reloadSettleMs);
|
|
162
174
|
currentRootState = await waitForRecommendRoots(client, {
|
|
163
175
|
timeoutMs: Math.max(30000, reloadSettleMs * 4),
|
|
@@ -202,8 +214,9 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
202
214
|
});
|
|
203
215
|
return {
|
|
204
216
|
ok: cardNodeIds.length > 0,
|
|
205
|
-
method
|
|
217
|
+
method,
|
|
206
218
|
attempts,
|
|
219
|
+
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
207
220
|
job_selection: jobSelection,
|
|
208
221
|
page_scope: pageScopeResult,
|
|
209
222
|
filter: filterResult,
|
|
@@ -214,10 +227,11 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
214
227
|
} catch (error) {
|
|
215
228
|
return {
|
|
216
229
|
ok: false,
|
|
217
|
-
method:
|
|
218
|
-
reason: "page_reload_failed",
|
|
230
|
+
method: fallbackMethod,
|
|
231
|
+
reason: fallbackMethod === "page_navigate" ? "page_navigate_failed" : "page_reload_failed",
|
|
219
232
|
error: error?.message || String(error),
|
|
220
233
|
attempts,
|
|
234
|
+
target_url: fallbackMethod === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
221
235
|
card_count: 0,
|
|
222
236
|
root_state: currentRootState,
|
|
223
237
|
forced_recent_not_view: forceRecentNotView
|