@reconcrap/boss-recommend-mcp 2.1.22 → 2.1.23
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 +9 -8
- package/src/core/browser/index.js +56 -29
- package/src/core/infinite-list/index.js +31 -22
- package/src/core/run/index.js +22 -5
- package/src/domains/recommend/detail.js +13 -4
- package/src/domains/recommend/refresh.js +95 -14
- package/src/domains/recommend/run-service.js +712 -107
- package/src/index.js +108 -4
- package/src/recommend-mcp.js +392 -146
- package/src/recommend-scheduler.js +75 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reconcrap/boss-recommend-mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.23",
|
|
4
4
|
"description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"boss",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"test:recommend-domain": "node src/test-recommend-domain.js",
|
|
38
38
|
"test:recommend-run-service": "node src/test-recommend-run-service.js",
|
|
39
39
|
"test:recommend-mcp": "node src/test-recommend-mcp.js",
|
|
40
|
+
"test:live-recommend-mcp-smoke": "node src/test-live-recommend-mcp-smoke.js",
|
|
40
41
|
"test:recruit-domain": "node src/test-recruit-domain.js",
|
|
41
42
|
"test:recruit-mcp": "node src/test-recruit-mcp.js",
|
|
42
43
|
"test:chat-domain": "node src/test-chat-domain.js",
|
|
@@ -64,10 +65,10 @@
|
|
|
64
65
|
"live:self-heal": "node scripts/live-self-heal-smoke.js",
|
|
65
66
|
"live:recommend-actions": "node scripts/live-recommend-actions-smoke.js",
|
|
66
67
|
"live:recommend-phase10-full": "node scripts/live-recommend-phase10-full.js",
|
|
67
|
-
"live:recommend-domain": "node scripts/live-recommend-domain-smoke.js",
|
|
68
|
-
"live:recommend-run-service": "node scripts/live-recommend-run-service-smoke.js",
|
|
69
|
-
"live:recommend-mcp": "node scripts/live-recommend-mcp-smoke.js",
|
|
70
|
-
"live:search-phase10-full": "node scripts/live-search-phase10-full.js",
|
|
68
|
+
"live:recommend-domain": "node scripts/live-recommend-domain-smoke.js",
|
|
69
|
+
"live:recommend-run-service": "node scripts/live-recommend-run-service-smoke.js",
|
|
70
|
+
"live:recommend-mcp": "node scripts/live-recommend-mcp-smoke.js",
|
|
71
|
+
"live:search-phase10-full": "node scripts/live-search-phase10-full.js",
|
|
71
72
|
"live:recruit-domain": "node scripts/live-recruit-domain-smoke.js",
|
|
72
73
|
"live:recruit-run-service": "node scripts/live-recruit-run-service-smoke.js",
|
|
73
74
|
"live:recruit-mcp": "node scripts/live-recruit-mcp-smoke.js",
|
|
@@ -82,9 +83,9 @@
|
|
|
82
83
|
"files": [
|
|
83
84
|
"bin",
|
|
84
85
|
"config/screening-config.example.json",
|
|
85
|
-
"skills",
|
|
86
|
-
"scripts/install-macos.sh",
|
|
87
|
-
"scripts/postinstall.cjs",
|
|
86
|
+
"skills",
|
|
87
|
+
"scripts/install-macos.sh",
|
|
88
|
+
"scripts/postinstall.cjs",
|
|
88
89
|
"src/core",
|
|
89
90
|
"src/domains",
|
|
90
91
|
"src/chat-mcp.js",
|
|
@@ -1691,14 +1691,34 @@ export function isClosedCdpTransportError(error) {
|
|
|
1691
1691
|
return CDP_CLOSED_TRANSPORT_PATTERN.test(String(error?.message || error || ""));
|
|
1692
1692
|
}
|
|
1693
1693
|
|
|
1694
|
-
function cloneCdpParams(params = {}) {
|
|
1695
|
-
if (!params || typeof params !== "object" || typeof params === "function") return params;
|
|
1696
|
-
try {
|
|
1697
|
-
return JSON.parse(JSON.stringify(params));
|
|
1698
|
-
} catch {
|
|
1699
|
-
return { ...params };
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1694
|
+
function cloneCdpParams(params = {}) {
|
|
1695
|
+
if (!params || typeof params !== "object" || typeof params === "function") return params;
|
|
1696
|
+
try {
|
|
1697
|
+
return JSON.parse(JSON.stringify(params));
|
|
1698
|
+
} catch {
|
|
1699
|
+
return { ...params };
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function annotateCdpError(error, method, params = {}) {
|
|
1704
|
+
if (!error || (typeof error !== "object" && typeof error !== "function")) return error;
|
|
1705
|
+
if (!error.cdp_method) error.cdp_method = String(method || "");
|
|
1706
|
+
if (!error.cdp_at) error.cdp_at = nowIso();
|
|
1707
|
+
const nodeId = Number(params?.nodeId);
|
|
1708
|
+
const backendNodeId = Number(params?.backendNodeId);
|
|
1709
|
+
const searchId = String(params?.searchId || "").trim();
|
|
1710
|
+
if (Number.isInteger(nodeId) && nodeId > 0 && !error.cdp_node_id) {
|
|
1711
|
+
error.cdp_node_id = nodeId;
|
|
1712
|
+
}
|
|
1713
|
+
if (Number.isInteger(backendNodeId) && backendNodeId > 0 && !error.cdp_backend_node_id) {
|
|
1714
|
+
error.cdp_backend_node_id = backendNodeId;
|
|
1715
|
+
}
|
|
1716
|
+
if (searchId && !error.cdp_search_id) error.cdp_search_id = searchId;
|
|
1717
|
+
if (!Array.isArray(error.cdp_param_keys)) {
|
|
1718
|
+
error.cdp_param_keys = Object.keys(params || {}).sort();
|
|
1719
|
+
}
|
|
1720
|
+
return error;
|
|
1721
|
+
}
|
|
1702
1722
|
|
|
1703
1723
|
function shouldReplayCdpSetupCall(domain, method) {
|
|
1704
1724
|
return method === "enable"
|
|
@@ -1745,23 +1765,28 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1745
1765
|
return reconnectInFlight;
|
|
1746
1766
|
}
|
|
1747
1767
|
|
|
1748
|
-
async function invokeWithReconnect({
|
|
1749
|
-
methodNameForLog,
|
|
1750
|
-
invoke,
|
|
1751
|
-
|
|
1752
|
-
|
|
1768
|
+
async function invokeWithReconnect({
|
|
1769
|
+
methodNameForLog,
|
|
1770
|
+
invoke,
|
|
1771
|
+
params = {},
|
|
1772
|
+
retryable = true
|
|
1773
|
+
}) {
|
|
1753
1774
|
recordMethod(methodLog, methodNameForLog);
|
|
1754
1775
|
try {
|
|
1755
1776
|
return await invoke(currentClient);
|
|
1756
|
-
} catch (error) {
|
|
1757
|
-
if (!retryable || !isClosedCdpTransportError(error) || typeof reconnect !== "function") {
|
|
1758
|
-
throw error;
|
|
1759
|
-
}
|
|
1760
|
-
await reconnectClient();
|
|
1761
|
-
recordMethod(methodLog, `${methodNameForLog}:retry_after_reconnect`);
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
if (!retryable || !isClosedCdpTransportError(error) || typeof reconnect !== "function") {
|
|
1779
|
+
throw annotateCdpError(error, methodNameForLog, params);
|
|
1780
|
+
}
|
|
1781
|
+
await reconnectClient();
|
|
1782
|
+
recordMethod(methodLog, `${methodNameForLog}:retry_after_reconnect`);
|
|
1783
|
+
try {
|
|
1784
|
+
return await invoke(currentClient);
|
|
1785
|
+
} catch (retryError) {
|
|
1786
|
+
throw annotateCdpError(retryError, methodNameForLog, params);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1765
1790
|
|
|
1766
1791
|
return new Proxy({}, {
|
|
1767
1792
|
get(_target, property, receiver) {
|
|
@@ -1770,10 +1795,11 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1770
1795
|
if (isForbiddenMethod(method)) {
|
|
1771
1796
|
throw new Error(`Forbidden CDP method blocked: ${method}`);
|
|
1772
1797
|
}
|
|
1773
|
-
return invokeWithReconnect({
|
|
1774
|
-
methodNameForLog: method,
|
|
1775
|
-
|
|
1776
|
-
|
|
1798
|
+
return invokeWithReconnect({
|
|
1799
|
+
methodNameForLog: method,
|
|
1800
|
+
params,
|
|
1801
|
+
invoke: (activeClient) => activeClient.send(method, params)
|
|
1802
|
+
});
|
|
1777
1803
|
};
|
|
1778
1804
|
}
|
|
1779
1805
|
|
|
@@ -1813,9 +1839,10 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1813
1839
|
params: cloneCdpParams(params)
|
|
1814
1840
|
});
|
|
1815
1841
|
}
|
|
1816
|
-
return invokeWithReconnect({
|
|
1817
|
-
methodNameForLog: fullMethod,
|
|
1818
|
-
|
|
1842
|
+
return invokeWithReconnect({
|
|
1843
|
+
methodNameForLog: fullMethod,
|
|
1844
|
+
params,
|
|
1845
|
+
invoke: (activeClient) => {
|
|
1819
1846
|
const activeDomain = activeClient?.[property];
|
|
1820
1847
|
const activeMethod = activeDomain?.[method];
|
|
1821
1848
|
if (typeof activeMethod !== "function") {
|
|
@@ -889,12 +889,13 @@ export async function detectInfiniteListBottomMarker(client, {
|
|
|
889
889
|
};
|
|
890
890
|
}
|
|
891
891
|
|
|
892
|
-
export async function readVisibleInfiniteListItems({
|
|
893
|
-
nodeIds = [],
|
|
894
|
-
readCandidate,
|
|
895
|
-
keyForCandidate = candidateKeyFromProfile,
|
|
896
|
-
state = null
|
|
897
|
-
|
|
892
|
+
export async function readVisibleInfiniteListItems({
|
|
893
|
+
nodeIds = [],
|
|
894
|
+
readCandidate,
|
|
895
|
+
keyForCandidate = candidateKeyFromProfile,
|
|
896
|
+
state = null,
|
|
897
|
+
shouldRethrowReadError = null
|
|
898
|
+
} = {}) {
|
|
898
899
|
if (typeof readCandidate !== "function") {
|
|
899
900
|
throw new Error("readVisibleInfiniteListItems requires readCandidate");
|
|
900
901
|
}
|
|
@@ -904,19 +905,25 @@ export async function readVisibleInfiniteListItems({
|
|
|
904
905
|
let candidate;
|
|
905
906
|
try {
|
|
906
907
|
candidate = await readCandidate(nodeId, { visibleIndex });
|
|
907
|
-
} catch (error) {
|
|
908
|
-
if (state) {
|
|
909
|
-
state.read_error_count = (state.read_error_count || 0) + 1;
|
|
908
|
+
} catch (error) {
|
|
909
|
+
if (state) {
|
|
910
|
+
state.read_error_count = (state.read_error_count || 0) + 1;
|
|
910
911
|
state.ledger?.push({
|
|
911
912
|
at: nowIso(),
|
|
912
913
|
event: "candidate_read_error",
|
|
913
914
|
node_id: nodeId,
|
|
914
915
|
visible_index: visibleIndex,
|
|
915
|
-
error: error?.message || String(error)
|
|
916
|
-
});
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
916
|
+
error: error?.message || String(error)
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
if (
|
|
920
|
+
typeof shouldRethrowReadError === "function"
|
|
921
|
+
&& shouldRethrowReadError(error, { nodeId, visibleIndex })
|
|
922
|
+
) {
|
|
923
|
+
throw error;
|
|
924
|
+
}
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
920
927
|
const key = keyForCandidate(candidate, {
|
|
921
928
|
nodeId,
|
|
922
929
|
visibleIndex,
|
|
@@ -1132,8 +1139,9 @@ export async function getNextInfiniteListCandidate({
|
|
|
1132
1139
|
state,
|
|
1133
1140
|
findNodeIds,
|
|
1134
1141
|
readCandidate,
|
|
1135
|
-
detectBottomMarker = null,
|
|
1136
|
-
keyForCandidate = candidateKeyFromProfile,
|
|
1142
|
+
detectBottomMarker = null,
|
|
1143
|
+
keyForCandidate = candidateKeyFromProfile,
|
|
1144
|
+
shouldRethrowReadError = null,
|
|
1137
1145
|
maxScrolls = 20,
|
|
1138
1146
|
stableSignatureLimit = 2,
|
|
1139
1147
|
minScrollsBeforeEnd = 3,
|
|
@@ -1156,12 +1164,13 @@ export async function getNextInfiniteListCandidate({
|
|
|
1156
1164
|
const maxAttempts = Math.max(0, Number(maxScrolls) || 0);
|
|
1157
1165
|
for (let scrollAttempt = 0; scrollAttempt <= maxAttempts; scrollAttempt += 1) {
|
|
1158
1166
|
const nodeIds = await findNodeIds();
|
|
1159
|
-
const items = await readVisibleInfiniteListItems({
|
|
1160
|
-
nodeIds,
|
|
1161
|
-
readCandidate,
|
|
1162
|
-
keyForCandidate,
|
|
1163
|
-
state
|
|
1164
|
-
|
|
1167
|
+
const items = await readVisibleInfiniteListItems({
|
|
1168
|
+
nodeIds,
|
|
1169
|
+
readCandidate,
|
|
1170
|
+
keyForCandidate,
|
|
1171
|
+
state,
|
|
1172
|
+
shouldRethrowReadError
|
|
1173
|
+
});
|
|
1165
1174
|
const signature = updateInfiniteListVisibleSignature(state, items);
|
|
1166
1175
|
const next = firstUnseenInfiniteListItem(state, items);
|
|
1167
1176
|
attempts.push({
|
package/src/core/run/index.js
CHANGED
|
@@ -32,15 +32,32 @@ function clone(value) {
|
|
|
32
32
|
return JSON.parse(JSON.stringify(value));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function errorDiagnostic(error) {
|
|
35
|
+
function errorDiagnostic(error) {
|
|
36
36
|
if (!error) return null;
|
|
37
37
|
const diagnostic = {
|
|
38
38
|
name: error?.name || "Error",
|
|
39
39
|
message: error?.message || String(error)
|
|
40
|
-
};
|
|
41
|
-
if (error?.code) diagnostic.code = error.code;
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
};
|
|
41
|
+
if (error?.code) diagnostic.code = error.code;
|
|
42
|
+
if (error?.cdp_method) diagnostic.cdp_method = error.cdp_method;
|
|
43
|
+
if (error?.cdp_at) diagnostic.cdp_at = error.cdp_at;
|
|
44
|
+
if (Number.isInteger(error?.cdp_node_id)) diagnostic.cdp_node_id = error.cdp_node_id;
|
|
45
|
+
if (Number.isInteger(error?.cdp_backend_node_id)) diagnostic.cdp_backend_node_id = error.cdp_backend_node_id;
|
|
46
|
+
if (error?.cdp_search_id) diagnostic.cdp_search_id = error.cdp_search_id;
|
|
47
|
+
if (Array.isArray(error?.cdp_param_keys)) diagnostic.cdp_param_keys = error.cdp_param_keys.slice(0, 20);
|
|
48
|
+
if (error?.stack) diagnostic.stack = String(error.stack).split(/\r?\n/).slice(0, 12).join("\n");
|
|
49
|
+
if (error?.cause && error.cause !== error) {
|
|
50
|
+
diagnostic.cause = {
|
|
51
|
+
name: error.cause?.name || "Error",
|
|
52
|
+
message: error.cause?.message || String(error.cause),
|
|
53
|
+
code: error.cause?.code || undefined,
|
|
54
|
+
cdp_method: error.cause?.cdp_method || undefined,
|
|
55
|
+
cdp_at: error.cause?.cdp_at || undefined,
|
|
56
|
+
cdp_node_id: Number.isInteger(error.cause?.cdp_node_id) ? error.cause.cdp_node_id : undefined
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return diagnostic;
|
|
60
|
+
}
|
|
44
61
|
|
|
45
62
|
function createDeferred() {
|
|
46
63
|
let resolve;
|
|
@@ -387,10 +387,19 @@ export async function readRecommendDetailHtml(client, detailState) {
|
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
export function isStaleRecommendNodeError(error) {
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
export function isStaleRecommendNodeError(error) {
|
|
391
|
+
const pattern = /Could not find node with given id|No node with given id|Node is detached|Cannot find node|Could not compute box model/i;
|
|
392
|
+
const seen = new Set();
|
|
393
|
+
let current = error;
|
|
394
|
+
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
395
|
+
if ((typeof current === "object" || typeof current === "function") && seen.has(current)) break;
|
|
396
|
+
if (typeof current === "object" || typeof current === "function") seen.add(current);
|
|
397
|
+
const message = String(current?.message || current || "");
|
|
398
|
+
if (pattern.test(message)) return true;
|
|
399
|
+
current = current?.cause || null;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
394
403
|
|
|
395
404
|
export function isRecommendDetailOpenMissError(error) {
|
|
396
405
|
const message = String(error?.message || error || "");
|
|
@@ -136,12 +136,88 @@ export async function applyRecommendFilterEnvelopeStages(filter = {}, {
|
|
|
136
136
|
|
|
137
137
|
function refreshFailureReason(method = "") {
|
|
138
138
|
return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
return
|
|
144
|
-
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function safeDiagnosticText(value, maxLength = 4000) {
|
|
142
|
+
const text = String(value ?? "");
|
|
143
|
+
return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function safeDiagnosticStack(stack) {
|
|
147
|
+
if (!stack) return null;
|
|
148
|
+
return safeDiagnosticText(stack, 8000)
|
|
149
|
+
.split(/\r?\n/)
|
|
150
|
+
.slice(0, 12)
|
|
151
|
+
.join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function buildErrorDiagnostic(error, {
|
|
155
|
+
depth = 0,
|
|
156
|
+
seen = new Set()
|
|
157
|
+
} = {}) {
|
|
158
|
+
if (!error) return null;
|
|
159
|
+
const isRecord = typeof error === "object" || typeof error === "function";
|
|
160
|
+
if (isRecord && seen.has(error)) {
|
|
161
|
+
return {
|
|
162
|
+
name: error?.name || "Error",
|
|
163
|
+
message: safeDiagnosticText(error?.message || String(error)),
|
|
164
|
+
circular: true
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (isRecord) seen.add(error);
|
|
168
|
+
const diagnostic = {
|
|
169
|
+
name: error?.name || "Error",
|
|
170
|
+
message: safeDiagnosticText(error?.message || String(error))
|
|
171
|
+
};
|
|
172
|
+
if (error?.code !== undefined && error?.code !== null && error.code !== "") {
|
|
173
|
+
diagnostic.code = typeof error.code === "number"
|
|
174
|
+
? error.code
|
|
175
|
+
: safeDiagnosticText(error.code, 300);
|
|
176
|
+
}
|
|
177
|
+
if (error?.phase) diagnostic.phase = safeDiagnosticText(error.phase, 300);
|
|
178
|
+
if (error?.cdp_method) diagnostic.cdp_method = safeDiagnosticText(error.cdp_method, 300);
|
|
179
|
+
if (error?.cdp_at) diagnostic.cdp_at = safeDiagnosticText(error.cdp_at, 100);
|
|
180
|
+
if (Number.isInteger(error?.cdp_node_id)) diagnostic.cdp_node_id = error.cdp_node_id;
|
|
181
|
+
if (Number.isInteger(error?.cdp_backend_node_id)) {
|
|
182
|
+
diagnostic.cdp_backend_node_id = error.cdp_backend_node_id;
|
|
183
|
+
}
|
|
184
|
+
if (Number.isInteger(error?.node_id)) diagnostic.node_id = error.node_id;
|
|
185
|
+
if (Number.isInteger(error?.backend_node_id)) diagnostic.backend_node_id = error.backend_node_id;
|
|
186
|
+
if (error?.cdp_search_id) diagnostic.cdp_search_id = safeDiagnosticText(error.cdp_search_id, 300);
|
|
187
|
+
if (Array.isArray(error?.cdp_param_keys)) {
|
|
188
|
+
diagnostic.cdp_param_keys = error.cdp_param_keys
|
|
189
|
+
.slice(0, 20)
|
|
190
|
+
.map((key) => safeDiagnosticText(key, 100));
|
|
191
|
+
}
|
|
192
|
+
const stack = safeDiagnosticStack(error?.stack);
|
|
193
|
+
if (stack) diagnostic.stack = stack;
|
|
194
|
+
if (depth < 2 && error?.cause && error.cause !== error) {
|
|
195
|
+
diagnostic.cause = buildErrorDiagnostic(error.cause, {
|
|
196
|
+
depth: depth + 1,
|
|
197
|
+
seen
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return diagnostic;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function compactRecommendRefreshErrorDiagnostic(error) {
|
|
204
|
+
return buildErrorDiagnostic(error);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function isRetryableRecommendFilterReapplyError(error) {
|
|
208
|
+
const messages = [];
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
let current = error;
|
|
211
|
+
for (let depth = 0; current && depth < 4; depth += 1) {
|
|
212
|
+
if ((typeof current === "object" || typeof current === "function") && seen.has(current)) break;
|
|
213
|
+
if (typeof current === "object" || typeof current === "function") seen.add(current);
|
|
214
|
+
if (isStaleRecommendNodeError(current)) return true;
|
|
215
|
+
messages.push(String(current?.message || current || ""));
|
|
216
|
+
current = current?.cause;
|
|
217
|
+
}
|
|
218
|
+
const message = messages.join("\n");
|
|
219
|
+
return /Recommend filter panel did not open|Recommend filter trigger was not found|Recommend filter confirm button was not found|No matching recommend filter option|Invalid (?:backend )?node(?:\s*id)?|Node with given id does not exist|No node found for given backend id/i.test(message);
|
|
220
|
+
}
|
|
145
221
|
|
|
146
222
|
function compactFilterReapplyError(error) {
|
|
147
223
|
return error?.message || String(error || "Recommend filter reapply failed");
|
|
@@ -163,9 +239,10 @@ function compactJobSelectionAttempt({
|
|
|
163
239
|
return {
|
|
164
240
|
ok: Boolean(ok),
|
|
165
241
|
method: "job_select",
|
|
166
|
-
reason: error ? "job_select_failed" : null,
|
|
167
|
-
error: error ? (error?.message || String(error)) : null,
|
|
168
|
-
|
|
242
|
+
reason: error ? "job_select_failed" : null,
|
|
243
|
+
error: error ? (error?.message || String(error)) : null,
|
|
244
|
+
error_diagnostic: error ? compactRecommendRefreshErrorDiagnostic(error) : null,
|
|
245
|
+
attempt,
|
|
169
246
|
iframe_document_node_id: iframeDocumentNodeId || 0,
|
|
170
247
|
selected: Boolean(selection?.selected),
|
|
171
248
|
selection_reason: selection?.reason || null,
|
|
@@ -324,9 +401,10 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
324
401
|
attempts.push({
|
|
325
402
|
ok: false,
|
|
326
403
|
method: "filter_reapply",
|
|
327
|
-
reason: "filter_reapply_failed",
|
|
328
|
-
error: compactFilterReapplyError(error),
|
|
329
|
-
|
|
404
|
+
reason: "filter_reapply_failed",
|
|
405
|
+
error: compactFilterReapplyError(error),
|
|
406
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
407
|
+
attempt
|
|
330
408
|
});
|
|
331
409
|
if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
|
|
332
410
|
break;
|
|
@@ -387,6 +465,7 @@ async function ensureRefreshCurrentCityOnly(client, rootState, {
|
|
|
387
465
|
method: "current_city_only_reapply",
|
|
388
466
|
reason: "current_city_only_reapply_failed",
|
|
389
467
|
error: compactCurrentCityOnlyReapplyError(error),
|
|
468
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
390
469
|
attempt
|
|
391
470
|
});
|
|
392
471
|
if (attempt >= maxAttempts || !isRetryableRecommendCurrentCityOnlyReapplyError(error)) {
|
|
@@ -525,8 +604,9 @@ async function applyRefreshMethod(client, method, {
|
|
|
525
604
|
return {
|
|
526
605
|
ok: false,
|
|
527
606
|
method,
|
|
528
|
-
reason: refreshFailureReason(method),
|
|
529
|
-
error: error?.message || String(error),
|
|
607
|
+
reason: refreshFailureReason(method),
|
|
608
|
+
error: error?.message || String(error),
|
|
609
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
530
610
|
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
531
611
|
job_selection: jobSelection,
|
|
532
612
|
job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
|
|
@@ -639,6 +719,7 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
639
719
|
method: "end_refresh_button_after_click",
|
|
640
720
|
reason: "end_refresh_reapply_failed",
|
|
641
721
|
error: error?.message || String(error),
|
|
722
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
642
723
|
page_scope: pageScopeResult,
|
|
643
724
|
current_city_only: currentCityOnlyResult,
|
|
644
725
|
current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
|