@enricai/barnacle 1.9.2 → 1.9.4

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.
@@ -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
- * Collapse any consecutive run of structurally-identical denormalized
209
- * steps into a single entry. Used by `persistReplannedFlow` before write-
210
- * back so cumulative-replan noise (each cascade-exhausted replan adds the
211
- * same recovery bridge to the tail; after 5 replans the persisted file
212
- * carries 5 stacked copies) doesn't pollute the on-disk flow.
213
- *
214
- * Comparison is structural via JSON-stringify equality:
215
- * - Two strings with the same value collapse.
216
- * - Two objects with the same {step, optional, upload} collapse.
217
- * - A string and an object with the same instruction do NOT collapse — they
218
- * are semantically different (bare = required, object = could be optional
219
- * or upload).
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
- * Only CONSECUTIVE duplicates collapse. A non-adjacent repeat is preserved
222
- * since flow authors sometimes intentionally re-fill a field after a
223
- * downstream interaction (e.g. re-fill First Name after the resume upload
224
- * triggers an Angular re-render). Adjacent duplicates are almost always
225
- * accumulation noise from successive replans converging on the same idea.
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 dedupeConsecutiveIdentical<T>(items: T[]): T[];
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 { dedupeConsecutiveIdentical, denormalizeStep, main, parseCli, persistReplannedFlow, readFailureDumpEvidence, replanRemainingFlow, snapshotAndPersistCookieJar, };
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":"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"}
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
- origin: "original",
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
- if (!step.optional && !step.upload && !step.submitStep && !hasSplicerHint) {
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
- * Collapse any consecutive run of structurally-identical denormalized
603
- * steps into a single entry. Used by `persistReplannedFlow` before write-
604
- * back so cumulative-replan noise (each cascade-exhausted replan adds the
605
- * same recovery bridge to the tail; after 5 replans the persisted file
606
- * carries 5 stacked copies) doesn't pollute the on-disk flow.
607
- *
608
- * Comparison is structural via JSON-stringify equality:
609
- * - Two strings with the same value collapse.
610
- * - Two objects with the same {step, optional, upload} collapse.
611
- * - A string and an object with the same instruction do NOT collapse — they
612
- * are semantically different (bare = required, object = could be optional
613
- * or upload).
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
- * Only CONSECUTIVE duplicates collapse. A non-adjacent repeat is preserved
616
- * since flow authors sometimes intentionally re-fill a field after a
617
- * downstream interaction (e.g. re-fill First Name after the resume upload
618
- * triggers an Angular re-render). Adjacent duplicates are almost always
619
- * accumulation noise from successive replans converging on the same idea.
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 dedupeConsecutiveIdentical(items) {
622
- if (items.length < 2)
623
- return [...items];
624
- const out = [items[0]];
625
- for (let i = 1; i < items.length; i++) {
626
- const prev = JSON.stringify(out[out.length - 1]);
627
- const curr = JSON.stringify(items[i]);
628
- if (prev !== curr)
629
- out.push(items[i]);
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
- const denormalizedSteps = finalPlan.map((step) => denormalizeStep(step.origin === "replan" && !step.optional ? { ...step, optional: true } : step));
791
- // Cumulative-replan dedupe: each cascade-exhausted replan appends a
792
- // recovery bridge to the tail; after several replans the persisted flow
793
- // would carry stacked copies of the same bridge. Collapse them so the
794
- // user reviewing the diff sees only the distinct LLM-discovered steps.
795
- const dedupedSteps = dedupeConsecutiveIdentical(denormalizedSteps);
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. Prefer structurally different recovery paths. If you re-use a prior step, pair it with new context that addresses why it failed before):
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 ? MAX_PROBE_REPLANS : MAX_CASCADE_REPLANS;
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