@enricai/barnacle 1.9.2 → 1.9.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/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/lib/llm/schemas.d.ts +10 -0
- package/dist/lib/llm/schemas.d.ts.map +1 -1
- package/dist/lib/llm/schemas.js +19 -0
- package/dist/lib/llm/schemas.js.map +1 -1
- package/dist/plugins/config-plugin.d.ts +5 -0
- package/dist/plugins/config-plugin.d.ts.map +1 -1
- package/dist/scraper/flow-runner.d.ts +1 -0
- package/dist/scraper/flow-runner.d.ts.map +1 -1
- package/dist/scraper/flow-runner.js +55 -41
- package/dist/scraper/flow-runner.js.map +1 -1
- package/dist/scripts/recon-browser.d.ts +79 -19
- package/dist/scripts/recon-browser.d.ts.map +1 -1
- package/dist/scripts/recon-browser.js +207 -47
- package/dist/scripts/recon-browser.js.map +1 -1
- package/dist/scripts/recon-generate.d.ts +32 -2
- package/dist/scripts/recon-generate.d.ts.map +1 -1
- package/dist/scripts/recon-generate.js +116 -13
- package/dist/scripts/recon-generate.js.map +1 -1
- package/package.json +1 -1
|
@@ -94,6 +94,28 @@ export declare function detectRejectionInResponseBody(body: unknown): {
|
|
|
94
94
|
rejected: boolean;
|
|
95
95
|
reason: string | null;
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* Records which captured POSTs are the real submission — the authoritative
|
|
99
|
+
* signal `recon-generate` needs to emit only the submit calls, rather than
|
|
100
|
+
* re-guessing from raw same-origin traffic (which cannot separate the
|
|
101
|
+
* submission from page-chrome POSTs that share its URL). Selection is by the
|
|
102
|
+
* flow's declared submit patterns: `submitEndpointPattern` on the request URL
|
|
103
|
+
* AND, when declared, `submitBodyPattern` on the request body — the same
|
|
104
|
+
* site-agnostic signals the verifier already trusts. No-op (writes nothing)
|
|
105
|
+
* when no `submitEndpointPattern` is declared, so runs without submit signals
|
|
106
|
+
* fall through to generate's existing heuristic unchanged.
|
|
107
|
+
*
|
|
108
|
+
* Capture files are named `<NNN>-<phase>-...json` with a zero-padded index, so
|
|
109
|
+
* lexicographic order == numeric order; `recon-generate`'s `readJsonDir` sorts
|
|
110
|
+
* identically, making the recorded `index` a stable cross-tool key.
|
|
111
|
+
*/
|
|
112
|
+
declare function writeSubmitManifest(params: {
|
|
113
|
+
runRoot: string;
|
|
114
|
+
capturesDir: string;
|
|
115
|
+
submitEndpointPattern: string | null;
|
|
116
|
+
submitBodyPattern: string | null;
|
|
117
|
+
logger: Logger;
|
|
118
|
+
}): void;
|
|
97
119
|
export declare function findWizardRestartSignal(params: {
|
|
98
120
|
preIdx: number;
|
|
99
121
|
capturesDir?: string;
|
|
@@ -120,6 +142,19 @@ export declare function isReplanCycle(priorReplans: readonly ReplanEvent[], newS
|
|
|
120
142
|
url: string;
|
|
121
143
|
htmlLength: number;
|
|
122
144
|
}): boolean;
|
|
145
|
+
declare const RECON_FLOW_SCHEMA: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
146
|
+
step: z.ZodString;
|
|
147
|
+
optional: z.ZodDefault<z.ZodBoolean>;
|
|
148
|
+
upload: z.ZodDefault<z.ZodBoolean>;
|
|
149
|
+
submitStep: z.ZodDefault<z.ZodBoolean>;
|
|
150
|
+
payloadField: z.ZodOptional<z.ZodString>;
|
|
151
|
+
payloadFieldNone: z.ZodOptional<z.ZodBoolean>;
|
|
152
|
+
targetId: z.ZodOptional<z.ZodString>;
|
|
153
|
+
origin: z.ZodOptional<z.ZodEnum<{
|
|
154
|
+
original: "original";
|
|
155
|
+
replan: "replan";
|
|
156
|
+
}>>;
|
|
157
|
+
}, z.core.$strip>]>>;
|
|
123
158
|
/**
|
|
124
159
|
* Two on-disk shapes are accepted:
|
|
125
160
|
*
|
|
@@ -152,6 +187,11 @@ export declare const RECON_FLOW_FILE_SCHEMA: z.ZodUnion<readonly [z.ZodArray<z.Z
|
|
|
152
187
|
submitStep: z.ZodDefault<z.ZodBoolean>;
|
|
153
188
|
payloadField: z.ZodOptional<z.ZodString>;
|
|
154
189
|
payloadFieldNone: z.ZodOptional<z.ZodBoolean>;
|
|
190
|
+
targetId: z.ZodOptional<z.ZodString>;
|
|
191
|
+
origin: z.ZodOptional<z.ZodEnum<{
|
|
192
|
+
original: "original";
|
|
193
|
+
replan: "replan";
|
|
194
|
+
}>>;
|
|
155
195
|
}, z.core.$strip>]>>, z.ZodObject<{
|
|
156
196
|
steps: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
157
197
|
step: z.ZodString;
|
|
@@ -160,9 +200,15 @@ export declare const RECON_FLOW_FILE_SCHEMA: z.ZodUnion<readonly [z.ZodArray<z.Z
|
|
|
160
200
|
submitStep: z.ZodDefault<z.ZodBoolean>;
|
|
161
201
|
payloadField: z.ZodOptional<z.ZodString>;
|
|
162
202
|
payloadFieldNone: z.ZodOptional<z.ZodBoolean>;
|
|
203
|
+
targetId: z.ZodOptional<z.ZodString>;
|
|
204
|
+
origin: z.ZodOptional<z.ZodEnum<{
|
|
205
|
+
original: "original";
|
|
206
|
+
replan: "replan";
|
|
207
|
+
}>>;
|
|
163
208
|
}, z.core.$strip>]>>;
|
|
164
209
|
frameSelector: z.ZodOptional<z.ZodString>;
|
|
165
210
|
submitEndpointPattern: z.ZodOptional<z.ZodString>;
|
|
211
|
+
submitBodyPattern: z.ZodOptional<z.ZodString>;
|
|
166
212
|
submittedStateSelectors: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
167
213
|
requireSubmitEndpointMatch: z.ZodOptional<z.ZodBoolean>;
|
|
168
214
|
successUrlFragments: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -188,8 +234,10 @@ interface NormalizedStep {
|
|
|
188
234
|
submitStep?: boolean;
|
|
189
235
|
payloadField?: string;
|
|
190
236
|
payloadFieldNone?: boolean;
|
|
237
|
+
targetId?: string;
|
|
191
238
|
origin: "original" | "replan";
|
|
192
239
|
}
|
|
240
|
+
declare function normalizeFlow(steps: z.infer<typeof RECON_FLOW_SCHEMA>): NormalizedStep[];
|
|
193
241
|
/**
|
|
194
242
|
* Inverse of normalizeFlow: maps an internal `NormalizedStep` back to the
|
|
195
243
|
* on-disk union shape. Bare string for the common case (required,
|
|
@@ -203,28 +251,37 @@ declare function denormalizeStep(step: NormalizedStep): string | {
|
|
|
203
251
|
submitStep?: true;
|
|
204
252
|
payloadField?: string;
|
|
205
253
|
payloadFieldNone?: true;
|
|
254
|
+
targetId?: string;
|
|
255
|
+
origin?: "replan";
|
|
206
256
|
};
|
|
207
257
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
*
|
|
258
|
+
* Order-independent structural dedup: drops any item byte-identical to an
|
|
259
|
+
* earlier kept one, anywhere in the list (not just adjacent). The real
|
|
260
|
+
* cross-run accretion is identical recovery blocks stacked NON-adjacently
|
|
261
|
+
* (each cascade-exhausted replan re-appends its bridge to the tail, separated
|
|
262
|
+
* by other steps), which `dedupeConsecutiveIdentical` never compares. Keys on
|
|
263
|
+
* full JSON content, so two items collapse iff identical in every field — it
|
|
264
|
+
* can never merge two genuinely-different steps. First occurrence wins,
|
|
265
|
+
* preserving flow order.
|
|
266
|
+
*/
|
|
267
|
+
export declare function dedupeIdenticalSteps<T>(items: T[]): T[];
|
|
268
|
+
/**
|
|
269
|
+
* Global, deterministic dedup on stable DOM target identity, applied at persist
|
|
270
|
+
* time. A wizard section the replanner re-discovers across runs is described in
|
|
271
|
+
* fresh prose each time (the replan prompt rewards structurally-different
|
|
272
|
+
* wording), so string/label dedup never collapses it and the persisted flow
|
|
273
|
+
* never converges. Because a step's `targetId` is the SAME DOM element
|
|
274
|
+
* regardless of wording, keying on it collapses those reworded duplicates
|
|
275
|
+
* deterministically.
|
|
220
276
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
277
|
+
* Conservative by construction: only drops a `"replan"`-origin step whose
|
|
278
|
+
* `targetId` an EARLIER kept step already covers. Authored (`"original"`) steps
|
|
279
|
+
* are never dropped — that protects a legitimately-repeated authored action
|
|
280
|
+
* (e.g. re-fill after a re-render). Steps without a `targetId` are never
|
|
281
|
+
* dropped here; they fall through to the structural pass. First occurrence
|
|
282
|
+
* wins.
|
|
226
283
|
*/
|
|
227
|
-
declare function
|
|
284
|
+
export declare function dedupeReplanStepsByTarget(steps: NormalizedStep[]): NormalizedStep[];
|
|
228
285
|
/**
|
|
229
286
|
* Resume-from-failure filter for replan output. The global replanner should emit
|
|
230
287
|
* only a recovery BRIDGE from the failure point (the original remaining tail is
|
|
@@ -421,8 +478,11 @@ declare function parseCli(): {
|
|
|
421
478
|
advancedStealth: boolean;
|
|
422
479
|
dumpDomBeforeStep: number | null;
|
|
423
480
|
allocateEmailEnvVar: string | null;
|
|
481
|
+
maxCascadeReplans: number | null;
|
|
482
|
+
maxProbeReplans: number | null;
|
|
424
483
|
frameSelector: string | null;
|
|
425
484
|
submitEndpointPattern: string | null;
|
|
485
|
+
submitBodyPattern: string | null;
|
|
426
486
|
submittedStateSelectors: string[];
|
|
427
487
|
requireSubmitEndpointMatch: boolean;
|
|
428
488
|
advanceTransitionBodyPattern: string | null;
|
|
@@ -439,5 +499,5 @@ declare function main(): Promise<void>;
|
|
|
439
499
|
export type { Html5DateFillResult, InvalidFormControl, LeafInvalidField, RadioGroupCandidate, ValidationRejectionPair, VerifyFillReadbackResult, } from "../scraper/flow-runner";
|
|
440
500
|
export { buildRadioIdXPath, capturesAfterIndex, chooseRequiredSelectOption, describeAttemptEffectSignals, extractGaEventEvidence, extractSubmitFailureEvidence, fillHtml5DateTimeInput, fillTextDatepickerInput, findRecentBackendError, findRecentPageTransition, formatValidationRejectedReason, hasBillingErrorBeenLogged, isAdvanceStalled, isAdvanceStep, isDomOnlyAdvanceVerified, isSubmitRevealedInvalid, isUploadAffordanceLabel, isWizardExitAction, latestCaptureIndex, logBillingErrorIfPresent, narrowInvalidFormControl, normalizeDateValue, pairInvalidWithErrors, parseCaptureIndex, parseRadioStep, parseSelectStep, pollEnumerate, probeLeafInvalidContainers, probeStepBeforeAttempts, renderLeafInvalidFields, renderUnfocusedObserve, rephraseWithLLM, resetBillingErrorFlagForTests, selectBodyExcerpt, selectRadioGroupOption, shouldSkipTechnique, shouldVetoFallbackAdvance, shouldWarnMissingAdvancePattern, verifyFillReadback, waitForTransitionBody, windowHasAdvanceTransition, windowHasTransitionBody, writeFixtureToTempFile, } from "../scraper/flow-runner";
|
|
441
501
|
export type { NormalizedStep, ReplanEvent };
|
|
442
|
-
export {
|
|
502
|
+
export { denormalizeStep, main, normalizeFlow, parseCli, persistReplannedFlow, readFailureDumpEvidence, replanRemainingFlow, snapshotAndPersistCookieJar, writeSubmitManifest, };
|
|
443
503
|
//# sourceMappingURL=recon-browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recon-browser.d.ts","sourceRoot":"","sources":["../../src/scripts/recon-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAMH,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAU,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAEnF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAwB3B,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,SAAS,EAkBf,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAO5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAM9C,UAAU,WAAW;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,GAAG,mBAAmB,CAAC;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAoED;;;;;;;;;GASG;AACH,iBAAe,2BAA2B,CACxC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAUf;
|
|
1
|
+
{"version":3,"file":"recon-browser.d.ts","sourceRoot":"","sources":["../../src/scripts/recon-browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,19 +54,22 @@ exports.findWizardRestartSignal = findWizardRestartSignal;
|
|
|
54
54
|
exports.isStructurallyBlocked = isStructurallyBlocked;
|
|
55
55
|
exports.summarizeReplanFailureKinds = summarizeReplanFailureKinds;
|
|
56
56
|
exports.isReplanCycle = isReplanCycle;
|
|
57
|
+
exports.dedupeIdenticalSteps = dedupeIdenticalSteps;
|
|
58
|
+
exports.dedupeReplanStepsByTarget = dedupeReplanStepsByTarget;
|
|
57
59
|
exports.filterCompletedFromReplan = filterCompletedFromReplan;
|
|
58
60
|
exports.filterReplanDuplicatingNextAuthored = filterReplanDuplicatingNextAuthored;
|
|
59
61
|
exports.isReplanReproposingFailedStep = isReplanReproposingFailedStep;
|
|
60
62
|
exports.renderStepWindow = renderStepWindow;
|
|
61
63
|
exports.countSlugPrefixMatches = countSlugPrefixMatches;
|
|
62
|
-
exports.dedupeConsecutiveIdentical = dedupeConsecutiveIdentical;
|
|
63
64
|
exports.denormalizeStep = denormalizeStep;
|
|
64
65
|
exports.main = main;
|
|
66
|
+
exports.normalizeFlow = normalizeFlow;
|
|
65
67
|
exports.parseCli = parseCli;
|
|
66
68
|
exports.persistReplannedFlow = persistReplannedFlow;
|
|
67
69
|
exports.readFailureDumpEvidence = readFailureDumpEvidence;
|
|
68
70
|
exports.replanRemainingFlow = replanRemainingFlow;
|
|
69
71
|
exports.snapshotAndPersistCookieJar = snapshotAndPersistCookieJar;
|
|
72
|
+
exports.writeSubmitManifest = writeSubmitManifest;
|
|
70
73
|
const node_crypto_1 = require("node:crypto");
|
|
71
74
|
const node_fs_1 = require("node:fs");
|
|
72
75
|
const node_path_1 = require("node:path");
|
|
@@ -171,14 +174,6 @@ async function snapshotAndPersistCookieJar(page, counter, label, phase, stepInde
|
|
|
171
174
|
logger.warn(`cookie-snapshot-write skipped for ${filename}: ${(0, errors_1.toErrorMessage)(err)}`);
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
|
-
const MAX_PROBE_REPLANS = 5;
|
|
175
|
-
/**
|
|
176
|
-
* Replans triggered by the full self-healing cascade exhausting all 4
|
|
177
|
-
* attempts (expensive: 4 attempts × backoff + LLM rephrase + observe
|
|
178
|
-
* calls). Counted separately from probe replans so cheap recoveries don't
|
|
179
|
-
* consume the budget reserved for expensive recoveries.
|
|
180
|
-
*/
|
|
181
|
-
const MAX_CASCADE_REPLANS = 5;
|
|
182
177
|
/**
|
|
183
178
|
* Validates the RECON_GOTO_WAIT_UNTIL override before it reaches Stagehand,
|
|
184
179
|
* which accepts a narrower set of load states than Playwright does — `commit`
|
|
@@ -344,6 +339,59 @@ function auditFinalSubmitMatch(params) {
|
|
|
344
339
|
}
|
|
345
340
|
return { auditFailed: true, rejectionReason: lastRejectionReason };
|
|
346
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Records which captured POSTs are the real submission — the authoritative
|
|
344
|
+
* signal `recon-generate` needs to emit only the submit calls, rather than
|
|
345
|
+
* re-guessing from raw same-origin traffic (which cannot separate the
|
|
346
|
+
* submission from page-chrome POSTs that share its URL). Selection is by the
|
|
347
|
+
* flow's declared submit patterns: `submitEndpointPattern` on the request URL
|
|
348
|
+
* AND, when declared, `submitBodyPattern` on the request body — the same
|
|
349
|
+
* site-agnostic signals the verifier already trusts. No-op (writes nothing)
|
|
350
|
+
* when no `submitEndpointPattern` is declared, so runs without submit signals
|
|
351
|
+
* fall through to generate's existing heuristic unchanged.
|
|
352
|
+
*
|
|
353
|
+
* Capture files are named `<NNN>-<phase>-...json` with a zero-padded index, so
|
|
354
|
+
* lexicographic order == numeric order; `recon-generate`'s `readJsonDir` sorts
|
|
355
|
+
* identically, making the recorded `index` a stable cross-tool key.
|
|
356
|
+
*/
|
|
357
|
+
function writeSubmitManifest(params) {
|
|
358
|
+
const { runRoot, capturesDir, submitEndpointPattern, submitBodyPattern, logger } = params;
|
|
359
|
+
if (submitEndpointPattern === null)
|
|
360
|
+
return;
|
|
361
|
+
const endpointRx = new RegExp(submitEndpointPattern);
|
|
362
|
+
const bodyRx = submitBodyPattern === null ? null : new RegExp(submitBodyPattern);
|
|
363
|
+
let entries;
|
|
364
|
+
try {
|
|
365
|
+
entries = (0, node_fs_1.readdirSync)(capturesDir)
|
|
366
|
+
.filter((f) => f.endsWith(".json") && !f.endsWith(".decoded.json"))
|
|
367
|
+
.sort();
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
logger.warn(`submit-manifest: could not read captures dir ${capturesDir}: ${(0, errors_1.toErrorMessage)(err)}`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const matched = [];
|
|
374
|
+
for (const [index, filename] of entries.entries()) {
|
|
375
|
+
try {
|
|
376
|
+
const data = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(capturesDir, filename), "utf8"));
|
|
377
|
+
if (data.method === "GET" || typeof data.url !== "string")
|
|
378
|
+
continue;
|
|
379
|
+
if (!endpointRx.test(data.url))
|
|
380
|
+
continue;
|
|
381
|
+
if (bodyRx !== null && !bodyRx.test(data.requestPostData ?? ""))
|
|
382
|
+
continue;
|
|
383
|
+
matched.push({ index, filename, url: data.url });
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// Ignore unparseable/non-capture files, as the audit does.
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (matched.length === 0) {
|
|
390
|
+
logger.warn(`submit-manifest: no capture matched submitEndpointPattern${submitBodyPattern !== null ? " + submitBodyPattern" : ""} — writing empty manifest so generate reports the gap rather than silently re-guessing`);
|
|
391
|
+
}
|
|
392
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(runRoot, "submit-manifest.json"), `${JSON.stringify(matched, null, 2)}\n`);
|
|
393
|
+
logger.info(`submit-manifest: recorded ${matched.length} submission capture(s)`);
|
|
394
|
+
}
|
|
347
395
|
function findWizardRestartSignal(params) {
|
|
348
396
|
const { preIdx, restartSignalUrlPatterns } = params;
|
|
349
397
|
if (restartSignalUrlPatterns.length === 0)
|
|
@@ -460,6 +508,17 @@ exports.RECON_FLOW_FILE_SCHEMA = v4_1.z.union([
|
|
|
460
508
|
*/
|
|
461
509
|
frameSelector: v4_1.z.string().min(1).optional(),
|
|
462
510
|
submitEndpointPattern: v4_1.z.string().min(1).optional(),
|
|
511
|
+
/**
|
|
512
|
+
* Regex tested against a captured POST's request BODY to distinguish the
|
|
513
|
+
* real submission from page-chrome POSTs that share the submit URL. Some
|
|
514
|
+
* ATSes overload one endpoint by a body discriminator (e.g. a submit and a
|
|
515
|
+
* reference-lookup both POST the same path, separable only by a body key),
|
|
516
|
+
* so `submitEndpointPattern` (URL) alone keeps both. When set,
|
|
517
|
+
* `recon-generate` emits only POSTs whose body ALSO matches this pattern —
|
|
518
|
+
* isolating the submission. Opt-in: sites that don't set it keep URL-only
|
|
519
|
+
* selection, so no cross-site regression.
|
|
520
|
+
*/
|
|
521
|
+
submitBodyPattern: v4_1.z.string().min(1).optional(),
|
|
463
522
|
submittedStateSelectors: v4_1.z.array(v4_1.z.string().min(1)).optional(),
|
|
464
523
|
/**
|
|
465
524
|
* When true, the final-step verifier accepts ONLY a `submitEndpointPattern`
|
|
@@ -549,7 +608,9 @@ function normalizeFlow(steps) {
|
|
|
549
608
|
submitStep: s.submitStep,
|
|
550
609
|
payloadField: s.payloadField,
|
|
551
610
|
payloadFieldNone: s.payloadFieldNone,
|
|
552
|
-
|
|
611
|
+
targetId: s.targetId,
|
|
612
|
+
// Preserve a persisted replan marker across runs; absence = authored.
|
|
613
|
+
origin: s.origin ?? "original",
|
|
553
614
|
});
|
|
554
615
|
}
|
|
555
616
|
/**
|
|
@@ -580,7 +641,12 @@ function substituteFlowEnvVars(steps) {
|
|
|
580
641
|
*/
|
|
581
642
|
function denormalizeStep(step) {
|
|
582
643
|
const hasSplicerHint = step.payloadField !== undefined || step.payloadFieldNone === true;
|
|
583
|
-
|
|
644
|
+
// targetId and a "replan" origin are load-bearing identity that must survive
|
|
645
|
+
// the round-trip, so a step carrying either is forced to object form even when
|
|
646
|
+
// it has no flags. Authored ("original") origin stays implicit — persisting it
|
|
647
|
+
// on every step would needlessly object-ify the whole flow file.
|
|
648
|
+
const hasIdentity = step.targetId !== undefined || step.origin === "replan";
|
|
649
|
+
if (!step.optional && !step.upload && !step.submitStep && !hasSplicerHint && !hasIdentity) {
|
|
584
650
|
return step.instruction;
|
|
585
651
|
}
|
|
586
652
|
const out = {
|
|
@@ -596,37 +662,60 @@ function denormalizeStep(step) {
|
|
|
596
662
|
out.payloadField = step.payloadField;
|
|
597
663
|
if (step.payloadFieldNone === true)
|
|
598
664
|
out.payloadFieldNone = true;
|
|
665
|
+
if (step.targetId !== undefined)
|
|
666
|
+
out.targetId = step.targetId;
|
|
667
|
+
if (step.origin === "replan")
|
|
668
|
+
out.origin = "replan";
|
|
599
669
|
return out;
|
|
600
670
|
}
|
|
601
671
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
672
|
+
* Order-independent structural dedup: drops any item byte-identical to an
|
|
673
|
+
* earlier kept one, anywhere in the list (not just adjacent). The real
|
|
674
|
+
* cross-run accretion is identical recovery blocks stacked NON-adjacently
|
|
675
|
+
* (each cascade-exhausted replan re-appends its bridge to the tail, separated
|
|
676
|
+
* by other steps), which `dedupeConsecutiveIdentical` never compares. Keys on
|
|
677
|
+
* full JSON content, so two items collapse iff identical in every field — it
|
|
678
|
+
* can never merge two genuinely-different steps. First occurrence wins,
|
|
679
|
+
* preserving flow order.
|
|
680
|
+
*/
|
|
681
|
+
function dedupeIdenticalSteps(items) {
|
|
682
|
+
const seen = new Set();
|
|
683
|
+
const out = [];
|
|
684
|
+
for (const item of items) {
|
|
685
|
+
const key = JSON.stringify(item);
|
|
686
|
+
if (seen.has(key))
|
|
687
|
+
continue;
|
|
688
|
+
seen.add(key);
|
|
689
|
+
out.push(item);
|
|
690
|
+
}
|
|
691
|
+
return out;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Global, deterministic dedup on stable DOM target identity, applied at persist
|
|
695
|
+
* time. A wizard section the replanner re-discovers across runs is described in
|
|
696
|
+
* fresh prose each time (the replan prompt rewards structurally-different
|
|
697
|
+
* wording), so string/label dedup never collapses it and the persisted flow
|
|
698
|
+
* never converges. Because a step's `targetId` is the SAME DOM element
|
|
699
|
+
* regardless of wording, keying on it collapses those reworded duplicates
|
|
700
|
+
* deterministically.
|
|
614
701
|
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
702
|
+
* Conservative by construction: only drops a `"replan"`-origin step whose
|
|
703
|
+
* `targetId` an EARLIER kept step already covers. Authored (`"original"`) steps
|
|
704
|
+
* are never dropped — that protects a legitimately-repeated authored action
|
|
705
|
+
* (e.g. re-fill after a re-render). Steps without a `targetId` are never
|
|
706
|
+
* dropped here; they fall through to the structural pass. First occurrence
|
|
707
|
+
* wins.
|
|
620
708
|
*/
|
|
621
|
-
function
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (
|
|
629
|
-
|
|
709
|
+
function dedupeReplanStepsByTarget(steps) {
|
|
710
|
+
const seenTargets = new Set();
|
|
711
|
+
const out = [];
|
|
712
|
+
for (const step of steps) {
|
|
713
|
+
if (step.origin === "replan" && step.targetId !== undefined && seenTargets.has(step.targetId)) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (step.targetId !== undefined)
|
|
717
|
+
seenTargets.add(step.targetId);
|
|
718
|
+
out.push(step);
|
|
630
719
|
}
|
|
631
720
|
return out;
|
|
632
721
|
}
|
|
@@ -787,12 +876,19 @@ function persistReplannedFlow(params) {
|
|
|
787
876
|
// cascade still fires for the original employer when the persisted flow
|
|
788
877
|
// is replayed. Required to keep cross-employer sweeps from regressing
|
|
789
878
|
// across runs.
|
|
790
|
-
|
|
791
|
-
//
|
|
792
|
-
//
|
|
793
|
-
//
|
|
794
|
-
//
|
|
795
|
-
const
|
|
879
|
+
// Deterministic convergence: first drop replan-origin steps whose stable DOM
|
|
880
|
+
// targetId an earlier step already covers (collapses reworded re-discoveries
|
|
881
|
+
// of the same section across runs — the churn no string/label dedup catches).
|
|
882
|
+
// Runs on NormalizedStep[] because it needs `.origin`/`.targetId`, both erased
|
|
883
|
+
// by denormalizeStep.
|
|
884
|
+
const targetDeduped = dedupeReplanStepsByTarget(finalPlan);
|
|
885
|
+
const denormalizedSteps = targetDeduped.map((step) => denormalizeStep(step.origin === "replan" && !step.optional ? { ...step, optional: true } : step));
|
|
886
|
+
// Then a global structural pass for identical blocks that carry no targetId
|
|
887
|
+
// (e.g. label-less clickable bridges): each cascade-exhausted replan re-appends
|
|
888
|
+
// its bridge to the tail, and successive runs stack byte-identical copies at
|
|
889
|
+
// NON-adjacent positions. Global JSON-equality collapses them; it can never
|
|
890
|
+
// merge two genuinely-different steps.
|
|
891
|
+
const dedupedSteps = dedupeIdenticalSteps(denormalizedSteps);
|
|
796
892
|
// 2-space indent + trailing newline matches the existing on-disk style
|
|
797
893
|
// (verified against site recon-flow.json files).
|
|
798
894
|
const payload = originalShape === "object"
|
|
@@ -1058,7 +1154,7 @@ ${errorTextList || "(none)"}
|
|
|
1058
1154
|
STRUCTURED SERVER-SIDE VALIDATION ERRORS (parsed from captured 4xx responses to the submit endpoint — when populated, the form's submit DID fire and the server rejected it with specific feedback):
|
|
1059
1155
|
${submitFailureList || "(none)"}
|
|
1060
1156
|
|
|
1061
|
-
PRIOR REPLAN HISTORY (proposals from previous replans for this same failure; the cascade executed each one and verification still failed at the time it ran. Between replans, the page may have advanced — a step that failed earlier could potentially work now if intervening steps filled missing prerequisites or dismissed blockers. But re-proposing the EXACT same multi-step sequence that already failed is unlikely to produce a different outcome.
|
|
1157
|
+
PRIOR REPLAN HISTORY (proposals from previous replans for this same failure; the cascade executed each one and verification still failed at the time it ran. Between replans, the page may have advanced — a step that failed earlier could potentially work now if intervening steps filled missing prerequisites or dismissed blockers. But re-proposing the EXACT same multi-step sequence that already failed is unlikely to produce a different outcome. When a prior proposal targeted the WRONG control (misread the DOM), target the correct control instead. Do NOT, however, merely re-describe an action on a control that a completed step already handled — same control, fresh wording is not progress and only bloats the flow; advance to the next unhandled control instead. If you re-use a prior step, pair it with new context that addresses why it failed before):
|
|
1062
1158
|
${priorReplanList || "(none)"}
|
|
1063
1159
|
|
|
1064
1160
|
PRIOR STEP TRAJECTORY (how the last few completed steps verified — url / submitted-state-dom signal pages that visibly transitioned; network / dom signal pages that stayed static. Use this to distinguish "page has been advancing through the flow" from "page has been static and the form is still in front of us"; avoid proposing regression-style steps if the trajectory shows recent transitions):
|
|
@@ -1280,6 +1376,8 @@ function parseCli() {
|
|
|
1280
1376
|
let advancedStealth = false;
|
|
1281
1377
|
let dumpDomBeforeStep = null;
|
|
1282
1378
|
let allocateEmailEnvVar = null;
|
|
1379
|
+
let maxCascadeReplans = null;
|
|
1380
|
+
let maxProbeReplans = null;
|
|
1283
1381
|
let allowEmptyFlow = false;
|
|
1284
1382
|
for (let i = 0; i < args.length; i++) {
|
|
1285
1383
|
if (args[i] === "--url" && args[i + 1]) {
|
|
@@ -1324,12 +1422,28 @@ function parseCli() {
|
|
|
1324
1422
|
}
|
|
1325
1423
|
allocateEmailEnvVar = name;
|
|
1326
1424
|
}
|
|
1425
|
+
else if (args[i] === "--max-cascade-replans" && args[i + 1]) {
|
|
1426
|
+
const n = Number.parseInt(args[++i], 10);
|
|
1427
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
1428
|
+
logger.error(`--max-cascade-replans must be a positive integer (got ${JSON.stringify(args[i])})`);
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
maxCascadeReplans = n;
|
|
1432
|
+
}
|
|
1433
|
+
else if (args[i] === "--max-probe-replans" && args[i + 1]) {
|
|
1434
|
+
const n = Number.parseInt(args[++i], 10);
|
|
1435
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
1436
|
+
logger.error(`--max-probe-replans must be a positive integer (got ${JSON.stringify(args[i])})`);
|
|
1437
|
+
process.exit(1);
|
|
1438
|
+
}
|
|
1439
|
+
maxProbeReplans = n;
|
|
1440
|
+
}
|
|
1327
1441
|
else if (args[i] === "--allow-empty-flow") {
|
|
1328
1442
|
allowEmptyFlow = true;
|
|
1329
1443
|
}
|
|
1330
1444
|
}
|
|
1331
1445
|
if (!url) {
|
|
1332
|
-
logger.error('usage: recon-browser.ts --url <url> [--flow \'["step1","step2"]\'] [--flow-file <path>] [--provider browserbase|steel] [--upload-fixture <path>] [--no-save-replan] [--advanced-stealth] [--dump-dom-before-step <N>] [--allocate-email <ENV_VAR_NAME>] [--allow-empty-flow]');
|
|
1446
|
+
logger.error('usage: recon-browser.ts --url <url> [--flow \'["step1","step2"]\'] [--flow-file <path>] [--provider browserbase|steel] [--upload-fixture <path>] [--no-save-replan] [--advanced-stealth] [--dump-dom-before-step <N>] [--allocate-email <ENV_VAR_NAME>] [--max-cascade-replans <N>] [--max-probe-replans <N>] [--allow-empty-flow]');
|
|
1333
1447
|
process.exit(1);
|
|
1334
1448
|
}
|
|
1335
1449
|
if (flowFile) {
|
|
@@ -1355,8 +1469,11 @@ function parseCli() {
|
|
|
1355
1469
|
advancedStealth,
|
|
1356
1470
|
dumpDomBeforeStep,
|
|
1357
1471
|
allocateEmailEnvVar,
|
|
1472
|
+
maxCascadeReplans,
|
|
1473
|
+
maxProbeReplans,
|
|
1358
1474
|
frameSelector: null,
|
|
1359
1475
|
submitEndpointPattern: null,
|
|
1476
|
+
submitBodyPattern: null,
|
|
1360
1477
|
submittedStateSelectors: [],
|
|
1361
1478
|
requireSubmitEndpointMatch: false,
|
|
1362
1479
|
advanceTransitionBodyPattern: null,
|
|
@@ -1380,6 +1497,9 @@ function parseCli() {
|
|
|
1380
1497
|
const submitEndpointPattern = Array.isArray(parsed.data)
|
|
1381
1498
|
? null
|
|
1382
1499
|
: (parsed.data.submitEndpointPattern ?? null);
|
|
1500
|
+
const submitBodyPattern = Array.isArray(parsed.data)
|
|
1501
|
+
? null
|
|
1502
|
+
: (parsed.data.submitBodyPattern ?? null);
|
|
1383
1503
|
const submittedStateSelectors = Array.isArray(parsed.data)
|
|
1384
1504
|
? []
|
|
1385
1505
|
: (parsed.data.submittedStateSelectors ?? []);
|
|
@@ -1419,6 +1539,15 @@ function parseCli() {
|
|
|
1419
1539
|
process.exit(1);
|
|
1420
1540
|
}
|
|
1421
1541
|
}
|
|
1542
|
+
if (submitBodyPattern !== null) {
|
|
1543
|
+
try {
|
|
1544
|
+
new RegExp(submitBodyPattern);
|
|
1545
|
+
}
|
|
1546
|
+
catch (err) {
|
|
1547
|
+
logger.error(`flow file: submitBodyPattern is not a valid regex: ${(0, errors_1.toErrorMessage)(err)}`);
|
|
1548
|
+
process.exit(1);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1422
1551
|
return {
|
|
1423
1552
|
url,
|
|
1424
1553
|
flow: normalizeFlow(stepsRaw),
|
|
@@ -1429,8 +1558,11 @@ function parseCli() {
|
|
|
1429
1558
|
advancedStealth,
|
|
1430
1559
|
dumpDomBeforeStep,
|
|
1431
1560
|
allocateEmailEnvVar,
|
|
1561
|
+
maxCascadeReplans,
|
|
1562
|
+
maxProbeReplans,
|
|
1432
1563
|
frameSelector,
|
|
1433
1564
|
submitEndpointPattern,
|
|
1565
|
+
submitBodyPattern,
|
|
1434
1566
|
submittedStateSelectors,
|
|
1435
1567
|
requireSubmitEndpointMatch,
|
|
1436
1568
|
advanceTransitionBodyPattern,
|
|
@@ -1468,7 +1600,13 @@ function loadUploadFixture(path) {
|
|
|
1468
1600
|
}
|
|
1469
1601
|
}
|
|
1470
1602
|
async function main() {
|
|
1471
|
-
const { url, flow: rawFlow, flowFile, provider, uploadFixturePath, saveReplan, advancedStealth, dumpDomBeforeStep, allocateEmailEnvVar, frameSelector, submitEndpointPattern, submittedStateSelectors, requireSubmitEndpointMatch, advanceTransitionBodyPattern, successUrlFragments, successPageTitleHints, ownBackendHostnames, knownErrorClassPrefixes, wizardExitButtonLabels, restartSignalUrlPatterns, originalShape, allowEmptyFlow, } = parseCli();
|
|
1603
|
+
const { url, flow: rawFlow, flowFile, provider, uploadFixturePath, saveReplan, advancedStealth, dumpDomBeforeStep, allocateEmailEnvVar, maxCascadeReplans, maxProbeReplans, frameSelector, submitEndpointPattern, submitBodyPattern, submittedStateSelectors, requireSubmitEndpointMatch, advanceTransitionBodyPattern, successUrlFragments, successPageTitleHints, ownBackendHostnames, knownErrorClassPrefixes, wizardExitButtonLabels, restartSignalUrlPatterns, originalShape, allowEmptyFlow, } = parseCli();
|
|
1604
|
+
// Replan-budget precedence: CLI flag > RECON_MAX_*_REPLANS env
|
|
1605
|
+
// (config.scraper.max*Replans) > the built-in default constants. A known-deep
|
|
1606
|
+
// wizard can raise its own ceiling per-run without a code change; nothing set
|
|
1607
|
+
// preserves the historical budget of 5.
|
|
1608
|
+
const resolvedCascadeBudget = maxCascadeReplans ?? config_1.config.scraper.maxCascadeReplans;
|
|
1609
|
+
const resolvedProbeBudget = maxProbeReplans ?? config_1.config.scraper.maxProbeReplans;
|
|
1472
1610
|
// Allocate a fresh testmail.app inbox + bind it to the requested env var
|
|
1473
1611
|
// BEFORE substituting placeholders in the flow. Subsequent ${ENV_VAR}
|
|
1474
1612
|
// tokens anywhere in the flow's instruction strings resolve to the
|
|
@@ -1726,6 +1864,17 @@ async function main() {
|
|
|
1726
1864
|
onStepFailure: dumpStepFailure,
|
|
1727
1865
|
});
|
|
1728
1866
|
await snapshotAndPersistCookieJar(page, jarCounter, "post-step", currentPhase, i);
|
|
1867
|
+
// Stamp the step's stable DOM identity from the fast primitive that just
|
|
1868
|
+
// resolved it (pushed onto trajectory), so persistReplannedFlow can dedup
|
|
1869
|
+
// reworded re-discoveries of this field across runs on `targetId`. Only a
|
|
1870
|
+
// non-empty id is load-bearing; an empty string (element had no id) is
|
|
1871
|
+
// left off so the step falls back to structural dedup.
|
|
1872
|
+
const lastTrajectory = trajectory[trajectory.length - 1];
|
|
1873
|
+
if (lastTrajectory?.stepIndex === i &&
|
|
1874
|
+
lastTrajectory.targetId !== undefined &&
|
|
1875
|
+
lastTrajectory.targetId !== "") {
|
|
1876
|
+
plan[i] = { ...plan[i], targetId: lastTrajectory.targetId };
|
|
1877
|
+
}
|
|
1729
1878
|
// Wizard-restart detection: if a configured restart-signal URL (e.g.
|
|
1730
1879
|
// a wizard ATS's `init-apply?...&application_canceled=true`) landed during
|
|
1731
1880
|
// this step, the multi-page wizard reset to page 1. Remaining steps now
|
|
@@ -1833,7 +1982,7 @@ async function main() {
|
|
|
1833
1982
|
// the step is unrecoverable). Separate budgets so cheap recoveries
|
|
1834
1983
|
// don't eat into the budget reserved for expensive ones.
|
|
1835
1984
|
const isProbe = err.kind === "probe-absent";
|
|
1836
|
-
const budget = isProbe ?
|
|
1985
|
+
const budget = isProbe ? resolvedProbeBudget : resolvedCascadeBudget;
|
|
1837
1986
|
const usedSoFar = isProbe ? probeReplansUsed : cascadeReplansUsed;
|
|
1838
1987
|
if (usedSoFar >= budget) {
|
|
1839
1988
|
const kindsSummary = callsNdjsonPath !== null
|
|
@@ -1957,6 +2106,17 @@ async function main() {
|
|
|
1957
2106
|
}
|
|
1958
2107
|
}
|
|
1959
2108
|
stopCapture();
|
|
2109
|
+
// Authoritative submission record for recon-generate: which captured POSTs
|
|
2110
|
+
// are the real submission, keyed on the flow's declared submit patterns.
|
|
2111
|
+
// Runs whenever a pattern is declared (independent of the audit gate below),
|
|
2112
|
+
// since generate consumes it even when requireSubmitEndpointMatch is off.
|
|
2113
|
+
writeSubmitManifest({
|
|
2114
|
+
runRoot: runDir.root,
|
|
2115
|
+
capturesDir: runDir.graphqlDir,
|
|
2116
|
+
submitEndpointPattern,
|
|
2117
|
+
submitBodyPattern,
|
|
2118
|
+
logger,
|
|
2119
|
+
});
|
|
1960
2120
|
// End-of-run audit: when the flow declared submitEndpointPattern AND
|
|
1961
2121
|
// opted into requireSubmitEndpointMatch=true, scan ALL captures from
|
|
1962
2122
|
// this run for a pattern-matching 200 before declaring success. If no
|