halo-agent 2.3.0 → 2.4.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.
- package/orchestrator.js +59 -0
- package/package.json +1 -1
package/orchestrator.js
CHANGED
|
@@ -54,6 +54,8 @@ async function fillFields(page, aep, opts) {
|
|
|
54
54
|
const { detectCaptcha, solveCaptcha, injectCaptchaToken } = require('./captcha');
|
|
55
55
|
const { visionFill, visionNavigateAndSubmit, visionFillSkipped } = require('./vision');
|
|
56
56
|
const { scanWorkday } = require('./scanWorkday');
|
|
57
|
+
const { scanAccessibility } = require('./scanAccessibility');
|
|
58
|
+
const { findApplyPath } = require('./findApplyPath');
|
|
57
59
|
|
|
58
60
|
// ATS types that need vision fallback due to shadow DOM / canvas.
|
|
59
61
|
// 'workday' was here historically because the AX-tree scan returned no usable
|
|
@@ -164,6 +166,63 @@ async function runJob(queueItem, chromeConn, config, reportStatus) {
|
|
|
164
166
|
// May return a new page object if Apply opened a new tab.
|
|
165
167
|
page = await clickApplyIfNeeded(page);
|
|
166
168
|
|
|
169
|
+
// STEP 1.5: Find the apply path if the page has no actual FORM fields.
|
|
170
|
+
//
|
|
171
|
+
// clickApplyIfNeeded only handles obvious cases (a literal "Apply" button
|
|
172
|
+
// outside the form). It misses:
|
|
173
|
+
// - tabs (Overview / Application on Mercor's Ashby-wrapped careers page)
|
|
174
|
+
// - "Continue to application" CTAs below the fold
|
|
175
|
+
// - non-semantic styled divs that act as the entry point
|
|
176
|
+
// - marketing-shell sites (like Artie's careers page) where scanAccessibility
|
|
177
|
+
// returns 29 nav links + 2 decorative buttons but ZERO form inputs
|
|
178
|
+
//
|
|
179
|
+
// The earlier version checked `earlyScan.length === 0` which let pages
|
|
180
|
+
// like Artie through the AX scanner happily reported "29 fields" but
|
|
181
|
+
// every single one was a Pricing/Blog/Login navlink. Now we count only
|
|
182
|
+
// roles that are actually fillable: textbox, combobox, checkbox, radio,
|
|
183
|
+
// radiogroup, listbox, switch, file. Buttons and links don't count.
|
|
184
|
+
//
|
|
185
|
+
// findApplyPath checks the cache, then probes the AX tree for apply-keyword
|
|
186
|
+
// clickables, then falls back to vision. Bounded to 2 click hops.
|
|
187
|
+
const FILLABLE_ROLES = new Set([
|
|
188
|
+
'textbox', 'combobox', 'checkbox', 'radio', 'radiogroup',
|
|
189
|
+
'listbox', 'switch', 'spinbutton', 'searchbox', 'slider',
|
|
190
|
+
]);
|
|
191
|
+
function countFormFields(scan) {
|
|
192
|
+
if (!Array.isArray(scan)) return 0;
|
|
193
|
+
let n = 0;
|
|
194
|
+
for (const f of scan) {
|
|
195
|
+
const role = (f && (f.role || f.type)) || '';
|
|
196
|
+
const tag = (f && f.tag) || '';
|
|
197
|
+
const inputType = (f && f.inputType) || '';
|
|
198
|
+
if (FILLABLE_ROLES.has(role)) { n++; continue; }
|
|
199
|
+
if (tag === 'textarea' || tag === 'select') { n++; continue; }
|
|
200
|
+
if (tag === 'input' && inputType && inputType !== 'button' && inputType !== 'submit' && inputType !== 'hidden') { n++; continue; }
|
|
201
|
+
if (inputType === 'file' || role === 'file') { n++; continue; }
|
|
202
|
+
}
|
|
203
|
+
return n;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const earlyScan = await scanAccessibility(page);
|
|
208
|
+
const formFieldCount = countFormFields(earlyScan);
|
|
209
|
+
const totalNodes = Array.isArray(earlyScan) ? earlyScan.length : 0;
|
|
210
|
+
if (formFieldCount === 0) {
|
|
211
|
+
console.log(`[orchestrator] No FORM fields detected (${totalNodes} AX nodes, 0 fillable) invoking findApplyPath`);
|
|
212
|
+
const result = await findApplyPath(page, { config, anthropicKey });
|
|
213
|
+
if (result.found) {
|
|
214
|
+
console.log(`[orchestrator] findApplyPath: ${result.method} succeeded (${result.fields_after} fields)`);
|
|
215
|
+
} else {
|
|
216
|
+
console.warn('[orchestrator] findApplyPath: no apply path found form may need manual navigation');
|
|
217
|
+
// We continue anyway handlePageGate / smartFill may still find
|
|
218
|
+
// something. If everything fails downstream, the existing NEEDS_ATTENTION
|
|
219
|
+
// surface fires from the orchestrator's terminal error handler.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.warn(`[orchestrator] findApplyPath threw: ${e.message}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
167
226
|
// STEP 2: Vision gate detection — handles modals, resume prompts, login walls, preamble screens.
|
|
168
227
|
// Runs before captcha check so the form is actually visible before we try to fill it.
|
|
169
228
|
const gateResult = await handlePageGate(page, aep, tempResumeFile, config);
|