@reclaimprotocol/agent 0.1.17 → 0.1.18

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/lib/consts.d.ts CHANGED
@@ -44,6 +44,13 @@ export declare function useOldDevtools(): boolean;
44
44
  * `ConfigStore` (which would create an import cycle).
45
45
  */
46
46
  export declare function resolveOldMode(persisted?: 'old' | 'builder'): boolean;
47
+ /**
48
+ * Where the effective mode came from — the same precedence as
49
+ * `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
50
+ * `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
51
+ * user can tell why the server is in the mode it reports.
52
+ */
53
+ export declare function oldModeSource(persisted?: 'old' | 'builder'): 'env' | 'config' | 'default';
47
54
  /** `%LOCALAPPDATA%` on Windows, used to probe a per-user Chrome install. */
48
55
  export declare function localAppData(): string | undefined;
49
56
  /** Explicit Chrome/Edge/Chromium binary path override. */
package/lib/consts.js CHANGED
@@ -76,6 +76,21 @@ export function resolveOldMode(persisted) {
76
76
  }
77
77
  return true;
78
78
  }
79
+ /**
80
+ * Where the effective mode came from — the same precedence as
81
+ * `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
82
+ * `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
83
+ * user can tell why the server is in the mode it reports.
84
+ */
85
+ export function oldModeSource(persisted) {
86
+ if (oldDevtoolsEnvSet()) {
87
+ return 'env';
88
+ }
89
+ if (persisted) {
90
+ return 'config';
91
+ }
92
+ return 'default';
93
+ }
79
94
  // ---------------------------------------------------------------------------
80
95
  // Chrome / CDP launcher.
81
96
  // ---------------------------------------------------------------------------
@@ -13,6 +13,11 @@ const RESPONSE_MATCHES_SCHEMA = {
13
13
  properties: {
14
14
  type: { type: 'string', enum: ['regex', 'contains'] },
15
15
  value: { type: 'string' },
16
+ isOptional: {
17
+ type: 'boolean',
18
+ description: 'Mark this match non-fatal (verification continues when it ' +
19
+ 'fails). Default false.',
20
+ },
16
21
  },
17
22
  required: ['type', 'value'],
18
23
  },
@@ -44,6 +49,12 @@ const REQUEST_DRAFT_PROPERTIES = {
44
49
  },
45
50
  method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH'] },
46
51
  body: { type: 'string', description: 'Request body template, if any.' },
52
+ credentials: {
53
+ type: 'string',
54
+ enum: ['omit', 'same-origin', 'include'],
55
+ description: 'How the verification client supplies cookies/auth when replaying ' +
56
+ 'this request. Default `include`.',
57
+ },
47
58
  responseMatches: RESPONSE_MATCHES_SCHEMA,
48
59
  responseRedactions: RESPONSE_REDACTIONS_SCHEMA,
49
60
  };
@@ -173,6 +184,27 @@ export function createVersionFromCaptureTool(client) {
173
184
  '`multiple`/`required` default to true.',
174
185
  items: ALLOWED_JS_REQUEST_SCHEMA,
175
186
  },
187
+ interceptorOptions: {
188
+ type: 'object',
189
+ description: 'In-app interception settings, stored as ' +
190
+ 'webSettings.clientOptions.inapp.interceptorOptions. ' +
191
+ '`isDocumentRequestReplayEnabled` is the INVERSE of ' +
192
+ 'old-devtools\' `disableRequestReplay`: true replays the ' +
193
+ 'main document request via the attestor (default false).',
194
+ properties: {
195
+ interceptorType: {
196
+ type: 'string',
197
+ enum: ['HAWKEYE', 'MSWJS', 'CDP'],
198
+ description: 'How traffic interception is implemented.',
199
+ },
200
+ isDocumentRequestReplayEnabled: { type: 'boolean' },
201
+ interceptorSettings: {
202
+ type: 'string',
203
+ description: 'Free-form JSON config for the chosen interceptor.',
204
+ },
205
+ },
206
+ required: ['interceptorType'],
207
+ },
176
208
  },
177
209
  required: ['providerId', 'initialUrl'],
178
210
  },
@@ -192,6 +224,7 @@ export function createVersionFromCaptureTool(client) {
192
224
  version: ver,
193
225
  jsUserScripts: args.jsUserScripts,
194
226
  allowedJsRequests: args.allowedJsRequests,
227
+ interceptorOptions: args.interceptorOptions,
195
228
  });
196
229
  const result = await client.call('CreateProviderVersion', {
197
230
  params: { providerId: args.providerId },
@@ -1,8 +1,9 @@
1
1
  import type { ConfigStore } from '../../../config/store.ts';
2
2
  import { type RegisteredTool } from '../../server.ts';
3
3
  /**
4
- * The `set_devtools_mode` tool: persist which backend the MCP server targets
5
- * (legacy `old` devtools vs the new `builder`). The choice is written to
4
+ * The `set_devtools_mode` / `get_devtools_mode` tools: persist which backend
5
+ * the MCP server targets (`old` devtools vs the `builder`), and
6
+ * report the mode currently in effect. The choice is written to
6
7
  * `~/.reclaim/config.json` and applied the NEXT time the server starts —
7
8
  * the registered tool set, server instructions, and backend client are all
8
9
  * fixed at connect time, so the switch requires a restart/reconnect. Follows
@@ -1,8 +1,10 @@
1
1
  import { defaultConfigPath } from "../../../config/store.js";
2
+ import { oldModeSource } from "../../../consts.js";
2
3
  import { defineTool } from "../../server.js";
3
4
  /**
4
- * The `set_devtools_mode` tool: persist which backend the MCP server targets
5
- * (legacy `old` devtools vs the new `builder`). The choice is written to
5
+ * The `set_devtools_mode` / `get_devtools_mode` tools: persist which backend
6
+ * the MCP server targets (`old` devtools vs the `builder`), and
7
+ * report the mode currently in effect. The choice is written to
6
8
  * `~/.reclaim/config.json` and applied the NEXT time the server starts —
7
9
  * the registered tool set, server instructions, and backend client are all
8
10
  * fixed at connect time, so the switch requires a restart/reconnect. Follows
@@ -18,8 +20,8 @@ export function buildModeTools(config, currentOldMode) {
18
20
  return [
19
21
  defineTool({
20
22
  name: 'set_devtools_mode',
21
- description: 'Choose which backend the MCP server targets: "old" (legacy '
22
- + 'Reclaim devtools) or "builder" (the new builder backend). '
23
+ description: 'Choose which backend the MCP server targets: "old" (devtools) '
24
+ + 'or "builder" (the builder backend). '
23
25
  + 'Persists the choice to ~/.reclaim/config.json; it takes effect '
24
26
  + 'the next time the MCP server starts, so restart/reconnect after '
25
27
  + 'switching. An explicit USE_OLD_DEVTOOLS env var overrides this.',
@@ -45,5 +47,23 @@ export function buildModeTools(config, currentOldMode) {
45
47
  : `Already running in "${parsed.mode}" mode; nothing to restart.`,
46
48
  };
47
49
  }),
50
+ defineTool({
51
+ name: 'get_devtools_mode',
52
+ description: 'Report which backend this MCP server is currently targeting: '
53
+ + '"old" (devtools) or "builder" (the builder '
54
+ + 'backend). Also reports WHY that mode is in effect (source: '
55
+ + 'a USE_OLD_DEVTOOLS env override, the persisted config, or '
56
+ + 'the default) and which mode is persisted for the next '
57
+ + 'start. Use set_devtools_mode to change it.',
58
+ inputSchema: { type: 'object', properties: {} },
59
+ }, async () => {
60
+ const persisted = config.read()?.mode;
61
+ return {
62
+ mode: current,
63
+ source: oldModeSource(persisted),
64
+ persistedMode: persisted ?? null,
65
+ configPath: defaultConfigPath(),
66
+ };
67
+ }),
48
68
  ];
49
69
  }
@@ -3,15 +3,15 @@ import type { AllowedJsRequest } from '../provider/to-version.ts';
3
3
  import type { RegisterProviderBody, UpdateProviderConfigBody } from './client.ts';
4
4
  /**
5
5
  * Translate a drafted `ReclaimProvider` (from `propose_provider`) into the
6
- * OLD devtools backend shapes. Deliberately a SEPARATE module from
6
+ * devtools backend shapes. Deliberately a SEPARATE module from
7
7
  * `provider/to-version.ts` (the builder translator) — the two backends have
8
8
  * structurally different bodies:
9
9
  *
10
10
  * - builder NESTS each redaction into its match's `extract`;
11
- * - old devtools keeps `responseMatches` and `responseRedactions` as
11
+ * - devtools keeps `responseMatches` and `responseRedactions` as
12
12
  * PARALLEL arrays on each `requestData` entry.
13
13
  *
14
- * Old-devtools limit we enforce here (a clear error beats a confusing
14
+ * Devtools limit we enforce here (a clear error beats a confusing
15
15
  * server-side 400): only `GET` / `POST` methods. OPRF redactions are passed
16
16
  * through unchanged — the backend's `hash` field accepts them.
17
17
  */
@@ -23,7 +23,7 @@ export interface OldProviderOpts {
23
23
  /** Defaults to `true` when omitted. Register only (Provider-level flag). */
24
24
  useProxy?: boolean;
25
25
  providerType?: 'PRIVATE' | 'PUBLIC';
26
- /** Page-injection script — old-devtools' name for the builder's
26
+ /** Page-injection script — the devtools name for the builder's
27
27
  * `webSettings.jsUserScripts`. Top-level on register, nested under
28
28
  * `providerConfig` on config (add-version). The backend rejects a few
29
29
  * deprecated injection APIs (e.g. `window.payloadData`) with a 400. */
@@ -50,6 +50,14 @@ export interface OldProviderOpts {
50
50
  /** Config (add-version) ONLY — the register endpoint never reads this
51
51
  * field from the request body. Free-form JSON. */
52
52
  extensionConfig?: unknown;
53
+ /** Provider-level `disableRequestReplay` (HAWKEYE document replay skip).
54
+ * Config (add-version) ONLY: the register endpoint hardcodes it to false
55
+ * — the publish tool follows a register up with an add-version call to
56
+ * apply it for new providers (see `old/tools/publish.ts`). NOTE the
57
+ * builder backend has NO equivalent; its inverse is
58
+ * `webSettings.clientOptions.inapp.interceptorOptions
59
+ * .isDocumentRequestReplayEnabled`. */
60
+ disableRequestReplay?: boolean;
53
61
  }
54
62
  interface OldResponseMatch {
55
63
  value: string;
@@ -68,11 +76,11 @@ interface OldResponseRedaction {
68
76
  }
69
77
  interface OldRequestData {
70
78
  url: string;
71
- urlType: 'CONSTANT' | 'TEMPLATE';
79
+ urlType: 'REGEX' | 'CONSTANT' | 'TEMPLATE';
72
80
  method: 'GET' | 'POST';
73
81
  responseMatches: OldResponseMatch[];
74
82
  responseRedactions: OldResponseRedaction[];
75
- credentials: 'include';
83
+ credentials: 'omit' | 'same-origin' | 'include';
76
84
  bodySniff?: {
77
85
  enabled: boolean;
78
86
  template: string;
@@ -88,7 +96,7 @@ export interface OldInjectedRequestData extends OldRequestData {
88
96
  }
89
97
  /** Translate fresh `allowedInjectedRequestData` drafts (the author's input
90
98
  * shape, `AllowedJsRequest[]` — same shape the builder translator uses for
91
- * its own `allowedJsRequests`) into the old backend's shape. A
99
+ * its own `allowedJsRequests`) into the devtools backend's shape. A
92
100
  * carried-forward value read back from an existing version is ALREADY in
93
101
  * this shape and should be passed straight through instead — don't run it
94
102
  * through this a second time. */
@@ -101,7 +109,7 @@ export declare function providerToRegisterBody(providers: ReclaimProvider[], opt
101
109
  * an EXISTING provider. `version` must not already exist; `versionInfo` is
102
110
  * required and must be ≥10 chars (enforced by the backend). */
103
111
  export declare function providerToConfigBody(providers: ReclaimProvider[], opts: OldProviderOpts, version: string, versionInfo: string): UpdateProviderConfigBody;
104
- /** Minimal version triplet as the old backend stores it. */
112
+ /** Minimal version triplet as the devtools backend stores it. */
105
113
  export interface SemverTriplet {
106
114
  major: number;
107
115
  minor: number;
@@ -1,5 +1,6 @@
1
+ import assert from 'node:assert';
1
2
  /** Default web settings, mirroring the builder openapi defaults
2
- * (`ProviderWebSettings.geoLocation`, `PortalClientOptions.useProxy`). The old
3
+ * (`ProviderWebSettings.geoLocation`, `PortalClientOptions.useProxy`). The
3
4
  * backend accepts both: `geoLocation` lives on the providerConfig, `useProxy`
4
5
  * is a Provider-level flag (register only). */
5
6
  const DEFAULT_GEO_LOCATION = '{{DYNAMIC_GEO}}';
@@ -10,29 +11,29 @@ const DEFAULT_USE_PROXY = true;
10
11
  * entries — a `ReclaimProvider` satisfies it structurally either way. */
11
12
  function requestDataFromProvider(provider) {
12
13
  const method = String(provider.method).toUpperCase();
13
- if (method !== 'GET' && method !== 'POST') {
14
- throw new Error('Old devtools supports only GET/POST requests; got ' +
15
- `"${provider.method}". Re-capture against a GET/POST endpoint.`);
16
- }
17
- // The old backend has no dotted interpolation: consumer-supplied params
14
+ assert(method === 'GET' || method === 'POST', new Error('Devtools supports only GET/POST requests; got ' +
15
+ `"${provider.method}". Re-capture against a GET/POST endpoint.`));
16
+ // The devtools backend has no dotted interpolation: consumer-supplied params
18
17
  // must use the `{{context_<name>}}` form here, not the builder's
19
18
  // `{{context.<name>}}`. Catch the mix-up at publish with a clear error
20
19
  // instead of a template that silently never resolves.
21
20
  const dotted = `${provider.url}\n${provider.body ?? ''}`
22
21
  .match(/\{\{context\.[A-Za-z_][A-Za-z0-9_]*\}\}/);
23
- if (dotted) {
24
- throw new Error(`Old devtools does not support ${dotted[0]} — rename ` +
25
- 'consumer-supplied params to the {{context_<name>}} form ' +
26
- 'for this backend.');
27
- }
22
+ assert(!dotted, new Error(`Devtools does not support ${dotted?.[0]} — rename ` +
23
+ 'consumer-supplied params to the {{context_<name>}} form ' +
24
+ 'for this backend.'));
28
25
  const responseMatches = (provider.responseMatches ?? [])
29
26
  .map((m, i) => ({
30
27
  value: m.value,
31
28
  type: m.type ?? 'contains',
32
- isOptional: false,
29
+ // The backend honors isOptional on BOTH register and add-version
30
+ // (responseMatches pass through verbatim) — forward the author's
31
+ // flag, default false. (`invert` has no old-backend equivalent and
32
+ // stays dropped.)
33
+ isOptional: m.isOptional ?? false,
33
34
  order: i,
34
35
  }));
35
- // Old devtools wants `responseMatches` and `responseRedactions` as PARALLEL
36
+ // Devtools wants `responseMatches` and `responseRedactions` as PARALLEL
36
37
  // arrays — same shape the builder uses, so pass them through directly. We do
37
38
  // NOT enforce any match/redaction count relationship here (and never pad —
38
39
  // empty placeholder matches break proof generation): the author keeps full
@@ -58,33 +59,35 @@ function requestDataFromProvider(provider) {
58
59
  });
59
60
  const requestData = {
60
61
  url: provider.url,
61
- // `{{param}}` placeholders → TEMPLATE so the backend templates them;
62
- // otherwise a fixed CONSTANT url. (REGEX is never drafted.)
63
- urlType: provider.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT',
62
+ // Explicit override wins; otherwise `{{param}}` placeholders → TEMPLATE
63
+ // so the backend templates them, else a fixed CONSTANT url.
64
+ urlType: provider.urlType
65
+ ?? (provider.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT'),
64
66
  method: method,
65
67
  responseMatches,
66
68
  responseRedactions,
67
69
  // The verification client re-supplies cookies/auth at runtime; secrets
68
70
  // are never persisted (propose_provider already stripped them).
69
- credentials: 'include',
71
+ credentials: provider.credentials ?? 'include',
70
72
  };
71
73
  if (provider.body !== undefined) {
72
74
  requestData.bodySniff = { enabled: true, template: provider.body };
73
75
  }
74
- // NOTE: `responseVariables` is intentionally omitted — the old backend
76
+ // NOTE: `responseVariables` is intentionally omitted — the devtools backend
75
77
  // auto-extracts it from `{{var}}` placeholders in responseMatches. Sending
76
78
  // it ourselves would risk drifting from the server's own extraction.
77
79
  return requestData;
78
80
  }
79
81
  /** One `requestData` entry per drafted provider. A multi-request provider
80
- * becomes parallel entries that the old backend runs together at verification
81
- * (the same way the builder version carries multiple `requests`). */
82
+ * becomes parallel entries that the devtools backend runs together at
83
+ * verification (the same way the builder version carries multiple
84
+ * `requests`). */
82
85
  function requestDataFrom(providers) {
83
86
  return providers.map(requestDataFromProvider);
84
87
  }
85
88
  /** Translate fresh `allowedInjectedRequestData` drafts (the author's input
86
89
  * shape, `AllowedJsRequest[]` — same shape the builder translator uses for
87
- * its own `allowedJsRequests`) into the old backend's shape. A
90
+ * its own `allowedJsRequests`) into the devtools backend's shape. A
88
91
  * carried-forward value read back from an existing version is ALREADY in
89
92
  * this shape and should be passed straight through instead — don't run it
90
93
  * through this a second time. */
@@ -165,6 +168,11 @@ export function providerToConfigBody(providers, opts, version, versionInfo) {
165
168
  if (opts.extensionConfig !== undefined) {
166
169
  providerConfig.extensionConfig = opts.extensionConfig;
167
170
  }
171
+ // The config (add-version) controller persists providerConfig verbatim,
172
+ // so this lands as-is; the register controller ignores it entirely.
173
+ if (opts.disableRequestReplay !== undefined) {
174
+ providerConfig.disableRequestReplay = opts.disableRequestReplay;
175
+ }
168
176
  return { providerConfig, version, versionInfo };
169
177
  }
170
178
  /**
@@ -4,6 +4,12 @@ import { INJECTION_GUIDE } from "../../provider/injection-doc.js";
4
4
  import { countsFor, matchRedactionWarnings, } from "../../provider/match-redaction-warning.js";
5
5
  import { bumpPatchString, extractVersions, injectedRequestDataFrom, providerToConfigBody, providerToRegisterBody, } from "../to-register.js";
6
6
  const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture';
7
+ /** Pull the new provider's id out of a register response, tolerant of the
8
+ * envelope (`{providerId}`, `{provider:{providerId}}`, `{id}`). */
9
+ function extractProviderId(resp) {
10
+ const obj = resp;
11
+ return obj?.providerId ?? obj?.provider?.providerId ?? obj?.id;
12
+ }
7
13
  /**
8
14
  * The old backend lets you add a version only to a provider YOU own. A
9
15
  * `providerId` for a public provider (or anyone else's) comes back as
@@ -44,7 +50,7 @@ function rethrowOwnershipError(err, providerId) {
44
50
  export function createVersionFromCaptureTool(client) {
45
51
  return defineTool({
46
52
  name: 'create_provider_version_from_capture',
47
- description: 'Publish drafted ReclaimProvider(s) to the OLD devtools backend. ' +
53
+ description: 'Publish drafted ReclaimProvider(s) to the devtools backend. ' +
48
54
  'Pass the `provider` object from propose_provider VERBATIM and the ' +
49
55
  '`initialUrl` the user landed on (the login/landing page, NOT the ' +
50
56
  'API url). \n\n' +
@@ -62,7 +68,7 @@ export function createVersionFromCaptureTool(client) {
62
68
  "PUBLIC provider you don't own, omit `providerId` to register your " +
63
69
  'own copy (you cannot edit someone else\'s provider here). \n\n' +
64
70
  'For data gated behind login/navigation, pass `customInjection` — a ' +
65
- 'page-injection script (old-devtools\' name for the builder\'s ' +
71
+ 'page-injection script (the devtools name for the builder\'s ' +
66
72
  'jsUserScripts) that drives the page to where the request fires. \n\n' +
67
73
  'BY DESIGN, this backend only ever CREATES A NEW VERSION (POST) — ' +
68
74
  'there is no partial-patch/edit endpoint, and this tool does NOT ' +
@@ -79,7 +85,14 @@ export function createVersionFromCaptureTool(client) {
79
85
  'values plus your change) back into this call. ' +
80
86
  '`allowedInjectedRequestData`/`useIncognitoWebview`/' +
81
87
  '`extensionConfig` only take effect on add-version — the register ' +
82
- 'endpoint (no `providerId`) ignores them. \n\n' +
88
+ 'endpoint (no `providerId`) ignores them. `disableRequestReplay` ' +
89
+ 'is also add-version-only, but when it is true the tool follows ' +
90
+ 'a register up with an add-version call automatically so it takes ' +
91
+ 'effect on new providers too. Per-request fields you may set on ' +
92
+ '`provider`/`providers`: `credentials` (omit/same-origin/include, ' +
93
+ 'default include), `urlType` (REGEX/CONSTANT/TEMPLATE, default ' +
94
+ 'derived from {{placeholders}}), and `isOptional` on each ' +
95
+ 'responseMatch (default false). \n\n' +
83
96
  'Captured requests must be GET/POST. OPRF redactions are supported.',
84
97
  inputSchema: {
85
98
  type: 'object',
@@ -94,6 +107,19 @@ export function createVersionFromCaptureTool(client) {
94
107
  method: { type: 'string' },
95
108
  headers: { type: 'object' },
96
109
  body: { type: 'string' },
110
+ credentials: {
111
+ type: 'string',
112
+ enum: ['omit', 'same-origin', 'include'],
113
+ description: 'How the verification client supplies cookies/auth ' +
114
+ 'when replaying this request. Default `include`.',
115
+ },
116
+ urlType: {
117
+ type: 'string',
118
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
119
+ description: 'How the backend treats the URL. Default derived: ' +
120
+ 'TEMPLATE when the URL has {{placeholders}}, else ' +
121
+ 'CONSTANT.',
122
+ },
97
123
  responseMatches: { type: 'array' },
98
124
  responseRedactions: { type: 'array' },
99
125
  paramValues: { type: 'object' },
@@ -122,6 +148,14 @@ export function createVersionFromCaptureTool(client) {
122
148
  method: { type: 'string' },
123
149
  headers: { type: 'object' },
124
150
  body: { type: 'string' },
151
+ credentials: {
152
+ type: 'string',
153
+ enum: ['omit', 'same-origin', 'include'],
154
+ },
155
+ urlType: {
156
+ type: 'string',
157
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
158
+ },
125
159
  responseMatches: { type: 'array' },
126
160
  responseRedactions: { type: 'array' },
127
161
  paramValues: { type: 'object' },
@@ -202,6 +236,13 @@ export function createVersionFromCaptureTool(client) {
202
236
  properties: {
203
237
  url: { type: 'string' },
204
238
  method: { type: 'string' },
239
+ urlType: {
240
+ type: 'string',
241
+ enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
242
+ description: 'How the backend treats this URL. Default derived ' +
243
+ 'from {{placeholders}} (TEMPLATE) like a normal ' +
244
+ 'request entry.',
245
+ },
205
246
  body: {
206
247
  type: 'string',
207
248
  description: 'Becomes `bodySniff: { enabled: true, template: <this ' +
@@ -315,6 +356,14 @@ export function createVersionFromCaptureTool(client) {
315
356
  description: 'Free-form JSON passed through to the client. Add-version ' +
316
357
  'ONLY.',
317
358
  },
359
+ disableRequestReplay: {
360
+ type: 'boolean',
361
+ description: 'Skip replaying the main document request (HAWKEYE ' +
362
+ 'provider-level flag). Honored on add-version; for a ' +
363
+ 'provider (no `providerId`) the register endpoint ignores ' +
364
+ 'it, so when true the tool automatically follows the ' +
365
+ 'register up with an add-version call that applies it.',
366
+ },
318
367
  },
319
368
  required: ['initialUrl'],
320
369
  },
@@ -348,6 +397,7 @@ export function createVersionFromCaptureTool(client) {
348
397
  stepsToFollow: args.stepsToFollow,
349
398
  useIncognitoWebview: args.useIncognitoWebview,
350
399
  extensionConfig: args.extensionConfig,
400
+ disableRequestReplay: args.disableRequestReplay,
351
401
  allowedInjectedRequestData: args.allowedInjectedRequestData
352
402
  ? injectedRequestDataFrom(args.allowedInjectedRequestData)
353
403
  : undefined
@@ -372,6 +422,11 @@ export function createVersionFromCaptureTool(client) {
372
422
  'every page load (before navigating to initialUrl), matching ' +
373
423
  'the builder\'s jsUserScripts.');
374
424
  }
425
+ // disableRequestReplay is honored ONLY on add-version, so a
426
+ // register can't set it — when true we immediately follow the
427
+ // register with an add-version call (which also lands any other
428
+ // register-ignored fields).
429
+ const followUp = args.disableRequestReplay === true;
375
430
  const REGISTER_IGNORED_KEYS = [
376
431
  'allowedInjectedRequestData',
377
432
  'useIncognitoWebview',
@@ -382,13 +437,36 @@ export function createVersionFromCaptureTool(client) {
382
437
  if (ignoredOnRegister.length) {
383
438
  notes.push(`${ignoredOnRegister.join(', ')} ` +
384
439
  `${ignoredOnRegister.length > 1 ? 'are' : 'is'} ` +
385
- 'ignored on register (no providerId) — the old backend only ' +
386
- 'reads them when adding a version. Publish once to create ' +
387
- 'the provider, then call this again WITH the returned ' +
388
- 'providerId to set them.');
440
+ 'ignored on register (no providerId) — the devtools backend only ' +
441
+ 'reads them when adding a version. ' +
442
+ (followUp
443
+ ? 'They were applied by the follow-up version the tool ' +
444
+ 'created to set disableRequestReplay.'
445
+ : 'Publish once to create the provider, then call this ' +
446
+ 'again WITH the returned providerId to set them.'));
389
447
  }
390
448
  const body = providerToRegisterBody(providers, opts);
391
- return withNotes(await client.registerProvider(body));
449
+ const registered = await client.registerProvider(body);
450
+ if (followUp) {
451
+ const newProviderId = extractProviderId(registered);
452
+ if (newProviderId) {
453
+ const versionsResp = await client.listVersions(newProviderId);
454
+ const followVersion = bumpPatchString(extractVersions(versionsResp));
455
+ const configBody = providerToConfigBody(providers, { ...opts, disableRequestReplay: true }, followVersion, DEFAULT_VERSION_INFO);
456
+ await client.updateProviderConfig(newProviderId, configBody);
457
+ notes.push('disableRequestReplay is ignored by the register endpoint — ' +
458
+ `applied it via a follow-up version (${followVersion}) on ` +
459
+ 'the new provider, which also lands any register-ignored ' +
460
+ 'fields you passed.');
461
+ }
462
+ else {
463
+ notes.push('Could not apply disableRequestReplay: the register ' +
464
+ 'response did not include the new providerId. Call this ' +
465
+ 'tool again WITH `providerId` and `disableRequestReplay: ' +
466
+ 'true` to set it.');
467
+ }
468
+ }
469
+ return withNotes(registered);
392
470
  }
393
471
  // New version of an existing provider — pick the next patch above
394
472
  // the highest existing version. A provider you don't own (e.g. a
@@ -14,9 +14,10 @@
14
14
  * It also covers the HAWKEYE-style live-interception path (`injectionType` /
15
15
  * `interceptorType`, `disableRequestReplay` / `isDocumentRequestReplayEnabled`)
16
16
  * and `bodySniff`, reverse-engineered from the old-devtools Prisma schema,
17
- * controller code, and dashboard field hints — neither publish tool exposes
18
- * these as inputs (both only build the standard replay flow), so this is
19
- * read-the-field-and-recognize-it documentation, not authoring guidance.
17
+ * controller code, and dashboard field hints. The replay flag IS settable via
18
+ * the publish tools (`disableRequestReplay` on old-devtools — add-version or
19
+ * the register follow-up; `interceptorOptions` on the builder), so this is
20
+ * recognition + authoring guidance for the remaining internals.
20
21
  */
21
22
  import { readFileSync } from 'node:fs';
22
23
  import { fileURLToPath } from 'node:url';
@@ -11,6 +11,24 @@ export type HttpMethod = HttpParams['method'];
11
11
  * jsonPath/xPath belong on the redaction, not here.
12
12
  */
13
13
  export type ResponseMatch = HttpParams['responseMatches'][number];
14
+ /**
15
+ * A response match as drafted for PUBLISH: the attestor's match plus
16
+ * `isOptional` — a backend-side matcher flag (both the old devtools backend
17
+ * and the builder store it; the attestor's own params have no such field, so
18
+ * `propose_provider` drafts never set it — only an explicit publish input
19
+ * does). Default false on both backends.
20
+ */
21
+ export type DraftResponseMatch = ResponseMatch & {
22
+ isOptional?: boolean;
23
+ };
24
+ /** How the verification client supplies cookies/auth when replaying a
25
+ * request. Both backends accept the same enum (old devtools on
26
+ * `requestData`, builder on `RequestSelection`); default `include`. */
27
+ export type WebCredentials = 'omit' | 'same-origin' | 'include';
28
+ /** Old-devtools `urlType` for a requestData entry. The builder backend has
29
+ * no such field (templating is implicit in the URL's `{{param}}`
30
+ * placeholders), so the builder translator ignores it. */
31
+ export type OldUrlType = 'REGEX' | 'CONSTANT' | 'TEMPLATE';
14
32
  /**
15
33
  * Selector telling the prover which portion of the response to reveal:
16
34
  * `xPath`/`jsonPath` narrows the slice, `regex` (with named groups) matches
@@ -59,8 +77,15 @@ export interface ReclaimProvider {
59
77
  method: HttpMethod;
60
78
  headers?: Record<string, string>;
61
79
  body?: string;
62
- responseMatches: ResponseMatch[];
80
+ responseMatches: DraftResponseMatch[];
63
81
  responseRedactions?: ResponseRedaction[];
82
+ /** How the verification client supplies cookies/auth on replay. Both
83
+ * backends default to `include` when unset. */
84
+ credentials?: WebCredentials;
85
+ /** Old-devtools-only url type override (`REGEX`/`CONSTANT`/`TEMPLATE`).
86
+ * Auto-derived from `{{param}}` placeholders when unset; ignored by the
87
+ * builder backend. */
88
+ urlType?: OldUrlType;
64
89
  /** Public param refs (templated into URL/body), populated by draft layer. */
65
90
  paramValues?: Record<string, string>;
66
91
  /** Redaction mode for the transcript (attestor default: 'key-update'). */
@@ -1,6 +1,6 @@
1
1
  import type { ReclaimClient } from '@reclaimprotocol/client';
2
2
  import type { CreateProviderVersionRequest, ProviderVersionRef } from '@reclaimprotocol/client/openapi';
3
- import type { HttpMethod, ReclaimProvider, ResponseMatch, ResponseRedaction } from './schema.ts';
3
+ import type { DraftResponseMatch, HttpMethod, OldUrlType, ReclaimProvider, ResponseRedaction, WebCredentials } from './schema.ts';
4
4
  /** One entry of the version's `requests[]` (a `RequestSelection`), derived
5
5
  * from the openapi type so it can't drift from what the server accepts. */
6
6
  type RequestSelection = CreateProviderVersionRequest['requests'][number];
@@ -13,8 +13,15 @@ export interface DraftRequest {
13
13
  body?: string;
14
14
  writeRedactionMode?: ReclaimProvider['writeRedactionMode'];
15
15
  additionalClientOptions?: ReclaimProvider['additionalClientOptions'];
16
- responseMatches?: ResponseMatch[];
16
+ responseMatches?: DraftResponseMatch[];
17
17
  responseRedactions?: ResponseRedaction[];
18
+ /** How the verification client supplies cookies/auth on replay. Default
19
+ * `include` (both backends). */
20
+ credentials?: WebCredentials;
21
+ /** Old-devtools `urlType` override. Ignored by THIS translator (the
22
+ * builder backend has no urlType) — read only by the old-devtools
23
+ * translator (`old/to-register.ts`). */
24
+ urlType?: OldUrlType;
18
25
  }
19
26
  /** A request the page's injection script is allowed to fire, plus the
20
27
  * pagination/optionality flags of `RequestSelectionTemplate`. Reuses the
@@ -47,6 +54,16 @@ export interface CreateVersionOptions {
47
54
  jsUserScripts?: string;
48
55
  /** Requests the injection script may fire, as `RequestSelectionTemplate`s. */
49
56
  allowedJsRequests?: AllowedJsRequest[];
57
+ /** In-app interception options, mapped verbatim to
58
+ * `webSettings.clientOptions.inapp.interceptorOptions`. `interceptorType`
59
+ * is required by the backend whenever the object is present.
60
+ * `isDocumentRequestReplayEnabled` is the INVERSE of old-devtools'
61
+ * `disableRequestReplay` (default false). */
62
+ interceptorOptions?: {
63
+ interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP';
64
+ isDocumentRequestReplayEnabled?: boolean;
65
+ interceptorSettings?: string;
66
+ };
50
67
  }
51
68
  /**
52
69
  * Derive the version's `requiredContext` JSON Schema from the