@reconcrap/boss-recommend-mcp 2.1.22 → 2.1.24

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.
Files changed (66) hide show
  1. package/README.md +5 -0
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/package.json +14 -8
  4. package/scripts/install-macos.sh +280 -280
  5. package/scripts/postinstall.cjs +44 -44
  6. package/skills/boss-chat/README.md +43 -42
  7. package/skills/boss-chat/SKILL.md +107 -106
  8. package/skills/boss-recommend-pipeline/README.md +13 -13
  9. package/skills/boss-recommend-pipeline/SKILL.md +47 -47
  10. package/skills/boss-recruit-pipeline/README.md +19 -19
  11. package/skills/boss-recruit-pipeline/SKILL.md +89 -89
  12. package/src/chat-mcp.js +301 -127
  13. package/src/core/boss-cards/index.js +199 -199
  14. package/src/core/browser/index.js +291 -114
  15. package/src/core/capture/index.js +2930 -1201
  16. package/src/core/cv-acquisition/index.js +512 -238
  17. package/src/core/cv-capture-target/index.js +513 -299
  18. package/src/core/greet-quota/index.js +71 -71
  19. package/src/core/infinite-list/index.js +11 -2
  20. package/src/core/reporting/legacy-csv.js +12 -12
  21. package/src/core/run/detached-launcher.js +305 -0
  22. package/src/core/run/index.js +112 -42
  23. package/src/core/run/timing.js +33 -33
  24. package/src/core/run/windows-detached-worker.ps1 +106 -0
  25. package/src/core/screening/index.js +2135 -2135
  26. package/src/core/self-heal/index.js +989 -973
  27. package/src/core/self-heal/viewport.js +1505 -564
  28. package/src/detached-worker.js +99 -99
  29. package/src/domains/chat/action-journal.js +443 -0
  30. package/src/domains/chat/cards.js +137 -137
  31. package/src/domains/chat/constants.js +9 -9
  32. package/src/domains/chat/detail.js +1684 -401
  33. package/src/domains/chat/index.js +8 -7
  34. package/src/domains/chat/jobs.js +620 -620
  35. package/src/domains/chat/page-guard.js +157 -122
  36. package/src/domains/chat/roots.js +56 -56
  37. package/src/domains/chat/run-service.js +1801 -760
  38. package/src/domains/common/account-rights-panel.js +314 -314
  39. package/src/domains/common/recovery-settle.js +159 -159
  40. package/src/domains/recommend/actions.js +515 -472
  41. package/src/domains/recommend/cards.js +243 -243
  42. package/src/domains/recommend/colleague-contact.js +333 -333
  43. package/src/domains/recommend/constants.js +92 -92
  44. package/src/domains/recommend/detail.js +12 -3
  45. package/src/domains/recommend/filters.js +611 -611
  46. package/src/domains/recommend/index.js +9 -9
  47. package/src/domains/recommend/jobs.js +542 -542
  48. package/src/domains/recommend/location.js +736 -736
  49. package/src/domains/recommend/refresh.js +410 -329
  50. package/src/domains/recommend/roots.js +80 -80
  51. package/src/domains/recommend/run-service.js +1783 -592
  52. package/src/domains/recommend/scopes.js +246 -246
  53. package/src/domains/recruit/actions.js +277 -277
  54. package/src/domains/recruit/cards.js +74 -74
  55. package/src/domains/recruit/constants.js +236 -236
  56. package/src/domains/recruit/detail.js +588 -588
  57. package/src/domains/recruit/index.js +9 -9
  58. package/src/domains/recruit/instruction-parser.js +866 -866
  59. package/src/domains/recruit/refresh.js +45 -45
  60. package/src/domains/recruit/roots.js +68 -68
  61. package/src/domains/recruit/run-service.js +1817 -1620
  62. package/src/domains/recruit/search.js +3229 -3229
  63. package/src/index.js +124 -5
  64. package/src/parser.js +1296 -1296
  65. package/src/recommend-mcp.js +515 -80
  66. package/src/recommend-scheduler.js +66 -0
@@ -11,17 +11,17 @@ import {
11
11
  clickRecommendEndRefreshButton,
12
12
  waitForRecommendCardNodeIds
13
13
  } from "./cards.js";
14
- import {
15
- RECOMMEND_RECENT_NOT_VIEW_LABEL,
16
- RECOMMEND_TARGET_URL
17
- } 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";
14
+ import {
15
+ RECOMMEND_RECENT_NOT_VIEW_LABEL,
16
+ RECOMMEND_TARGET_URL
17
+ } 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
25
  import {
26
26
  getRecommendRoots,
27
27
  waitForRecommendRoots
@@ -31,19 +31,19 @@ import { isStaleRecommendNodeError } from "./detail.js";
31
31
  function normalizeLabels(labels = []) {
32
32
  return labels.map((label) => String(label || "").trim()).filter(Boolean);
33
33
  }
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
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
47
47
  } = {}) {
48
48
  const groups = [];
49
49
  const sourceGroups = Array.isArray(filter.filterGroups)
@@ -68,19 +68,19 @@ export function buildRecommendFilterGroups(filter = {}, {
68
68
  if (!recentGroup.labels.some((label) => label.replace(/\s+/g, "") === RECOMMEND_RECENT_NOT_VIEW_LABEL)) {
69
69
  recentGroup.labels.push(RECOMMEND_RECENT_NOT_VIEW_LABEL);
70
70
  }
71
- recentGroup.selectAllLabels = true;
72
- } else {
73
- groups.unshift({
74
- group: "recentNotView",
75
- labels: [RECOMMEND_RECENT_NOT_VIEW_LABEL],
76
- selectAllLabels: true,
77
- allowUnlimited: false,
78
- verifySticky: false
79
- });
80
- }
81
- }
82
-
83
- return groups;
71
+ recentGroup.selectAllLabels = true;
72
+ } else {
73
+ groups.unshift({
74
+ group: "recentNotView",
75
+ labels: [RECOMMEND_RECENT_NOT_VIEW_LABEL],
76
+ selectAllLabels: true,
77
+ allowUnlimited: false,
78
+ verifySticky: false
79
+ });
80
+ }
81
+ }
82
+
83
+ return groups;
84
84
  }
85
85
 
86
86
  export function buildRecommendFilterSelectionOptions(filter = {}, {
@@ -90,57 +90,133 @@ export function buildRecommendFilterSelectionOptions(filter = {}, {
90
90
  if (filterGroups.length > 1 || forceRecentNotView || Array.isArray(filter.filterGroups) || Array.isArray(filter.groups)) {
91
91
  return { filterGroups };
92
92
  }
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
- };
110
- }
111
-
112
- export async function applyRecommendFilterEnvelopeStages(filter = {}, {
113
- applyCurrentCityOnly,
114
- applyFilterPanel
115
- } = {}) {
116
- if (filter.enabled === false) {
117
- return {
118
- applied: false,
119
- skipped: true,
120
- current_city_only: null,
121
- filter: null
122
- };
123
- }
124
- if (typeof applyCurrentCityOnly !== "function" || typeof applyFilterPanel !== "function") {
125
- throw new Error("Recommend native filter stages require location and filter-panel callbacks");
126
- }
127
- const currentCityOnlyResult = await applyCurrentCityOnly();
128
- const filterResult = await applyFilterPanel();
129
- return {
130
- applied: true,
131
- skipped: false,
132
- current_city_only: currentCityOnlyResult,
133
- filter: filterResult
134
- };
135
- }
136
-
137
- function refreshFailureReason(method = "") {
138
- return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
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
+ };
110
+ }
111
+
112
+ export async function applyRecommendFilterEnvelopeStages(filter = {}, {
113
+ applyCurrentCityOnly,
114
+ applyFilterPanel
115
+ } = {}) {
116
+ if (filter.enabled === false) {
117
+ return {
118
+ applied: false,
119
+ skipped: true,
120
+ current_city_only: null,
121
+ filter: null
122
+ };
123
+ }
124
+ if (typeof applyCurrentCityOnly !== "function" || typeof applyFilterPanel !== "function") {
125
+ throw new Error("Recommend native filter stages require location and filter-panel callbacks");
126
+ }
127
+ const currentCityOnlyResult = await applyCurrentCityOnly();
128
+ const filterResult = await applyFilterPanel();
129
+ return {
130
+ applied: true,
131
+ skipped: false,
132
+ current_city_only: currentCityOnlyResult,
133
+ filter: filterResult
134
+ };
135
+ }
136
+
137
+ function refreshFailureReason(method = "") {
138
+ return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
139
+ }
140
+
141
+ function safeDiagnosticText(value, maxLength = 4000) {
142
+ const text = String(value ?? "");
143
+ return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
144
+ }
145
+
146
+ function safeDiagnosticStack(stack) {
147
+ if (!stack) return null;
148
+ return safeDiagnosticText(stack, 8000)
149
+ .split(/\r?\n/)
150
+ .slice(0, 12)
151
+ .join("\n");
152
+ }
153
+
154
+ function buildErrorDiagnostic(error, {
155
+ depth = 0,
156
+ seen = new Set()
157
+ } = {}) {
158
+ if (!error) return null;
159
+ const isRecord = typeof error === "object" || typeof error === "function";
160
+ if (isRecord && seen.has(error)) {
161
+ return {
162
+ name: error?.name || "Error",
163
+ message: safeDiagnosticText(error?.message || String(error)),
164
+ circular: true
165
+ };
166
+ }
167
+ if (isRecord) seen.add(error);
168
+ const diagnostic = {
169
+ name: error?.name || "Error",
170
+ message: safeDiagnosticText(error?.message || String(error))
171
+ };
172
+ if (error?.code !== undefined && error?.code !== null && error.code !== "") {
173
+ diagnostic.code = typeof error.code === "number"
174
+ ? error.code
175
+ : safeDiagnosticText(error.code, 300);
176
+ }
177
+ if (error?.phase) diagnostic.phase = safeDiagnosticText(error.phase, 300);
178
+ if (error?.cdp_method) diagnostic.cdp_method = safeDiagnosticText(error.cdp_method, 300);
179
+ if (error?.cdp_at) diagnostic.cdp_at = safeDiagnosticText(error.cdp_at, 100);
180
+ if (Number.isInteger(error?.cdp_node_id)) diagnostic.cdp_node_id = error.cdp_node_id;
181
+ if (Number.isInteger(error?.cdp_backend_node_id)) {
182
+ diagnostic.cdp_backend_node_id = error.cdp_backend_node_id;
183
+ }
184
+ if (Number.isInteger(error?.node_id)) diagnostic.node_id = error.node_id;
185
+ if (Number.isInteger(error?.backend_node_id)) diagnostic.backend_node_id = error.backend_node_id;
186
+ if (error?.cdp_search_id) diagnostic.cdp_search_id = safeDiagnosticText(error.cdp_search_id, 300);
187
+ if (Array.isArray(error?.cdp_param_keys)) {
188
+ diagnostic.cdp_param_keys = error.cdp_param_keys
189
+ .slice(0, 20)
190
+ .map((key) => safeDiagnosticText(key, 100));
191
+ }
192
+ const stack = safeDiagnosticStack(error?.stack);
193
+ if (stack) diagnostic.stack = stack;
194
+ if (depth < 2 && error?.cause && error.cause !== error) {
195
+ diagnostic.cause = buildErrorDiagnostic(error.cause, {
196
+ depth: depth + 1,
197
+ seen
198
+ });
199
+ }
200
+ return diagnostic;
201
+ }
202
+
203
+ export function compactRecommendRefreshErrorDiagnostic(error) {
204
+ return buildErrorDiagnostic(error);
139
205
  }
140
206
 
141
207
  export function isRetryableRecommendFilterReapplyError(error) {
142
- const message = String(error?.message || error || "");
143
- 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);
208
+ const messages = [];
209
+ const seen = new Set();
210
+ let current = error;
211
+ for (let depth = 0; current && depth < 4; depth += 1) {
212
+ if ((typeof current === "object" || typeof current === "function") && seen.has(current)) break;
213
+ if (typeof current === "object" || typeof current === "function") seen.add(current);
214
+ if (isStaleRecommendNodeError(current)) return true;
215
+ messages.push(String(current?.message || current || ""));
216
+ current = current?.cause;
217
+ }
218
+ const message = messages.join("\n");
219
+ return /Recommend filter panel did not open|Recommend filter trigger was not found|Recommend filter confirm button was not found|No matching recommend filter option|Invalid (?:backend )?node(?:\s*id)?|Node with given id does not exist|No node found for given backend id/i.test(message);
144
220
  }
145
221
 
146
222
  function compactFilterReapplyError(error) {
@@ -165,6 +241,7 @@ function compactJobSelectionAttempt({
165
241
  method: "job_select",
166
242
  reason: error ? "job_select_failed" : null,
167
243
  error: error ? (error?.message || String(error)) : null,
244
+ error_diagnostic: error ? compactRecommendRefreshErrorDiagnostic(error) : null,
168
245
  attempt,
169
246
  iframe_document_node_id: iframeDocumentNodeId || 0,
170
247
  selected: Boolean(selection?.selected),
@@ -326,6 +403,7 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
326
403
  method: "filter_reapply",
327
404
  reason: "filter_reapply_failed",
328
405
  error: compactFilterReapplyError(error),
406
+ error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
329
407
  attempt
330
408
  });
331
409
  if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
@@ -337,94 +415,95 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
337
415
  }
338
416
 
339
417
  const wrapped = new Error(compactFilterReapplyError(lastError));
340
- wrapped.cause = lastError;
341
- wrapped.filter_reapply_attempts = attempts;
342
- throw wrapped;
343
- }
344
-
345
- function compactCurrentCityOnlyReapplyError(error) {
346
- return error?.message || String(error || "Recommend current-city-only reapply failed");
347
- }
348
-
349
- function isRetryableRecommendCurrentCityOnlyReapplyError(error) {
350
- if (isStaleRecommendNodeError(error)) return true;
351
- const message = compactCurrentCityOnlyReapplyError(error);
352
- return /current-city|current city|location|popover|trigger|checkbox|stale|did not (?:open|mount|close)/i.test(message)
353
- && !/unavailable/i.test(message);
354
- }
355
-
356
- async function ensureRefreshCurrentCityOnly(client, rootState, {
357
- enabled = false,
358
- maxAttempts = 3,
359
- retryDelayMs = 1500
360
- } = {}) {
361
- const attempts = [];
362
- let currentRootState = rootState;
363
- let lastError = null;
364
-
365
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
366
- try {
367
- const result = await ensureRecommendCurrentCityOnly(
368
- client,
369
- currentRootState.iframe.documentNodeId,
370
- { enabled }
371
- );
372
- attempts.push({
373
- ok: true,
374
- method: "current_city_only_reapply",
375
- attempt,
376
- result
377
- });
378
- return {
379
- current_city_only: result,
380
- root_state: currentRootState,
381
- attempts
382
- };
383
- } catch (error) {
384
- lastError = error;
385
- attempts.push({
386
- ok: false,
387
- method: "current_city_only_reapply",
388
- reason: "current_city_only_reapply_failed",
389
- error: compactCurrentCityOnlyReapplyError(error),
390
- attempt
391
- });
392
- if (attempt >= maxAttempts || !isRetryableRecommendCurrentCityOnlyReapplyError(error)) {
393
- break;
394
- }
395
- if (retryDelayMs > 0) await sleep(retryDelayMs);
396
- currentRootState = await getRecommendRoots(client);
397
- }
398
- }
399
-
400
- const wrapped = new Error(compactCurrentCityOnlyReapplyError(lastError));
401
- wrapped.cause = lastError;
402
- wrapped.current_city_only_attempts = attempts;
403
- throw wrapped;
404
- }
405
-
406
- async function applyRefreshMethod(client, method, {
407
- jobLabel = "",
408
- pageScope = "recommend",
409
- fallbackPageScope = "recommend",
410
- filter = {},
418
+ wrapped.cause = lastError;
419
+ wrapped.filter_reapply_attempts = attempts;
420
+ throw wrapped;
421
+ }
422
+
423
+ function compactCurrentCityOnlyReapplyError(error) {
424
+ return error?.message || String(error || "Recommend current-city-only reapply failed");
425
+ }
426
+
427
+ function isRetryableRecommendCurrentCityOnlyReapplyError(error) {
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 = {},
411
490
  targetUrl = RECOMMEND_TARGET_URL,
412
491
  forceRecentNotView = true,
413
492
  cardTimeoutMs = 30000,
414
493
  reloadSettleMs = 8000
415
494
  } = {}) {
416
- const started = Date.now();
417
- let currentRootState = null;
418
- let jobSelection = null;
419
- let jobSelectionAttempts = [];
420
- let pageScopeResult = null;
421
- let currentCityOnlyResult = null;
422
- let currentCityOnlyAttempts = [];
423
- let filterResult = null;
424
- let filterReapplyAttempts = [];
425
- let recoverySettle = null;
426
- try {
427
- if (method === "page_navigate") {
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") {
428
507
  await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
429
508
  } else {
430
509
  await client.Page.reload({ ignoreCache: true });
@@ -467,83 +546,84 @@ async function applyRefreshMethod(client, method, {
467
546
  settleMs: reloadSettleMs > 10000 ? 3000 : 1200,
468
547
  timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
469
548
  }
470
- );
471
- if (!pageScopeResult.selected) {
472
- throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
473
- }
474
- currentRootState = await getRecommendRoots(client);
475
- const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)));
476
- const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
477
- applyCurrentCityOnly: async () => {
478
- const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
479
- enabled: filter.currentCityOnly === true || filter.current_city_only === true,
480
- retryDelayMs
481
- });
482
- currentCityOnlyAttempts = selection.attempts;
483
- currentRootState = await getRecommendRoots(client);
484
- return selection.current_city_only;
485
- },
486
- applyFilterPanel: async () => {
487
- const selection = await selectAndConfirmRefreshFilter(
488
- client,
489
- currentRootState,
490
- buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
491
- { retryDelayMs }
492
- );
493
- filterReapplyAttempts = selection.attempts;
494
- currentRootState = await getRecommendRoots(client);
495
- return selection.filter;
496
- }
497
- });
498
- currentCityOnlyResult = filterStages.current_city_only;
499
- filterResult = filterStages.filter;
500
- const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
501
- timeoutMs: cardTimeoutMs,
502
- intervalMs: 500
503
- });
504
- if (!cardNodeIds.length) {
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;
579
+ const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
580
+ timeoutMs: cardTimeoutMs,
581
+ intervalMs: 500
582
+ });
583
+ if (!cardNodeIds.length) {
505
584
  throw new Error("No recommend candidate cards were found after refresh reload");
506
585
  }
507
586
  return {
508
587
  ok: true,
509
588
  method,
510
- target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
511
- job_selection: jobSelection,
512
- job_selection_attempts: jobSelectionAttempts,
513
- recovery_settle: recoverySettle,
514
- page_scope: pageScopeResult,
515
- current_city_only: currentCityOnlyResult,
516
- current_city_only_attempts: currentCityOnlyAttempts,
517
- filter: filterResult,
518
- filter_reapply_attempts: filterReapplyAttempts,
519
- card_count: cardNodeIds.length,
520
- root_state: currentRootState,
521
- forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
522
- elapsed_ms: Date.now() - started
523
- };
524
- } catch (error) {
525
- return {
526
- ok: false,
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,
596
+ filter: filterResult,
597
+ filter_reapply_attempts: filterReapplyAttempts,
598
+ card_count: cardNodeIds.length,
599
+ 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,
527
606
  method,
528
607
  reason: refreshFailureReason(method),
529
608
  error: error?.message || String(error),
530
- target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
531
- job_selection: jobSelection,
532
- job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
533
- recovery_settle: error?.recovery_settle || recoverySettle,
534
- page_scope: pageScopeResult,
535
- current_city_only: currentCityOnlyResult,
536
- current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
537
- filter: filterResult,
538
- filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
539
- card_count: 0,
540
- root_state: currentRootState,
541
- forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
542
- elapsed_ms: Date.now() - started
543
- };
544
- }
545
- }
546
-
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
+
547
627
  export async function refreshRecommendListAtEnd(client, {
548
628
  rootState = null,
549
629
  jobLabel = "",
@@ -565,91 +645,92 @@ export async function refreshRecommendListAtEnd(client, {
565
645
  currentRootState = currentRootState || await getRecommendRoots(client);
566
646
  const buttonResult = await clickRecommendEndRefreshButton(
567
647
  client,
568
- currentRootState.iframe.documentNodeId,
569
- { settleMs: buttonSettleMs }
570
- );
571
- attempts.push(buttonResult);
572
- if (buttonResult.ok) {
573
- let pageScopeResult = null;
574
- let currentCityOnlyResult = null;
575
- let currentCityOnlyAttempts = [];
576
- let filterResult = null;
577
- let filterReapplyAttempts = [];
578
- try {
579
- currentRootState = await getRecommendRoots(client);
580
- pageScopeResult = await selectRecommendPageScope(
581
- client,
582
- currentRootState.iframe.documentNodeId,
583
- {
584
- pageScope,
585
- fallbackScope: fallbackPageScope,
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,
586
666
  settleMs: buttonSettleMs > 10000 ? 3000 : 1200,
587
667
  timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
588
668
  }
589
- );
590
- if (!pageScopeResult.selected) {
591
- throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
592
- }
593
- currentRootState = await getRecommendRoots(client);
594
- const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)));
595
- const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
596
- applyCurrentCityOnly: async () => {
597
- const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
598
- enabled: filter.currentCityOnly === true || filter.current_city_only === true,
599
- retryDelayMs
600
- });
601
- currentCityOnlyAttempts = selection.attempts;
602
- currentRootState = await getRecommendRoots(client);
603
- return selection.current_city_only;
604
- },
605
- applyFilterPanel: async () => {
606
- const selection = await selectAndConfirmRefreshFilter(
607
- client,
608
- currentRootState,
609
- buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
610
- { retryDelayMs }
611
- );
612
- filterReapplyAttempts = selection.attempts;
613
- currentRootState = await getRecommendRoots(client);
614
- return selection.filter;
615
- }
616
- });
617
- currentCityOnlyResult = filterStages.current_city_only;
618
- filterResult = filterStages.filter;
619
- const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
620
- timeoutMs: cardTimeoutMs,
621
- intervalMs: 500
622
- });
623
- return {
624
- ok: cardNodeIds.length > 0,
625
- method: "end_refresh_button",
626
- attempts,
627
- page_scope: pageScopeResult,
628
- current_city_only: currentCityOnlyResult,
629
- current_city_only_attempts: currentCityOnlyAttempts,
630
- filter: filterResult,
631
- filter_reapply_attempts: filterReapplyAttempts,
632
- card_count: cardNodeIds.length,
633
- root_state: currentRootState,
634
- forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
635
- };
636
- } catch (error) {
637
- attempts.push({
638
- ok: false,
639
- method: "end_refresh_button_after_click",
640
- reason: "end_refresh_reapply_failed",
641
- error: error?.message || String(error),
642
- page_scope: pageScopeResult,
643
- current_city_only: currentCityOnlyResult,
644
- current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
645
- filter: filterResult,
646
- filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
647
- forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
648
- });
649
- }
650
- }
651
- }
652
-
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;
699
+ const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
700
+ timeoutMs: cardTimeoutMs,
701
+ intervalMs: 500
702
+ });
703
+ return {
704
+ ok: cardNodeIds.length > 0,
705
+ method: "end_refresh_button",
706
+ attempts,
707
+ page_scope: pageScopeResult,
708
+ current_city_only: currentCityOnlyResult,
709
+ current_city_only_attempts: currentCityOnlyAttempts,
710
+ filter: filterResult,
711
+ filter_reapply_attempts: filterReapplyAttempts,
712
+ card_count: cardNodeIds.length,
713
+ root_state: currentRootState,
714
+ forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
715
+ };
716
+ } catch (error) {
717
+ attempts.push({
718
+ ok: false,
719
+ method: "end_refresh_button_after_click",
720
+ reason: "end_refresh_reapply_failed",
721
+ error: error?.message || String(error),
722
+ error_diagnostic: compactRecommendRefreshErrorDiagnostic(error),
723
+ page_scope: pageScopeResult,
724
+ current_city_only: currentCityOnlyResult,
725
+ current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
726
+ filter: filterResult,
727
+ filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
728
+ forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
729
+ });
730
+ }
731
+ }
732
+ }
733
+
653
734
  const fallbackMethods = [];
654
735
  if (forceNavigate && typeof client?.Page?.navigate === "function") {
655
736
  fallbackMethods.push("page_navigate");
@@ -686,13 +767,13 @@ export async function refreshRecommendListAtEnd(client, {
686
767
  return {
687
768
  ...(lastRefreshResult || {
688
769
  ok: false,
689
- method: fallbackMethods[fallbackMethods.length - 1] || "page_reload",
690
- reason: "refresh_failed",
691
- error: "Recommend refresh did not run",
692
- card_count: 0,
693
- root_state: currentRootState,
694
- forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
695
- }),
696
- attempts
697
- };
698
- }
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
+ }