@reconcrap/boss-recommend-mcp 2.1.23 → 2.1.25
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/README.md +5 -0
- package/bin/boss-recommend-mcp.js +4 -4
- package/package.json +6 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +43 -42
- package/skills/boss-chat/SKILL.md +107 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +47 -47
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +301 -127
- package/src/chat-runtime-config.js +7 -5
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +302 -145
- package/src/core/capture/index.js +2930 -1201
- package/src/core/cv-acquisition/index.js +512 -238
- package/src/core/cv-capture-target/index.js +513 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +31 -31
- package/src/core/reporting/legacy-csv.js +12 -12
- package/src/core/run/detached-launcher.js +305 -0
- package/src/core/run/index.js +109 -55
- package/src/core/run/timing.js +33 -33
- package/src/core/run/windows-detached-worker.ps1 +106 -0
- package/src/core/screening/index.js +2400 -2135
- package/src/core/self-heal/index.js +989 -973
- package/src/core/self-heal/viewport.js +1505 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/action-journal.js +536 -0
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +1684 -401
- package/src/domains/chat/index.js +8 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +157 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1801 -760
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +1219 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +1079 -333
- package/src/domains/recommend/constants.js +92 -92
- package/src/domains/recommend/detail.js +4037 -136
- package/src/domains/recommend/filters.js +608 -590
- package/src/domains/recommend/index.js +9 -9
- package/src/domains/recommend/jobs.js +571 -542
- package/src/domains/recommend/location.js +754 -707
- package/src/domains/recommend/refresh.js +677 -392
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +4048 -1447
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1817 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +16 -1
- package/src/parser.js +1296 -1296
- package/src/recommend-mcp.js +1061 -450
- package/src/recommend-scheduler.js +75 -75
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getNodeBox,
|
|
3
|
+
getOuterHTML,
|
|
4
|
+
querySelectorAll,
|
|
5
|
+
sleep
|
|
6
|
+
} from "../../core/browser/index.js";
|
|
7
|
+
import { htmlToText, normalizeText } from "../../core/screening/index.js";
|
|
2
8
|
import {
|
|
3
9
|
buildRecommendSelfHealConfig,
|
|
4
10
|
resolveRecommendSelfHealRoots
|
|
@@ -12,16 +18,17 @@ import {
|
|
|
12
18
|
waitForRecommendCardNodeIds
|
|
13
19
|
} from "./cards.js";
|
|
14
20
|
import {
|
|
21
|
+
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
15
22
|
RECOMMEND_RECENT_NOT_VIEW_LABEL,
|
|
16
23
|
RECOMMEND_TARGET_URL
|
|
17
24
|
} from "./constants.js";
|
|
18
|
-
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
19
|
-
import { ensureRecommendCurrentCityOnly } from "./location.js";
|
|
20
|
-
import {
|
|
21
|
-
selectRecommendJob,
|
|
22
|
-
verifyRecommendJobSelection
|
|
23
|
-
} from "./jobs.js";
|
|
24
|
-
import { selectRecommendPageScope } from "./scopes.js";
|
|
25
|
+
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
26
|
+
import { ensureRecommendCurrentCityOnly } from "./location.js";
|
|
27
|
+
import {
|
|
28
|
+
selectRecommendJob,
|
|
29
|
+
verifyRecommendJobSelection
|
|
30
|
+
} from "./jobs.js";
|
|
31
|
+
import { selectRecommendPageScope } from "./scopes.js";
|
|
25
32
|
import {
|
|
26
33
|
getRecommendRoots,
|
|
27
34
|
waitForRecommendRoots
|
|
@@ -31,19 +38,19 @@ import { isStaleRecommendNodeError } from "./detail.js";
|
|
|
31
38
|
function normalizeLabels(labels = []) {
|
|
32
39
|
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
function normalizeFilterGroup(spec = {}) {
|
|
36
|
-
return {
|
|
37
|
-
group: String(spec.group || "").trim(),
|
|
38
|
-
labels: normalizeLabels(spec.labels || spec.filterLabels || []),
|
|
39
|
-
selectAllLabels: spec.selectAllLabels !== false,
|
|
40
|
-
allowUnlimited: spec.allowUnlimited === true,
|
|
41
|
-
verifySticky: spec.verifySticky === true
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function buildRecommendFilterGroups(filter = {}, {
|
|
46
|
-
forceRecentNotView = false
|
|
41
|
+
|
|
42
|
+
function normalizeFilterGroup(spec = {}) {
|
|
43
|
+
return {
|
|
44
|
+
group: String(spec.group || "").trim(),
|
|
45
|
+
labels: normalizeLabels(spec.labels || spec.filterLabels || []),
|
|
46
|
+
selectAllLabels: spec.selectAllLabels !== false,
|
|
47
|
+
allowUnlimited: spec.allowUnlimited === true,
|
|
48
|
+
verifySticky: spec.verifySticky === true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildRecommendFilterGroups(filter = {}, {
|
|
53
|
+
forceRecentNotView = false
|
|
47
54
|
} = {}) {
|
|
48
55
|
const groups = [];
|
|
49
56
|
const sourceGroups = Array.isArray(filter.filterGroups)
|
|
@@ -52,171 +59,399 @@ export function buildRecommendFilterGroups(filter = {}, {
|
|
|
52
59
|
? filter.groups
|
|
53
60
|
: [];
|
|
54
61
|
|
|
55
|
-
for (const spec of sourceGroups) {
|
|
56
|
-
const group = normalizeFilterGroup(spec);
|
|
57
|
-
if (group.group || group.labels.length)
|
|
58
|
-
|
|
62
|
+
for (const spec of sourceGroups) {
|
|
63
|
+
const group = normalizeFilterGroup(spec);
|
|
64
|
+
if (group.group || group.labels.length) {
|
|
65
|
+
group.verifySticky = true;
|
|
66
|
+
groups.push(group);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
59
69
|
|
|
60
70
|
const rootGroup = normalizeFilterGroup(filter);
|
|
61
|
-
if ((rootGroup.group || rootGroup.labels.length) && !groups.length) {
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
if ((rootGroup.group || rootGroup.labels.length) && !groups.length) {
|
|
72
|
+
rootGroup.verifySticky = true;
|
|
73
|
+
groups.push(rootGroup);
|
|
74
|
+
}
|
|
64
75
|
|
|
65
76
|
if (forceRecentNotView) {
|
|
66
77
|
const recentGroup = groups.find((item) => item.group === "recentNotView");
|
|
67
78
|
if (recentGroup) {
|
|
68
79
|
if (!recentGroup.labels.some((label) => label.replace(/\s+/g, "") === RECOMMEND_RECENT_NOT_VIEW_LABEL)) {
|
|
69
80
|
recentGroup.labels.push(RECOMMEND_RECENT_NOT_VIEW_LABEL);
|
|
70
|
-
}
|
|
81
|
+
}
|
|
71
82
|
recentGroup.selectAllLabels = true;
|
|
83
|
+
recentGroup.verifySticky = true;
|
|
72
84
|
} else {
|
|
73
85
|
groups.unshift({
|
|
74
86
|
group: "recentNotView",
|
|
75
87
|
labels: [RECOMMEND_RECENT_NOT_VIEW_LABEL],
|
|
76
88
|
selectAllLabels: true,
|
|
77
89
|
allowUnlimited: false,
|
|
78
|
-
verifySticky:
|
|
90
|
+
verifySticky: true
|
|
79
91
|
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return groups;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return groups;
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
export function buildRecommendFilterSelectionOptions(filter = {}, {
|
|
98
|
+
export function buildRecommendFilterSelectionOptions(filter = {}, {
|
|
87
99
|
forceRecentNotView = false
|
|
88
100
|
} = {}) {
|
|
89
101
|
const filterGroups = buildRecommendFilterGroups(filter, { forceRecentNotView });
|
|
90
102
|
if (filterGroups.length > 1 || forceRecentNotView || Array.isArray(filter.filterGroups) || Array.isArray(filter.groups)) {
|
|
91
103
|
return { filterGroups };
|
|
92
104
|
}
|
|
93
|
-
const [singleGroup] = filterGroups;
|
|
94
|
-
if (singleGroup) {
|
|
95
|
-
return {
|
|
96
|
-
group: singleGroup.group,
|
|
97
|
-
labels: singleGroup.labels,
|
|
98
|
-
selectAllLabels: singleGroup.selectAllLabels,
|
|
99
|
-
allowUnlimited: singleGroup.allowUnlimited,
|
|
100
|
-
verifySticky: singleGroup.verifySticky
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
group: filter.group || "",
|
|
105
|
-
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
106
|
-
selectAllLabels: filter.selectAllLabels !== false,
|
|
107
|
-
allowUnlimited: filter.allowUnlimited === true,
|
|
108
|
-
verifySticky: filter.verifySticky === true
|
|
109
|
-
};
|
|
105
|
+
const [singleGroup] = filterGroups;
|
|
106
|
+
if (singleGroup) {
|
|
107
|
+
return {
|
|
108
|
+
group: singleGroup.group,
|
|
109
|
+
labels: singleGroup.labels,
|
|
110
|
+
selectAllLabels: singleGroup.selectAllLabels,
|
|
111
|
+
allowUnlimited: singleGroup.allowUnlimited,
|
|
112
|
+
verifySticky: singleGroup.verifySticky
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
group: filter.group || "",
|
|
117
|
+
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
118
|
+
selectAllLabels: filter.selectAllLabels !== false,
|
|
119
|
+
allowUnlimited: filter.allowUnlimited === true,
|
|
120
|
+
verifySticky: filter.verifySticky === true
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function applyRecommendFilterEnvelopeStages(filter = {}, {
|
|
125
|
+
applyCurrentCityOnly,
|
|
126
|
+
applyFilterPanel
|
|
127
|
+
} = {}) {
|
|
128
|
+
if (filter.enabled === false) {
|
|
129
|
+
return {
|
|
130
|
+
applied: false,
|
|
131
|
+
skipped: true,
|
|
132
|
+
current_city_only: null,
|
|
133
|
+
filter: null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (typeof applyCurrentCityOnly !== "function" || typeof applyFilterPanel !== "function") {
|
|
137
|
+
throw new Error("Recommend native filter stages require location and filter-panel callbacks");
|
|
138
|
+
}
|
|
139
|
+
const currentCityOnlyResult = await applyCurrentCityOnly();
|
|
140
|
+
const filterResult = await applyFilterPanel();
|
|
141
|
+
return {
|
|
142
|
+
applied: true,
|
|
143
|
+
skipped: false,
|
|
144
|
+
current_city_only: currentCityOnlyResult,
|
|
145
|
+
filter: filterResult
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function refreshFailureReason(method = "") {
|
|
150
|
+
return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
|
|
110
151
|
}
|
|
111
152
|
|
|
112
|
-
export
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
export function isVerifiedRecommendFilterApplication(filterResult, filterOptions = {}) {
|
|
154
|
+
if (filterResult?.confirmed !== true) return false;
|
|
155
|
+
const requestedGroups = Array.isArray(filterOptions.filterGroups) && filterOptions.filterGroups.length
|
|
156
|
+
? filterOptions.filterGroups
|
|
157
|
+
: (filterOptions.group || filterOptions.labels?.length ? [filterOptions] : []);
|
|
158
|
+
const normalizedRequested = requestedGroups
|
|
159
|
+
.filter((group) => group && String(group.group || "").trim() && Array.isArray(group.labels) && group.labels.length)
|
|
160
|
+
.map((group) => ({
|
|
161
|
+
group: String(group.group || "").trim(),
|
|
162
|
+
labels: [...new Set(normalizeLabels(group.labels).map((label) => label.replace(/\s+/g, "")))],
|
|
163
|
+
allowUnlimited: group.allowUnlimited === true,
|
|
164
|
+
selectAllLabels: group.selectAllLabels !== false
|
|
165
|
+
}));
|
|
166
|
+
if (!normalizedRequested.length) return true;
|
|
167
|
+
if (filterResult?.sticky_verification?.verified !== true) return false;
|
|
168
|
+
const verifiedGroups = Array.isArray(filterResult.sticky_verification.groups)
|
|
169
|
+
? filterResult.sticky_verification.groups
|
|
170
|
+
: [];
|
|
171
|
+
if (verifiedGroups.length !== normalizedRequested.length) return false;
|
|
172
|
+
return normalizedRequested.every((requested) => {
|
|
173
|
+
const matches = verifiedGroups.filter((group) => String(group?.group || "") === requested.group);
|
|
174
|
+
if (matches.length !== 1 || matches[0]?.verified !== true) return false;
|
|
175
|
+
const verifiedLabels = normalizeLabels(matches[0].requested_labels || [])
|
|
176
|
+
.map((label) => label.replace(/\s+/g, ""));
|
|
177
|
+
const uniqueVerifiedLabels = [...new Set(verifiedLabels)];
|
|
178
|
+
if (
|
|
179
|
+
uniqueVerifiedLabels.length !== requested.labels.length
|
|
180
|
+
|| !requested.labels.every((label) => uniqueVerifiedLabels.includes(label))
|
|
181
|
+
) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const activeLabels = [...new Set(normalizeLabels(matches[0].active_labels || [])
|
|
185
|
+
.map((label) => label.replace(/\s+/g, "")))];
|
|
186
|
+
if (matches[0].unavailable === true) {
|
|
187
|
+
return requested.allowUnlimited
|
|
188
|
+
&& requested.labels.length === 1
|
|
189
|
+
&& requested.labels[0] === "不限"
|
|
190
|
+
&& activeLabels.length === 0;
|
|
191
|
+
}
|
|
192
|
+
if (!requested.selectAllLabels) {
|
|
193
|
+
return activeLabels.length === 1 && requested.labels.includes(activeLabels[0]);
|
|
194
|
+
}
|
|
195
|
+
return activeLabels.length === requested.labels.length
|
|
196
|
+
&& requested.labels.every((label) => activeLabels.includes(label));
|
|
197
|
+
});
|
|
135
198
|
}
|
|
136
199
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
200
|
+
const RECOMMEND_FILTERED_EMPTY_TEXTS = Object.freeze([
|
|
201
|
+
"没有相关数据",
|
|
202
|
+
"暂无相关数据"
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
const RECOMMEND_FILTERED_EMPTY_TEXT_SCAN_SELECTORS = Object.freeze([
|
|
206
|
+
...RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
207
|
+
"[class*=\"empty\"]",
|
|
208
|
+
"[class*=\"no-data\"]",
|
|
209
|
+
"[class*=\"nodata\"]",
|
|
210
|
+
"div, span, p, section, li"
|
|
211
|
+
]);
|
|
140
212
|
|
|
141
|
-
function
|
|
142
|
-
|
|
143
|
-
return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
|
|
213
|
+
function normalizeRecommendFilteredEmptyText(value = "") {
|
|
214
|
+
return normalizeText(value).replace(/\s+/g, "");
|
|
144
215
|
}
|
|
145
216
|
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
return safeDiagnosticText(stack, 8000)
|
|
149
|
-
.split(/\r?\n/)
|
|
150
|
-
.slice(0, 12)
|
|
151
|
-
.join("\n");
|
|
217
|
+
function isUsableVisibleBox(box) {
|
|
218
|
+
return Number(box?.rect?.width || 0) > 2 && Number(box?.rect?.height || 0) > 2;
|
|
152
219
|
}
|
|
153
220
|
|
|
154
|
-
function
|
|
155
|
-
|
|
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)) {
|
|
221
|
+
async function inspectRecommendEmptyStateAccessibility(client, nodeId, expectedText) {
|
|
222
|
+
if (typeof client?.Accessibility?.getPartialAXTree !== "function") {
|
|
161
223
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
224
|
+
available: false,
|
|
225
|
+
verified: false,
|
|
226
|
+
reason: "accessibility_unavailable"
|
|
165
227
|
};
|
|
166
228
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
:
|
|
229
|
+
try {
|
|
230
|
+
const result = await client.Accessibility.getPartialAXTree({
|
|
231
|
+
nodeId,
|
|
232
|
+
fetchRelatives: true
|
|
233
|
+
});
|
|
234
|
+
const expected = normalizeRecommendFilteredEmptyText(expectedText);
|
|
235
|
+
const nodes = (result?.nodes || []).map((node) => ({
|
|
236
|
+
ignored: node?.ignored === true,
|
|
237
|
+
role: node?.role?.value || "",
|
|
238
|
+
name: node?.name?.value || "",
|
|
239
|
+
value: node?.value?.value || ""
|
|
240
|
+
}));
|
|
241
|
+
const match = nodes.find((node) => (
|
|
242
|
+
!node.ignored
|
|
243
|
+
&& [node.name, node.value].some((value) => (
|
|
244
|
+
normalizeRecommendFilteredEmptyText(value) === expected
|
|
245
|
+
))
|
|
246
|
+
));
|
|
247
|
+
return {
|
|
248
|
+
available: true,
|
|
249
|
+
verified: Boolean(match),
|
|
250
|
+
reason: match ? "exact_accessible_text" : "exact_accessible_text_missing",
|
|
251
|
+
match: match || null,
|
|
252
|
+
node_count: nodes.length
|
|
253
|
+
};
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
available: true,
|
|
257
|
+
verified: false,
|
|
258
|
+
reason: "accessibility_probe_failed",
|
|
259
|
+
error: error?.message || String(error)
|
|
260
|
+
};
|
|
176
261
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function inspectRecommendFilteredEmptyState(client, rootNodeId, {
|
|
265
|
+
selectors = RECOMMEND_FILTERED_EMPTY_TEXT_SCAN_SELECTORS,
|
|
266
|
+
exactTexts = RECOMMEND_FILTERED_EMPTY_TEXTS,
|
|
267
|
+
maxNodes = 2500
|
|
268
|
+
} = {}) {
|
|
269
|
+
if (!client || !rootNodeId) {
|
|
270
|
+
return {
|
|
271
|
+
verified: false,
|
|
272
|
+
reason: "missing_client_or_root",
|
|
273
|
+
text: null,
|
|
274
|
+
node_id: null
|
|
275
|
+
};
|
|
183
276
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
277
|
+
const expectedTexts = new Set(
|
|
278
|
+
(exactTexts || []).map(normalizeRecommendFilteredEmptyText).filter(Boolean)
|
|
279
|
+
);
|
|
280
|
+
const selectorCounts = {};
|
|
281
|
+
const queryErrors = [];
|
|
282
|
+
const nodeIds = [];
|
|
283
|
+
for (const selector of selectors || []) {
|
|
284
|
+
try {
|
|
285
|
+
const matches = await querySelectorAll(client, rootNodeId, selector);
|
|
286
|
+
selectorCounts[selector] = matches.length;
|
|
287
|
+
nodeIds.push(...matches);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
selectorCounts[selector] = null;
|
|
290
|
+
queryErrors.push({
|
|
291
|
+
selector,
|
|
292
|
+
error: error?.message || String(error)
|
|
293
|
+
});
|
|
294
|
+
}
|
|
191
295
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
296
|
+
|
|
297
|
+
const uniqueNodeIds = Array.from(new Set(nodeIds.filter(Boolean)))
|
|
298
|
+
.slice(0, Math.max(0, Number(maxNodes) || 0));
|
|
299
|
+
let checkedNodeCount = 0;
|
|
300
|
+
for (const nodeId of uniqueNodeIds) {
|
|
301
|
+
let text = "";
|
|
302
|
+
try {
|
|
303
|
+
text = normalizeRecommendFilteredEmptyText(htmlToText(await getOuterHTML(client, nodeId)));
|
|
304
|
+
} catch {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (!expectedTexts.has(text)) continue;
|
|
308
|
+
checkedNodeCount += 1;
|
|
309
|
+
let box = null;
|
|
310
|
+
try {
|
|
311
|
+
box = await getNodeBox(client, nodeId);
|
|
312
|
+
} catch {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (!isUsableVisibleBox(box)) continue;
|
|
316
|
+
const accessibility = await inspectRecommendEmptyStateAccessibility(client, nodeId, text);
|
|
317
|
+
if (accessibility.verified !== true) continue;
|
|
318
|
+
return {
|
|
319
|
+
verified: true,
|
|
320
|
+
reason: "exact_visible_filtered_empty_state",
|
|
321
|
+
text,
|
|
322
|
+
node_id: nodeId,
|
|
323
|
+
box: {
|
|
324
|
+
x: box.rect.x,
|
|
325
|
+
y: box.rect.y,
|
|
326
|
+
width: box.rect.width,
|
|
327
|
+
height: box.rect.height
|
|
328
|
+
},
|
|
329
|
+
accessibility,
|
|
330
|
+
selector_counts: selectorCounts,
|
|
331
|
+
checked_node_count: checkedNodeCount,
|
|
332
|
+
candidate_node_count: uniqueNodeIds.length,
|
|
333
|
+
query_errors: queryErrors.slice(0, 20)
|
|
334
|
+
};
|
|
199
335
|
}
|
|
200
|
-
return diagnostic;
|
|
201
|
-
}
|
|
202
336
|
|
|
203
|
-
|
|
204
|
-
|
|
337
|
+
return {
|
|
338
|
+
verified: false,
|
|
339
|
+
reason: checkedNodeCount > 0
|
|
340
|
+
? "exact_empty_text_not_visible_or_accessible"
|
|
341
|
+
: "exact_empty_text_not_found",
|
|
342
|
+
text: null,
|
|
343
|
+
node_id: null,
|
|
344
|
+
selector_counts: selectorCounts,
|
|
345
|
+
checked_node_count: checkedNodeCount,
|
|
346
|
+
candidate_node_count: uniqueNodeIds.length,
|
|
347
|
+
query_errors: queryErrors.slice(0, 20)
|
|
348
|
+
};
|
|
205
349
|
}
|
|
206
350
|
|
|
207
|
-
export function
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
351
|
+
export function isVerifiedRecommendRefreshExhaustion({
|
|
352
|
+
cardCount = 0,
|
|
353
|
+
filter = {},
|
|
354
|
+
filterResult = null,
|
|
355
|
+
pageScopeResult = null,
|
|
356
|
+
currentCityOnlyResult = null,
|
|
357
|
+
emptyState = null
|
|
358
|
+
} = {}) {
|
|
359
|
+
if (
|
|
360
|
+
Number(cardCount) !== 0
|
|
361
|
+
|| pageScopeResult?.selected !== true
|
|
362
|
+
|| emptyState?.verified !== true
|
|
363
|
+
) return false;
|
|
364
|
+
const filterEnabled = filter?.enabled !== false;
|
|
365
|
+
if (filterEnabled && !isVerifiedRecommendFilterApplication(
|
|
366
|
+
filterResult,
|
|
367
|
+
buildRecommendFilterSelectionOptions(filter)
|
|
368
|
+
)) {
|
|
369
|
+
return false;
|
|
217
370
|
}
|
|
218
|
-
const
|
|
219
|
-
|
|
371
|
+
const currentCityOnlyRequested = filter?.currentCityOnly === true || filter?.current_city_only === true;
|
|
372
|
+
if (currentCityOnlyRequested && currentCityOnlyResult?.effective !== true) return false;
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function safeDiagnosticText(value, maxLength = 4000) {
|
|
377
|
+
const text = String(value ?? "");
|
|
378
|
+
return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function safeDiagnosticStack(stack) {
|
|
382
|
+
if (!stack) return null;
|
|
383
|
+
return safeDiagnosticText(stack, 8000)
|
|
384
|
+
.split(/\r?\n/)
|
|
385
|
+
.slice(0, 12)
|
|
386
|
+
.join("\n");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function buildErrorDiagnostic(error, {
|
|
390
|
+
depth = 0,
|
|
391
|
+
seen = new Set()
|
|
392
|
+
} = {}) {
|
|
393
|
+
if (!error) return null;
|
|
394
|
+
const isRecord = typeof error === "object" || typeof error === "function";
|
|
395
|
+
if (isRecord && seen.has(error)) {
|
|
396
|
+
return {
|
|
397
|
+
name: error?.name || "Error",
|
|
398
|
+
message: safeDiagnosticText(error?.message || String(error)),
|
|
399
|
+
circular: true
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (isRecord) seen.add(error);
|
|
403
|
+
const diagnostic = {
|
|
404
|
+
name: error?.name || "Error",
|
|
405
|
+
message: safeDiagnosticText(error?.message || String(error))
|
|
406
|
+
};
|
|
407
|
+
if (error?.code !== undefined && error?.code !== null && error.code !== "") {
|
|
408
|
+
diagnostic.code = typeof error.code === "number"
|
|
409
|
+
? error.code
|
|
410
|
+
: safeDiagnosticText(error.code, 300);
|
|
411
|
+
}
|
|
412
|
+
if (error?.phase) diagnostic.phase = safeDiagnosticText(error.phase, 300);
|
|
413
|
+
if (error?.cdp_method) diagnostic.cdp_method = safeDiagnosticText(error.cdp_method, 300);
|
|
414
|
+
if (error?.cdp_at) diagnostic.cdp_at = safeDiagnosticText(error.cdp_at, 100);
|
|
415
|
+
if (Number.isInteger(error?.cdp_node_id)) diagnostic.cdp_node_id = error.cdp_node_id;
|
|
416
|
+
if (Number.isInteger(error?.cdp_backend_node_id)) {
|
|
417
|
+
diagnostic.cdp_backend_node_id = error.cdp_backend_node_id;
|
|
418
|
+
}
|
|
419
|
+
if (Number.isInteger(error?.node_id)) diagnostic.node_id = error.node_id;
|
|
420
|
+
if (Number.isInteger(error?.backend_node_id)) diagnostic.backend_node_id = error.backend_node_id;
|
|
421
|
+
if (error?.cdp_search_id) diagnostic.cdp_search_id = safeDiagnosticText(error.cdp_search_id, 300);
|
|
422
|
+
if (Array.isArray(error?.cdp_param_keys)) {
|
|
423
|
+
diagnostic.cdp_param_keys = error.cdp_param_keys
|
|
424
|
+
.slice(0, 20)
|
|
425
|
+
.map((key) => safeDiagnosticText(key, 100));
|
|
426
|
+
}
|
|
427
|
+
const stack = safeDiagnosticStack(error?.stack);
|
|
428
|
+
if (stack) diagnostic.stack = stack;
|
|
429
|
+
if (depth < 2 && error?.cause && error.cause !== error) {
|
|
430
|
+
diagnostic.cause = buildErrorDiagnostic(error.cause, {
|
|
431
|
+
depth: depth + 1,
|
|
432
|
+
seen
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return diagnostic;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function compactRecommendRefreshErrorDiagnostic(error) {
|
|
439
|
+
return buildErrorDiagnostic(error);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function isRetryableRecommendFilterReapplyError(error) {
|
|
443
|
+
const messages = [];
|
|
444
|
+
const seen = new Set();
|
|
445
|
+
let current = error;
|
|
446
|
+
for (let depth = 0; current && depth < 4; depth += 1) {
|
|
447
|
+
if ((typeof current === "object" || typeof current === "function") && seen.has(current)) break;
|
|
448
|
+
if (typeof current === "object" || typeof current === "function") seen.add(current);
|
|
449
|
+
if (isStaleRecommendNodeError(current)) return true;
|
|
450
|
+
messages.push(String(current?.message || current || ""));
|
|
451
|
+
current = current?.cause;
|
|
452
|
+
}
|
|
453
|
+
const message = messages.join("\n");
|
|
454
|
+
return /Recommend filter panel did not open|Recommend filter trigger was not found|Recommend filter confirm button was not found|Recommend filter application was not verified|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
455
|
}
|
|
221
456
|
|
|
222
457
|
function compactFilterReapplyError(error) {
|
|
@@ -239,10 +474,10 @@ function compactJobSelectionAttempt({
|
|
|
239
474
|
return {
|
|
240
475
|
ok: Boolean(ok),
|
|
241
476
|
method: "job_select",
|
|
242
|
-
reason: error ? "job_select_failed" : null,
|
|
243
|
-
error: error ? (error?.message || String(error)) : null,
|
|
244
|
-
error_diagnostic: error ? compactRecommendRefreshErrorDiagnostic(error) : null,
|
|
245
|
-
attempt,
|
|
477
|
+
reason: error ? "job_select_failed" : null,
|
|
478
|
+
error: error ? (error?.message || String(error)) : null,
|
|
479
|
+
error_diagnostic: error ? compactRecommendRefreshErrorDiagnostic(error) : null,
|
|
480
|
+
attempt,
|
|
246
481
|
iframe_document_node_id: iframeDocumentNodeId || 0,
|
|
247
482
|
selected: Boolean(selection?.selected),
|
|
248
483
|
selection_reason: selection?.reason || null,
|
|
@@ -371,7 +606,75 @@ export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
|
371
606
|
throw wrapped;
|
|
372
607
|
}
|
|
373
608
|
|
|
374
|
-
async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
609
|
+
export async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
610
|
+
maxAttempts = 3,
|
|
611
|
+
retryDelayMs = 1500,
|
|
612
|
+
selectFilter = selectAndConfirmFirstSafeFilter
|
|
613
|
+
} = {}) {
|
|
614
|
+
const attempts = [];
|
|
615
|
+
let currentRootState = rootState;
|
|
616
|
+
let lastError = null;
|
|
617
|
+
|
|
618
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
619
|
+
try {
|
|
620
|
+
const filter = await selectFilter(
|
|
621
|
+
client,
|
|
622
|
+
currentRootState.iframe.documentNodeId,
|
|
623
|
+
filterOptions
|
|
624
|
+
);
|
|
625
|
+
if (!isVerifiedRecommendFilterApplication(filter, filterOptions)) {
|
|
626
|
+
const verificationError = new Error("Recommend filter application was not verified after refresh");
|
|
627
|
+
verificationError.code = "RECOMMEND_FILTER_APPLICATION_UNVERIFIED";
|
|
628
|
+
verificationError.filter_result = filter;
|
|
629
|
+
throw verificationError;
|
|
630
|
+
}
|
|
631
|
+
attempts.push({
|
|
632
|
+
ok: true,
|
|
633
|
+
method: "filter_reapply",
|
|
634
|
+
attempt
|
|
635
|
+
});
|
|
636
|
+
return {
|
|
637
|
+
filter,
|
|
638
|
+
root_state: currentRootState,
|
|
639
|
+
attempts
|
|
640
|
+
};
|
|
641
|
+
} catch (error) {
|
|
642
|
+
lastError = error;
|
|
643
|
+
attempts.push({
|
|
644
|
+
ok: false,
|
|
645
|
+
method: "filter_reapply",
|
|
646
|
+
reason: "filter_reapply_failed",
|
|
647
|
+
error: compactFilterReapplyError(error),
|
|
648
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
649
|
+
attempt
|
|
650
|
+
});
|
|
651
|
+
if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
655
|
+
currentRootState = await getRecommendRoots(client);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const wrapped = new Error(compactFilterReapplyError(lastError));
|
|
660
|
+
wrapped.cause = lastError;
|
|
661
|
+
wrapped.filter_reapply_attempts = attempts;
|
|
662
|
+
throw wrapped;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function compactCurrentCityOnlyReapplyError(error) {
|
|
666
|
+
return error?.message || String(error || "Recommend current-city-only reapply failed");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function isRetryableRecommendCurrentCityOnlyReapplyError(error) {
|
|
670
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
671
|
+
const message = compactCurrentCityOnlyReapplyError(error);
|
|
672
|
+
return /current-city|current city|location|popover|trigger|checkbox|stale|did not (?:open|mount|close)/i.test(message)
|
|
673
|
+
&& !/unavailable/i.test(message);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function ensureRefreshCurrentCityOnly(client, rootState, {
|
|
677
|
+
enabled = false,
|
|
375
678
|
maxAttempts = 3,
|
|
376
679
|
retryDelayMs = 1500
|
|
377
680
|
} = {}) {
|
|
@@ -381,18 +684,19 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
381
684
|
|
|
382
685
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
383
686
|
try {
|
|
384
|
-
const
|
|
687
|
+
const result = await ensureRecommendCurrentCityOnly(
|
|
385
688
|
client,
|
|
386
689
|
currentRootState.iframe.documentNodeId,
|
|
387
|
-
|
|
690
|
+
{ enabled }
|
|
388
691
|
);
|
|
389
692
|
attempts.push({
|
|
390
693
|
ok: true,
|
|
391
|
-
method: "
|
|
392
|
-
attempt
|
|
694
|
+
method: "current_city_only_reapply",
|
|
695
|
+
attempt,
|
|
696
|
+
result
|
|
393
697
|
});
|
|
394
698
|
return {
|
|
395
|
-
|
|
699
|
+
current_city_only: result,
|
|
396
700
|
root_state: currentRootState,
|
|
397
701
|
attempts
|
|
398
702
|
};
|
|
@@ -400,13 +704,13 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
400
704
|
lastError = error;
|
|
401
705
|
attempts.push({
|
|
402
706
|
ok: false,
|
|
403
|
-
method: "
|
|
404
|
-
reason: "
|
|
405
|
-
error:
|
|
406
|
-
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
407
|
-
attempt
|
|
707
|
+
method: "current_city_only_reapply",
|
|
708
|
+
reason: "current_city_only_reapply_failed",
|
|
709
|
+
error: compactCurrentCityOnlyReapplyError(error),
|
|
710
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
711
|
+
attempt
|
|
408
712
|
});
|
|
409
|
-
if (attempt >= maxAttempts || !
|
|
713
|
+
if (attempt >= maxAttempts || !isRetryableRecommendCurrentCityOnlyReapplyError(error)) {
|
|
410
714
|
break;
|
|
411
715
|
}
|
|
412
716
|
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
@@ -414,96 +718,34 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
414
718
|
}
|
|
415
719
|
}
|
|
416
720
|
|
|
417
|
-
const wrapped = new Error(
|
|
418
|
-
wrapped.cause = lastError;
|
|
419
|
-
wrapped.
|
|
420
|
-
throw wrapped;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (isStaleRecommendNodeError(error)) return true;
|
|
429
|
-
const message = compactCurrentCityOnlyReapplyError(error);
|
|
430
|
-
return /current-city|current city|location|popover|trigger|checkbox|stale|did not (?:open|mount|close)/i.test(message)
|
|
431
|
-
&& !/unavailable/i.test(message);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async function ensureRefreshCurrentCityOnly(client, rootState, {
|
|
435
|
-
enabled = false,
|
|
436
|
-
maxAttempts = 3,
|
|
437
|
-
retryDelayMs = 1500
|
|
438
|
-
} = {}) {
|
|
439
|
-
const attempts = [];
|
|
440
|
-
let currentRootState = rootState;
|
|
441
|
-
let lastError = null;
|
|
442
|
-
|
|
443
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
444
|
-
try {
|
|
445
|
-
const result = await ensureRecommendCurrentCityOnly(
|
|
446
|
-
client,
|
|
447
|
-
currentRootState.iframe.documentNodeId,
|
|
448
|
-
{ enabled }
|
|
449
|
-
);
|
|
450
|
-
attempts.push({
|
|
451
|
-
ok: true,
|
|
452
|
-
method: "current_city_only_reapply",
|
|
453
|
-
attempt,
|
|
454
|
-
result
|
|
455
|
-
});
|
|
456
|
-
return {
|
|
457
|
-
current_city_only: result,
|
|
458
|
-
root_state: currentRootState,
|
|
459
|
-
attempts
|
|
460
|
-
};
|
|
461
|
-
} catch (error) {
|
|
462
|
-
lastError = error;
|
|
463
|
-
attempts.push({
|
|
464
|
-
ok: false,
|
|
465
|
-
method: "current_city_only_reapply",
|
|
466
|
-
reason: "current_city_only_reapply_failed",
|
|
467
|
-
error: compactCurrentCityOnlyReapplyError(error),
|
|
468
|
-
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
469
|
-
attempt
|
|
470
|
-
});
|
|
471
|
-
if (attempt >= maxAttempts || !isRetryableRecommendCurrentCityOnlyReapplyError(error)) {
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
475
|
-
currentRootState = await getRecommendRoots(client);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const wrapped = new Error(compactCurrentCityOnlyReapplyError(lastError));
|
|
480
|
-
wrapped.cause = lastError;
|
|
481
|
-
wrapped.current_city_only_attempts = attempts;
|
|
482
|
-
throw wrapped;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async function applyRefreshMethod(client, method, {
|
|
486
|
-
jobLabel = "",
|
|
487
|
-
pageScope = "recommend",
|
|
488
|
-
fallbackPageScope = "recommend",
|
|
489
|
-
filter = {},
|
|
721
|
+
const wrapped = new Error(compactCurrentCityOnlyReapplyError(lastError));
|
|
722
|
+
wrapped.cause = lastError;
|
|
723
|
+
wrapped.current_city_only_attempts = attempts;
|
|
724
|
+
throw wrapped;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function applyRefreshMethod(client, method, {
|
|
728
|
+
jobLabel = "",
|
|
729
|
+
pageScope = "recommend",
|
|
730
|
+
fallbackPageScope = "recommend",
|
|
731
|
+
filter = {},
|
|
490
732
|
targetUrl = RECOMMEND_TARGET_URL,
|
|
491
733
|
forceRecentNotView = true,
|
|
492
734
|
cardTimeoutMs = 30000,
|
|
493
735
|
reloadSettleMs = 8000
|
|
494
736
|
} = {}) {
|
|
495
|
-
const started = Date.now();
|
|
496
|
-
let currentRootState = null;
|
|
497
|
-
let jobSelection = null;
|
|
498
|
-
let jobSelectionAttempts = [];
|
|
499
|
-
let pageScopeResult = null;
|
|
500
|
-
let currentCityOnlyResult = null;
|
|
501
|
-
let currentCityOnlyAttempts = [];
|
|
502
|
-
let filterResult = null;
|
|
503
|
-
let filterReapplyAttempts = [];
|
|
504
|
-
let recoverySettle = null;
|
|
505
|
-
try {
|
|
506
|
-
if (method === "page_navigate") {
|
|
737
|
+
const started = Date.now();
|
|
738
|
+
let currentRootState = null;
|
|
739
|
+
let jobSelection = null;
|
|
740
|
+
let jobSelectionAttempts = [];
|
|
741
|
+
let pageScopeResult = null;
|
|
742
|
+
let currentCityOnlyResult = null;
|
|
743
|
+
let currentCityOnlyAttempts = [];
|
|
744
|
+
let filterResult = null;
|
|
745
|
+
let filterReapplyAttempts = [];
|
|
746
|
+
let recoverySettle = null;
|
|
747
|
+
try {
|
|
748
|
+
if (method === "page_navigate") {
|
|
507
749
|
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
508
750
|
} else {
|
|
509
751
|
await client.Page.reload({ ignoreCache: true });
|
|
@@ -546,84 +788,98 @@ async function applyRefreshMethod(client, method, {
|
|
|
546
788
|
settleMs: reloadSettleMs > 10000 ? 3000 : 1200,
|
|
547
789
|
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
548
790
|
}
|
|
549
|
-
);
|
|
550
|
-
if (!pageScopeResult.selected) {
|
|
551
|
-
throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
|
|
552
|
-
}
|
|
553
|
-
currentRootState = await getRecommendRoots(client);
|
|
554
|
-
const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)));
|
|
555
|
-
const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
|
|
556
|
-
applyCurrentCityOnly: async () => {
|
|
557
|
-
const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
|
|
558
|
-
enabled: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
559
|
-
retryDelayMs
|
|
560
|
-
});
|
|
561
|
-
currentCityOnlyAttempts = selection.attempts;
|
|
562
|
-
currentRootState = await getRecommendRoots(client);
|
|
563
|
-
return selection.current_city_only;
|
|
564
|
-
},
|
|
565
|
-
applyFilterPanel: async () => {
|
|
566
|
-
const selection = await selectAndConfirmRefreshFilter(
|
|
567
|
-
client,
|
|
568
|
-
currentRootState,
|
|
569
|
-
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
570
|
-
{ retryDelayMs }
|
|
571
|
-
);
|
|
572
|
-
filterReapplyAttempts = selection.attempts;
|
|
573
|
-
currentRootState = await getRecommendRoots(client);
|
|
574
|
-
return selection.filter;
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
currentCityOnlyResult = filterStages.current_city_only;
|
|
578
|
-
filterResult = filterStages.filter;
|
|
791
|
+
);
|
|
792
|
+
if (!pageScopeResult.selected) {
|
|
793
|
+
throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
|
|
794
|
+
}
|
|
795
|
+
currentRootState = await getRecommendRoots(client);
|
|
796
|
+
const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)));
|
|
797
|
+
const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
|
|
798
|
+
applyCurrentCityOnly: async () => {
|
|
799
|
+
const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
|
|
800
|
+
enabled: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
801
|
+
retryDelayMs
|
|
802
|
+
});
|
|
803
|
+
currentCityOnlyAttempts = selection.attempts;
|
|
804
|
+
currentRootState = await getRecommendRoots(client);
|
|
805
|
+
return selection.current_city_only;
|
|
806
|
+
},
|
|
807
|
+
applyFilterPanel: async () => {
|
|
808
|
+
const selection = await selectAndConfirmRefreshFilter(
|
|
809
|
+
client,
|
|
810
|
+
currentRootState,
|
|
811
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
812
|
+
{ retryDelayMs }
|
|
813
|
+
);
|
|
814
|
+
filterReapplyAttempts = selection.attempts;
|
|
815
|
+
currentRootState = await getRecommendRoots(client);
|
|
816
|
+
return selection.filter;
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
currentCityOnlyResult = filterStages.current_city_only;
|
|
820
|
+
filterResult = filterStages.filter;
|
|
579
821
|
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
580
822
|
timeoutMs: cardTimeoutMs,
|
|
581
823
|
intervalMs: 500
|
|
582
824
|
});
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
825
|
+
const emptyState = cardNodeIds.length === 0
|
|
826
|
+
? await inspectRecommendFilteredEmptyState(client, currentRootState.iframe.documentNodeId)
|
|
827
|
+
: null;
|
|
828
|
+
const exhausted = isVerifiedRecommendRefreshExhaustion({
|
|
829
|
+
cardCount: cardNodeIds.length,
|
|
830
|
+
filter,
|
|
831
|
+
filterResult,
|
|
832
|
+
pageScopeResult,
|
|
833
|
+
currentCityOnlyResult,
|
|
834
|
+
emptyState
|
|
835
|
+
});
|
|
836
|
+
if (!cardNodeIds.length && !exhausted) {
|
|
837
|
+
throw new Error("No recommend candidate cards were found after refresh reload");
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
ok: true,
|
|
841
|
+
exhausted,
|
|
842
|
+
reason: exhausted ? "filtered_list_exhausted" : null,
|
|
588
843
|
method,
|
|
589
|
-
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
590
|
-
job_selection: jobSelection,
|
|
591
|
-
job_selection_attempts: jobSelectionAttempts,
|
|
592
|
-
recovery_settle: recoverySettle,
|
|
593
|
-
page_scope: pageScopeResult,
|
|
594
|
-
current_city_only: currentCityOnlyResult,
|
|
595
|
-
current_city_only_attempts: currentCityOnlyAttempts,
|
|
844
|
+
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
845
|
+
job_selection: jobSelection,
|
|
846
|
+
job_selection_attempts: jobSelectionAttempts,
|
|
847
|
+
recovery_settle: recoverySettle,
|
|
848
|
+
page_scope: pageScopeResult,
|
|
849
|
+
current_city_only: currentCityOnlyResult,
|
|
850
|
+
current_city_only_attempts: currentCityOnlyAttempts,
|
|
596
851
|
filter: filterResult,
|
|
597
852
|
filter_reapply_attempts: filterReapplyAttempts,
|
|
598
853
|
card_count: cardNodeIds.length,
|
|
854
|
+
empty_state: emptyState,
|
|
599
855
|
root_state: currentRootState,
|
|
600
|
-
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
|
|
601
|
-
elapsed_ms: Date.now() - started
|
|
602
|
-
};
|
|
603
|
-
} catch (error) {
|
|
604
|
-
return {
|
|
605
|
-
ok: false,
|
|
856
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
|
|
857
|
+
elapsed_ms: Date.now() - started
|
|
858
|
+
};
|
|
859
|
+
} catch (error) {
|
|
860
|
+
return {
|
|
861
|
+
ok: false,
|
|
606
862
|
method,
|
|
607
|
-
reason: refreshFailureReason(method),
|
|
608
|
-
error: error?.message || String(error),
|
|
609
|
-
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
610
|
-
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
611
|
-
job_selection: jobSelection,
|
|
612
|
-
job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
|
|
613
|
-
recovery_settle: error?.recovery_settle || recoverySettle,
|
|
614
|
-
page_scope: pageScopeResult,
|
|
615
|
-
current_city_only: currentCityOnlyResult,
|
|
616
|
-
current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
|
|
617
|
-
filter: filterResult,
|
|
618
|
-
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
619
|
-
card_count: 0,
|
|
620
|
-
root_state: currentRootState,
|
|
621
|
-
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
|
|
622
|
-
elapsed_ms: Date.now() - started
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
863
|
+
reason: refreshFailureReason(method),
|
|
864
|
+
error: error?.message || String(error),
|
|
865
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
866
|
+
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
867
|
+
job_selection: jobSelection,
|
|
868
|
+
job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
|
|
869
|
+
recovery_settle: error?.recovery_settle || recoverySettle,
|
|
870
|
+
page_scope: pageScopeResult,
|
|
871
|
+
current_city_only: currentCityOnlyResult,
|
|
872
|
+
current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
|
|
873
|
+
filter: filterResult,
|
|
874
|
+
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
875
|
+
card_count: 0,
|
|
876
|
+
root_state: currentRootState,
|
|
877
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
|
|
878
|
+
elapsed_ms: Date.now() - started
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
627
883
|
export async function refreshRecommendListAtEnd(client, {
|
|
628
884
|
rootState = null,
|
|
629
885
|
jobLabel = "",
|
|
@@ -645,92 +901,121 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
645
901
|
currentRootState = currentRootState || await getRecommendRoots(client);
|
|
646
902
|
const buttonResult = await clickRecommendEndRefreshButton(
|
|
647
903
|
client,
|
|
648
|
-
currentRootState.iframe.documentNodeId,
|
|
649
|
-
{ settleMs: buttonSettleMs }
|
|
650
|
-
);
|
|
651
|
-
attempts.push(buttonResult);
|
|
652
|
-
if (buttonResult.ok) {
|
|
653
|
-
let pageScopeResult = null;
|
|
654
|
-
let currentCityOnlyResult = null;
|
|
655
|
-
let currentCityOnlyAttempts = [];
|
|
656
|
-
let filterResult = null;
|
|
657
|
-
let filterReapplyAttempts = [];
|
|
658
|
-
try {
|
|
659
|
-
currentRootState = await getRecommendRoots(client);
|
|
660
|
-
pageScopeResult = await selectRecommendPageScope(
|
|
661
|
-
client,
|
|
662
|
-
currentRootState.iframe.documentNodeId,
|
|
663
|
-
{
|
|
664
|
-
pageScope,
|
|
665
|
-
fallbackScope: fallbackPageScope,
|
|
904
|
+
currentRootState.iframe.documentNodeId,
|
|
905
|
+
{ settleMs: buttonSettleMs }
|
|
906
|
+
);
|
|
907
|
+
attempts.push(buttonResult);
|
|
908
|
+
if (buttonResult.ok) {
|
|
909
|
+
let pageScopeResult = null;
|
|
910
|
+
let currentCityOnlyResult = null;
|
|
911
|
+
let currentCityOnlyAttempts = [];
|
|
912
|
+
let filterResult = null;
|
|
913
|
+
let filterReapplyAttempts = [];
|
|
914
|
+
try {
|
|
915
|
+
currentRootState = await getRecommendRoots(client);
|
|
916
|
+
pageScopeResult = await selectRecommendPageScope(
|
|
917
|
+
client,
|
|
918
|
+
currentRootState.iframe.documentNodeId,
|
|
919
|
+
{
|
|
920
|
+
pageScope,
|
|
921
|
+
fallbackScope: fallbackPageScope,
|
|
666
922
|
settleMs: buttonSettleMs > 10000 ? 3000 : 1200,
|
|
667
923
|
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
668
924
|
}
|
|
669
|
-
);
|
|
670
|
-
if (!pageScopeResult.selected) {
|
|
671
|
-
throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
|
|
672
|
-
}
|
|
673
|
-
currentRootState = await getRecommendRoots(client);
|
|
674
|
-
const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)));
|
|
675
|
-
const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
|
|
676
|
-
applyCurrentCityOnly: async () => {
|
|
677
|
-
const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
|
|
678
|
-
enabled: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
679
|
-
retryDelayMs
|
|
680
|
-
});
|
|
681
|
-
currentCityOnlyAttempts = selection.attempts;
|
|
682
|
-
currentRootState = await getRecommendRoots(client);
|
|
683
|
-
return selection.current_city_only;
|
|
684
|
-
},
|
|
685
|
-
applyFilterPanel: async () => {
|
|
686
|
-
const selection = await selectAndConfirmRefreshFilter(
|
|
687
|
-
client,
|
|
688
|
-
currentRootState,
|
|
689
|
-
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
690
|
-
{ retryDelayMs }
|
|
691
|
-
);
|
|
692
|
-
filterReapplyAttempts = selection.attempts;
|
|
693
|
-
currentRootState = await getRecommendRoots(client);
|
|
694
|
-
return selection.filter;
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
currentCityOnlyResult = filterStages.current_city_only;
|
|
698
|
-
filterResult = filterStages.filter;
|
|
925
|
+
);
|
|
926
|
+
if (!pageScopeResult.selected) {
|
|
927
|
+
throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
|
|
928
|
+
}
|
|
929
|
+
currentRootState = await getRecommendRoots(client);
|
|
930
|
+
const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)));
|
|
931
|
+
const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
|
|
932
|
+
applyCurrentCityOnly: async () => {
|
|
933
|
+
const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
|
|
934
|
+
enabled: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
935
|
+
retryDelayMs
|
|
936
|
+
});
|
|
937
|
+
currentCityOnlyAttempts = selection.attempts;
|
|
938
|
+
currentRootState = await getRecommendRoots(client);
|
|
939
|
+
return selection.current_city_only;
|
|
940
|
+
},
|
|
941
|
+
applyFilterPanel: async () => {
|
|
942
|
+
const selection = await selectAndConfirmRefreshFilter(
|
|
943
|
+
client,
|
|
944
|
+
currentRootState,
|
|
945
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
946
|
+
{ retryDelayMs }
|
|
947
|
+
);
|
|
948
|
+
filterReapplyAttempts = selection.attempts;
|
|
949
|
+
currentRootState = await getRecommendRoots(client);
|
|
950
|
+
return selection.filter;
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
currentCityOnlyResult = filterStages.current_city_only;
|
|
954
|
+
filterResult = filterStages.filter;
|
|
699
955
|
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
700
956
|
timeoutMs: cardTimeoutMs,
|
|
701
957
|
intervalMs: 500
|
|
702
958
|
});
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
959
|
+
const emptyState = cardNodeIds.length === 0
|
|
960
|
+
? await inspectRecommendFilteredEmptyState(client, currentRootState.iframe.documentNodeId)
|
|
961
|
+
: null;
|
|
962
|
+
const exhausted = isVerifiedRecommendRefreshExhaustion({
|
|
963
|
+
cardCount: cardNodeIds.length,
|
|
964
|
+
filter,
|
|
965
|
+
filterResult,
|
|
966
|
+
pageScopeResult,
|
|
967
|
+
currentCityOnlyResult,
|
|
968
|
+
emptyState
|
|
969
|
+
});
|
|
970
|
+
if (cardNodeIds.length > 0 || exhausted) {
|
|
971
|
+
return {
|
|
972
|
+
ok: true,
|
|
973
|
+
exhausted,
|
|
974
|
+
reason: exhausted ? "filtered_list_exhausted" : null,
|
|
975
|
+
method: "end_refresh_button",
|
|
976
|
+
attempts,
|
|
977
|
+
page_scope: pageScopeResult,
|
|
978
|
+
current_city_only: currentCityOnlyResult,
|
|
979
|
+
current_city_only_attempts: currentCityOnlyAttempts,
|
|
980
|
+
filter: filterResult,
|
|
981
|
+
filter_reapply_attempts: filterReapplyAttempts,
|
|
982
|
+
card_count: cardNodeIds.length,
|
|
983
|
+
empty_state: emptyState,
|
|
984
|
+
root_state: currentRootState,
|
|
985
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
986
|
+
};
|
|
987
|
+
}
|
|
717
988
|
attempts.push({
|
|
718
989
|
ok: false,
|
|
719
990
|
method: "end_refresh_button_after_click",
|
|
720
|
-
reason: "
|
|
721
|
-
error: error?.message || String(error),
|
|
722
|
-
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
991
|
+
reason: "end_refresh_empty_state_unverified",
|
|
723
992
|
page_scope: pageScopeResult,
|
|
724
993
|
current_city_only: currentCityOnlyResult,
|
|
725
|
-
current_city_only_attempts:
|
|
994
|
+
current_city_only_attempts: currentCityOnlyAttempts,
|
|
726
995
|
filter: filterResult,
|
|
727
|
-
filter_reapply_attempts:
|
|
996
|
+
filter_reapply_attempts: filterReapplyAttempts,
|
|
997
|
+
card_count: 0,
|
|
998
|
+
empty_state: emptyState,
|
|
728
999
|
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
729
1000
|
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
attempts.push({
|
|
1003
|
+
ok: false,
|
|
1004
|
+
method: "end_refresh_button_after_click",
|
|
1005
|
+
reason: "end_refresh_reapply_failed",
|
|
1006
|
+
error: error?.message || String(error),
|
|
1007
|
+
error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
|
|
1008
|
+
page_scope: pageScopeResult,
|
|
1009
|
+
current_city_only: currentCityOnlyResult,
|
|
1010
|
+
current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
|
|
1011
|
+
filter: filterResult,
|
|
1012
|
+
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
1013
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
734
1019
|
const fallbackMethods = [];
|
|
735
1020
|
if (forceNavigate && typeof client?.Page?.navigate === "function") {
|
|
736
1021
|
fallbackMethods.push("page_navigate");
|
|
@@ -767,13 +1052,13 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
767
1052
|
return {
|
|
768
1053
|
...(lastRefreshResult || {
|
|
769
1054
|
ok: false,
|
|
770
|
-
method: fallbackMethods[fallbackMethods.length - 1] || "page_reload",
|
|
771
|
-
reason: "refresh_failed",
|
|
772
|
-
error: "Recommend refresh did not run",
|
|
773
|
-
card_count: 0,
|
|
774
|
-
root_state: currentRootState,
|
|
775
|
-
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
776
|
-
}),
|
|
777
|
-
attempts
|
|
778
|
-
};
|
|
779
|
-
}
|
|
1055
|
+
method: fallbackMethods[fallbackMethods.length - 1] || "page_reload",
|
|
1056
|
+
reason: "refresh_failed",
|
|
1057
|
+
error: "Recommend refresh did not run",
|
|
1058
|
+
card_count: 0,
|
|
1059
|
+
root_state: currentRootState,
|
|
1060
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
1061
|
+
}),
|
|
1062
|
+
attempts
|
|
1063
|
+
};
|
|
1064
|
+
}
|