halo-agent 2.0.5 → 2.0.6

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.
@@ -143,14 +143,37 @@ async function detectFormErrors(page) {
143
143
  if (type === 'file') return !el.files || el.files.length === 0;
144
144
  // contenteditable: empty when innerText is whitespace.
145
145
  if (el.isContentEditable) return !(el.innerText || '').trim();
146
- // Combobox role with no inner input (custom dropdown trigger): look
147
- // for the displayed value via aria-activedescendant or innerText.
148
- if (el.getAttribute('role') === 'combobox') {
146
+ // Combobox / custom-select: React-Select (which Greenhouse uses for
147
+ // ALL its dropdowns) renders the selected value as a
148
+ // .select__single-value or .select__multi-value chip INSIDE the
149
+ // control wrapper, NOT as the input's .value. We must look there or
150
+ // we falsely flag every filled dropdown as empty (the bug that made
151
+ // the retry re-pick Gender + Ethnicity).
152
+ if (el.getAttribute('role') === 'combobox' || tag === 'select') {
153
+ // Walk up to the React-Select control wrapper (a few class variants).
154
+ const wrap = el.closest(
155
+ '[class*="select__control"], [class*="select-control"], ' +
156
+ '[class*="select__value-container"], [class*="select"], ' +
157
+ '[class*="combobox"], [class*="Select"]'
158
+ ) || el.parentElement;
159
+ if (wrap) {
160
+ // Any rendered value chip → NOT empty.
161
+ const valueEl = wrap.querySelector(
162
+ '[class*="single-value"], [class*="multi-value"], ' +
163
+ '[class*="multiValue"], [class*="singleValue"], ' +
164
+ '[class*="chip"], [class*="tag"], [class*="__value"]'
165
+ );
166
+ if (valueEl && (valueEl.innerText || valueEl.textContent || '').trim()) return false;
167
+ // Also: the control's own text, minus the placeholder. If the
168
+ // control shows "Male" it's filled; if it shows "Select..." it's empty.
169
+ const wrapText = (wrap.innerText || '').trim();
170
+ const placeholderEl = wrap.querySelector('[class*="placeholder"]');
171
+ const placeholderText = placeholderEl ? (placeholderEl.innerText || '').trim() : '';
172
+ if (wrapText && wrapText !== placeholderText && !/^select\b|^choose\b|^\.\.\./i.test(wrapText)) {
173
+ return false;
174
+ }
175
+ }
149
176
  const v = (el.value || el.innerText || '').trim();
150
- // Empty AND no chip/pill rendered as a sibling means truly empty.
151
- const wrap = el.closest('[class*="select"], [class*="combobox"]');
152
- const chip = wrap?.querySelector('[class*="chip"], [class*="multi-value"], [class*="tag"]');
153
- if (chip && (chip.innerText || '').trim()) return false;
154
177
  return !v;
155
178
  }
156
179
  // Plain inputs / textareas
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halo-agent",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "HALO local apply agent — auto-fills job applications using your real Chrome session",
5
5
  "main": "index.js",
6
6
  "bin": {
package/smartFill.js CHANGED
@@ -362,6 +362,21 @@ async function openAndPickOption(page, triggerLocator, value, llmCtx) {
362
362
  }
363
363
 
364
364
  if (candidates.length === 0) {
365
+ // DIAGNOSTIC: dump the field's actual DOM so we can see what widget
366
+ // this really is (intl-tel? react-select? custom?). One-line summary
367
+ // of the element + its 2 nearest ancestors' classes + any sibling
368
+ // input/button. This is how we stop guessing selectors.
369
+ const diag = await triggerLocator.evaluate((el) => {
370
+ const summ = (n) => n ? `<${n.tagName.toLowerCase()} class="${(n.className || '').toString().slice(0, 120)}" role="${n.getAttribute?.('role') || ''}" aria-haspopup="${n.getAttribute?.('aria-haspopup') || ''}">` : 'null';
371
+ const p1 = el.parentElement;
372
+ const p2 = p1?.parentElement;
373
+ const siblingInputs = Array.from((p2 || p1 || el).querySelectorAll('input, button, [role="combobox"], [role="button"]'))
374
+ .slice(0, 5)
375
+ .map((s) => `${s.tagName.toLowerCase()}.${(s.className || '').toString().split(' ')[0]}`)
376
+ .join(', ');
377
+ return `self=${summ(el)} | p1=${summ(p1)} | p2=${summ(p2)} | nearby=[${siblingInputs}]`;
378
+ }).catch(() => 'diag failed');
379
+ console.warn(`[smartFill] DROPDOWN-DIAG: ${diag}`);
365
380
  await page.keyboard.press('Escape').catch(() => {});
366
381
  return { ok: false, reason: 'dropdown opened but no visible options found' };
367
382
  }