@enricai/barnacle 1.6.1 → 1.6.3
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/README.md +36 -29
- package/dist/api/errors.d.ts +3 -3
- package/dist/api/errors.d.ts.map +1 -1
- package/dist/api/errors.js +4 -4
- package/dist/api/errors.js.map +1 -1
- package/dist/api/helpers/reply.d.ts +1 -1
- package/dist/api/helpers/reply.js +1 -1
- package/dist/cache/keyed-ttl-cache.d.ts +4 -4
- package/dist/cache/keyed-ttl-cache.js +2 -2
- package/dist/lib/applicant-payload.d.ts +4 -4
- package/dist/lib/applicant-payload.js +4 -4
- package/dist/lib/application-address.d.ts +3 -3
- package/dist/lib/application-address.js +5 -5
- package/dist/lib/application-address.js.map +1 -1
- package/dist/lib/application-resume.d.ts +4 -4
- package/dist/lib/application-resume.js +4 -4
- package/dist/lib/dispatch-metrics.js +1 -1
- package/dist/lib/llm/judges/verify-submit.d.ts +1 -1
- package/dist/lib/llm/judges/verify-submit.js +1 -1
- package/dist/lib/llm/schemas.js +3 -3
- package/dist/lib/phone.d.ts +1 -1
- package/dist/lib/phone.js +1 -1
- package/dist/lib/telemetry/call-types.d.ts +1 -1
- package/dist/lib/telemetry/call-types.js +1 -1
- package/dist/lib/tracking-click.d.ts +5 -3
- package/dist/lib/tracking-click.d.ts.map +1 -1
- package/dist/lib/tracking-click.js +5 -3
- package/dist/lib/tracking-click.js.map +1 -1
- package/dist/lib/us-states.d.ts +1 -1
- package/dist/lib/us-states.js +1 -1
- package/dist/plugins/loader.js +2 -2
- package/dist/scraper/cookie-jar.d.ts +2 -2
- package/dist/scraper/cookie-jar.js +2 -2
- package/dist/scraper/deep-query.d.ts +36 -0
- package/dist/scraper/deep-query.d.ts.map +1 -0
- package/dist/scraper/deep-query.js +87 -0
- package/dist/scraper/deep-query.js.map +1 -0
- package/dist/scraper/errors.d.ts +12 -14
- package/dist/scraper/errors.d.ts.map +1 -1
- package/dist/scraper/errors.js +12 -17
- package/dist/scraper/errors.js.map +1 -1
- package/dist/scraper/flow-runner.d.ts +97 -30
- package/dist/scraper/flow-runner.d.ts.map +1 -1
- package/dist/scraper/flow-runner.js +304 -104
- package/dist/scraper/flow-runner.js.map +1 -1
- package/dist/scraper/http-client.d.ts +11 -1
- package/dist/scraper/http-client.d.ts.map +1 -1
- package/dist/scraper/http-client.js +90 -3
- package/dist/scraper/http-client.js.map +1 -1
- package/dist/scraper/phantom-click.d.ts +45 -0
- package/dist/scraper/phantom-click.d.ts.map +1 -0
- package/dist/scraper/phantom-click.js +37 -0
- package/dist/scraper/phantom-click.js.map +1 -0
- package/dist/scraper/raw-fetch.d.ts +2 -2
- package/dist/scraper/raw-fetch.js +1 -1
- package/dist/scraper/require-response-field.d.ts +1 -1
- package/dist/scraper/require-response-field.js +1 -1
- package/dist/scraper/session-browserbase.d.ts +48 -0
- package/dist/scraper/session-browserbase.d.ts.map +1 -1
- package/dist/scraper/session-browserbase.js +10 -5
- package/dist/scraper/session-browserbase.js.map +1 -1
- package/dist/scraper/session-shared.d.ts +6 -0
- package/dist/scraper/session-shared.d.ts.map +1 -1
- package/dist/scraper/session-shared.js.map +1 -1
- package/dist/scraper/session-warmup.d.ts +1 -1
- package/dist/scraper/session-warmup.js +1 -1
- package/dist/scraper/submit-control.d.ts +51 -0
- package/dist/scraper/submit-control.d.ts.map +1 -0
- package/dist/scraper/submit-control.js +142 -0
- package/dist/scraper/submit-control.js.map +1 -0
- package/dist/scripts/migrate-telemetry-dir-names.d.ts +2 -2
- package/dist/scripts/migrate-telemetry-dir-names.d.ts.map +1 -1
- package/dist/scripts/migrate-telemetry-dir-names.js +1 -1
- package/dist/scripts/recon-browser.d.ts +25 -20
- package/dist/scripts/recon-browser.d.ts.map +1 -1
- package/dist/scripts/recon-browser.js +65 -56
- package/dist/scripts/recon-browser.js.map +1 -1
- package/dist/scripts/recon-generate.d.ts +52 -7
- package/dist/scripts/recon-generate.d.ts.map +1 -1
- package/dist/scripts/recon-generate.js +73 -31
- package/dist/scripts/recon-generate.js.map +1 -1
- package/dist/scripts/recon-http.d.ts +36 -5
- package/dist/scripts/recon-http.d.ts.map +1 -1
- package/dist/scripts/recon-http.js +77 -47
- package/dist/scripts/recon-http.js.map +1 -1
- package/dist/scripts/recon-replay-jobs.d.ts +32 -3
- package/dist/scripts/recon-replay-jobs.d.ts.map +1 -1
- package/dist/scripts/recon-replay-jobs.js +46 -29
- package/dist/scripts/recon-replay-jobs.js.map +1 -1
- package/dist/scripts/recon-shared.d.ts +45 -0
- package/dist/scripts/recon-shared.d.ts.map +1 -1
- package/dist/scripts/recon-shared.js +117 -0
- package/dist/scripts/recon-shared.js.map +1 -1
- package/dist/scripts/recon-summarize.d.ts +5 -1
- package/dist/scripts/recon-summarize.d.ts.map +1 -1
- package/dist/scripts/recon-summarize.js +19 -7
- package/dist/scripts/recon-summarize.js.map +1 -1
- package/dist/site-plugin.d.ts +4 -4
- package/dist/testing/persona-fixture.d.ts +4 -4
- package/dist/testing/persona-fixture.d.ts.map +1 -1
- package/dist/testing/persona-fixture.js +4 -4
- package/dist/testing/persona-fixture.js.map +1 -1
- package/dist/types/dispatch-metrics.d.ts +1 -1
- package/dist/types/dispatch-metrics.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow-DOM-piercing element resolver. Every other primitive in
|
|
3
|
+
* flow-runner.ts locates elements via `document.querySelectorAll` /
|
|
4
|
+
* `document.evaluate`, which cannot see inside a shadow root — so a
|
|
5
|
+
* submit-shaped control rendered by a web component (Angular Elements,
|
|
6
|
+
* Stencil, etc.) is invisible to the engine even though it's live on the
|
|
7
|
+
* page. This module composes a `page.evaluate` expression string (the
|
|
8
|
+
* repo's established interpolation pattern — see `INVALID_MARKER_EL_EXPR`
|
|
9
|
+
* in flow-runner.ts) that recurses through `el.shadowRoot` for OPEN roots
|
|
10
|
+
* to find and click such a control. Closed roots are unreachable from page
|
|
11
|
+
* script by design; the traversal treats them as a dead end rather than
|
|
12
|
+
* throwing.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Builds a self-contained `page.evaluate` expression string that locates
|
|
16
|
+
* the first submit-shaped control anywhere in the document — piercing open
|
|
17
|
+
* shadow roots — clicks it (setting focus, then dispatching bubbling
|
|
18
|
+
* mousedown/mouseup/click events, matching the repo's controlled-state
|
|
19
|
+
* click convention rather than a bare `el.click()`, see flow-runner.ts's
|
|
20
|
+
* checkbox/radio primitives), and returns a structured result so the
|
|
21
|
+
* caller can verify what happened without a second round-trip.
|
|
22
|
+
*
|
|
23
|
+
* Locate-only mode (`{ clickIfFound: false }`) is exposed for callers that
|
|
24
|
+
* want to probe for a deep submit control before deciding whether to act
|
|
25
|
+
* on it (e.g. to distinguish "no candidate anywhere" from "found but the
|
|
26
|
+
* click cascade should try a different technique first").
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildDeepSubmitClickExpr(options?: {
|
|
29
|
+
clickIfFound?: boolean;
|
|
30
|
+
}): string;
|
|
31
|
+
/** Structured result of {@link buildDeepSubmitClickExpr}'s `page.evaluate` call. */
|
|
32
|
+
export interface DeepSubmitClickResult {
|
|
33
|
+
found: boolean;
|
|
34
|
+
clicked: boolean;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=deep-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-query.d.ts","sourceRoot":"","sources":["../../src/scraper/deep-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA4CH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAerF;AAED,oFAAoF;AACpF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shadow-DOM-piercing element resolver. Every other primitive in
|
|
4
|
+
* flow-runner.ts locates elements via `document.querySelectorAll` /
|
|
5
|
+
* `document.evaluate`, which cannot see inside a shadow root — so a
|
|
6
|
+
* submit-shaped control rendered by a web component (Angular Elements,
|
|
7
|
+
* Stencil, etc.) is invisible to the engine even though it's live on the
|
|
8
|
+
* page. This module composes a `page.evaluate` expression string (the
|
|
9
|
+
* repo's established interpolation pattern — see `INVALID_MARKER_EL_EXPR`
|
|
10
|
+
* in flow-runner.ts) that recurses through `el.shadowRoot` for OPEN roots
|
|
11
|
+
* to find and click such a control. Closed roots are unreachable from page
|
|
12
|
+
* script by design; the traversal treats them as a dead end rather than
|
|
13
|
+
* throwing.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.buildDeepSubmitClickExpr = buildDeepSubmitClickExpr;
|
|
17
|
+
/**
|
|
18
|
+
* Text/attribute predicate for "this element is submit-shaped": a native
|
|
19
|
+
* `type="submit"` control, a `<button>` with no explicit `type` inside a
|
|
20
|
+
* `<form>` (the HTML default is submit), or a button-role element whose
|
|
21
|
+
* visible text/aria-label contains "submit". Kept as a standalone
|
|
22
|
+
* expression (not a RegExp) so it can be interpolated into a browser-
|
|
23
|
+
* context `page.evaluate` string, mirroring `INVALID_MARKER_EL_EXPR`.
|
|
24
|
+
*/
|
|
25
|
+
const SUBMIT_SHAPED_EL_EXPR = `((el) => {
|
|
26
|
+
const tag = (el.tagName || "").toLowerCase();
|
|
27
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
28
|
+
if ((tag === "button" || tag === "input") && type === "submit") return true;
|
|
29
|
+
if (tag === "button" && !el.getAttribute("type") && el.closest("form")) return true;
|
|
30
|
+
const role = (el.getAttribute("role") || "").toLowerCase();
|
|
31
|
+
const isButtonLike = tag === "button" || role === "button";
|
|
32
|
+
if (!isButtonLike) return false;
|
|
33
|
+
const norm = (s) => (s || "").replace(/\\s+/g, " ").trim().toLowerCase();
|
|
34
|
+
const text = norm(el.getAttribute("aria-label") || el.textContent || "");
|
|
35
|
+
return /\\bsubmit\\b/.test(text);
|
|
36
|
+
})`;
|
|
37
|
+
/**
|
|
38
|
+
* Recursive open-shadow-root walker: returns every element in `root`
|
|
39
|
+
* (light DOM or a shadow root) plus, for each child with an OPEN
|
|
40
|
+
* `shadowRoot`, every element inside that shadow tree, arbitrarily deep.
|
|
41
|
+
* A closed shadow root (`element.shadowRoot === null` from page script's
|
|
42
|
+
* perspective) is simply not descended into — it contributes no elements,
|
|
43
|
+
* it does not throw.
|
|
44
|
+
*/
|
|
45
|
+
const DEEP_ELEMENTS_EXPR = `((root) => {
|
|
46
|
+
const out = [];
|
|
47
|
+
const walk = (node) => {
|
|
48
|
+
const kids = node.querySelectorAll ? Array.from(node.querySelectorAll("*")) : [];
|
|
49
|
+
for (const el of kids) {
|
|
50
|
+
out.push(el);
|
|
51
|
+
if (el.shadowRoot) walk(el.shadowRoot);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
walk(root);
|
|
55
|
+
return out;
|
|
56
|
+
})`;
|
|
57
|
+
/**
|
|
58
|
+
* Builds a self-contained `page.evaluate` expression string that locates
|
|
59
|
+
* the first submit-shaped control anywhere in the document — piercing open
|
|
60
|
+
* shadow roots — clicks it (setting focus, then dispatching bubbling
|
|
61
|
+
* mousedown/mouseup/click events, matching the repo's controlled-state
|
|
62
|
+
* click convention rather than a bare `el.click()`, see flow-runner.ts's
|
|
63
|
+
* checkbox/radio primitives), and returns a structured result so the
|
|
64
|
+
* caller can verify what happened without a second round-trip.
|
|
65
|
+
*
|
|
66
|
+
* Locate-only mode (`{ clickIfFound: false }`) is exposed for callers that
|
|
67
|
+
* want to probe for a deep submit control before deciding whether to act
|
|
68
|
+
* on it (e.g. to distinguish "no candidate anywhere" from "found but the
|
|
69
|
+
* click cascade should try a different technique first").
|
|
70
|
+
*/
|
|
71
|
+
function buildDeepSubmitClickExpr(options) {
|
|
72
|
+
const clickIfFound = options?.clickIfFound ?? true;
|
|
73
|
+
return `(() => {
|
|
74
|
+
const isSubmitShaped = ${SUBMIT_SHAPED_EL_EXPR};
|
|
75
|
+
const deepElements = ${DEEP_ELEMENTS_EXPR};
|
|
76
|
+
const candidates = deepElements(document).filter(isSubmitShaped);
|
|
77
|
+
if (candidates.length === 0) return { found: false, clicked: false };
|
|
78
|
+
const el = candidates[0];
|
|
79
|
+
if (!${JSON.stringify(clickIfFound)}) return { found: true, clicked: false };
|
|
80
|
+
if (typeof el.focus === "function") { try { el.focus(); } catch (e) {} }
|
|
81
|
+
el.dispatchEvent(new Event("mousedown", { bubbles: true }));
|
|
82
|
+
el.dispatchEvent(new Event("mouseup", { bubbles: true }));
|
|
83
|
+
el.dispatchEvent(new Event("click", { bubbles: true }));
|
|
84
|
+
return { found: true, clicked: true };
|
|
85
|
+
})()`;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=deep-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-query.js","sourceRoot":"","sources":["../../src/scraper/deep-query.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH;;;;;;;GAOG;AACH,MAAM,qBAAqB,GAAG;;;;;;;;;;;GAW3B,CAAC;AAEJ;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG;;;;;;;;;;;GAWxB,CAAC;AAEJ;;;;;;;;;;;;;GAaG;AACH,kCAAyC,OAAoC;IAC3E,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,IAAI,CAAC;IACnD,OAAO;6BACoB,qBAAqB;2BACvB,kBAAkB;;;;WAIlC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;;;;;;OAMhC,CAAC;AACR,CAAC"}
|
package/dist/scraper/errors.d.ts
CHANGED
|
@@ -59,15 +59,24 @@ export declare class UnknownScraperError extends ScraperError {
|
|
|
59
59
|
constructor(message?: string);
|
|
60
60
|
}
|
|
61
61
|
/** Discriminator for {@link StepVerificationError}. See the class TSDoc for the per-variant semantics. */
|
|
62
|
-
export type StepVerificationErrorKind = "cascade-exhausted" | "probe-absent" | "backend-error-unrecoverable" | "replan-cycle-detected" | "wizard-regression";
|
|
62
|
+
export type StepVerificationErrorKind = "cascade-exhausted" | "phantom-click-exhausted" | "probe-absent" | "backend-error-unrecoverable" | "replan-cycle-detected" | "wizard-regression";
|
|
63
63
|
/**
|
|
64
|
-
* Recon-only: a flow step in recon-browser.ts could not be acted on.
|
|
64
|
+
* Recon-only: a flow step in recon-browser.ts could not be acted on. Six
|
|
65
65
|
* variants per `kind`:
|
|
66
66
|
*
|
|
67
67
|
* - "cascade-exhausted": the full 4-attempt self-healing cascade ran and
|
|
68
68
|
* none of the attempts produced an observable effect. Expensive (the
|
|
69
69
|
* cascade burned its full LLM/observe budget) — counted against the
|
|
70
70
|
* cascade replan budget.
|
|
71
|
+
* - "phantom-click-exhausted": attempt 1 phantom-clicked (Stagehand
|
|
72
|
+
* reported success with zero network/url/DOM effect) and the cascade
|
|
73
|
+
* escalated straight to the deep submit-control locator on attempt 2,
|
|
74
|
+
* but every attempt through {@link MAX_STEP_ATTEMPTS} still produced no
|
|
75
|
+
* observable effect — the submit control is unreachable by any
|
|
76
|
+
* resolution strategy the cascade has, not just the light-DOM one.
|
|
77
|
+
* Distinguished from "cascade-exhausted" so a replan/triager can skip
|
|
78
|
+
* straight to a structural fix (e.g. a new locator strategy) instead of
|
|
79
|
+
* re-trying the same techniques.
|
|
71
80
|
* - "probe-absent": the cheap page-state probe ran BEFORE the cascade
|
|
72
81
|
* and observed zero candidates for the step's instruction. We skip the
|
|
73
82
|
* cascade and ask for a replan immediately because the page state is
|
|
@@ -140,22 +149,11 @@ export declare class HttpRateLimitError extends ScraperError {
|
|
|
140
149
|
* HTTP request nor a fresh Stagehand browser session can succeed. The caller must
|
|
141
150
|
* back off and surface a "retry later" state rather than burning a Steel session.
|
|
142
151
|
* Kept distinct from HttpRateLimitError so metrics/logs (classifyDispatchError)
|
|
143
|
-
* can tell a
|
|
152
|
+
* can tell a resource lock apart from a self-inflicted 429 rate limit.
|
|
144
153
|
*/
|
|
145
154
|
export declare class HttpUrlLockedError extends ScraperError {
|
|
146
155
|
constructor(message?: string);
|
|
147
156
|
}
|
|
148
|
-
/**
|
|
149
|
-
* @deprecated Removed in 2.0.0. Oracle-specific and no longer thrown by the
|
|
150
|
-
* engine — response-body sentinel detection now lives in the plugin via
|
|
151
|
-
* `HttpClientOptions.classifyResponseBody`. A plugin that needs a retryable
|
|
152
|
-
* token-expiry signal should define its own `ScraperError` subclass (see the
|
|
153
|
-
* encompasshealth plugin). Retained only so existing importers of
|
|
154
|
-
* `@enricai/barnacle/scraper/errors` keep resolving under a minor release.
|
|
155
|
-
*/
|
|
156
|
-
export declare class OracleTokenExpiredError extends ScraperError {
|
|
157
|
-
constructor(message?: string);
|
|
158
|
-
}
|
|
159
157
|
/**
|
|
160
158
|
* Structured result returned (not thrown) by the hot path when it cannot
|
|
161
159
|
* complete the application because the user must supply additional data.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/scraper/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,8BAAsB,YAAa,SAAQ,KAAK;IAC9C,SAAgB,SAAS,EAAE,OAAO,CAAC;IAEnC,YAAY,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAI9C;CACF;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,YAAY,OAAO,SAAkC,EAEpD;CACF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,YAAY,OAAO,SAA+B,EAEjD;CACF;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,YAAY,OAAO,SAA6B,EAE/C;CACF;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,YAAY,OAAO,SAA8B,EAEhD;CACF;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,YAAY,OAAO,SAA4B,EAE9C;CACF;AAED,0GAA0G;AAC1G,MAAM,MAAM,yBAAyB,GACjC,mBAAmB,GACnB,cAAc,GACd,6BAA6B,GAC7B,uBAAuB,GACvB,mBAAmB,CAAC;AAExB
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/scraper/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,8BAAsB,YAAa,SAAQ,KAAK;IAC9C,SAAgB,SAAS,EAAE,OAAO,CAAC;IAEnC,YAAY,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAI9C;CACF;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,YAAY,OAAO,SAAkC,EAEpD;CACF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,YAAY,OAAO,SAA+B,EAEjD;CACF;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,YAAY,OAAO,SAA6B,EAE/C;CACF;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,YAAY,OAAO,SAA8B,EAEhD;CACF;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,YAAY;IACnD,YAAY,OAAO,SAA4B,EAE9C;CACF;AAED,0GAA0G;AAC1G,MAAM,MAAM,yBAAyB,GACjC,mBAAmB,GACnB,yBAAyB,GACzB,cAAc,GACd,6BAA6B,GAC7B,uBAAuB,GACvB,mBAAmB,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,YACE,OAAO,SAA2D,EAClE,IAAI,GAAE,yBAA+C,EAItD;CACF;AAED;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAO,SAAkC,EAEpD;CACF;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,YAAY,OAAO,SAAwC,EAE1D;CACF;AAED;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAO,SAA0B,EAE5C;CACF;AAED;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,YAAY,OAAO,SAAiC,EAEnD;CACF;AAED;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,YAAY,OAAO,SAA2B,EAE7C;CACF;AAED;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACrD,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,YAAY,WAAW,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAI1D;CACF"}
|
package/dist/scraper/errors.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* and the diagnostic it represents.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.MissingFormMapKeyError = exports.
|
|
16
|
+
exports.MissingFormMapKeyError = exports.HttpUrlLockedError = exports.HttpRateLimitError = exports.HttpServerError = exports.HttpBotChallengeError = exports.HttpSchemaError = exports.StepVerificationError = exports.UnknownScraperError = exports.SessionTimeoutError = exports.SelectorFailureError = exports.EmptyResultsError = exports.CaptchaError = exports.ScraperError = void 0;
|
|
17
17
|
/**
|
|
18
18
|
* Base class for all scraper-internal errors. Each subclass encodes a
|
|
19
19
|
* distinct recovery policy (retryable or not) that retry.ts and loader.ts
|
|
@@ -82,13 +82,22 @@ class UnknownScraperError extends ScraperError {
|
|
|
82
82
|
}
|
|
83
83
|
exports.UnknownScraperError = UnknownScraperError;
|
|
84
84
|
/**
|
|
85
|
-
* Recon-only: a flow step in recon-browser.ts could not be acted on.
|
|
85
|
+
* Recon-only: a flow step in recon-browser.ts could not be acted on. Six
|
|
86
86
|
* variants per `kind`:
|
|
87
87
|
*
|
|
88
88
|
* - "cascade-exhausted": the full 4-attempt self-healing cascade ran and
|
|
89
89
|
* none of the attempts produced an observable effect. Expensive (the
|
|
90
90
|
* cascade burned its full LLM/observe budget) — counted against the
|
|
91
91
|
* cascade replan budget.
|
|
92
|
+
* - "phantom-click-exhausted": attempt 1 phantom-clicked (Stagehand
|
|
93
|
+
* reported success with zero network/url/DOM effect) and the cascade
|
|
94
|
+
* escalated straight to the deep submit-control locator on attempt 2,
|
|
95
|
+
* but every attempt through {@link MAX_STEP_ATTEMPTS} still produced no
|
|
96
|
+
* observable effect — the submit control is unreachable by any
|
|
97
|
+
* resolution strategy the cascade has, not just the light-DOM one.
|
|
98
|
+
* Distinguished from "cascade-exhausted" so a replan/triager can skip
|
|
99
|
+
* straight to a structural fix (e.g. a new locator strategy) instead of
|
|
100
|
+
* re-trying the same techniques.
|
|
92
101
|
* - "probe-absent": the cheap page-state probe ran BEFORE the cascade
|
|
93
102
|
* and observed zero candidates for the step's instruction. We skip the
|
|
94
103
|
* cascade and ask for a replan immediately because the page state is
|
|
@@ -177,7 +186,7 @@ exports.HttpRateLimitError = HttpRateLimitError;
|
|
|
177
186
|
* HTTP request nor a fresh Stagehand browser session can succeed. The caller must
|
|
178
187
|
* back off and surface a "retry later" state rather than burning a Steel session.
|
|
179
188
|
* Kept distinct from HttpRateLimitError so metrics/logs (classifyDispatchError)
|
|
180
|
-
* can tell a
|
|
189
|
+
* can tell a resource lock apart from a self-inflicted 429 rate limit.
|
|
181
190
|
*/
|
|
182
191
|
class HttpUrlLockedError extends ScraperError {
|
|
183
192
|
constructor(message = "requisition url locked") {
|
|
@@ -185,20 +194,6 @@ class HttpUrlLockedError extends ScraperError {
|
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
exports.HttpUrlLockedError = HttpUrlLockedError;
|
|
188
|
-
/**
|
|
189
|
-
* @deprecated Removed in 2.0.0. Oracle-specific and no longer thrown by the
|
|
190
|
-
* engine — response-body sentinel detection now lives in the plugin via
|
|
191
|
-
* `HttpClientOptions.classifyResponseBody`. A plugin that needs a retryable
|
|
192
|
-
* token-expiry signal should define its own `ScraperError` subclass (see the
|
|
193
|
-
* encompasshealth plugin). Retained only so existing importers of
|
|
194
|
-
* `@enricai/barnacle/scraper/errors` keep resolving under a minor release.
|
|
195
|
-
*/
|
|
196
|
-
class OracleTokenExpiredError extends ScraperError {
|
|
197
|
-
constructor(message = "oracle token expired (ORA_IRC_TOKEN_EXPIRED)") {
|
|
198
|
-
super(message, true);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
exports.OracleTokenExpiredError = OracleTokenExpiredError;
|
|
202
197
|
/**
|
|
203
198
|
* Thrown when code that translates stable semantic field keys (`address.city`,
|
|
204
199
|
* `firstName`, `applicantGender`) into tenant-specific GUIDs via a runtime
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/scraper/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH;;;;GAIG;AACH,kBAAmC,SAAQ,KAAK;IAC9B,SAAS,CAAU;IAEnC,YAAY,OAAe,EAAE,SAAkB;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;;AAED;;;GAGG;AACH,kBAA0B,SAAQ,YAAY;IAC5C,YAAY,OAAO,GAAG,+BAA+B;QACnD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;GAIG;AACH,uBAA+B,SAAQ,YAAY;IACjD,YAAY,OAAO,GAAG,4BAA4B;QAChD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;GAIG;AACH,0BAAkC,SAAQ,YAAY;IACpD,YAAY,OAAO,GAAG,0BAA0B;QAC9C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC;CACF;;AAED;;;;GAIG;AACH,yBAAiC,SAAQ,YAAY;IACnD,YAAY,OAAO,GAAG,2BAA2B;QAC/C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC;CACF;;AAED;;;GAGG;AACH,yBAAiC,SAAQ,YAAY;IACnD,YAAY,OAAO,GAAG,yBAAyB;QAC7C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC;CACF;;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/scraper/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH;;;;GAIG;AACH,kBAAmC,SAAQ,KAAK;IAC9B,SAAS,CAAU;IAEnC,YAAY,OAAe,EAAE,SAAkB;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;;AAED;;;GAGG;AACH,kBAA0B,SAAQ,YAAY;IAC5C,YAAY,OAAO,GAAG,+BAA+B;QACnD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;GAIG;AACH,uBAA+B,SAAQ,YAAY;IACjD,YAAY,OAAO,GAAG,4BAA4B;QAChD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;GAIG;AACH,0BAAkC,SAAQ,YAAY;IACpD,YAAY,OAAO,GAAG,0BAA0B;QAC9C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC;CACF;;AAED;;;;GAIG;AACH,yBAAiC,SAAQ,YAAY;IACnD,YAAY,OAAO,GAAG,2BAA2B;QAC/C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC;CACF;;AAED;;;GAGG;AACH,yBAAiC,SAAQ,YAAY;IACnD,YAAY,OAAO,GAAG,yBAAyB;QAC7C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvB,CAAC;CACF;;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,2BAAmC,SAAQ,YAAY;IAC5C,IAAI,CAA4B;IACzC,YACE,OAAO,GAAG,wDAAwD,EAClE,IAAI,GAA8B,mBAAmB;QAErD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;;AAED;;;;GAIG;AACH,qBAA6B,SAAQ,YAAY;IAC/C,YAAY,OAAO,GAAG,+BAA+B;QACnD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;GAGG;AACH,2BAAmC,SAAQ,YAAY;IACrD,YAAY,OAAO,GAAG,qCAAqC;QACzD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;;GAKG;AACH,qBAA6B,SAAQ,YAAY;IAC/C,YAAY,OAAO,GAAG,uBAAuB;QAC3C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;;GAKG;AACH,wBAAgC,SAAQ,YAAY;IAClD,YAAY,OAAO,GAAG,8BAA8B;QAClD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAED;;;;;;;;;GASG;AACH,wBAAgC,SAAQ,YAAY;IAClD,YAAY,OAAO,GAAG,wBAAwB;QAC5C,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;;AAeD;;;;;;;;;;;;;GAaG;AACH,4BAAoC,SAAQ,YAAY;IAC7C,WAAW,CAAoB;IAC/B,OAAO,CAAS;IACzB,YAAY,WAA8B,EAAE,OAAe;QACzD,KAAK,CAAC,mCAAmC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC;QACzF,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type Anthropic from "@anthropic-ai/sdk";
|
|
16
16
|
import type { Action, Page, Stagehand } from "@browserbasehq/stagehand";
|
|
17
17
|
import { type LlmCallInput } from "../lib/telemetry/call-capture";
|
|
18
|
+
import { type PhantomClickVerdict } from "../scraper/phantom-click";
|
|
18
19
|
import { type Capture } from "../scripts/recon-shared";
|
|
19
20
|
import type { Logger } from "../types/logging";
|
|
20
21
|
/**
|
|
@@ -102,7 +103,7 @@ interface StepSnapshot {
|
|
|
102
103
|
/** One attempt's audit trail — included verbatim in the failure dump. */
|
|
103
104
|
export interface AttemptRecord {
|
|
104
105
|
attempt: number;
|
|
105
|
-
technique: "act-string" | "observe-act" | "structured-click" | "observe-act-exclude" | "llm-rephrase";
|
|
106
|
+
technique: "act-string" | "observe-act" | "structured-click" | "observe-act-exclude" | "deep-submit-locator" | "llm-rephrase";
|
|
106
107
|
instruction: string | null;
|
|
107
108
|
triedSelectors: string[];
|
|
108
109
|
actResultSuccess: boolean | null;
|
|
@@ -130,6 +131,15 @@ export interface AttemptRecord {
|
|
|
130
131
|
* - `null`: failure path.
|
|
131
132
|
*/
|
|
132
133
|
verifiedBy: "network" | "url" | "dom" | "submitted-state-dom" | null;
|
|
134
|
+
/**
|
|
135
|
+
* {@link classifyPhantomClick}'s verdict for this attempt, computed from
|
|
136
|
+
* the same pre/post snapshot pair `describeAttemptEffectSignals` already
|
|
137
|
+
* renders — `null` until the no-observable-effect branch runs (verified
|
|
138
|
+
* attempts never reach it). `"phantom"` (Stagehand claimed success but
|
|
139
|
+
* nothing observably happened) is what escalates the next attempt to
|
|
140
|
+
* `deep-submit-locator` instead of repeating a light-DOM technique.
|
|
141
|
+
*/
|
|
142
|
+
phantomClickVerdict: PhantomClickVerdict | null;
|
|
133
143
|
}
|
|
134
144
|
export declare function snapshotPage(page: Page, signalCounter: {
|
|
135
145
|
n: number;
|
|
@@ -137,11 +147,11 @@ export declare function snapshotPage(page: Page, signalCounter: {
|
|
|
137
147
|
/**
|
|
138
148
|
* Detect whether a 2xx response body indicates the server REJECTED the
|
|
139
149
|
* application despite returning a 2xx HTTP status. Many ATSs use a "200 OK
|
|
140
|
-
* with rejection envelope" pattern instead of a 4xx:
|
|
150
|
+
* with rejection envelope" pattern instead of a 4xx: one JSON-envelope ATS returns
|
|
141
151
|
* `{not_qualified: true, error: "Not qualified reason: <field>"}`,
|
|
142
152
|
* Greenhouse uses `{rejected: true, reason: "..."}`, Lever uses
|
|
143
153
|
* `{qualified: false, reason: "..."}`, Workday uses
|
|
144
|
-
* `{status: "rejected"}`. Empirically verified on
|
|
154
|
+
* `{status: "rejected"}`. Empirically verified on a JSON-envelope ATS: 4/6 historical
|
|
145
155
|
* /integrated_apply 200s on this codebase had `not_qualified: true` and
|
|
146
156
|
* we treated them as wins because the audit only checked HTTP status.
|
|
147
157
|
*
|
|
@@ -247,7 +257,7 @@ export declare function latestCaptureIndex(recentCaptures: readonly string[]): n
|
|
|
247
257
|
* read straight from `capturesDir` — NOT from the in-memory `recentCaptures`
|
|
248
258
|
* array, which is front-evicted to `RECENT_CAPTURES_WINDOW` and therefore drops
|
|
249
259
|
* a step's transition when >20 captures flood during the step (measured 43
|
|
250
|
-
* across one
|
|
260
|
+
* across one wizard-ATS cascade). Scanning disk by filename index is eviction-proof:
|
|
251
261
|
* the transition file is always on disk regardless of array churn. `.decoded.json`
|
|
252
262
|
* sidecars are excluded (the raw file carries `requestPostData`). Sorted by index
|
|
253
263
|
* so callers scan in capture order. Returns [] when the dir is unreadable.
|
|
@@ -257,7 +267,7 @@ export declare function capturesAfterIndex(preIdx: number, capturesDir: string):
|
|
|
257
267
|
* Detects a multi-page-wizard RESTART / backward navigation by scanning the
|
|
258
268
|
* captures written during this step for a configured restart-signal pattern
|
|
259
269
|
* (e.g. `init-apply`, `application_canceled=true`). The restart signal is often
|
|
260
|
-
* a plain GET (
|
|
270
|
+
* a plain GET (the wizard ATS's `GET .../init-apply?...&application_canceled=true`),
|
|
261
271
|
* so it scans the raw capture files' `url` field (GETs are written to disk even
|
|
262
272
|
* though they're dropped from `recentCaptureMeta`). Window scoped by
|
|
263
273
|
* `preIdx` via {@link capturesAfterIndex} (eviction-proof). Returns the matching
|
|
@@ -267,7 +277,7 @@ export declare function capturesAfterIndex(preIdx: number, capturesDir: string):
|
|
|
267
277
|
* Does any same-window network capture's REQUEST BODY match the configured
|
|
268
278
|
* transition pattern? Proves an interior "advance"/"Next" step really moved the
|
|
269
279
|
* wizard forward when advance and non-advance mutations share one endpoint URL
|
|
270
|
-
* (e.g.
|
|
280
|
+
* (e.g. the wizard ATS's `/gq`: a real advance is a `TransitionWorklet` mutation, a
|
|
271
281
|
* field edit is `EditQuestionItem` — same URL, only the body differs, so a
|
|
272
282
|
* URL/meta-based check can't tell them apart). Window scoped by `preIdx` via
|
|
273
283
|
* {@link capturesAfterIndex} (disk-scan by filename index — eviction-proof, so a
|
|
@@ -283,7 +293,7 @@ export declare function windowHasTransitionBody(params: {
|
|
|
283
293
|
* Stricter sibling of {@link windowHasTransitionBody}: a same-window capture
|
|
284
294
|
* whose request body matches the transition pattern AND whose parsed
|
|
285
295
|
* `variables.input.type === "next"`. The mutation NAME alone is a weak
|
|
286
|
-
* distinguisher — on
|
|
296
|
+
* distinguisher — on the wizard ATS a `back` bounce is ALSO a `TransitionWorklet`
|
|
287
297
|
* mutation (its body contains the pattern too) and would wrongly count as an
|
|
288
298
|
* advance; and the fast `WorkletPayload` autosave that precedes the real
|
|
289
299
|
* transition is a different mutation with no `input.type`. Requiring the parsed
|
|
@@ -304,7 +314,7 @@ export declare function windowHasAdvanceTransition(params: {
|
|
|
304
314
|
* the RC2 gate is unit-testable.
|
|
305
315
|
*
|
|
306
316
|
* An interior "Next" on an SPA where an advance and a mere field-edit share one
|
|
307
|
-
* endpoint (
|
|
317
|
+
* endpoint (the wizard ATS's `/gq`: TransitionWorklet vs EditQuestionItem — same URL,
|
|
308
318
|
* different body) can fire a network POST that is NOT a real advance; the
|
|
309
319
|
* fallback's htmlDelta/textChanged/checked-radio signals are then validation
|
|
310
320
|
* re-renders / field toggles that don't move the wizard. So for an opted-in
|
|
@@ -329,7 +339,7 @@ export declare function shouldVetoFallbackAdvance(params: {
|
|
|
329
339
|
* transition fired — a URL change OR a real `type=next` (`networkIsRealAdvance`).
|
|
330
340
|
* A DOM change alone is a validation re-render / field toggle that never moves
|
|
331
341
|
* the wizard. Crucially this must veto even when a NON-advancing network POST
|
|
332
|
-
* fired (
|
|
342
|
+
* fired (the wizard ATS's `WorkletPayload` autosave): keying the veto on "no network
|
|
333
343
|
* at all" let a rephrase that triggered an autosave + DOM reflow false-verify an
|
|
334
344
|
* advance, desyncing the flow from the wizard. Returns whether the DOM signal is
|
|
335
345
|
* ALLOWED to verify: false = veto it. Non-advance/field steps, sites without the
|
|
@@ -386,7 +396,7 @@ export declare function describeAttemptEffectSignals(pre: StepSnapshot, post: St
|
|
|
386
396
|
* keeps healing opportunistically.
|
|
387
397
|
*/
|
|
388
398
|
export declare function shouldSkipTechnique(params: {
|
|
389
|
-
technique: "act-string" | "observe-act" | "structured-click" | "observe-act-exclude" | "llm-rephrase";
|
|
399
|
+
technique: "act-string" | "observe-act" | "structured-click" | "observe-act-exclude" | "deep-submit-locator" | "llm-rephrase";
|
|
390
400
|
priorAttempts: readonly {
|
|
391
401
|
technique: string;
|
|
392
402
|
triedSelectors: readonly string[];
|
|
@@ -398,6 +408,32 @@ export declare function shouldSkipTechnique(params: {
|
|
|
398
408
|
* effect at all. Optional so existing callers are unchanged.
|
|
399
409
|
*/
|
|
400
410
|
advanceUnmovedAfterAttempt1?: boolean;
|
|
411
|
+
/**
|
|
412
|
+
* True when attempt 1 reported success but the pre/post snapshot shows zero
|
|
413
|
+
* observable effect (see {@link classifyPhantomClick}) — a phantom click.
|
|
414
|
+
* Re-observing/re-clicking the light DOM will no-op identically (the target
|
|
415
|
+
* is almost certainly unreachable via `document.querySelectorAll`, e.g.
|
|
416
|
+
* inside a shadow root), so skip straight to the deep submit-control locator
|
|
417
|
+
* instead of burning attempts 2-4 repeating the same no-op. Optional so
|
|
418
|
+
* existing callers are unchanged. Gated by {@link submitShapedStep} below —
|
|
419
|
+
* on a non-submit control the deep submit-control locator cannot resolve
|
|
420
|
+
* the target either, so this short-circuit only applies when the step is
|
|
421
|
+
* submit-shaped.
|
|
422
|
+
*/
|
|
423
|
+
phantomClickAfterAttempt1?: boolean;
|
|
424
|
+
/**
|
|
425
|
+
* True when the current step is a submit-shaped action (`isFinalStep ||
|
|
426
|
+
* submitStep`, the canonical gate used elsewhere in this file). The deep
|
|
427
|
+
* submit-control locator ranks submit-shaped candidates only and
|
|
428
|
+
* deliberately excludes Back/Cancel/Save-draft controls, so it is a
|
|
429
|
+
* guaranteed no-op on a non-submit control (e.g. a radio/checkbox). Scopes
|
|
430
|
+
* the phantom-click short-circuit above to submit-shaped steps only —
|
|
431
|
+
* on a non-submit step the fallback ladder (`structured-click`,
|
|
432
|
+
* `observe-act-exclude`) is left intact since those are the techniques
|
|
433
|
+
* that can actually click a radio or checkbox. Optional so existing
|
|
434
|
+
* callers are unchanged.
|
|
435
|
+
*/
|
|
436
|
+
submitShapedStep?: boolean;
|
|
401
437
|
}): {
|
|
402
438
|
skip: boolean;
|
|
403
439
|
reason: string;
|
|
@@ -445,7 +481,7 @@ export declare function isSubmitRevealedInvalid(params: {
|
|
|
445
481
|
* landed within the poll window (`networkIsRealAdvance` false), the button works
|
|
446
482
|
* but the wizard is refusing to advance (a precondition isn't met, e.g. a
|
|
447
483
|
* required field the flow answers on a LATER step). Re-clicking the same button
|
|
448
|
-
* only re-fires the autosave / a `back` bounce — measured across
|
|
484
|
+
* only re-fires the autosave / a `back` bounce — measured across wizard-ATS runs as the
|
|
449
485
|
* next→back oscillation. Break to replan instead, which can reorder a later step
|
|
450
486
|
* forward. Conservative: any condition unmet → run the full cascade as before.
|
|
451
487
|
* Never fires on final/submit steps (they own `isSubmitRevealedInvalid`) or on
|
|
@@ -486,7 +522,7 @@ export declare function rephraseWithLLM(client: Anthropic, originalStep: string,
|
|
|
486
522
|
* invalid form fields and visible error messages so the rephrase LLM can
|
|
487
523
|
* propose corrective fills instead of
|
|
488
524
|
* just "click harder" — the previous limitation observed in the
|
|
489
|
-
* telemetry of the
|
|
525
|
+
* telemetry of the JSON-envelope ATS run (every rephrase converged on
|
|
490
526
|
* "Click Submit Application using JavaScript" because the prompt had no
|
|
491
527
|
* signal that the form was invalid).
|
|
492
528
|
*/
|
|
@@ -618,8 +654,8 @@ export declare function formatValidationRejectedReason(pair: ValidationRejection
|
|
|
618
654
|
* Default: first 8KB. That window held for tenants whose form was at the
|
|
619
655
|
* top of the page (early sweeps in 2026-06).
|
|
620
656
|
*
|
|
621
|
-
* For pages where the form HTML lives below 8KB (verified on
|
|
622
|
-
*
|
|
657
|
+
* For pages where the form HTML lives below 8KB (verified on an Angular
|
|
658
|
+
* apply SPA: ng-invalid first appears at byte ~15,500 after a header
|
|
623
659
|
* of Angular hydration JS + chrome), the 8KB cap silently produced
|
|
624
660
|
* "FORM FIELDS CURRENTLY MARKED INVALID: (none)" in the replan prompt,
|
|
625
661
|
* leaving the LLM with no evidence and causing it to hallucinate steps
|
|
@@ -630,7 +666,7 @@ export declare function formatValidationRejectedReason(pair: ValidationRejection
|
|
|
630
666
|
*
|
|
631
667
|
* Site-agnostic: the markers we look for (ng-invalid, mat-form-field-
|
|
632
668
|
* invalid, is-invalid, <form) are framework-level CSS-class conventions
|
|
633
|
-
* used across countless SPAs, not
|
|
669
|
+
* used across countless SPAs, not specific to any one ATS.
|
|
634
670
|
*/
|
|
635
671
|
export declare function selectBodyExcerpt(body: string): string;
|
|
636
672
|
/**
|
|
@@ -674,7 +710,7 @@ export interface LeafInvalidField {
|
|
|
674
710
|
* matches `ng-invalid` because of bubbling, but its child `<app-input
|
|
675
711
|
* class="ng-invalid">` ALSO matches; `:not(:has(...))` filters out the parent.
|
|
676
712
|
*
|
|
677
|
-
*
|
|
713
|
+
* A JSON-envelope ATS tenant's smoke (run 1781478440322) showed E1's prompt
|
|
678
714
|
* instruction ("prefer the leaf, not the bubbled parent") only got Haiku from
|
|
679
715
|
* 5 wrong fields → 1 wrong field — still surfaced `(unlabeled) <ol>` instead
|
|
680
716
|
* of `<app-input autocomplete="zip-code">` at byte 95,033. All 3 replans + 4
|
|
@@ -703,8 +739,9 @@ export declare function renderLeafInvalidFields(fields: readonly LeafInvalidFiel
|
|
|
703
739
|
/**
|
|
704
740
|
* Scan recent capture files for failed submit-endpoint requests and pull
|
|
705
741
|
* out structured field-level errors from the response body. The cascade
|
|
706
|
-
* already saves every captured request to
|
|
707
|
-
*
|
|
742
|
+
* already saves every captured request to the run's graphql capture dir
|
|
743
|
+
* (see {@link resolveReconRunDir}) with its parsed `responseBody`; this
|
|
744
|
+
* helper reads those files back, filters to captures
|
|
708
745
|
* matching the configured submit pattern with status >= 400, and walks
|
|
709
746
|
* common error-shape conventions (`{ errors: [{ field, message }] }`,
|
|
710
747
|
* `{ validation/fieldErrors: { … } }`, `{ message }`).
|
|
@@ -724,7 +761,7 @@ export declare function extractSubmitFailureEvidence(recentCaptureFilenames: rea
|
|
|
724
761
|
ownBackendHostnames: readonly string[], capturesDir?: string, mode?: "strict" | "any-4xx"): string;
|
|
725
762
|
/**
|
|
726
763
|
* Surface Google Analytics Measurement Protocol events captured during a
|
|
727
|
-
* step's attempt window.
|
|
764
|
+
* step's attempt window. A JSON-envelope ATS (and most GA4-instrumented SPAs) emits
|
|
728
765
|
* `view_secondPage`, `view_thankYouPage`, `form_submit` and similar events
|
|
729
766
|
* via `https://www.google-analytics.com/g/collect` — the engine already
|
|
730
767
|
* stores these in `recentCaptures[]` but no code reads them. Without
|
|
@@ -741,8 +778,8 @@ ownBackendHostnames: readonly string[], capturesDir?: string, mode?: "strict" |
|
|
|
741
778
|
export declare function extractGaEventEvidence(recentCaptureFilenames: readonly string[], capturesDir?: string): string;
|
|
742
779
|
/**
|
|
743
780
|
* Render a long step list as a small head + tail window with an elision
|
|
744
|
-
* marker. Replan prompts grew to ~114KB on
|
|
745
|
-
* (verified
|
|
781
|
+
* marker. Replan prompts grew to ~114KB on a JSON-envelope ATS's 331-step flow
|
|
782
|
+
* (verified on a 2026-06-14 tenant run), causing Sonnet 4.6 to TTFT-stall
|
|
746
783
|
* out at 187s with `APIConnectionTimeoutError: Request timed out.` —
|
|
747
784
|
* the ONLY non-API-quota replan failure across ~30+ historical calls.
|
|
748
785
|
*
|
|
@@ -853,7 +890,7 @@ export declare function verifyFillReadback(page: Page, xpath: string, expectedVa
|
|
|
853
890
|
* True when a control's text/aria-label denotes a resume-upload affordance
|
|
854
891
|
* (the button that surfaces a hidden `<input type=file>` or opens a chooser).
|
|
855
892
|
* Pure + exported for unit tests; the vocabulary is intentionally generic so it
|
|
856
|
-
* benefits any MUI/React/dropzone ATS, not just
|
|
893
|
+
* benefits any MUI/React/dropzone ATS, not just the wizard ATS. Rejects negative
|
|
857
894
|
* lookalikes ("upload later", "no file", a bare "submit") so the click-to-
|
|
858
895
|
* surface path never fires a skip/decline/submit control.
|
|
859
896
|
*/
|
|
@@ -873,7 +910,7 @@ export declare function writeFixtureToTempFile(fixture: {
|
|
|
873
910
|
* Parse a select/dropdown flow step into the option to choose and (when
|
|
874
911
|
* present) the question label that scopes which dropdown it targets.
|
|
875
912
|
*
|
|
876
|
-
* Why:
|
|
913
|
+
* Why: the Angular/MUI wizard ATS renders dropdowns as `MuiNativeSelect` native `<select>`
|
|
877
914
|
* with `tabindex="-1"` — removed from the accessibility tree, so Stagehand
|
|
878
915
|
* observe returns `[]` and the cascade can never select an option. The select
|
|
879
916
|
* primitive answers these directly from the DOM, but needs the target option
|
|
@@ -899,7 +936,7 @@ export declare function parseSelectStep(instruction: string): {
|
|
|
899
936
|
* Why this exists (sibling of `parseSelectStep`): `parseSelectStep`
|
|
900
937
|
* deliberately excludes bare radio steps ("a bare 'click the Yes answer' is a
|
|
901
938
|
* radio"), leaving radios with no DOM-direct primitive — they fall to the
|
|
902
|
-
* observe cascade, which on
|
|
939
|
+
* observe cascade, which on the wizard ATS's MUI radio markup resolves the step
|
|
903
940
|
* to a wrapper `<div>`/`<span>` (not the `<input type=radio>`) and commits via
|
|
904
941
|
* a bare `el.click()` that never triggers React's controlled-state `onChange`.
|
|
905
942
|
* The field stays `Mui-error` "required", Next no-ops, and the wizard walls at
|
|
@@ -919,7 +956,7 @@ export declare function parseRadioStep(instruction: string): {
|
|
|
919
956
|
} | null;
|
|
920
957
|
/**
|
|
921
958
|
* Run a primitive's read-only DOM enumerate with a bounded settle-retry. SPA
|
|
922
|
-
* wizards
|
|
959
|
+
* wizards frequently render the target widget a beat AFTER the flow
|
|
923
960
|
* step fires — the first evaluate sees an empty page, so the primitive would
|
|
924
961
|
* give up even though the widget appears moments later. Re-run the enumerate up
|
|
925
962
|
* to `PRIMITIVE_ENUMERATE_ATTEMPTS` times, waiting `PRIMITIVE_ENUMERATE_RETRY_MS`
|
|
@@ -940,7 +977,7 @@ export declare function pollEnumerate<T>(page: Page, expr: string, isPresent: (r
|
|
|
940
977
|
* Bounded poll for the real advance-transition POST to appear in this step's
|
|
941
978
|
* capture window. The verifiers snapshot once after `STEP_PAUSE_MS`, but the
|
|
942
979
|
* genuine `TransitionWorklet(type="next")` POST can land hundreds of ms to 2s+
|
|
943
|
-
* AFTER that snapshot (
|
|
980
|
+
* AFTER that snapshot (the wizard ATS fires a fast `WorkletPayload` autosave first). A
|
|
944
981
|
* one-shot check false-negatives the advance, retries the click, and the stale
|
|
945
982
|
* retry fires a `back` — a next→back oscillation that never leaves the page.
|
|
946
983
|
* Re-check {@link windowHasAdvanceTransition} every `intervalMs` until it matches
|
|
@@ -963,7 +1000,7 @@ export declare function waitForTransitionBody(params: {
|
|
|
963
1000
|
* Pick an option to satisfy a REQUIRED select on a catch-all step, from the
|
|
964
1001
|
* select's option TEXTS (placeholder already excluded upstream). Policy: take
|
|
965
1002
|
* the first non-decline option (a plausible substantive answer — the operator
|
|
966
|
-
* accepts LLM-plausible answers reaching
|
|
1003
|
+
* accepts LLM-plausible answers reaching ATS prod); fall back to the first
|
|
967
1004
|
* option only if every option is a decline/placeholder. Returns null when there
|
|
968
1005
|
* is nothing selectable. Pure + exported so the policy is unit-testable; the LLM
|
|
969
1006
|
* path ({@link judgeSelectOptionWithLLM}) is preferred when a client is present,
|
|
@@ -992,7 +1029,7 @@ export type RadioGroupCandidate = {
|
|
|
992
1029
|
};
|
|
993
1030
|
/**
|
|
994
1031
|
* Build an XPath predicate that matches an `<input>` by its `id`, safe for any
|
|
995
|
-
* id value. MUI
|
|
1032
|
+
* id value. MUI wizard radio ids are base64-ish (no double-quote), so a plain
|
|
996
1033
|
* quoted literal suffices — but if an id ever contains a `"`, fall back to
|
|
997
1034
|
* `concat(...)` so the XPath stays valid. Pure + exported for unit tests.
|
|
998
1035
|
*/
|
|
@@ -1002,7 +1039,7 @@ export declare function buildRadioIdXPath(id: string): string;
|
|
|
1002
1039
|
* positionally. Pure (no DOM/LLM) so it is unit-testable — the crux of the
|
|
1003
1040
|
* unlabeled-radio disambiguation.
|
|
1004
1041
|
*
|
|
1005
|
-
* Why this exists:
|
|
1042
|
+
* Why this exists: the wizard ATS's Basic Info has multiple UNLABELED yes/no groups
|
|
1006
1043
|
* (visa-sponsorship, common-domicile), answered by consecutive flow steps. The
|
|
1007
1044
|
* old in-browser matcher treated an unlabeled group (`label===""`) as matching
|
|
1008
1045
|
* ANY question (`"".includes(q)`/`q.includes("")===0`), so two unlabeled "No"
|
|
@@ -1101,6 +1138,18 @@ export interface InvalidFormControl {
|
|
|
1101
1138
|
* action+value object; anything else becomes null).
|
|
1102
1139
|
*/
|
|
1103
1140
|
export declare function narrowInvalidFormControl(entry: unknown): InvalidFormControl | null;
|
|
1141
|
+
/**
|
|
1142
|
+
* Single source of truth for step-line prefixes. Exists because the cascade
|
|
1143
|
+
* here has only ever received `stepIndex`, while the orchestrator loop in
|
|
1144
|
+
* recon-browser owns the plan array — so half the step lines in a run printed
|
|
1145
|
+
* a `N/total` denominator and half printed a bare `N`.
|
|
1146
|
+
*
|
|
1147
|
+
* Takes a getter rather than a number: a global replan splices new steps into
|
|
1148
|
+
* the live plan array mid-run, so the total must be read when the line is
|
|
1149
|
+
* emitted, not when the step started. Callers with no total omit it and get
|
|
1150
|
+
* the bare form.
|
|
1151
|
+
*/
|
|
1152
|
+
export declare function formatStepPrefix(stepIndex: number, totalSteps?: () => number): string;
|
|
1104
1153
|
/**
|
|
1105
1154
|
* Cheap pre-cascade reachability gate. Runs before the 5-attempt healing cascade
|
|
1106
1155
|
* (and any global replan) so a step aimed at the wrong page state fails fast
|
|
@@ -1114,6 +1163,7 @@ export declare function probeStepBeforeAttempts(params: {
|
|
|
1114
1163
|
stagehand: Stagehand;
|
|
1115
1164
|
step: string;
|
|
1116
1165
|
stepIndex: number;
|
|
1166
|
+
totalSteps?: () => number;
|
|
1117
1167
|
logger: Logger;
|
|
1118
1168
|
captureFn?: CaptureFn;
|
|
1119
1169
|
}): Promise<"present" | "absent">;
|
|
@@ -1137,7 +1187,7 @@ export declare function executeStepWithHealing(params: {
|
|
|
1137
1187
|
/**
|
|
1138
1188
|
* When true, treat this step as the canonical submit click for the
|
|
1139
1189
|
* `submitEndpointPattern` verifier even if it is NOT the last step in
|
|
1140
|
-
* the flow. Set from the flow file's `submitStep: true`.
|
|
1190
|
+
* the flow. Set from the flow file's `submitStep: true`. One JSON-envelope ATS flow
|
|
1141
1191
|
* has its Submit click at index 55/328 — without this flag, the pre-
|
|
1142
1192
|
* submit DOM probe (gated on isFinalStep alone) never fires on the real
|
|
1143
1193
|
* Submit, so unfilled required fields produce silent submit failures.
|
|
@@ -1145,6 +1195,12 @@ export declare function executeStepWithHealing(params: {
|
|
|
1145
1195
|
*/
|
|
1146
1196
|
submitStep: boolean;
|
|
1147
1197
|
stepIndex: number;
|
|
1198
|
+
/**
|
|
1199
|
+
* Getter, not a number: a global replan splices new steps into the live plan
|
|
1200
|
+
* array mid-run, so the denominator must be read at log time or every line
|
|
1201
|
+
* after a replan prints a stale total.
|
|
1202
|
+
*/
|
|
1203
|
+
totalSteps?: () => number;
|
|
1148
1204
|
phase: string;
|
|
1149
1205
|
signalCounter: {
|
|
1150
1206
|
n: number;
|
|
@@ -1228,6 +1284,17 @@ export declare function executeStepWithHealing(params: {
|
|
|
1228
1284
|
* so an advance step never fires a save-and-exit / cancel / restart control.
|
|
1229
1285
|
*/
|
|
1230
1286
|
wizardExitButtonLabels: string[];
|
|
1287
|
+
/**
|
|
1288
|
+
* Live accessor for the running count of suppressed Stagehand AISDK
|
|
1289
|
+
* elementId-regex errors this session (see
|
|
1290
|
+
* `BrowserSession.getSuppressedAisdkElementIdErrorCount`). Corroborating
|
|
1291
|
+
* evidence only when a phantom click is detected — logged alongside the
|
|
1292
|
+
* escalation, never a trigger by itself (a nonzero count alone is too weak
|
|
1293
|
+
* a signal across a whole run). Omitted or absent on providers that don't
|
|
1294
|
+
* expose it (e.g. Steel); the phantom-click detection is unaffected either
|
|
1295
|
+
* way since it is keyed on the pre/post snapshot delta.
|
|
1296
|
+
*/
|
|
1297
|
+
getSuppressedAisdkElementIdErrorCount?: () => number;
|
|
1231
1298
|
/**
|
|
1232
1299
|
* Optional accumulator the cascade pushes onto when this step verifies.
|
|
1233
1300
|
* Lets the main loop maintain a short cross-step trajectory of `verifiedBy`
|