@cutleryapp/agent 1.0.36 → 1.0.37
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/dist/mcp-executor.js +50 -35
- package/package.json +1 -1
package/dist/mcp-executor.js
CHANGED
|
@@ -765,9 +765,7 @@ async function tryAIClick(page, selector) {
|
|
|
765
765
|
}
|
|
766
766
|
/** Select an option from a React-select / autocomplete / combobox / dropdown */
|
|
767
767
|
async function tryAutocomplete(page, fieldLabel, value) {
|
|
768
|
-
|
|
769
|
-
const labelRe = new RegExp(esc(fieldLabel), 'i');
|
|
770
|
-
// Click an option from whatever dropdown is currently open on the page
|
|
768
|
+
// Click the matching option in whatever dropdown is currently open — returns true only on success
|
|
771
769
|
async function clickOpenOption() {
|
|
772
770
|
const optionSelectors = [
|
|
773
771
|
`[role="option"]:has-text("${value}")`,
|
|
@@ -789,38 +787,57 @@ async function tryAutocomplete(page, fieldLabel, value) {
|
|
|
789
787
|
}
|
|
790
788
|
return false;
|
|
791
789
|
}
|
|
792
|
-
// Strategy 1: React-select —
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
790
|
+
// Strategy 1: React-select — find the control that BELONGS to this label via DOM traversal
|
|
791
|
+
// so we don't accidentally open the wrong dropdown on a page with multiple selects
|
|
792
|
+
try {
|
|
793
|
+
const opened = await page.evaluate((lbl) => {
|
|
794
|
+
const allEls = Array.from(document.querySelectorAll('label, legend, [class*="label"]'));
|
|
795
|
+
const labelEl = allEls.find(el => (el.textContent || '').trim().toLowerCase().includes(lbl.toLowerCase()));
|
|
796
|
+
if (!labelEl)
|
|
797
|
+
return false;
|
|
798
|
+
// Walk up ancestors looking for a container that holds a react-select control
|
|
799
|
+
let ancestor = labelEl.parentElement;
|
|
800
|
+
for (let i = 0; i < 6 && ancestor; i++) {
|
|
801
|
+
const ctrl = ancestor.querySelector('[class*="react-select__control"],[class*="select__control"],[class*="Select__control"]');
|
|
802
|
+
if (ctrl) {
|
|
803
|
+
ctrl.click();
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
ancestor = ancestor.parentElement;
|
|
807
|
+
}
|
|
808
|
+
// Try label[for] → wrapper containing a react-select
|
|
809
|
+
if (labelEl.tagName === 'LABEL') {
|
|
810
|
+
const forId = labelEl.htmlFor;
|
|
811
|
+
const wrapper = forId ? document.getElementById(forId) : null;
|
|
812
|
+
const ctrl = wrapper?.querySelector('[class*="control"]');
|
|
813
|
+
if (ctrl) {
|
|
814
|
+
ctrl.click();
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return false;
|
|
819
|
+
}, fieldLabel);
|
|
820
|
+
if (opened) {
|
|
821
|
+
await page.waitForTimeout(300);
|
|
822
|
+
// Type into the now-visible input inside the react-select
|
|
823
|
+
const innerInput = page.locator('[class*="react-select__input"] input,[class*="select__input"] input').first();
|
|
824
|
+
try {
|
|
825
|
+
await innerInput.waitFor({ state: 'visible', timeout: 800 });
|
|
826
|
+
await innerInput.type(value, { delay: 60 });
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
await page.keyboard.type(value, { delay: 60 });
|
|
830
|
+
}
|
|
831
|
+
await page.waitForTimeout(600);
|
|
814
832
|
if (await clickOpenOption())
|
|
815
833
|
return true;
|
|
816
|
-
//
|
|
817
|
-
await page.keyboard.press('Enter');
|
|
818
|
-
await page.waitForTimeout(200);
|
|
819
|
-
return true;
|
|
834
|
+
// No confirmed click → don't claim success, fall through to next strategy
|
|
820
835
|
}
|
|
821
|
-
catch { /* try next */ }
|
|
822
836
|
}
|
|
823
|
-
|
|
837
|
+
catch { /* DOM eval failed, try next */ }
|
|
838
|
+
// Strategy 2: combobox / text input by label or placeholder — type + pick option
|
|
839
|
+
const esc = (s) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
840
|
+
const labelRe = new RegExp(esc(fieldLabel), 'i');
|
|
824
841
|
const inputLocators = [
|
|
825
842
|
page.getByRole('combobox', { name: labelRe }),
|
|
826
843
|
page.getByLabel(labelRe),
|
|
@@ -829,8 +846,8 @@ async function tryAutocomplete(page, fieldLabel, value) {
|
|
|
829
846
|
for (const loc of inputLocators) {
|
|
830
847
|
try {
|
|
831
848
|
const input = loc.first();
|
|
849
|
+
// Skip wrapper divs — getByLabel can return the React-select container div
|
|
832
850
|
const tag = await input.evaluate((el) => el.tagName.toLowerCase()).catch(() => '');
|
|
833
|
-
// Skip non-input elements (e.g. wrapper divs returned by getByLabel for React-select)
|
|
834
851
|
if (tag && !['input', 'textarea'].includes(tag))
|
|
835
852
|
continue;
|
|
836
853
|
await input.waitFor({ state: 'visible', timeout: 1500 });
|
|
@@ -840,9 +857,7 @@ async function tryAutocomplete(page, fieldLabel, value) {
|
|
|
840
857
|
await page.waitForTimeout(500);
|
|
841
858
|
if (await clickOpenOption())
|
|
842
859
|
return true;
|
|
843
|
-
|
|
844
|
-
await page.waitForTimeout(200);
|
|
845
|
-
return true;
|
|
860
|
+
// Only count success when the option was actually clicked
|
|
846
861
|
}
|
|
847
862
|
catch { /* try next */ }
|
|
848
863
|
}
|