@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.
@@ -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 findRecommendJobTrigger(client, frameNodeId);
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
- filterResult = await selectAndConfirmFirstSafeFilter(
208
+ const filterSelection = await selectAndConfirmRefreshFilter(
149
209
  client,
150
- currentRootState.iframe.documentNodeId,
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 filterResult = await selectAndConfirmFirstSafeFilter(
300
+ const filterSelection = await selectAndConfirmRefreshFilter(
233
301
  client,
234
- currentRootState.iframe.documentNodeId,
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