@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.1

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 (88) hide show
  1. package/README.md +86 -33
  2. package/package.json +62 -9
  3. package/skills/boss-chat/SKILL.md +5 -4
  4. package/skills/boss-recommend-pipeline/SKILL.md +21 -31
  5. package/skills/boss-recruit-pipeline/README.md +17 -0
  6. package/skills/boss-recruit-pipeline/SKILL.md +55 -0
  7. package/src/chat-mcp.js +1333 -0
  8. package/src/chat-runtime-config.js +559 -0
  9. package/src/cli.js +1254 -225
  10. package/src/core/browser/index.js +378 -0
  11. package/src/core/capture/index.js +298 -0
  12. package/src/core/cv-acquisition/index.js +219 -0
  13. package/src/core/greet-quota/index.js +54 -0
  14. package/src/core/infinite-list/index.js +459 -0
  15. package/src/core/reporting/legacy-csv.js +332 -0
  16. package/src/core/run/index.js +286 -0
  17. package/src/core/screening/index.js +1166 -0
  18. package/src/core/self-heal/index.js +848 -0
  19. package/src/domains/chat/cards.js +129 -0
  20. package/src/domains/chat/constants.js +183 -0
  21. package/src/domains/chat/detail.js +1369 -0
  22. package/src/domains/chat/index.js +7 -0
  23. package/src/domains/chat/jobs.js +334 -0
  24. package/src/domains/chat/page-guard.js +88 -0
  25. package/src/domains/chat/roots.js +56 -0
  26. package/src/domains/chat/run-service.js +1101 -0
  27. package/src/domains/recommend/actions.js +457 -0
  28. package/src/domains/recommend/cards.js +228 -0
  29. package/src/domains/recommend/constants.js +141 -0
  30. package/src/domains/recommend/detail.js +341 -0
  31. package/src/domains/recommend/filters.js +581 -0
  32. package/src/domains/recommend/index.js +10 -0
  33. package/src/domains/recommend/jobs.js +232 -0
  34. package/src/domains/recommend/refresh.js +204 -0
  35. package/src/domains/recommend/roots.js +78 -0
  36. package/src/domains/recommend/run-service.js +903 -0
  37. package/src/domains/recommend/scopes.js +245 -0
  38. package/src/domains/recruit/actions.js +277 -0
  39. package/src/domains/recruit/cards.js +66 -0
  40. package/src/domains/recruit/constants.js +130 -0
  41. package/src/domains/recruit/detail.js +414 -0
  42. package/src/domains/recruit/index.js +9 -0
  43. package/src/domains/recruit/instruction-parser.js +451 -0
  44. package/src/domains/recruit/refresh.js +40 -0
  45. package/src/domains/recruit/roots.js +67 -0
  46. package/src/domains/recruit/run-service.js +580 -0
  47. package/src/domains/recruit/search.js +1149 -0
  48. package/src/index.js +578 -419
  49. package/src/recommend-mcp.js +1257 -0
  50. package/src/recruit-mcp.js +1035 -0
  51. package/src/adapters.js +0 -3079
  52. package/src/boss-chat.js +0 -1037
  53. package/src/pipeline.js +0 -2249
  54. package/src/recommend-healing-config.js +0 -131
  55. package/src/recommend-healing-rules.json +0 -261
  56. package/src/self-heal.js +0 -2237
  57. package/src/test-adapters-runtime.js +0 -628
  58. package/src/test-boss-chat.js +0 -3196
  59. package/src/test-index-async.js +0 -498
  60. package/src/test-parser.js +0 -742
  61. package/src/test-pipeline.js +0 -2703
  62. package/src/test-run-state.js +0 -152
  63. package/src/test-self-heal.js +0 -224
  64. package/vendor/boss-chat-cli/README.md +0 -134
  65. package/vendor/boss-chat-cli/package.json +0 -53
  66. package/vendor/boss-chat-cli/src/app.js +0 -1501
  67. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  68. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  69. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  70. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  71. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  72. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  73. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  74. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  75. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  76. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  77. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  78. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  79. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  80. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  81. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  82. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  83. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
  84. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  85. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  86. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
  87. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  88. package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
@@ -0,0 +1,581 @@
1
+ import {
2
+ clickNodeCenter,
3
+ countSelectors,
4
+ findFirstNode,
5
+ getAttributesMap,
6
+ getNodeBox,
7
+ getOuterHTML,
8
+ pressKey,
9
+ querySelectorAll,
10
+ sleep,
11
+ waitForSelector
12
+ } from "../../core/browser/index.js";
13
+ import { htmlToText, normalizeText } from "../../core/screening/index.js";
14
+ import {
15
+ RECOMMEND_CARD_SELECTOR,
16
+ RECOMMEND_FILTER_GROUP_ORDER,
17
+ RECOMMEND_FILTER_SELECTORS
18
+ } from "./constants.js";
19
+
20
+ const SKIP_OPTION_LABELS = new Set(["不限", "全部", "all"]);
21
+
22
+ export function normalizeFilterOptionLabel(label) {
23
+ return normalizeText(label).replace(/\s+/g, "");
24
+ }
25
+
26
+ export function isSafeFilterOptionLabel(label) {
27
+ const normalized = normalizeFilterOptionLabel(label);
28
+ return Boolean(normalized) && !SKIP_OPTION_LABELS.has(normalized.toLowerCase());
29
+ }
30
+
31
+ export function isActiveOption(attributes = {}, outerHTML = "") {
32
+ const className = attributes.class || "";
33
+ return /\bactive\b/.test(className) || /\bactive\b/.test(String(outerHTML || "").split(">")[0] || "");
34
+ }
35
+
36
+ export function chooseFirstSafeFilterOption(options = [], groupOrder = RECOMMEND_FILTER_GROUP_ORDER) {
37
+ for (const group of groupOrder) {
38
+ const option = options.find((item) => (
39
+ item.group === group
40
+ && !item.active
41
+ && isSafeFilterOptionLabel(item.label)
42
+ ));
43
+ if (option) return option;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ export function chooseFilterOptionByLabels(options = [], {
49
+ group = "",
50
+ labels = []
51
+ } = {}) {
52
+ const normalizedGroup = normalizeText(group);
53
+ const normalizedLabels = labels.map(normalizeFilterOptionLabel).filter(Boolean);
54
+ for (const label of normalizedLabels) {
55
+ const option = options.find((item) => (
56
+ (!normalizedGroup || item.group === normalizedGroup)
57
+ && !item.active
58
+ && normalizeFilterOptionLabel(item.label) === label
59
+ && isSafeFilterOptionLabel(item.label)
60
+ ));
61
+ if (option) return option;
62
+ }
63
+ return null;
64
+ }
65
+
66
+ export function chooseFilterOptionsByLabels(options = [], {
67
+ group = "",
68
+ labels = []
69
+ } = {}) {
70
+ const normalizedGroup = normalizeText(group);
71
+ const normalizedLabels = labels.map(normalizeFilterOptionLabel).filter(Boolean);
72
+ return normalizedLabels.map((label) => {
73
+ const option = options.find((item) => (
74
+ (!normalizedGroup || item.group === normalizedGroup)
75
+ && normalizeFilterOptionLabel(item.label) === label
76
+ && isSafeFilterOptionLabel(item.label)
77
+ ));
78
+ return {
79
+ label,
80
+ option: option || null
81
+ };
82
+ });
83
+ }
84
+
85
+ export async function getFilterPanelCount(client, frameNodeId) {
86
+ return (await querySelectorAll(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel)).length;
87
+ }
88
+
89
+ export async function getRecommendFilterCounts(client, frameNodeId) {
90
+ return countSelectors(client, frameNodeId, {
91
+ filter_trigger: RECOMMEND_FILTER_SELECTORS.trigger,
92
+ filter_panel: RECOMMEND_FILTER_SELECTORS.panel,
93
+ check_box: RECOMMEND_FILTER_SELECTORS.checkBox,
94
+ option: `.filter-panel ${RECOMMEND_FILTER_SELECTORS.option}`,
95
+ active_option: RECOMMEND_FILTER_SELECTORS.activeOption,
96
+ recommend_card: RECOMMEND_CARD_SELECTOR
97
+ });
98
+ }
99
+
100
+ export async function findFilterTrigger(client, frameNodeId) {
101
+ return findFirstNode(client, frameNodeId, [
102
+ RECOMMEND_FILTER_SELECTORS.trigger,
103
+ ".recommend-filter.op-filter"
104
+ ]);
105
+ }
106
+
107
+ export async function ensureFilterPanelClosed(client, frameNodeId, triggerNodeId = 0) {
108
+ const attempts = [];
109
+ if (await getFilterPanelCount(client, frameNodeId) === 0) return attempts;
110
+
111
+ await pressKey(client, "Escape", {
112
+ code: "Escape",
113
+ windowsVirtualKeyCode: 27,
114
+ nativeVirtualKeyCode: 27
115
+ });
116
+ await sleep(400);
117
+ attempts.push("Escape");
118
+
119
+ if (await getFilterPanelCount(client, frameNodeId) > 0 && triggerNodeId) {
120
+ await clickNodeCenter(client, triggerNodeId);
121
+ await sleep(500);
122
+ attempts.push("filter-trigger-toggle");
123
+ }
124
+
125
+ return attempts;
126
+ }
127
+
128
+ async function findFilterTriggerCandidates(client, frameNodeId) {
129
+ const candidates = [];
130
+ const seen = new Set();
131
+ for (const selector of [
132
+ RECOMMEND_FILTER_SELECTORS.trigger,
133
+ ".recommend-filter.op-filter"
134
+ ]) {
135
+ const candidate = await findFirstNode(client, frameNodeId, [selector]);
136
+ if (candidate && !seen.has(candidate.nodeId)) {
137
+ candidates.push(candidate);
138
+ seen.add(candidate.nodeId);
139
+ }
140
+ }
141
+ return candidates;
142
+ }
143
+
144
+ export async function openFilterPanel(client, frameNodeId) {
145
+ let triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
146
+ if (!triggerCandidates.length) {
147
+ throw new Error("Recommend filter trigger was not found");
148
+ }
149
+
150
+ const existingPanelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
151
+ timeoutMs: 300,
152
+ intervalMs: 100
153
+ });
154
+ if (existingPanelNodeId) {
155
+ const triggerBox = await getNodeBox(client, triggerCandidates[0].nodeId);
156
+ return {
157
+ trigger: triggerCandidates[0],
158
+ trigger_box: triggerBox,
159
+ panel_node_id: existingPanelNodeId,
160
+ initial_close_attempts: [],
161
+ already_open: true
162
+ };
163
+ }
164
+
165
+ const closeAttempts = await ensureFilterPanelClosed(client, frameNodeId, triggerCandidates[0].nodeId);
166
+
167
+ const attempts = [];
168
+ for (let round = 0; round < 3; round += 1) {
169
+ triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
170
+ for (const trigger of triggerCandidates) {
171
+ const triggerBox = await getNodeBox(client, trigger.nodeId);
172
+ await clickNodeCenter(client, trigger.nodeId);
173
+ attempts.push({
174
+ selector: trigger.selector,
175
+ node_id: trigger.nodeId,
176
+ center: triggerBox.center
177
+ });
178
+ const panelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
179
+ timeoutMs: 2500,
180
+ intervalMs: 200
181
+ });
182
+ if (panelNodeId) {
183
+ return {
184
+ trigger,
185
+ trigger_box: triggerBox,
186
+ panel_node_id: panelNodeId,
187
+ initial_close_attempts: closeAttempts,
188
+ open_attempts: attempts
189
+ };
190
+ }
191
+ }
192
+ await sleep(500);
193
+ }
194
+
195
+ throw new Error(`Recommend filter panel did not open after ${attempts.length} trigger attempts`);
196
+ }
197
+
198
+ async function readOptionNode(client, group, nodeId) {
199
+ const [attributes, outerHTML] = await Promise.all([
200
+ getAttributesMap(client, nodeId),
201
+ getOuterHTML(client, nodeId)
202
+ ]);
203
+ const label = normalizeFilterOptionLabel(htmlToText(outerHTML));
204
+ return {
205
+ group,
206
+ node_id: nodeId,
207
+ label,
208
+ active: isActiveOption(attributes, outerHTML),
209
+ attributes: {
210
+ class: attributes.class || "",
211
+ value: attributes.value || "",
212
+ type: attributes.type || ""
213
+ }
214
+ };
215
+ }
216
+
217
+ export async function listFilterOptions(client, frameNodeId, {
218
+ groupOrder = RECOMMEND_FILTER_GROUP_ORDER
219
+ } = {}) {
220
+ const options = [];
221
+ for (const group of groupOrder) {
222
+ const groupSelector = RECOMMEND_FILTER_SELECTORS.groups[group];
223
+ if (!groupSelector) continue;
224
+ const groupNodeIds = await querySelectorAll(client, frameNodeId, groupSelector);
225
+ for (const groupNodeId of groupNodeIds) {
226
+ const optionNodeIds = await querySelectorAll(client, groupNodeId, RECOMMEND_FILTER_SELECTORS.option);
227
+ for (const optionNodeId of optionNodeIds) {
228
+ options.push(await readOptionNode(client, group, optionNodeId));
229
+ }
230
+ }
231
+ }
232
+ return options;
233
+ }
234
+
235
+ async function clickFirstAvailableNode(client, nodeIds) {
236
+ const errors = [];
237
+ for (const nodeId of nodeIds) {
238
+ try {
239
+ const box = await clickNodeCenter(client, nodeId);
240
+ return {
241
+ clicked: true,
242
+ node_id: nodeId,
243
+ box
244
+ };
245
+ } catch (error) {
246
+ errors.push({
247
+ node_id: nodeId,
248
+ message: error?.message || String(error)
249
+ });
250
+ }
251
+ }
252
+ return {
253
+ clicked: false,
254
+ errors
255
+ };
256
+ }
257
+
258
+ function normalizeButtonLabel(label) {
259
+ return normalizeFilterOptionLabel(label).toLowerCase();
260
+ }
261
+
262
+ function buttonRank(candidate) {
263
+ const label = normalizeButtonLabel(candidate.label);
264
+ if (/确定|确认|完成|ok|confirm/.test(label)) return 0;
265
+ if (/重置|清空|取消|reset|cancel/.test(label)) return 2;
266
+ return 1;
267
+ }
268
+
269
+ async function readButtonCandidate(client, nodeId, index) {
270
+ const [attributes, outerHTML] = await Promise.all([
271
+ getAttributesMap(client, nodeId),
272
+ getOuterHTML(client, nodeId)
273
+ ]);
274
+ return {
275
+ node_id: nodeId,
276
+ index,
277
+ label: normalizeButtonLabel(htmlToText(outerHTML)),
278
+ class_name: attributes.class || ""
279
+ };
280
+ }
281
+
282
+ async function readConfirmButtonCandidates(client, frameNodeId) {
283
+ const nodeIds = await querySelectorAll(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.confirmButton);
284
+ const candidates = [];
285
+ for (let index = 0; index < nodeIds.length; index += 1) {
286
+ candidates.push(await readButtonCandidate(client, nodeIds[index], index));
287
+ }
288
+ return candidates.sort((left, right) => {
289
+ const rankDiff = buttonRank(left) - buttonRank(right);
290
+ if (rankDiff !== 0) return rankDiff;
291
+ return right.index - left.index;
292
+ });
293
+ }
294
+
295
+ export async function selectFirstSafeFilterOption(client, frameNodeId, {
296
+ groupOrder = RECOMMEND_FILTER_GROUP_ORDER
297
+ } = {}) {
298
+ const options = await listFilterOptions(client, frameNodeId, { groupOrder });
299
+ const selected = chooseFirstSafeFilterOption(options, groupOrder);
300
+ if (!selected) {
301
+ throw new Error("No safe non-active recommend filter option was found");
302
+ }
303
+
304
+ const box = await clickNodeCenter(client, selected.node_id, { scrollIntoView: true });
305
+ await sleep(300);
306
+
307
+ return {
308
+ selected_option: {
309
+ group: selected.group,
310
+ label: selected.label,
311
+ node_id: selected.node_id,
312
+ was_active: selected.active
313
+ },
314
+ option_box: box,
315
+ discovered_options: options.map((option) => ({
316
+ group: option.group,
317
+ label: option.label,
318
+ active: option.active,
319
+ node_id: option.node_id
320
+ }))
321
+ };
322
+ }
323
+
324
+ export async function selectFilterOption(client, frameNodeId, {
325
+ group = "",
326
+ labels = [],
327
+ groupOrder = RECOMMEND_FILTER_GROUP_ORDER
328
+ } = {}) {
329
+ const options = await listFilterOptions(client, frameNodeId, { groupOrder });
330
+ const selected = labels.length
331
+ ? chooseFilterOptionByLabels(options, { group, labels })
332
+ : chooseFirstSafeFilterOption(options, groupOrder);
333
+
334
+ if (!selected) {
335
+ const target = labels.length
336
+ ? `${group || "any group"} / ${labels.join(", ")}`
337
+ : "first safe non-active option";
338
+ throw new Error(`No matching recommend filter option was found for ${target}`);
339
+ }
340
+
341
+ const box = await clickNodeCenter(client, selected.node_id, { scrollIntoView: true });
342
+ await sleep(300);
343
+
344
+ return {
345
+ selected_option: {
346
+ group: selected.group,
347
+ label: selected.label,
348
+ node_id: selected.node_id,
349
+ was_active: selected.active,
350
+ requested_group: group || null,
351
+ requested_labels: labels
352
+ },
353
+ option_box: box,
354
+ discovered_options: options.map((option) => ({
355
+ group: option.group,
356
+ label: option.label,
357
+ active: option.active,
358
+ node_id: option.node_id
359
+ }))
360
+ };
361
+ }
362
+
363
+ export async function selectFilterOptions(client, frameNodeId, {
364
+ group = "",
365
+ labels = [],
366
+ groupOrder = RECOMMEND_FILTER_GROUP_ORDER
367
+ } = {}) {
368
+ if (!labels.length) {
369
+ return selectFilterOption(client, frameNodeId, { group, labels, groupOrder });
370
+ }
371
+
372
+ const selectedOptions = [];
373
+ const missingLabels = [];
374
+ let discoveredOptions = [];
375
+
376
+ for (const label of labels) {
377
+ if (await getFilterPanelCount(client, frameNodeId) === 0) {
378
+ await openFilterPanel(client, frameNodeId);
379
+ }
380
+
381
+ const options = await listFilterOptions(client, frameNodeId, { groupOrder });
382
+ discoveredOptions = options.map((option) => ({
383
+ group: option.group,
384
+ label: option.label,
385
+ active: option.active,
386
+ node_id: option.node_id
387
+ }));
388
+ const selected = chooseFilterOptionByLabels(options, { group, labels: [label] });
389
+ const alreadyActive = options.find((option) => (
390
+ (!group || option.group === group)
391
+ && normalizeFilterOptionLabel(option.label) === normalizeFilterOptionLabel(label)
392
+ && option.active
393
+ ));
394
+
395
+ if (alreadyActive) {
396
+ selectedOptions.push({
397
+ group: alreadyActive.group,
398
+ label: alreadyActive.label,
399
+ node_id: alreadyActive.node_id,
400
+ was_active: true,
401
+ clicked: false,
402
+ requested_group: group || null
403
+ });
404
+ continue;
405
+ }
406
+
407
+ if (!selected) {
408
+ missingLabels.push(label);
409
+ continue;
410
+ }
411
+
412
+ const box = await clickNodeCenter(client, selected.node_id, { scrollIntoView: true });
413
+ selectedOptions.push({
414
+ group: selected.group,
415
+ label: selected.label,
416
+ node_id: selected.node_id,
417
+ was_active: false,
418
+ clicked: true,
419
+ requested_group: group || null,
420
+ option_box: box
421
+ });
422
+ await sleep(450);
423
+ }
424
+
425
+ if (missingLabels.length) {
426
+ throw new Error(`No matching recommend filter options were found for ${group || "any group"} / ${missingLabels.join(", ")}`);
427
+ }
428
+
429
+ return {
430
+ selected_option: selectedOptions[0] || null,
431
+ selected_options: selectedOptions.map((option) => ({
432
+ group: option.group,
433
+ label: option.label,
434
+ node_id: option.node_id,
435
+ was_active: option.was_active,
436
+ clicked: option.clicked,
437
+ requested_group: option.requested_group,
438
+ requested_labels: labels
439
+ })),
440
+ option_box: selectedOptions.find((option) => option.option_box)?.option_box || null,
441
+ discovered_options: discoveredOptions
442
+ };
443
+ }
444
+
445
+ export async function selectFilterGroups(client, frameNodeId, {
446
+ filterGroups = [],
447
+ groupOrder = RECOMMEND_FILTER_GROUP_ORDER
448
+ } = {}) {
449
+ const selectedOptions = [];
450
+ const discoveredOptions = [];
451
+ const groups = filterGroups.filter((item) => item && (item.group || item.labels?.length));
452
+ if (!groups.length) {
453
+ return selectFilterOption(client, frameNodeId, { groupOrder });
454
+ }
455
+
456
+ for (const spec of groups) {
457
+ const labels = Array.isArray(spec.labels) ? spec.labels : [];
458
+ const selection = spec.selectAllLabels === false
459
+ ? await selectFilterOption(client, frameNodeId, {
460
+ group: spec.group || "",
461
+ labels,
462
+ groupOrder
463
+ })
464
+ : await selectFilterOptions(client, frameNodeId, {
465
+ group: spec.group || "",
466
+ labels,
467
+ groupOrder
468
+ });
469
+ if (selection.selected_option) selectedOptions.push(selection.selected_option);
470
+ for (const option of selection.selected_options || []) {
471
+ selectedOptions.push(option);
472
+ }
473
+ for (const option of selection.discovered_options || []) {
474
+ discoveredOptions.push(option);
475
+ }
476
+ }
477
+
478
+ const dedupedSelected = [];
479
+ const seenSelected = new Set();
480
+ for (const option of selectedOptions) {
481
+ const key = `${option.group || ""}:${normalizeFilterOptionLabel(option.label || "")}`;
482
+ if (seenSelected.has(key)) continue;
483
+ seenSelected.add(key);
484
+ dedupedSelected.push(option);
485
+ }
486
+
487
+ return {
488
+ selected_option: dedupedSelected[0] || null,
489
+ selected_options: dedupedSelected,
490
+ option_box: dedupedSelected.find((option) => option.option_box)?.option_box || null,
491
+ discovered_options: discoveredOptions
492
+ };
493
+ }
494
+
495
+ export async function confirmFilterPanel(client, frameNodeId, {
496
+ timeoutMs = 8000
497
+ } = {}) {
498
+ const candidates = await readConfirmButtonCandidates(client, frameNodeId);
499
+ if (!candidates.length && await getFilterPanelCount(client, frameNodeId) === 0) {
500
+ return {
501
+ confirmed: true,
502
+ confirm_node_id: null,
503
+ confirm_label: "auto-closed",
504
+ confirm_candidates: [],
505
+ confirm_attempts: [],
506
+ panel_count: 0
507
+ };
508
+ }
509
+ if (!candidates.length) {
510
+ throw new Error("Recommend filter confirm button was not found");
511
+ }
512
+
513
+ const attempts = [];
514
+ for (const candidate of candidates) {
515
+ const clickResult = await clickFirstAvailableNode(client, [candidate.node_id]);
516
+ attempts.push({
517
+ node_id: candidate.node_id,
518
+ label: candidate.label,
519
+ clicked: clickResult.clicked,
520
+ errors: clickResult.errors
521
+ });
522
+ if (!clickResult.clicked) continue;
523
+
524
+ const started = Date.now();
525
+ while (Date.now() - started <= timeoutMs) {
526
+ const panelCount = await getFilterPanelCount(client, frameNodeId);
527
+ if (panelCount === 0) {
528
+ return {
529
+ confirmed: true,
530
+ confirm_node_id: clickResult.node_id,
531
+ confirm_label: candidate.label,
532
+ confirm_box: clickResult.box,
533
+ confirm_candidates: candidates,
534
+ confirm_attempts: attempts,
535
+ panel_count: 0
536
+ };
537
+ }
538
+ await sleep(250);
539
+ }
540
+ }
541
+
542
+ return {
543
+ confirmed: false,
544
+ confirm_node_id: attempts.at(-1)?.node_id || null,
545
+ confirm_label: attempts.at(-1)?.label || null,
546
+ confirm_candidates: candidates,
547
+ confirm_attempts: attempts,
548
+ panel_count: await getFilterPanelCount(client, frameNodeId)
549
+ };
550
+ }
551
+
552
+ export async function selectAndConfirmFirstSafeFilter(client, frameNodeId, options = {}) {
553
+ const beforeCounts = await getRecommendFilterCounts(client, frameNodeId);
554
+ const openResult = await openFilterPanel(client, frameNodeId);
555
+ const afterOpenCounts = await getRecommendFilterCounts(client, frameNodeId);
556
+ const filterGroups = Array.isArray(options.filterGroups) ? options.filterGroups : [];
557
+ const selection = filterGroups.length
558
+ ? await selectFilterGroups(client, frameNodeId, { filterGroups, groupOrder: options.groupOrder })
559
+ : options.selectAllLabels
560
+ ? await selectFilterOptions(client, frameNodeId, options)
561
+ : await selectFilterOption(client, frameNodeId, options);
562
+ const confirm = await confirmFilterPanel(client, frameNodeId);
563
+ await sleep(1200);
564
+ const afterConfirmCounts = await getRecommendFilterCounts(client, frameNodeId);
565
+
566
+ return {
567
+ opened_panel: true,
568
+ trigger: {
569
+ node_id: openResult.trigger.nodeId,
570
+ selector: openResult.trigger.selector,
571
+ center: openResult.trigger_box.center,
572
+ rect: openResult.trigger_box.rect
573
+ },
574
+ initial_close_attempts: openResult.initial_close_attempts,
575
+ before_counts: beforeCounts,
576
+ after_open_counts: afterOpenCounts,
577
+ ...selection,
578
+ ...confirm,
579
+ after_confirm_counts: afterConfirmCounts
580
+ };
581
+ }
@@ -0,0 +1,10 @@
1
+ export * from "./constants.js";
2
+ export * from "./roots.js";
3
+ export * from "./filters.js";
4
+ export * from "./cards.js";
5
+ export * from "./detail.js";
6
+ export * from "./actions.js";
7
+ export * from "./jobs.js";
8
+ export * from "./scopes.js";
9
+ export * from "./refresh.js";
10
+ export * from "./run-service.js";