@reconcrap/boss-recommend-mcp 2.0.45 → 2.0.47
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/bin/boss-recommend-mcp.js +4 -4
- package/config/screening-config.example.json +27 -27
- package/package.json +1 -1
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +39 -39
- package/skills/boss-chat/SKILL.md +93 -93
- package/skills/boss-recommend-pipeline/README.md +12 -12
- package/skills/boss-recommend-pipeline/SKILL.md +180 -180
- package/skills/boss-recruit-pipeline/README.md +17 -17
- package/skills/boss-recruit-pipeline/SKILL.md +58 -58
- package/src/chat-mcp.js +1780 -1780
- package/src/chat-runtime-config.js +749 -749
- package/src/cli.js +3054 -3054
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +1453 -1446
- package/src/core/capture/index.js +1201 -1201
- package/src/core/cv-acquisition/index.js +238 -238
- package/src/core/cv-capture-target/index.js +299 -299
- package/src/core/greet-quota/index.js +54 -54
- package/src/core/infinite-list/index.js +1326 -1326
- package/src/core/reporting/legacy-csv.js +341 -341
- package/src/core/run/timing.js +33 -33
- package/src/core/screening/index.js +50 -3
- package/src/core/self-heal/index.js +973 -973
- package/src/core/self-heal/viewport.js +564 -564
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +221 -221
- package/src/domains/chat/detail.js +1668 -1661
- package/src/domains/chat/index.js +7 -7
- package/src/domains/chat/jobs.js +592 -588
- package/src/domains/chat/page-guard.js +98 -98
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1977 -1955
- package/src/domains/recommend/actions.js +457 -457
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/constants.js +165 -165
- package/src/domains/recommend/detail.js +36 -28
- package/src/domains/recommend/filters.js +610 -581
- package/src/domains/recommend/index.js +10 -10
- package/src/domains/recommend/jobs.js +316 -263
- package/src/domains/recommend/refresh.js +472 -472
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +75 -35
- package/src/domains/recommend/scopes.js +246 -245
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +167 -167
- package/src/domains/recruit/detail.js +461 -460
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +451 -451
- package/src/domains/recruit/refresh.js +44 -44
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1207 -1161
- package/src/domains/recruit/search.js +1202 -1149
- package/src/recommend-mcp.js +22 -22
- package/src/recruit-mcp.js +1338 -1338
|
@@ -1,472 +1,472 @@
|
|
|
1
|
-
import { sleep } from "../../core/browser/index.js";
|
|
2
|
-
import {
|
|
3
|
-
clickRecommendEndRefreshButton,
|
|
4
|
-
waitForRecommendCardNodeIds
|
|
5
|
-
} from "./cards.js";
|
|
6
|
-
import {
|
|
7
|
-
RECOMMEND_RECENT_NOT_VIEW_LABEL,
|
|
8
|
-
RECOMMEND_TARGET_URL
|
|
9
|
-
} from "./constants.js";
|
|
10
|
-
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
11
|
-
import { selectRecommendJob } from "./jobs.js";
|
|
12
|
-
import { selectRecommendPageScope } from "./scopes.js";
|
|
13
|
-
import {
|
|
14
|
-
getRecommendRoots,
|
|
15
|
-
waitForRecommendRoots
|
|
16
|
-
} from "./roots.js";
|
|
17
|
-
|
|
18
|
-
function normalizeLabels(labels = []) {
|
|
19
|
-
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function normalizeFilterGroup(spec = {}) {
|
|
23
|
-
return {
|
|
24
|
-
group: String(spec.group || "").trim(),
|
|
25
|
-
labels: normalizeLabels(spec.labels || spec.filterLabels || []),
|
|
26
|
-
selectAllLabels: spec.selectAllLabels !== false
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function buildRecommendFilterGroups(filter = {}, {
|
|
31
|
-
forceRecentNotView = false
|
|
32
|
-
} = {}) {
|
|
33
|
-
const groups = [];
|
|
34
|
-
const sourceGroups = Array.isArray(filter.filterGroups)
|
|
35
|
-
? filter.filterGroups
|
|
36
|
-
: Array.isArray(filter.groups)
|
|
37
|
-
? filter.groups
|
|
38
|
-
: [];
|
|
39
|
-
|
|
40
|
-
for (const spec of sourceGroups) {
|
|
41
|
-
const group = normalizeFilterGroup(spec);
|
|
42
|
-
if (group.group || group.labels.length) groups.push(group);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const rootGroup = normalizeFilterGroup(filter);
|
|
46
|
-
if ((rootGroup.group || rootGroup.labels.length) && !groups.length) {
|
|
47
|
-
groups.push(rootGroup);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (forceRecentNotView) {
|
|
51
|
-
const recentGroup = groups.find((item) => item.group === "recentNotView");
|
|
52
|
-
if (recentGroup) {
|
|
53
|
-
if (!recentGroup.labels.some((label) => label.replace(/\s+/g, "") === RECOMMEND_RECENT_NOT_VIEW_LABEL)) {
|
|
54
|
-
recentGroup.labels.push(RECOMMEND_RECENT_NOT_VIEW_LABEL);
|
|
55
|
-
}
|
|
56
|
-
recentGroup.selectAllLabels = true;
|
|
57
|
-
} else {
|
|
58
|
-
groups.unshift({
|
|
59
|
-
group: "recentNotView",
|
|
60
|
-
labels: [RECOMMEND_RECENT_NOT_VIEW_LABEL],
|
|
61
|
-
selectAllLabels: true
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return groups;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function buildRecommendFilterSelectionOptions(filter = {}, {
|
|
70
|
-
forceRecentNotView = false
|
|
71
|
-
} = {}) {
|
|
72
|
-
const filterGroups = buildRecommendFilterGroups(filter, { forceRecentNotView });
|
|
73
|
-
if (filterGroups.length > 1 || forceRecentNotView || Array.isArray(filter.filterGroups) || Array.isArray(filter.groups)) {
|
|
74
|
-
return { filterGroups };
|
|
75
|
-
}
|
|
76
|
-
const [singleGroup] = filterGroups;
|
|
77
|
-
if (singleGroup) {
|
|
78
|
-
return {
|
|
79
|
-
group: singleGroup.group,
|
|
80
|
-
labels: singleGroup.labels,
|
|
81
|
-
selectAllLabels: singleGroup.selectAllLabels
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
group: filter.group || "",
|
|
86
|
-
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
87
|
-
selectAllLabels: filter.selectAllLabels !== false
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function refreshFailureReason(method = "") {
|
|
92
|
-
return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function isRetryableRecommendFilterReapplyError(error) {
|
|
96
|
-
const message = String(error?.message || error || "");
|
|
97
|
-
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/i.test(message);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function compactFilterReapplyError(error) {
|
|
101
|
-
return error?.message || String(error || "Recommend filter reapply failed");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function isRetryableRecommendJobSelectionError(error) {
|
|
105
|
-
const message = String(error?.message || error || "");
|
|
106
|
-
return /Recommend job trigger was not found|Recommend job dropdown did not mount options/i.test(message);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function compactJobSelectionAttempt({
|
|
110
|
-
ok = false,
|
|
111
|
-
attempt = 0,
|
|
112
|
-
iframeDocumentNodeId = 0,
|
|
113
|
-
error = null,
|
|
114
|
-
selection = null
|
|
115
|
-
} = {}) {
|
|
116
|
-
return {
|
|
117
|
-
ok: Boolean(ok),
|
|
118
|
-
method: "job_select",
|
|
119
|
-
reason: error ? "job_select_failed" : null,
|
|
120
|
-
error: error ? (error?.message || String(error)) : null,
|
|
121
|
-
attempt,
|
|
122
|
-
iframe_document_node_id: iframeDocumentNodeId || 0,
|
|
123
|
-
selected: Boolean(selection?.selected),
|
|
124
|
-
selection_reason: selection?.reason || null
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
129
|
-
jobLabel = "",
|
|
130
|
-
settleMs = 6000,
|
|
131
|
-
dropdownTimeoutMs = 4000,
|
|
132
|
-
totalTimeoutMs = 30000,
|
|
133
|
-
retryDelayMs = 1000
|
|
134
|
-
} = {}) {
|
|
135
|
-
const started = Date.now();
|
|
136
|
-
const attempts = [];
|
|
137
|
-
let currentRootState = rootState || null;
|
|
138
|
-
let lastError = null;
|
|
139
|
-
let attempt = 0;
|
|
140
|
-
|
|
141
|
-
while (Date.now() - started <= totalTimeoutMs) {
|
|
142
|
-
attempt += 1;
|
|
143
|
-
if (!currentRootState?.iframe?.documentNodeId) {
|
|
144
|
-
currentRootState = await getRecommendRoots(client);
|
|
145
|
-
}
|
|
146
|
-
const iframeDocumentNodeId = currentRootState?.iframe?.documentNodeId || 0;
|
|
147
|
-
try {
|
|
148
|
-
const selection = await selectRecommendJob(client, iframeDocumentNodeId, {
|
|
149
|
-
jobLabel,
|
|
150
|
-
settleMs,
|
|
151
|
-
dropdownTimeoutMs
|
|
152
|
-
});
|
|
153
|
-
attempts.push(compactJobSelectionAttempt({
|
|
154
|
-
ok: true,
|
|
155
|
-
attempt,
|
|
156
|
-
iframeDocumentNodeId,
|
|
157
|
-
selection
|
|
158
|
-
}));
|
|
159
|
-
return {
|
|
160
|
-
job_selection: {
|
|
161
|
-
...selection,
|
|
162
|
-
refresh_attempts: attempts
|
|
163
|
-
},
|
|
164
|
-
root_state: currentRootState,
|
|
165
|
-
attempts
|
|
166
|
-
};
|
|
167
|
-
} catch (error) {
|
|
168
|
-
lastError = error;
|
|
169
|
-
attempts.push(compactJobSelectionAttempt({
|
|
170
|
-
ok: false,
|
|
171
|
-
attempt,
|
|
172
|
-
iframeDocumentNodeId,
|
|
173
|
-
error
|
|
174
|
-
}));
|
|
175
|
-
if (!isRetryableRecommendJobSelectionError(error) || Date.now() - started >= totalTimeoutMs) {
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
179
|
-
currentRootState = await getRecommendRoots(client);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const wrapped = new Error(lastError?.message || "Recommend job selection failed after refresh reload");
|
|
184
|
-
wrapped.cause = lastError;
|
|
185
|
-
wrapped.job_selection_attempts = attempts;
|
|
186
|
-
throw wrapped;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
190
|
-
maxAttempts = 3,
|
|
191
|
-
retryDelayMs = 1500
|
|
192
|
-
} = {}) {
|
|
193
|
-
const attempts = [];
|
|
194
|
-
let currentRootState = rootState;
|
|
195
|
-
let lastError = null;
|
|
196
|
-
|
|
197
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
198
|
-
try {
|
|
199
|
-
const filter = await selectAndConfirmFirstSafeFilter(
|
|
200
|
-
client,
|
|
201
|
-
currentRootState.iframe.documentNodeId,
|
|
202
|
-
filterOptions
|
|
203
|
-
);
|
|
204
|
-
attempts.push({
|
|
205
|
-
ok: true,
|
|
206
|
-
method: "filter_reapply",
|
|
207
|
-
attempt
|
|
208
|
-
});
|
|
209
|
-
return {
|
|
210
|
-
filter,
|
|
211
|
-
root_state: currentRootState,
|
|
212
|
-
attempts
|
|
213
|
-
};
|
|
214
|
-
} catch (error) {
|
|
215
|
-
lastError = error;
|
|
216
|
-
attempts.push({
|
|
217
|
-
ok: false,
|
|
218
|
-
method: "filter_reapply",
|
|
219
|
-
reason: "filter_reapply_failed",
|
|
220
|
-
error: compactFilterReapplyError(error),
|
|
221
|
-
attempt
|
|
222
|
-
});
|
|
223
|
-
if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
227
|
-
currentRootState = await getRecommendRoots(client);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const wrapped = new Error(compactFilterReapplyError(lastError));
|
|
232
|
-
wrapped.cause = lastError;
|
|
233
|
-
wrapped.filter_reapply_attempts = attempts;
|
|
234
|
-
throw wrapped;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async function applyRefreshMethod(client, method, {
|
|
238
|
-
jobLabel = "",
|
|
239
|
-
pageScope = "recommend",
|
|
240
|
-
fallbackPageScope = "recommend",
|
|
241
|
-
filter = {},
|
|
242
|
-
targetUrl = RECOMMEND_TARGET_URL,
|
|
243
|
-
forceRecentNotView = true,
|
|
244
|
-
cardTimeoutMs = 30000,
|
|
245
|
-
reloadSettleMs = 8000
|
|
246
|
-
} = {}) {
|
|
247
|
-
const started = Date.now();
|
|
248
|
-
let currentRootState = null;
|
|
249
|
-
let jobSelection = null;
|
|
250
|
-
let jobSelectionAttempts = [];
|
|
251
|
-
let pageScopeResult = null;
|
|
252
|
-
let filterResult = null;
|
|
253
|
-
let filterReapplyAttempts = [];
|
|
254
|
-
try {
|
|
255
|
-
if (method === "page_navigate") {
|
|
256
|
-
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
257
|
-
} else {
|
|
258
|
-
await client.Page.reload({ ignoreCache: true });
|
|
259
|
-
}
|
|
260
|
-
if (reloadSettleMs > 0) await sleep(reloadSettleMs);
|
|
261
|
-
currentRootState = await waitForRecommendRoots(client, {
|
|
262
|
-
timeoutMs: Math.max(45000, reloadSettleMs * 6),
|
|
263
|
-
intervalMs: 500
|
|
264
|
-
});
|
|
265
|
-
if (!currentRootState?.iframe?.documentNodeId) {
|
|
266
|
-
throw new Error("Recommend iframe was not ready after refresh reload");
|
|
267
|
-
}
|
|
268
|
-
if (jobLabel) {
|
|
269
|
-
const jobSelectionResult = await selectRecommendJobWithRootRefresh(client, currentRootState, {
|
|
270
|
-
jobLabel,
|
|
271
|
-
settleMs: reloadSettleMs > 10000 ? 12000 : 6000,
|
|
272
|
-
dropdownTimeoutMs: 4000,
|
|
273
|
-
totalTimeoutMs: reloadSettleMs > 10000 ? 45000 : 30000,
|
|
274
|
-
retryDelayMs: 1200
|
|
275
|
-
});
|
|
276
|
-
jobSelection = jobSelectionResult.job_selection;
|
|
277
|
-
jobSelectionAttempts = jobSelectionResult.attempts;
|
|
278
|
-
if (!jobSelection.selected) {
|
|
279
|
-
throw new Error(`Requested recommend job was not selected after refresh reload: ${jobSelection.reason}`);
|
|
280
|
-
}
|
|
281
|
-
currentRootState = jobSelectionResult.root_state || await getRecommendRoots(client);
|
|
282
|
-
}
|
|
283
|
-
pageScopeResult = await selectRecommendPageScope(
|
|
284
|
-
client,
|
|
285
|
-
currentRootState.iframe.documentNodeId,
|
|
286
|
-
{
|
|
287
|
-
pageScope,
|
|
288
|
-
fallbackScope: fallbackPageScope,
|
|
289
|
-
settleMs: reloadSettleMs > 10000 ? 3000 : 1200,
|
|
290
|
-
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
291
|
-
}
|
|
292
|
-
);
|
|
293
|
-
if (!pageScopeResult.selected) {
|
|
294
|
-
throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
|
|
295
|
-
}
|
|
296
|
-
currentRootState = await getRecommendRoots(client);
|
|
297
|
-
const filterSelection = await selectAndConfirmRefreshFilter(
|
|
298
|
-
client,
|
|
299
|
-
currentRootState,
|
|
300
|
-
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
301
|
-
{
|
|
302
|
-
retryDelayMs: Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)))
|
|
303
|
-
}
|
|
304
|
-
);
|
|
305
|
-
filterResult = filterSelection.filter;
|
|
306
|
-
filterReapplyAttempts = filterSelection.attempts;
|
|
307
|
-
currentRootState = await getRecommendRoots(client);
|
|
308
|
-
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
309
|
-
timeoutMs: cardTimeoutMs,
|
|
310
|
-
intervalMs: 500
|
|
311
|
-
});
|
|
312
|
-
if (!cardNodeIds.length) {
|
|
313
|
-
throw new Error("No recommend candidate cards were found after refresh reload");
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
ok: true,
|
|
317
|
-
method,
|
|
318
|
-
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
319
|
-
job_selection: jobSelection,
|
|
320
|
-
job_selection_attempts: jobSelectionAttempts,
|
|
321
|
-
page_scope: pageScopeResult,
|
|
322
|
-
filter: filterResult,
|
|
323
|
-
filter_reapply_attempts: filterReapplyAttempts,
|
|
324
|
-
card_count: cardNodeIds.length,
|
|
325
|
-
root_state: currentRootState,
|
|
326
|
-
forced_recent_not_view: forceRecentNotView,
|
|
327
|
-
elapsed_ms: Date.now() - started
|
|
328
|
-
};
|
|
329
|
-
} catch (error) {
|
|
330
|
-
return {
|
|
331
|
-
ok: false,
|
|
332
|
-
method,
|
|
333
|
-
reason: refreshFailureReason(method),
|
|
334
|
-
error: error?.message || String(error),
|
|
335
|
-
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
336
|
-
job_selection: jobSelection,
|
|
337
|
-
job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
|
|
338
|
-
page_scope: pageScopeResult,
|
|
339
|
-
filter: filterResult,
|
|
340
|
-
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
341
|
-
card_count: 0,
|
|
342
|
-
root_state: currentRootState,
|
|
343
|
-
forced_recent_not_view: forceRecentNotView,
|
|
344
|
-
elapsed_ms: Date.now() - started
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export async function refreshRecommendListAtEnd(client, {
|
|
350
|
-
rootState = null,
|
|
351
|
-
jobLabel = "",
|
|
352
|
-
pageScope = "recommend",
|
|
353
|
-
fallbackPageScope = "recommend",
|
|
354
|
-
filter = {},
|
|
355
|
-
preferEndRefreshButton = true,
|
|
356
|
-
forceNavigate = false,
|
|
357
|
-
targetUrl = RECOMMEND_TARGET_URL,
|
|
358
|
-
forceRecentNotView = true,
|
|
359
|
-
cardTimeoutMs = 30000,
|
|
360
|
-
buttonSettleMs = 8000,
|
|
361
|
-
reloadSettleMs = 8000
|
|
362
|
-
} = {}) {
|
|
363
|
-
const attempts = [];
|
|
364
|
-
let currentRootState = rootState || null;
|
|
365
|
-
|
|
366
|
-
if (preferEndRefreshButton) {
|
|
367
|
-
currentRootState = currentRootState || await getRecommendRoots(client);
|
|
368
|
-
const buttonResult = await clickRecommendEndRefreshButton(
|
|
369
|
-
client,
|
|
370
|
-
currentRootState.iframe.documentNodeId,
|
|
371
|
-
{ settleMs: buttonSettleMs }
|
|
372
|
-
);
|
|
373
|
-
attempts.push(buttonResult);
|
|
374
|
-
if (buttonResult.ok) {
|
|
375
|
-
try {
|
|
376
|
-
currentRootState = await getRecommendRoots(client);
|
|
377
|
-
const pageScopeResult = await selectRecommendPageScope(
|
|
378
|
-
client,
|
|
379
|
-
currentRootState.iframe.documentNodeId,
|
|
380
|
-
{
|
|
381
|
-
pageScope,
|
|
382
|
-
fallbackScope: fallbackPageScope,
|
|
383
|
-
settleMs: buttonSettleMs > 10000 ? 3000 : 1200,
|
|
384
|
-
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
385
|
-
}
|
|
386
|
-
);
|
|
387
|
-
if (!pageScopeResult.selected) {
|
|
388
|
-
throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
|
|
389
|
-
}
|
|
390
|
-
currentRootState = await getRecommendRoots(client);
|
|
391
|
-
const filterSelection = await selectAndConfirmRefreshFilter(
|
|
392
|
-
client,
|
|
393
|
-
currentRootState,
|
|
394
|
-
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
395
|
-
{
|
|
396
|
-
retryDelayMs: Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)))
|
|
397
|
-
}
|
|
398
|
-
);
|
|
399
|
-
const filterResult = filterSelection.filter;
|
|
400
|
-
currentRootState = await getRecommendRoots(client);
|
|
401
|
-
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
402
|
-
timeoutMs: cardTimeoutMs,
|
|
403
|
-
intervalMs: 500
|
|
404
|
-
});
|
|
405
|
-
return {
|
|
406
|
-
ok: cardNodeIds.length > 0,
|
|
407
|
-
method: "end_refresh_button",
|
|
408
|
-
attempts,
|
|
409
|
-
page_scope: pageScopeResult,
|
|
410
|
-
filter: filterResult,
|
|
411
|
-
filter_reapply_attempts: filterSelection.attempts,
|
|
412
|
-
card_count: cardNodeIds.length,
|
|
413
|
-
root_state: currentRootState,
|
|
414
|
-
forced_recent_not_view: forceRecentNotView
|
|
415
|
-
};
|
|
416
|
-
} catch (error) {
|
|
417
|
-
attempts.push({
|
|
418
|
-
ok: false,
|
|
419
|
-
method: "end_refresh_button_after_click",
|
|
420
|
-
reason: "end_refresh_reapply_failed",
|
|
421
|
-
error: error?.message || String(error)
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const fallbackMethods = [];
|
|
428
|
-
if (forceNavigate && typeof client?.Page?.navigate === "function") {
|
|
429
|
-
fallbackMethods.push("page_navigate");
|
|
430
|
-
}
|
|
431
|
-
if (typeof client?.Page?.reload === "function") {
|
|
432
|
-
fallbackMethods.push("page_reload");
|
|
433
|
-
}
|
|
434
|
-
if (!fallbackMethods.length) {
|
|
435
|
-
fallbackMethods.push("page_reload");
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
let lastRefreshResult = null;
|
|
439
|
-
for (const method of fallbackMethods) {
|
|
440
|
-
const refreshResult = await applyRefreshMethod(client, method, {
|
|
441
|
-
jobLabel,
|
|
442
|
-
pageScope,
|
|
443
|
-
fallbackPageScope,
|
|
444
|
-
filter,
|
|
445
|
-
targetUrl,
|
|
446
|
-
forceRecentNotView,
|
|
447
|
-
cardTimeoutMs,
|
|
448
|
-
reloadSettleMs
|
|
449
|
-
});
|
|
450
|
-
if (refreshResult.ok) {
|
|
451
|
-
return {
|
|
452
|
-
...refreshResult,
|
|
453
|
-
attempts
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
attempts.push(refreshResult);
|
|
457
|
-
lastRefreshResult = refreshResult;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return {
|
|
461
|
-
...(lastRefreshResult || {
|
|
462
|
-
ok: false,
|
|
463
|
-
method: fallbackMethods[fallbackMethods.length - 1] || "page_reload",
|
|
464
|
-
reason: "refresh_failed",
|
|
465
|
-
error: "Recommend refresh did not run",
|
|
466
|
-
card_count: 0,
|
|
467
|
-
root_state: currentRootState,
|
|
468
|
-
forced_recent_not_view: forceRecentNotView
|
|
469
|
-
}),
|
|
470
|
-
attempts
|
|
471
|
-
};
|
|
472
|
-
}
|
|
1
|
+
import { sleep } from "../../core/browser/index.js";
|
|
2
|
+
import {
|
|
3
|
+
clickRecommendEndRefreshButton,
|
|
4
|
+
waitForRecommendCardNodeIds
|
|
5
|
+
} from "./cards.js";
|
|
6
|
+
import {
|
|
7
|
+
RECOMMEND_RECENT_NOT_VIEW_LABEL,
|
|
8
|
+
RECOMMEND_TARGET_URL
|
|
9
|
+
} from "./constants.js";
|
|
10
|
+
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
11
|
+
import { selectRecommendJob } from "./jobs.js";
|
|
12
|
+
import { selectRecommendPageScope } from "./scopes.js";
|
|
13
|
+
import {
|
|
14
|
+
getRecommendRoots,
|
|
15
|
+
waitForRecommendRoots
|
|
16
|
+
} from "./roots.js";
|
|
17
|
+
|
|
18
|
+
function normalizeLabels(labels = []) {
|
|
19
|
+
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeFilterGroup(spec = {}) {
|
|
23
|
+
return {
|
|
24
|
+
group: String(spec.group || "").trim(),
|
|
25
|
+
labels: normalizeLabels(spec.labels || spec.filterLabels || []),
|
|
26
|
+
selectAllLabels: spec.selectAllLabels !== false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildRecommendFilterGroups(filter = {}, {
|
|
31
|
+
forceRecentNotView = false
|
|
32
|
+
} = {}) {
|
|
33
|
+
const groups = [];
|
|
34
|
+
const sourceGroups = Array.isArray(filter.filterGroups)
|
|
35
|
+
? filter.filterGroups
|
|
36
|
+
: Array.isArray(filter.groups)
|
|
37
|
+
? filter.groups
|
|
38
|
+
: [];
|
|
39
|
+
|
|
40
|
+
for (const spec of sourceGroups) {
|
|
41
|
+
const group = normalizeFilterGroup(spec);
|
|
42
|
+
if (group.group || group.labels.length) groups.push(group);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rootGroup = normalizeFilterGroup(filter);
|
|
46
|
+
if ((rootGroup.group || rootGroup.labels.length) && !groups.length) {
|
|
47
|
+
groups.push(rootGroup);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (forceRecentNotView) {
|
|
51
|
+
const recentGroup = groups.find((item) => item.group === "recentNotView");
|
|
52
|
+
if (recentGroup) {
|
|
53
|
+
if (!recentGroup.labels.some((label) => label.replace(/\s+/g, "") === RECOMMEND_RECENT_NOT_VIEW_LABEL)) {
|
|
54
|
+
recentGroup.labels.push(RECOMMEND_RECENT_NOT_VIEW_LABEL);
|
|
55
|
+
}
|
|
56
|
+
recentGroup.selectAllLabels = true;
|
|
57
|
+
} else {
|
|
58
|
+
groups.unshift({
|
|
59
|
+
group: "recentNotView",
|
|
60
|
+
labels: [RECOMMEND_RECENT_NOT_VIEW_LABEL],
|
|
61
|
+
selectAllLabels: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return groups;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildRecommendFilterSelectionOptions(filter = {}, {
|
|
70
|
+
forceRecentNotView = false
|
|
71
|
+
} = {}) {
|
|
72
|
+
const filterGroups = buildRecommendFilterGroups(filter, { forceRecentNotView });
|
|
73
|
+
if (filterGroups.length > 1 || forceRecentNotView || Array.isArray(filter.filterGroups) || Array.isArray(filter.groups)) {
|
|
74
|
+
return { filterGroups };
|
|
75
|
+
}
|
|
76
|
+
const [singleGroup] = filterGroups;
|
|
77
|
+
if (singleGroup) {
|
|
78
|
+
return {
|
|
79
|
+
group: singleGroup.group,
|
|
80
|
+
labels: singleGroup.labels,
|
|
81
|
+
selectAllLabels: singleGroup.selectAllLabels
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
group: filter.group || "",
|
|
86
|
+
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
87
|
+
selectAllLabels: filter.selectAllLabels !== false
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function refreshFailureReason(method = "") {
|
|
92
|
+
return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isRetryableRecommendFilterReapplyError(error) {
|
|
96
|
+
const message = String(error?.message || error || "");
|
|
97
|
+
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/i.test(message);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function compactFilterReapplyError(error) {
|
|
101
|
+
return error?.message || String(error || "Recommend filter reapply failed");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function isRetryableRecommendJobSelectionError(error) {
|
|
105
|
+
const message = String(error?.message || error || "");
|
|
106
|
+
return /Recommend job trigger was not found|Recommend job dropdown did not mount options|Recommend job dropdown did not expose visible options|Matched recommend job has no clickable center|Matched recommend job has no visible clickable option/i.test(message);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function compactJobSelectionAttempt({
|
|
110
|
+
ok = false,
|
|
111
|
+
attempt = 0,
|
|
112
|
+
iframeDocumentNodeId = 0,
|
|
113
|
+
error = null,
|
|
114
|
+
selection = null
|
|
115
|
+
} = {}) {
|
|
116
|
+
return {
|
|
117
|
+
ok: Boolean(ok),
|
|
118
|
+
method: "job_select",
|
|
119
|
+
reason: error ? "job_select_failed" : null,
|
|
120
|
+
error: error ? (error?.message || String(error)) : null,
|
|
121
|
+
attempt,
|
|
122
|
+
iframe_document_node_id: iframeDocumentNodeId || 0,
|
|
123
|
+
selected: Boolean(selection?.selected),
|
|
124
|
+
selection_reason: selection?.reason || null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
129
|
+
jobLabel = "",
|
|
130
|
+
settleMs = 6000,
|
|
131
|
+
dropdownTimeoutMs = 4000,
|
|
132
|
+
totalTimeoutMs = 30000,
|
|
133
|
+
retryDelayMs = 1000
|
|
134
|
+
} = {}) {
|
|
135
|
+
const started = Date.now();
|
|
136
|
+
const attempts = [];
|
|
137
|
+
let currentRootState = rootState || null;
|
|
138
|
+
let lastError = null;
|
|
139
|
+
let attempt = 0;
|
|
140
|
+
|
|
141
|
+
while (Date.now() - started <= totalTimeoutMs) {
|
|
142
|
+
attempt += 1;
|
|
143
|
+
if (!currentRootState?.iframe?.documentNodeId) {
|
|
144
|
+
currentRootState = await getRecommendRoots(client);
|
|
145
|
+
}
|
|
146
|
+
const iframeDocumentNodeId = currentRootState?.iframe?.documentNodeId || 0;
|
|
147
|
+
try {
|
|
148
|
+
const selection = await selectRecommendJob(client, iframeDocumentNodeId, {
|
|
149
|
+
jobLabel,
|
|
150
|
+
settleMs,
|
|
151
|
+
dropdownTimeoutMs
|
|
152
|
+
});
|
|
153
|
+
attempts.push(compactJobSelectionAttempt({
|
|
154
|
+
ok: true,
|
|
155
|
+
attempt,
|
|
156
|
+
iframeDocumentNodeId,
|
|
157
|
+
selection
|
|
158
|
+
}));
|
|
159
|
+
return {
|
|
160
|
+
job_selection: {
|
|
161
|
+
...selection,
|
|
162
|
+
refresh_attempts: attempts
|
|
163
|
+
},
|
|
164
|
+
root_state: currentRootState,
|
|
165
|
+
attempts
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
lastError = error;
|
|
169
|
+
attempts.push(compactJobSelectionAttempt({
|
|
170
|
+
ok: false,
|
|
171
|
+
attempt,
|
|
172
|
+
iframeDocumentNodeId,
|
|
173
|
+
error
|
|
174
|
+
}));
|
|
175
|
+
if (!isRetryableRecommendJobSelectionError(error) || Date.now() - started >= totalTimeoutMs) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
179
|
+
currentRootState = await getRecommendRoots(client);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const wrapped = new Error(lastError?.message || "Recommend job selection failed after refresh reload");
|
|
184
|
+
wrapped.cause = lastError;
|
|
185
|
+
wrapped.job_selection_attempts = attempts;
|
|
186
|
+
throw wrapped;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
190
|
+
maxAttempts = 3,
|
|
191
|
+
retryDelayMs = 1500
|
|
192
|
+
} = {}) {
|
|
193
|
+
const attempts = [];
|
|
194
|
+
let currentRootState = rootState;
|
|
195
|
+
let lastError = null;
|
|
196
|
+
|
|
197
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
198
|
+
try {
|
|
199
|
+
const filter = await selectAndConfirmFirstSafeFilter(
|
|
200
|
+
client,
|
|
201
|
+
currentRootState.iframe.documentNodeId,
|
|
202
|
+
filterOptions
|
|
203
|
+
);
|
|
204
|
+
attempts.push({
|
|
205
|
+
ok: true,
|
|
206
|
+
method: "filter_reapply",
|
|
207
|
+
attempt
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
filter,
|
|
211
|
+
root_state: currentRootState,
|
|
212
|
+
attempts
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
lastError = error;
|
|
216
|
+
attempts.push({
|
|
217
|
+
ok: false,
|
|
218
|
+
method: "filter_reapply",
|
|
219
|
+
reason: "filter_reapply_failed",
|
|
220
|
+
error: compactFilterReapplyError(error),
|
|
221
|
+
attempt
|
|
222
|
+
});
|
|
223
|
+
if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
227
|
+
currentRootState = await getRecommendRoots(client);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const wrapped = new Error(compactFilterReapplyError(lastError));
|
|
232
|
+
wrapped.cause = lastError;
|
|
233
|
+
wrapped.filter_reapply_attempts = attempts;
|
|
234
|
+
throw wrapped;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function applyRefreshMethod(client, method, {
|
|
238
|
+
jobLabel = "",
|
|
239
|
+
pageScope = "recommend",
|
|
240
|
+
fallbackPageScope = "recommend",
|
|
241
|
+
filter = {},
|
|
242
|
+
targetUrl = RECOMMEND_TARGET_URL,
|
|
243
|
+
forceRecentNotView = true,
|
|
244
|
+
cardTimeoutMs = 30000,
|
|
245
|
+
reloadSettleMs = 8000
|
|
246
|
+
} = {}) {
|
|
247
|
+
const started = Date.now();
|
|
248
|
+
let currentRootState = null;
|
|
249
|
+
let jobSelection = null;
|
|
250
|
+
let jobSelectionAttempts = [];
|
|
251
|
+
let pageScopeResult = null;
|
|
252
|
+
let filterResult = null;
|
|
253
|
+
let filterReapplyAttempts = [];
|
|
254
|
+
try {
|
|
255
|
+
if (method === "page_navigate") {
|
|
256
|
+
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
257
|
+
} else {
|
|
258
|
+
await client.Page.reload({ ignoreCache: true });
|
|
259
|
+
}
|
|
260
|
+
if (reloadSettleMs > 0) await sleep(reloadSettleMs);
|
|
261
|
+
currentRootState = await waitForRecommendRoots(client, {
|
|
262
|
+
timeoutMs: Math.max(45000, reloadSettleMs * 6),
|
|
263
|
+
intervalMs: 500
|
|
264
|
+
});
|
|
265
|
+
if (!currentRootState?.iframe?.documentNodeId) {
|
|
266
|
+
throw new Error("Recommend iframe was not ready after refresh reload");
|
|
267
|
+
}
|
|
268
|
+
if (jobLabel) {
|
|
269
|
+
const jobSelectionResult = await selectRecommendJobWithRootRefresh(client, currentRootState, {
|
|
270
|
+
jobLabel,
|
|
271
|
+
settleMs: reloadSettleMs > 10000 ? 12000 : 6000,
|
|
272
|
+
dropdownTimeoutMs: 4000,
|
|
273
|
+
totalTimeoutMs: reloadSettleMs > 10000 ? 45000 : 30000,
|
|
274
|
+
retryDelayMs: 1200
|
|
275
|
+
});
|
|
276
|
+
jobSelection = jobSelectionResult.job_selection;
|
|
277
|
+
jobSelectionAttempts = jobSelectionResult.attempts;
|
|
278
|
+
if (!jobSelection.selected) {
|
|
279
|
+
throw new Error(`Requested recommend job was not selected after refresh reload: ${jobSelection.reason}`);
|
|
280
|
+
}
|
|
281
|
+
currentRootState = jobSelectionResult.root_state || await getRecommendRoots(client);
|
|
282
|
+
}
|
|
283
|
+
pageScopeResult = await selectRecommendPageScope(
|
|
284
|
+
client,
|
|
285
|
+
currentRootState.iframe.documentNodeId,
|
|
286
|
+
{
|
|
287
|
+
pageScope,
|
|
288
|
+
fallbackScope: fallbackPageScope,
|
|
289
|
+
settleMs: reloadSettleMs > 10000 ? 3000 : 1200,
|
|
290
|
+
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
if (!pageScopeResult.selected) {
|
|
294
|
+
throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
|
|
295
|
+
}
|
|
296
|
+
currentRootState = await getRecommendRoots(client);
|
|
297
|
+
const filterSelection = await selectAndConfirmRefreshFilter(
|
|
298
|
+
client,
|
|
299
|
+
currentRootState,
|
|
300
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
301
|
+
{
|
|
302
|
+
retryDelayMs: Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)))
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
filterResult = filterSelection.filter;
|
|
306
|
+
filterReapplyAttempts = filterSelection.attempts;
|
|
307
|
+
currentRootState = await getRecommendRoots(client);
|
|
308
|
+
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
309
|
+
timeoutMs: cardTimeoutMs,
|
|
310
|
+
intervalMs: 500
|
|
311
|
+
});
|
|
312
|
+
if (!cardNodeIds.length) {
|
|
313
|
+
throw new Error("No recommend candidate cards were found after refresh reload");
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
method,
|
|
318
|
+
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
319
|
+
job_selection: jobSelection,
|
|
320
|
+
job_selection_attempts: jobSelectionAttempts,
|
|
321
|
+
page_scope: pageScopeResult,
|
|
322
|
+
filter: filterResult,
|
|
323
|
+
filter_reapply_attempts: filterReapplyAttempts,
|
|
324
|
+
card_count: cardNodeIds.length,
|
|
325
|
+
root_state: currentRootState,
|
|
326
|
+
forced_recent_not_view: forceRecentNotView,
|
|
327
|
+
elapsed_ms: Date.now() - started
|
|
328
|
+
};
|
|
329
|
+
} catch (error) {
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
method,
|
|
333
|
+
reason: refreshFailureReason(method),
|
|
334
|
+
error: error?.message || String(error),
|
|
335
|
+
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
336
|
+
job_selection: jobSelection,
|
|
337
|
+
job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
|
|
338
|
+
page_scope: pageScopeResult,
|
|
339
|
+
filter: filterResult,
|
|
340
|
+
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
341
|
+
card_count: 0,
|
|
342
|
+
root_state: currentRootState,
|
|
343
|
+
forced_recent_not_view: forceRecentNotView,
|
|
344
|
+
elapsed_ms: Date.now() - started
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export async function refreshRecommendListAtEnd(client, {
|
|
350
|
+
rootState = null,
|
|
351
|
+
jobLabel = "",
|
|
352
|
+
pageScope = "recommend",
|
|
353
|
+
fallbackPageScope = "recommend",
|
|
354
|
+
filter = {},
|
|
355
|
+
preferEndRefreshButton = true,
|
|
356
|
+
forceNavigate = false,
|
|
357
|
+
targetUrl = RECOMMEND_TARGET_URL,
|
|
358
|
+
forceRecentNotView = true,
|
|
359
|
+
cardTimeoutMs = 30000,
|
|
360
|
+
buttonSettleMs = 8000,
|
|
361
|
+
reloadSettleMs = 8000
|
|
362
|
+
} = {}) {
|
|
363
|
+
const attempts = [];
|
|
364
|
+
let currentRootState = rootState || null;
|
|
365
|
+
|
|
366
|
+
if (preferEndRefreshButton) {
|
|
367
|
+
currentRootState = currentRootState || await getRecommendRoots(client);
|
|
368
|
+
const buttonResult = await clickRecommendEndRefreshButton(
|
|
369
|
+
client,
|
|
370
|
+
currentRootState.iframe.documentNodeId,
|
|
371
|
+
{ settleMs: buttonSettleMs }
|
|
372
|
+
);
|
|
373
|
+
attempts.push(buttonResult);
|
|
374
|
+
if (buttonResult.ok) {
|
|
375
|
+
try {
|
|
376
|
+
currentRootState = await getRecommendRoots(client);
|
|
377
|
+
const pageScopeResult = await selectRecommendPageScope(
|
|
378
|
+
client,
|
|
379
|
+
currentRootState.iframe.documentNodeId,
|
|
380
|
+
{
|
|
381
|
+
pageScope,
|
|
382
|
+
fallbackScope: fallbackPageScope,
|
|
383
|
+
settleMs: buttonSettleMs > 10000 ? 3000 : 1200,
|
|
384
|
+
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
if (!pageScopeResult.selected) {
|
|
388
|
+
throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
|
|
389
|
+
}
|
|
390
|
+
currentRootState = await getRecommendRoots(client);
|
|
391
|
+
const filterSelection = await selectAndConfirmRefreshFilter(
|
|
392
|
+
client,
|
|
393
|
+
currentRootState,
|
|
394
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
395
|
+
{
|
|
396
|
+
retryDelayMs: Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)))
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
const filterResult = filterSelection.filter;
|
|
400
|
+
currentRootState = await getRecommendRoots(client);
|
|
401
|
+
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
402
|
+
timeoutMs: cardTimeoutMs,
|
|
403
|
+
intervalMs: 500
|
|
404
|
+
});
|
|
405
|
+
return {
|
|
406
|
+
ok: cardNodeIds.length > 0,
|
|
407
|
+
method: "end_refresh_button",
|
|
408
|
+
attempts,
|
|
409
|
+
page_scope: pageScopeResult,
|
|
410
|
+
filter: filterResult,
|
|
411
|
+
filter_reapply_attempts: filterSelection.attempts,
|
|
412
|
+
card_count: cardNodeIds.length,
|
|
413
|
+
root_state: currentRootState,
|
|
414
|
+
forced_recent_not_view: forceRecentNotView
|
|
415
|
+
};
|
|
416
|
+
} catch (error) {
|
|
417
|
+
attempts.push({
|
|
418
|
+
ok: false,
|
|
419
|
+
method: "end_refresh_button_after_click",
|
|
420
|
+
reason: "end_refresh_reapply_failed",
|
|
421
|
+
error: error?.message || String(error)
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const fallbackMethods = [];
|
|
428
|
+
if (forceNavigate && typeof client?.Page?.navigate === "function") {
|
|
429
|
+
fallbackMethods.push("page_navigate");
|
|
430
|
+
}
|
|
431
|
+
if (typeof client?.Page?.reload === "function") {
|
|
432
|
+
fallbackMethods.push("page_reload");
|
|
433
|
+
}
|
|
434
|
+
if (!fallbackMethods.length) {
|
|
435
|
+
fallbackMethods.push("page_reload");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let lastRefreshResult = null;
|
|
439
|
+
for (const method of fallbackMethods) {
|
|
440
|
+
const refreshResult = await applyRefreshMethod(client, method, {
|
|
441
|
+
jobLabel,
|
|
442
|
+
pageScope,
|
|
443
|
+
fallbackPageScope,
|
|
444
|
+
filter,
|
|
445
|
+
targetUrl,
|
|
446
|
+
forceRecentNotView,
|
|
447
|
+
cardTimeoutMs,
|
|
448
|
+
reloadSettleMs
|
|
449
|
+
});
|
|
450
|
+
if (refreshResult.ok) {
|
|
451
|
+
return {
|
|
452
|
+
...refreshResult,
|
|
453
|
+
attempts
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
attempts.push(refreshResult);
|
|
457
|
+
lastRefreshResult = refreshResult;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
...(lastRefreshResult || {
|
|
462
|
+
ok: false,
|
|
463
|
+
method: fallbackMethods[fallbackMethods.length - 1] || "page_reload",
|
|
464
|
+
reason: "refresh_failed",
|
|
465
|
+
error: "Recommend refresh did not run",
|
|
466
|
+
card_count: 0,
|
|
467
|
+
root_state: currentRootState,
|
|
468
|
+
forced_recent_not_view: forceRecentNotView
|
|
469
|
+
}),
|
|
470
|
+
attempts
|
|
471
|
+
};
|
|
472
|
+
}
|