@reconcrap/boss-recommend-mcp 2.0.38 → 2.0.40
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 +0 -0
- package/config/screening-config.example.json +1 -1
- package/package.json +119 -119
- package/src/core/run/index.js +310 -310
- package/src/domains/recommend/detail.js +612 -612
- package/src/domains/recommend/jobs.js +26 -4
- package/src/domains/recommend/refresh.js +81 -7
- package/src/domains/recommend/run-service.js +1421 -1414
- package/src/recommend-mcp.js +1719 -1719
- package/src/run-state.js +358 -358
|
@@ -92,10 +92,28 @@ export async function findRecommendJobTrigger(client, frameNodeId) {
|
|
|
92
92
|
return null;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
export async function waitForRecommendJobTrigger(client, frameNodeId, {
|
|
96
|
+
timeoutMs = 8000,
|
|
97
|
+
intervalMs = 250
|
|
98
|
+
} = {}) {
|
|
99
|
+
const started = Date.now();
|
|
100
|
+
while (Date.now() - started <= timeoutMs) {
|
|
101
|
+
const trigger = await findRecommendJobTrigger(client, frameNodeId);
|
|
102
|
+
if (trigger) return trigger;
|
|
103
|
+
await sleep(intervalMs);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
95
108
|
export async function openRecommendJobDropdown(client, frameNodeId, {
|
|
96
|
-
timeoutMs = 4000
|
|
109
|
+
timeoutMs = 4000,
|
|
110
|
+
triggerTimeoutMs = Math.max(8000, timeoutMs),
|
|
111
|
+
triggerIntervalMs = 250
|
|
97
112
|
} = {}) {
|
|
98
|
-
const trigger = await
|
|
113
|
+
const trigger = await waitForRecommendJobTrigger(client, frameNodeId, {
|
|
114
|
+
timeoutMs: triggerTimeoutMs,
|
|
115
|
+
intervalMs: triggerIntervalMs
|
|
116
|
+
});
|
|
99
117
|
if (!trigger) {
|
|
100
118
|
throw new Error("Recommend job trigger was not found");
|
|
101
119
|
}
|
|
@@ -166,7 +184,8 @@ export async function closeRecommendJobDropdown(client) {
|
|
|
166
184
|
|
|
167
185
|
export async function selectRecommendJob(client, frameNodeId, {
|
|
168
186
|
jobLabel = "",
|
|
169
|
-
settleMs = 6000
|
|
187
|
+
settleMs = 6000,
|
|
188
|
+
dropdownTimeoutMs = Math.max(8000, settleMs)
|
|
170
189
|
} = {}) {
|
|
171
190
|
const target = normalizeText(jobLabel);
|
|
172
191
|
if (!target) {
|
|
@@ -178,7 +197,10 @@ export async function selectRecommendJob(client, frameNodeId, {
|
|
|
178
197
|
};
|
|
179
198
|
}
|
|
180
199
|
|
|
181
|
-
const opened = await openRecommendJobDropdown(client, frameNodeId
|
|
200
|
+
const opened = await openRecommendJobDropdown(client, frameNodeId, {
|
|
201
|
+
timeoutMs: dropdownTimeoutMs,
|
|
202
|
+
triggerTimeoutMs: dropdownTimeoutMs
|
|
203
|
+
});
|
|
182
204
|
const options = opened.options.length
|
|
183
205
|
? opened.options
|
|
184
206
|
: await listRecommendJobOptions(client, frameNodeId, { openDropdown: false });
|
|
@@ -92,6 +92,63 @@ function refreshFailureReason(method = "") {
|
|
|
92
92
|
return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
|
|
93
93
|
}
|
|
94
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
|
+
async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
105
|
+
maxAttempts = 3,
|
|
106
|
+
retryDelayMs = 1500
|
|
107
|
+
} = {}) {
|
|
108
|
+
const attempts = [];
|
|
109
|
+
let currentRootState = rootState;
|
|
110
|
+
let lastError = null;
|
|
111
|
+
|
|
112
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
113
|
+
try {
|
|
114
|
+
const filter = await selectAndConfirmFirstSafeFilter(
|
|
115
|
+
client,
|
|
116
|
+
currentRootState.iframe.documentNodeId,
|
|
117
|
+
filterOptions
|
|
118
|
+
);
|
|
119
|
+
attempts.push({
|
|
120
|
+
ok: true,
|
|
121
|
+
method: "filter_reapply",
|
|
122
|
+
attempt
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
filter,
|
|
126
|
+
root_state: currentRootState,
|
|
127
|
+
attempts
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
lastError = error;
|
|
131
|
+
attempts.push({
|
|
132
|
+
ok: false,
|
|
133
|
+
method: "filter_reapply",
|
|
134
|
+
reason: "filter_reapply_failed",
|
|
135
|
+
error: compactFilterReapplyError(error),
|
|
136
|
+
attempt
|
|
137
|
+
});
|
|
138
|
+
if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
142
|
+
currentRootState = await getRecommendRoots(client);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const wrapped = new Error(compactFilterReapplyError(lastError));
|
|
147
|
+
wrapped.cause = lastError;
|
|
148
|
+
wrapped.filter_reapply_attempts = attempts;
|
|
149
|
+
throw wrapped;
|
|
150
|
+
}
|
|
151
|
+
|
|
95
152
|
async function applyRefreshMethod(client, method, {
|
|
96
153
|
jobLabel = "",
|
|
97
154
|
pageScope = "recommend",
|
|
@@ -107,6 +164,7 @@ async function applyRefreshMethod(client, method, {
|
|
|
107
164
|
let jobSelection = null;
|
|
108
165
|
let pageScopeResult = null;
|
|
109
166
|
let filterResult = null;
|
|
167
|
+
let filterReapplyAttempts = [];
|
|
110
168
|
try {
|
|
111
169
|
if (method === "page_navigate") {
|
|
112
170
|
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
@@ -122,9 +180,11 @@ async function applyRefreshMethod(client, method, {
|
|
|
122
180
|
throw new Error("Recommend iframe was not ready after refresh reload");
|
|
123
181
|
}
|
|
124
182
|
if (jobLabel) {
|
|
183
|
+
const jobDropdownTimeoutMs = reloadSettleMs > 10000 ? 15000 : 12000;
|
|
125
184
|
jobSelection = await selectRecommendJob(client, currentRootState.iframe.documentNodeId, {
|
|
126
185
|
jobLabel,
|
|
127
|
-
settleMs: reloadSettleMs > 10000 ? 12000 : 6000
|
|
186
|
+
settleMs: reloadSettleMs > 10000 ? 12000 : 6000,
|
|
187
|
+
dropdownTimeoutMs: jobDropdownTimeoutMs
|
|
128
188
|
});
|
|
129
189
|
if (!jobSelection.selected) {
|
|
130
190
|
throw new Error(`Requested recommend job was not selected after refresh reload: ${jobSelection.reason}`);
|
|
@@ -145,11 +205,17 @@ async function applyRefreshMethod(client, method, {
|
|
|
145
205
|
throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
|
|
146
206
|
}
|
|
147
207
|
currentRootState = await getRecommendRoots(client);
|
|
148
|
-
|
|
208
|
+
const filterSelection = await selectAndConfirmRefreshFilter(
|
|
149
209
|
client,
|
|
150
|
-
currentRootState
|
|
151
|
-
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView })
|
|
210
|
+
currentRootState,
|
|
211
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
212
|
+
{
|
|
213
|
+
retryDelayMs: Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)))
|
|
214
|
+
}
|
|
152
215
|
);
|
|
216
|
+
filterResult = filterSelection.filter;
|
|
217
|
+
filterReapplyAttempts = filterSelection.attempts;
|
|
218
|
+
currentRootState = await getRecommendRoots(client);
|
|
153
219
|
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
154
220
|
timeoutMs: cardTimeoutMs,
|
|
155
221
|
intervalMs: 500
|
|
@@ -164,6 +230,7 @@ async function applyRefreshMethod(client, method, {
|
|
|
164
230
|
job_selection: jobSelection,
|
|
165
231
|
page_scope: pageScopeResult,
|
|
166
232
|
filter: filterResult,
|
|
233
|
+
filter_reapply_attempts: filterReapplyAttempts,
|
|
167
234
|
card_count: cardNodeIds.length,
|
|
168
235
|
root_state: currentRootState,
|
|
169
236
|
forced_recent_not_view: forceRecentNotView,
|
|
@@ -179,6 +246,7 @@ async function applyRefreshMethod(client, method, {
|
|
|
179
246
|
job_selection: jobSelection,
|
|
180
247
|
page_scope: pageScopeResult,
|
|
181
248
|
filter: filterResult,
|
|
249
|
+
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
182
250
|
card_count: 0,
|
|
183
251
|
root_state: currentRootState,
|
|
184
252
|
forced_recent_not_view: forceRecentNotView,
|
|
@@ -229,11 +297,16 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
229
297
|
throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
|
|
230
298
|
}
|
|
231
299
|
currentRootState = await getRecommendRoots(client);
|
|
232
|
-
const
|
|
300
|
+
const filterSelection = await selectAndConfirmRefreshFilter(
|
|
233
301
|
client,
|
|
234
|
-
currentRootState
|
|
235
|
-
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView })
|
|
302
|
+
currentRootState,
|
|
303
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
304
|
+
{
|
|
305
|
+
retryDelayMs: Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)))
|
|
306
|
+
}
|
|
236
307
|
);
|
|
308
|
+
const filterResult = filterSelection.filter;
|
|
309
|
+
currentRootState = await getRecommendRoots(client);
|
|
237
310
|
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
238
311
|
timeoutMs: cardTimeoutMs,
|
|
239
312
|
intervalMs: 500
|
|
@@ -244,6 +317,7 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
244
317
|
attempts,
|
|
245
318
|
page_scope: pageScopeResult,
|
|
246
319
|
filter: filterResult,
|
|
320
|
+
filter_reapply_attempts: filterSelection.attempts,
|
|
247
321
|
card_count: cardNodeIds.length,
|
|
248
322
|
root_state: currentRootState,
|
|
249
323
|
forced_recent_not_view: forceRecentNotView
|