@enricai/barnacle 1.6.2 → 1.6.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.
Files changed (83) hide show
  1. package/README.md +23 -18
  2. package/dist/api/errors.d.ts +3 -3
  3. package/dist/api/errors.d.ts.map +1 -1
  4. package/dist/api/errors.js +4 -4
  5. package/dist/api/errors.js.map +1 -1
  6. package/dist/api/helpers/reply.d.ts +1 -1
  7. package/dist/api/helpers/reply.js +1 -1
  8. package/dist/cache/keyed-ttl-cache.d.ts +4 -4
  9. package/dist/cache/keyed-ttl-cache.js +2 -2
  10. package/dist/lib/applicant-payload.d.ts +4 -4
  11. package/dist/lib/applicant-payload.js +4 -4
  12. package/dist/lib/application-address.d.ts +3 -3
  13. package/dist/lib/application-address.js +5 -5
  14. package/dist/lib/application-address.js.map +1 -1
  15. package/dist/lib/application-resume.d.ts +4 -4
  16. package/dist/lib/application-resume.js +4 -4
  17. package/dist/lib/dispatch-metrics.js +1 -1
  18. package/dist/lib/llm/judges/verify-submit.d.ts +1 -1
  19. package/dist/lib/llm/judges/verify-submit.js +1 -1
  20. package/dist/lib/llm/schemas.js +3 -3
  21. package/dist/lib/phone.d.ts +1 -1
  22. package/dist/lib/phone.js +1 -1
  23. package/dist/lib/telemetry/call-types.d.ts +1 -1
  24. package/dist/lib/telemetry/call-types.js +1 -1
  25. package/dist/lib/tracking-click.d.ts +5 -3
  26. package/dist/lib/tracking-click.d.ts.map +1 -1
  27. package/dist/lib/tracking-click.js +5 -3
  28. package/dist/lib/tracking-click.js.map +1 -1
  29. package/dist/lib/us-states.d.ts +1 -1
  30. package/dist/lib/us-states.js +1 -1
  31. package/dist/plugins/loader.js +2 -2
  32. package/dist/scraper/cookie-jar.d.ts +2 -2
  33. package/dist/scraper/cookie-jar.js +2 -2
  34. package/dist/scraper/errors.d.ts +1 -12
  35. package/dist/scraper/errors.d.ts.map +1 -1
  36. package/dist/scraper/errors.js +2 -16
  37. package/dist/scraper/errors.js.map +1 -1
  38. package/dist/scraper/flow-runner.d.ts +43 -27
  39. package/dist/scraper/flow-runner.d.ts.map +1 -1
  40. package/dist/scraper/flow-runner.js +70 -60
  41. package/dist/scraper/flow-runner.js.map +1 -1
  42. package/dist/scraper/http-client.d.ts +25 -3
  43. package/dist/scraper/http-client.d.ts.map +1 -1
  44. package/dist/scraper/http-client.js +95 -4
  45. package/dist/scraper/http-client.js.map +1 -1
  46. package/dist/scraper/rate-limited-json-client.d.ts +1 -1
  47. package/dist/scraper/rate-limited-json-client.d.ts.map +1 -1
  48. package/dist/scraper/raw-fetch.d.ts +2 -2
  49. package/dist/scraper/raw-fetch.js +1 -1
  50. package/dist/scraper/require-response-field.d.ts +1 -1
  51. package/dist/scraper/require-response-field.js +1 -1
  52. package/dist/scraper/session-warmup.d.ts +1 -1
  53. package/dist/scraper/session-warmup.js +1 -1
  54. package/dist/scripts/migrate-telemetry-dir-names.d.ts +2 -2
  55. package/dist/scripts/migrate-telemetry-dir-names.d.ts.map +1 -1
  56. package/dist/scripts/migrate-telemetry-dir-names.js +1 -1
  57. package/dist/scripts/recon-browser.d.ts +2 -2
  58. package/dist/scripts/recon-browser.js +14 -14
  59. package/dist/scripts/recon-browser.js.map +1 -1
  60. package/dist/scripts/recon-generate-multicall-fixture.d.ts +36 -0
  61. package/dist/scripts/recon-generate-multicall-fixture.d.ts.map +1 -0
  62. package/dist/scripts/recon-generate-multicall-fixture.js +102 -0
  63. package/dist/scripts/recon-generate-multicall-fixture.js.map +1 -0
  64. package/dist/scripts/recon-generate.d.ts +49 -0
  65. package/dist/scripts/recon-generate.d.ts.map +1 -1
  66. package/dist/scripts/recon-generate.js +129 -45
  67. package/dist/scripts/recon-generate.js.map +1 -1
  68. package/dist/scripts/recon-replay-jobs.d.ts +9 -4
  69. package/dist/scripts/recon-replay-jobs.d.ts.map +1 -1
  70. package/dist/scripts/recon-replay-jobs.js +15 -8
  71. package/dist/scripts/recon-replay-jobs.js.map +1 -1
  72. package/dist/scripts/recon-shared.d.ts +10 -0
  73. package/dist/scripts/recon-shared.d.ts.map +1 -1
  74. package/dist/scripts/recon-shared.js +25 -0
  75. package/dist/scripts/recon-shared.js.map +1 -1
  76. package/dist/site-plugin.d.ts +4 -4
  77. package/dist/testing/persona-fixture.d.ts +4 -4
  78. package/dist/testing/persona-fixture.d.ts.map +1 -1
  79. package/dist/testing/persona-fixture.js +4 -4
  80. package/dist/testing/persona-fixture.js.map +1 -1
  81. package/dist/types/dispatch-metrics.d.ts +1 -1
  82. package/dist/types/dispatch-metrics.js +1 -1
  83. package/package.json +1 -1
@@ -24,11 +24,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.resolveStepPayloadField = resolveStepPayloadField;
25
25
  exports.inferZodSchemaFromSamples = inferZodSchemaFromSamples;
26
26
  exports.selectPayloadAction = selectPayloadAction;
27
+ exports.selectReturnAction = selectReturnAction;
28
+ exports.selectEffectiveResponseBody = selectEffectiveResponseBody;
27
29
  exports.extractActionSequence = extractActionSequence;
28
30
  exports.detectFormSchemaFieldNames = detectFormSchemaFieldNames;
29
31
  exports.walkSetCookiePairs = walkSetCookiePairs;
30
32
  exports.indexStateValues = indexStateValues;
31
33
  exports.compileActionSteps = compileActionSteps;
34
+ exports.collectHeaderBindings = collectHeaderBindings;
32
35
  exports.loadQuestionPromptKeywords = loadQuestionPromptKeywords;
33
36
  exports.emitMultiStepExecuteHttp = emitMultiStepExecuteHttp;
34
37
  exports.emitContractTs = emitContractTs;
@@ -270,6 +273,31 @@ function isVoidResponse(body) {
270
273
  return body.length === 0;
271
274
  return Object.keys(body).length === 0;
272
275
  }
276
+ /**
277
+ * Finds actions whose endpoint was re-hit with a varying, non-void body —
278
+ * the signature of a search/inventory endpoint the page queries repeatedly,
279
+ * as opposed to an endpoint that merely fired once or repeated an identical
280
+ * (retry) or void (chatter/beacon) call. Shared by `selectPayloadAction` and
281
+ * `selectReturnAction`, which each pick a different fallback when nothing
282
+ * was re-queried.
283
+ */
284
+ function findRequeriedActions(steps) {
285
+ const bodiesByEndpoint = new Map();
286
+ for (const step of steps) {
287
+ const key = endpointKey(step.capture.url);
288
+ const bodies = bodiesByEndpoint.get(key) ?? new Set();
289
+ bodies.add(step.capture.requestPostData ?? "");
290
+ bodiesByEndpoint.set(key, bodies);
291
+ }
292
+ return steps.filter((step) => {
293
+ const bodies = bodiesByEndpoint.get(endpointKey(step.capture.url));
294
+ if (!bodies || bodies.size < 2)
295
+ return false;
296
+ // An endpoint re-hit with varying bodies but nothing to show for it is
297
+ // chatter — client-side error reporting, beacons — not the flow's subject.
298
+ return !isVoidResponse(step.capture.responseBody);
299
+ });
300
+ }
273
301
  /**
274
302
  * Picks the action whose request body should define the payload schema.
275
303
  *
@@ -291,23 +319,40 @@ function selectPayloadAction(steps) {
291
319
  const first = steps[0];
292
320
  if (!first)
293
321
  return null;
294
- const bodiesByEndpoint = new Map();
295
- for (const step of steps) {
296
- const key = endpointKey(step.capture.url);
297
- const bodies = bodiesByEndpoint.get(key) ?? new Set();
298
- bodies.add(step.capture.requestPostData ?? "");
299
- bodiesByEndpoint.set(key, bodies);
300
- }
301
- const requeried = steps.filter((step) => {
302
- const bodies = bodiesByEndpoint.get(endpointKey(step.capture.url));
303
- if (!bodies || bodies.size < 2)
304
- return false;
305
- // An endpoint re-hit with varying bodies but nothing to show for it is
306
- // chatter — client-side error reporting, beacons — not the flow's subject.
307
- return !isVoidResponse(step.capture.responseBody);
308
- });
322
+ const requeried = findRequeriedActions(steps);
309
323
  return requeried[0] ?? first;
310
324
  }
325
+ /**
326
+ * Picks the action whose response body `executeHttp` should return.
327
+ *
328
+ * Defaults to the last action, which is right for a transactional/submission
329
+ * flow: the final call is the terminal success signal the caller wants back.
330
+ * It is wrong when the flow is a read/search whose last call happens to be an
331
+ * incidental drill-down (e.g. previewing one result) rather than the search
332
+ * result itself — the same re-queried-endpoint signal `selectPayloadAction`
333
+ * uses to find the flow's subject applies here: an endpoint hit repeatedly
334
+ * with varying, non-void bodies is what the flow is about, and its most
335
+ * recent response is the freshest instance of that answer.
336
+ */
337
+ function selectReturnAction(steps) {
338
+ const last = steps[steps.length - 1] ?? null;
339
+ if (!last)
340
+ return null;
341
+ const requeried = findRequeriedActions(steps);
342
+ return requeried[requeried.length - 1] ?? last;
343
+ }
344
+ /**
345
+ * Picks the response body used to infer the emitted contract's response
346
+ * shape. MUST target the same call `selectReturnAction` returns — a
347
+ * submission flow's `executeHttp` and its inferred type/schema have to agree
348
+ * on which call they describe, or the emitted type disagrees with the value
349
+ * actually returned. Falls back to the replay body for single-endpoint sites.
350
+ */
351
+ function selectEffectiveResponseBody(isSubmissionFlow, actionSteps, replayResponseBody) {
352
+ if (!isSubmissionFlow)
353
+ return replayResponseBody;
354
+ return selectReturnAction(actionSteps)?.capture.responseBody ?? replayResponseBody;
355
+ }
311
356
  function deriveBaseUrl(captures) {
312
357
  for (const c of captures) {
313
358
  try {
@@ -1365,21 +1410,36 @@ function compileActionSteps(actions, stateIndex) {
1365
1410
  * step order — this is what `emitContractTs` renders as `createHttpClient`'s
1366
1411
  * `bind` option so the generated `executeHttp` actually forwards a value like
1367
1412
  * disneycruise's `Set-Cookie: __pa=<jwt>` mint to the stateful call that 401s
1368
- * without it. Deduped by `targetHeader`: `HttpResponseBinding` (http-client.ts)
1369
- * is one binding per target header, so if two steps somehow produced the same
1370
- * target the earliest wins.
1413
+ * without it. Deduped by `targetHeader.toLowerCase()` + `cookieName` (HTTP
1414
+ * header names are case-insensitive, and compileActionSteps derives
1415
+ * `targetHeader` verbatim from observed request-header casing, so the same
1416
+ * logical target can show up as e.g. `Cookie` on one step and `cookie` on
1417
+ * another): the `Cookie` request header carries many cookies by design, so
1418
+ * every distinct cookie-origin produce targeting it must survive (the
1419
+ * runtime accumulates them into one `Cookie` header per binding — see
1420
+ * http-client.ts); only a produce that re-mints the SAME cookie on a later
1421
+ * step collapses to its earliest occurrence. Non-cookie targets (e.g.
1422
+ * `X-Conversation-Id`) still keep first-wins, since `HttpResponseBinding` is
1423
+ * one binding per target header there and two steps producing the same
1424
+ * non-cookie target (even under differing casing) would otherwise race with
1425
+ * no defined winner.
1426
+ *
1427
+ * Exported for unit testing (as `walkSetCookiePairs` is) — lets tests exercise
1428
+ * the produce → bind collection step directly against synthetic ActionStep
1429
+ * sequences.
1371
1430
  */
1372
1431
  function collectHeaderBindings(actionSteps) {
1373
- const byTarget = new Map();
1432
+ const byKey = new Map();
1374
1433
  for (const step of actionSteps) {
1375
1434
  for (const p of step.produces) {
1376
1435
  if (p.kind !== "header")
1377
1436
  continue;
1378
- if (!byTarget.has(p.targetHeader))
1379
- byTarget.set(p.targetHeader, p);
1437
+ const key = `${p.targetHeader.toLowerCase()}\0${p.cookieName ?? ""}`;
1438
+ if (!byKey.has(key))
1439
+ byKey.set(key, p);
1380
1440
  }
1381
1441
  }
1382
- return [...byTarget.values()];
1442
+ return [...byKey.values()];
1383
1443
  }
1384
1444
  /**
1385
1445
  * Replaces occurrences of state values in `template` with `${varName}`
@@ -1859,7 +1919,14 @@ function emitMultiStepExecuteHttp(actions, inputBody, errorSignals, fieldNameMap
1859
1919
  .join(", ")} },`
1860
1920
  : "";
1861
1921
  const bodyArg = bodyTemplate ? `body: \`${bodyTemplate}\`,` : "";
1862
- rendered.push({ url, method: cap.method, headersExpr, bodyArg });
1922
+ // G2: each call gets its own schema, inferred from this step's captured
1923
+ // response — the client-level schema (z.unknown() for multi-step flows,
1924
+ // see emitContractTs) stays the plugin's caller-facing contract, not what
1925
+ // validates any individual call. Without this override, HttpRequestInit.schema
1926
+ // would default to the client's z.unknown() and narrowing the caller-facing
1927
+ // contract would enforce that narrowed shape on every call in the chain.
1928
+ const schemaExpr = inferZodSchema(cap.responseBody);
1929
+ rendered.push({ url, method: cap.method, headersExpr, bodyArg, schemaExpr });
1863
1930
  }
1864
1931
  // Identifier scan against the rendered text — captures `${foo}`, `${foo.bar}`,
1865
1932
  // etc. The first segment (anchored at `${`) is the binding's name. Closed
@@ -1879,9 +1946,11 @@ function emitMultiStepExecuteHttp(actions, inputBody, errorSignals, fieldNameMap
1879
1946
  }
1880
1947
  }
1881
1948
  }
1882
- // The last step's var is also referenced by the closing `return { data }`.
1883
- if (actions.length > 0)
1884
- referencedNames.add(actions[actions.length - 1].varName);
1949
+ // The relevance-selected step's var is also referenced by the closing
1950
+ // `return { data }` — see selectReturnAction.
1951
+ const returnAction = selectReturnAction(actions);
1952
+ if (returnAction)
1953
+ referencedNames.add(returnAction.varName);
1885
1954
  // Base64 Content overrides reference variables inside function calls
1886
1955
  // (e.g. buildBase64Content(payload, questionnaireId, ...)) that the
1887
1956
  // ${name} regex above doesn't capture. Add them explicitly.
@@ -1981,6 +2050,7 @@ function emitMultiStepExecuteHttp(actions, inputBody, errorSignals, fieldNameMap
1981
2050
  if (joined !== "") {
1982
2051
  lines.push(` ${joined}`);
1983
2052
  }
2053
+ lines.push(` schema: ${r.schemaExpr},`);
1984
2054
  if (needsBinding) {
1985
2055
  lines.push(` })) as Record<string, unknown>;`);
1986
2056
  }
@@ -2010,8 +2080,8 @@ function emitMultiStepExecuteHttp(actions, inputBody, errorSignals, fieldNameMap
2010
2080
  }
2011
2081
  lines.push("");
2012
2082
  }
2013
- const lastVar = actions.length > 0 ? actions[actions.length - 1].varName : "undefined";
2014
- lines.push(` return { data: ${lastVar} };`);
2083
+ const returnVar = returnAction ? returnAction.varName : "undefined";
2084
+ lines.push(` return { data: ${returnVar} };`);
2015
2085
  return lines.join("\n");
2016
2086
  }
2017
2087
  function summariseResponseShape(value) {
@@ -2049,16 +2119,25 @@ function bindOptionLiteral(headerBindings) {
2049
2119
  * unit tests can drive the emitter directly without spawning the CLI. */
2050
2120
  function emitContractTs(opts) {
2051
2121
  const { siteId, pascal, baseUrl, baseHeaders, minTime, safeRps, responseBody, gql, gqlQuery, endpointPath, auxFiles, multiStepBody, inputBody, hasMultipartStep = false, discoveredFormFields, fieldOptionsMap, discoveredOptionFields, discoveredRawOptionFields, discoveredAdditionalBodyKeys, payloadFieldNames, base64ContentHelper = "", headerBindings = [], } = opts;
2052
- // Multi-step plugins thread responses through many different shapes that a
2053
- // single Zod schema can't cover use z.unknown() so each per-step access
2054
- // compiles cleanly. Single-endpoint plugins keep the inferred schema.
2122
+ // This is the CLIENT-level schema createHttpClient's default, and the
2123
+ // plugin's caller-facing contract (what executeHttp's return value promises
2124
+ // its own caller). It does NOT validate any individual call in a multi-step
2125
+ // flow: emitMultiStepExecuteHttp threads a per-call `schema:` override
2126
+ // (inferred from that step's own capture) onto every httpClient(...)
2127
+ // invocation, so heterogeneous per-call shapes are each checked against
2128
+ // their own inferred schema regardless of what this client-level schema is.
2055
2129
  //
2056
- // This is deliberate, not an unfinished schema: a submission flow's terminal
2057
- // shape is the plugin's OWN contract with its caller (e.g. { verified: boolean }),
2058
- // a field that appears in zero captured responses. Inferring a schema from the
2059
- // captures would emit the wrong shape with false confidence. z.unknown() plus
2060
- // the generated `[ ] Narrow ResponseSchema` checklist item is the intended
2061
- // hand-off to the plugin author, who alone knows that contract.
2130
+ // For multi-step flows this stays z.unknown(), not an unfinished schema: a
2131
+ // submission flow's terminal shape is the plugin's OWN contract with its
2132
+ // caller (e.g. { verified: boolean }), a field that appears in zero
2133
+ // captured responses. Inferring a schema from the captures would emit the
2134
+ // wrong shape with false confidence. z.unknown() plus the generated
2135
+ // `[ ] Narrow ResponseSchema` checklist item is the intended hand-off to
2136
+ // the plugin author, who alone knows that contract — narrowing it only
2137
+ // changes what executeHttp promises its caller, and is now safe to do
2138
+ // without affecting per-call validation. Single-endpoint plugins keep the
2139
+ // inferred schema, since there both roles (client default and sole call)
2140
+ // coincide.
2062
2141
  const responseSchemaExpr = multiStepBody ? `z.unknown()` : inferZodSchema(responseBody);
2063
2142
  // Multi-step flows that include a multipart upload need the binary asset
2064
2143
  // on the payload. Add Resume/ResumeContentType/ResumeFilename as required
@@ -2235,12 +2314,20 @@ const httpClient = createHttpClient({ schema: ${pascal}ResponseSchema, bottlenec
2235
2314
  const queryChecklistLine = gql
2236
2315
  ? `\n * [ ] Trim UI-only fields from ${pascal.toUpperCase()}_QUERY (keep only fields you need)`
2237
2316
  : "";
2317
+ // Multi-step flows validate each call against its own per-call inferred
2318
+ // schema (emitMultiStepExecuteHttp) — narrowing ResponseSchema only changes
2319
+ // what executeHttp promises ITS OWN caller, never a per-call validator, so
2320
+ // the checklist item must say that explicitly. Single-endpoint plugins have
2321
+ // exactly one call, so the client schema and that call's validator are the
2322
+ // same schema and the shorter wording stays accurate.
2323
+ const narrowSchemaChecklistLine = multiStepBody
2324
+ ? `\n * [ ] Narrow ${pascal}ResponseSchema to match what executeHttp should promise ITS CALLER — this is the plugin's own return-value contract, not a per-call validator (each call in the flow is already checked against its own inferred schema)`
2325
+ : `\n * [ ] Narrow ${pascal}ResponseSchema to match the real response shape`;
2238
2326
  const camel = siteId.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2239
2327
  return `/**
2240
2328
  * Generated by recon-generate.ts — review before shipping.
2241
2329
  *
2242
- * Checklist:${queryChecklistLine}
2243
- * [ ] Narrow ${pascal}ResponseSchema to match the real response shape
2330
+ * Checklist:${queryChecklistLine}${narrowSchemaChecklistLine}
2244
2331
  * [ ] Adjust ${pascal}PayloadSchema to your actual request parameters
2245
2332
  * [ ] Verify BASE_HEADERS — remove any that aren't load-bearing
2246
2333
  * [ ] Out-of-tree: \`pnpm add bottleneck zod\` — this file imports both
@@ -2970,12 +3057,9 @@ async function main() {
2970
3057
  : multiStepBody;
2971
3058
  const hasMultipartStep = actionSteps.some((s) => s.isMultipart);
2972
3059
  const headerBindings = collectHeaderBindings(actionSteps);
2973
- // For submission flows the final action's response body is the most useful
2974
- // shape inference target (it's the terminal success signal). Fall back to
2975
- // the replay body for single-endpoint sites.
2976
- const effectiveResponseBody = isSubmissionFlow
2977
- ? (actionSteps[actionSteps.length - 1].capture.responseBody ?? responseBody)
2978
- : responseBody;
3060
+ // Shape inference targets the SAME call executeHttp returns see
3061
+ // selectEffectiveResponseBody so the two surfaces can't describe different calls.
3062
+ const effectiveResponseBody = selectEffectiveResponseBody(isSubmissionFlow, actionSteps, responseBody);
2979
3063
  logger.info(`generating plugin for ${siteId} (${gql ? "GraphQL" : isSubmissionFlow ? `submission flow, ${actionSteps.length} steps` : "single-endpoint REST"}, baseUrl: ${baseUrl})`);
2980
3064
  if (emit === "config") {
2981
3065
  (0, node_fs_1.mkdirSync)(outDir, { recursive: true });