halo-agent 2.4.4 → 2.4.5
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/package.json +1 -1
- package/smartFill.js +63 -25
package/package.json
CHANGED
package/smartFill.js
CHANGED
|
@@ -123,7 +123,7 @@ async function executePlanItem(page, item, fieldByMmid, ctx) {
|
|
|
123
123
|
// it as click_option.
|
|
124
124
|
if (field.role === 'combobox' || field.role === 'listbox') {
|
|
125
125
|
const result = await openAndPickOption(root, locator, item.value, {
|
|
126
|
-
config: ctx.config, jobId: ctx.jobId, label: field.label,
|
|
126
|
+
config: ctx.config, jobId: ctx.jobId, label: field.label, field,
|
|
127
127
|
});
|
|
128
128
|
if (result.ok) return result;
|
|
129
129
|
// Fall through to text-fill if no option matched at all
|
|
@@ -151,7 +151,7 @@ async function executePlanItem(page, item, fieldByMmid, ctx) {
|
|
|
151
151
|
|
|
152
152
|
case 'click_option': {
|
|
153
153
|
return await openAndPickOption(root, locator, item.value, {
|
|
154
|
-
config: ctx.config, jobId: ctx.jobId, label: field.label,
|
|
154
|
+
config: ctx.config, jobId: ctx.jobId, label: field.label, field,
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -636,6 +636,67 @@ async function openAndPickOption(root, triggerLocator, value, llmCtx) {
|
|
|
636
636
|
// need the Page. For in-frame fields, option menus render inside the
|
|
637
637
|
// frame so root.locator finds them.
|
|
638
638
|
const page = (root && typeof root.page === 'function') ? root.page() : root;
|
|
639
|
+
|
|
640
|
+
// ── Async-listbox typeahead (shape-agnostic): try FIRST. ─────────────
|
|
641
|
+
// Any combobox where options are NOT pre-rendered — Ashby Location,
|
|
642
|
+
// Greenhouse Google Places, Workday city pickers, school/company
|
|
643
|
+
// autocompletes — must be typed into BEFORE options can appear. The
|
|
644
|
+
// generic "click and snapshot" path below opens the menu, waits 350ms,
|
|
645
|
+
// and bails on empty — but Ashby's geo fetch alone takes ~700ms.
|
|
646
|
+
//
|
|
647
|
+
// We probe DOM signals on the located element AND on its descendants AND
|
|
648
|
+
// on its closest field-group, because the planner's mmid may resolve to
|
|
649
|
+
// the hidden input, the wrapper div, or the toggle button — and Ashby's
|
|
650
|
+
// wrapper has no role/aria attrs of its own.
|
|
651
|
+
//
|
|
652
|
+
// Triggers:
|
|
653
|
+
// - role=combobox + (aria-haspopup=listbox | aria-autocomplete=list)
|
|
654
|
+
// - aria-autocomplete=list (canonical ARIA for async listbox)
|
|
655
|
+
// - scanAccessibility-tagged field.isTypeahead === true
|
|
656
|
+
// - Ashby's emotion-hashed _input_ class inside a _fieldEntry_ group
|
|
657
|
+
try {
|
|
658
|
+
const fieldIsTypeahead = !!(llmCtx && llmCtx.field && llmCtx.field.isTypeahead);
|
|
659
|
+
const domIsTypeahead = await triggerLocator.evaluate((el) => {
|
|
660
|
+
const probe = (n) => {
|
|
661
|
+
if (!n || n.nodeType !== 1) return false;
|
|
662
|
+
const role = n.getAttribute('role');
|
|
663
|
+
const ac = n.getAttribute('aria-autocomplete');
|
|
664
|
+
const hp = n.getAttribute('aria-haspopup');
|
|
665
|
+
if (ac === 'list' || ac === 'both') return true;
|
|
666
|
+
if (role === 'combobox' && (hp === 'listbox' || hp === 'true')) return true;
|
|
667
|
+
return false;
|
|
668
|
+
};
|
|
669
|
+
// 1. element itself
|
|
670
|
+
if (probe(el)) return true;
|
|
671
|
+
// 2. descendant input/combobox (when locator is wrapper or toggle button)
|
|
672
|
+
const innerSelectors = 'input[role="combobox"], [aria-autocomplete="list"], [aria-autocomplete="both"], [role="combobox"][aria-haspopup="listbox"]';
|
|
673
|
+
const inner = el.querySelector?.(innerSelectors);
|
|
674
|
+
if (probe(inner)) return true;
|
|
675
|
+
// 3. closest field-group, then re-descend (handles ATS-specific wrappers)
|
|
676
|
+
const groupSel = '[class*="_fieldEntry_"], .ashby-application-form-field-entry, .application-question, [data-question-id], [class*="formField"], .field';
|
|
677
|
+
const group = el.closest?.(groupSel);
|
|
678
|
+
if (group) {
|
|
679
|
+
const groupInner = group.querySelector(innerSelectors);
|
|
680
|
+
if (probe(groupInner)) return true;
|
|
681
|
+
}
|
|
682
|
+
// 4. Ashby's emotion-hashed input class as a supplementary signal
|
|
683
|
+
const cls = String(el.className || '');
|
|
684
|
+
if (/_input_/.test(cls) && el.closest?.('[class*="_fieldEntry_"], .ashby-application-form-field-entry')) return true;
|
|
685
|
+
return false;
|
|
686
|
+
}).catch(() => false);
|
|
687
|
+
|
|
688
|
+
if (fieldIsTypeahead || domIsTypeahead) {
|
|
689
|
+
const why = [];
|
|
690
|
+
if (fieldIsTypeahead) why.push('field.isTypeahead');
|
|
691
|
+
if (domIsTypeahead) why.push('dom signals');
|
|
692
|
+
console.log(`[smartFill] openAndPickOption: detected typeahead (${why.join(', ')}) → typeAndPickSuggestion first`);
|
|
693
|
+
const r = await typeAndPickSuggestion(root, triggerLocator, value);
|
|
694
|
+
if (r && r.ok) return r;
|
|
695
|
+
// Fall through to specialized + generic paths on no-match.
|
|
696
|
+
console.log(`[smartFill] typeahead path did not land (${r && r.reason}); falling through to generic dispatch.`);
|
|
697
|
+
}
|
|
698
|
+
} catch {}
|
|
699
|
+
|
|
639
700
|
try {
|
|
640
701
|
const rs = await tryReactSelect(root, triggerLocator, value);
|
|
641
702
|
if (rs !== null) {
|
|
@@ -652,29 +713,6 @@ async function openAndPickOption(root, triggerLocator, value, llmCtx) {
|
|
|
652
713
|
if (itlResult !== null && itlResult.ok) return itlResult;
|
|
653
714
|
} catch {}
|
|
654
715
|
|
|
655
|
-
try {
|
|
656
|
-
// Ashby async typeahead. Ashby's Location field is role="combobox" with
|
|
657
|
-
// aria-haspopup="listbox" and an input class like `_input_v5ami_28`. It
|
|
658
|
-
// renders NO options until you type, then fetches matches over the
|
|
659
|
-
// network (~700ms) and shows them as [role="option"]. The generic
|
|
660
|
-
// open-and-look path opened it, saw an empty menu, and bailed — that's
|
|
661
|
-
// the Gen Digital "dropdown opened but no visible options found" failure.
|
|
662
|
-
// Detect Ashby's widget and route to type-then-wait-for-async-options.
|
|
663
|
-
const isAshbyCombo = await triggerLocator.evaluate((el) => {
|
|
664
|
-
const role = el.getAttribute('role');
|
|
665
|
-
const hasPopup = el.getAttribute('aria-haspopup');
|
|
666
|
-
const cls = String(el.className || '');
|
|
667
|
-
// Ashby's emotion-hashed input class, or the generic combobox+listbox shape.
|
|
668
|
-
const ashbyClass = /_input_/.test(cls) && !!el.closest('[class*="_fieldEntry_"], .ashby-application-form-field-entry');
|
|
669
|
-
return (role === 'combobox' && (hasPopup === 'listbox' || hasPopup === 'true')) || ashbyClass;
|
|
670
|
-
}).catch(() => false);
|
|
671
|
-
if (isAshbyCombo) {
|
|
672
|
-
const r = await typeAndPickSuggestion(root, triggerLocator, value);
|
|
673
|
-
if (r.ok) return r;
|
|
674
|
-
// If typeahead didn't land, fall through to the generic path below.
|
|
675
|
-
}
|
|
676
|
-
} catch {}
|
|
677
|
-
|
|
678
716
|
try {
|
|
679
717
|
// Workday combobox / multi-select. Workday wraps every form widget in a
|
|
680
718
|
// [data-automation-id^="formField-"] container and its dropdowns mount a
|