@reclaimprotocol/agent 0.1.21 → 0.1.23

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 (50) hide show
  1. package/lib/authoring/backend.d.ts +9 -7
  2. package/lib/authoring/tools.js +98 -1
  3. package/lib/mcp/tools/agent/attach.js +29 -1
  4. package/lib/mcp/tools/agent/propose.js +27 -3
  5. package/lib/mcp/tools/agent/publish.js +7 -5
  6. package/lib/mcp/tools/agent/shared.js +17 -8
  7. package/lib/mcp/tools/authenticate/authenticate.d.ts +11 -0
  8. package/lib/mcp/tools/authenticate/authenticate.js +128 -119
  9. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -3
  10. package/lib/mcp/tools/authenticate/builder-access.js +8 -3
  11. package/lib/mcp/tools/mode/mode.js +32 -1
  12. package/lib/old/client.d.ts +9 -0
  13. package/lib/old/client.js +19 -1
  14. package/lib/old/to-register.d.ts +17 -6
  15. package/lib/old/to-register.js +6 -0
  16. package/lib/old/tools/me-providers.js +3 -1
  17. package/lib/old/tools/publish.js +82 -38
  18. package/lib/proof/page-replay.d.ts +36 -0
  19. package/lib/proof/page-replay.js +76 -0
  20. package/lib/proof/replay.d.ts +28 -0
  21. package/lib/proof/replay.js +39 -18
  22. package/lib/provider/draft.d.ts +3 -1
  23. package/lib/provider/draft.js +39 -7
  24. package/lib/provider/matcher.js +116 -7
  25. package/lib/provider/to-version.d.ts +8 -6
  26. package/lib/provider/to-version.js +15 -3
  27. package/package.json +3 -2
  28. package/skill/SKILL.builder.md +4 -2
  29. package/skill/SKILL.common.md +10 -1
  30. package/skill/SKILL.old.md +1 -1
  31. package/skill/injection-api.md +44 -15
  32. package/skill/old-mode.md +3 -1
  33. package/src/authoring/backend.ts +9 -7
  34. package/src/authoring/tools.ts +119 -1
  35. package/src/mcp/tools/agent/attach.ts +40 -1
  36. package/src/mcp/tools/agent/propose.ts +29 -3
  37. package/src/mcp/tools/agent/publish.ts +7 -6
  38. package/src/mcp/tools/agent/shared.ts +19 -8
  39. package/src/mcp/tools/authenticate/authenticate.ts +150 -163
  40. package/src/mcp/tools/authenticate/builder-access.ts +8 -3
  41. package/src/mcp/tools/mode/mode.ts +34 -1
  42. package/src/old/client.ts +30 -1
  43. package/src/old/to-register.ts +25 -6
  44. package/src/old/tools/me-providers.ts +3 -1
  45. package/src/old/tools/publish.ts +86 -40
  46. package/src/proof/page-replay.ts +131 -0
  47. package/src/proof/replay.ts +60 -19
  48. package/src/provider/draft.ts +46 -6
  49. package/src/provider/matcher.ts +145 -5
  50. package/src/provider/to-version.ts +24 -9
@@ -171,7 +171,7 @@ function jsonMatcher(
171
171
  return undefined
172
172
  }
173
173
 
174
- const anchor = jsonKeyPrefix(body, target)
174
+ const anchor = jsonKeyPrefix(body, target, lastPathKey(jsonPath))
175
175
  if(!anchor) {
176
176
  return undefined
177
177
  }
@@ -182,15 +182,34 @@ function jsonMatcher(
182
182
  )
183
183
  }
184
184
 
185
+ /** The trailing object-property name of a jsonPath (`$.total_records` →
186
+ * `"total_records"`), or undefined for an array-index/bracket leaf, which
187
+ * has no literal key to anchor a search on. */
188
+ function lastPathKey(jsonPath: string | undefined): string | undefined {
189
+ const m = jsonPath ? /\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonPath) : null
190
+ return m?.[1]
191
+ }
192
+
185
193
  /**
186
194
  * The literal `"key":"` (or `"key":` for a raw number/bool) immediately before
187
195
  * `target` in `text`, plus the closing quote (`"` for a string value, `''`
188
196
  * otherwise). Returns undefined when the value isn't preceded by a JSON key.
197
+ *
198
+ * When `key` is given, anchors the search on occurrences of the KEY (not the
199
+ * value) — so a short/common value elsewhere in the body (e.g. the digit "5"
200
+ * inside `"investment_amount":354.85`) can't be mistaken for this field. Only
201
+ * falls back to a whole-body value search when there's no resolvable key
202
+ * (e.g. `findJsonPath` returned an array-index leaf).
189
203
  */
190
204
  function jsonKeyPrefix(
191
205
  text: string,
192
206
  target: string,
207
+ key?: string,
193
208
  ): { prefix: string, close: string } | undefined {
209
+ if(key !== undefined) {
210
+ return jsonKeyPrefixByKey(text, target, key)
211
+ }
212
+
194
213
  const { idx } = firstIndex(text, target)
195
214
  if(idx < 0) {
196
215
  return undefined
@@ -227,6 +246,78 @@ function jsonKeyPrefix(
227
246
  return { prefix: text.slice(keyStart, idx), close: quoted ? '"' : '' }
228
247
  }
229
248
 
249
+ /** `jsonKeyPrefix`'s key-anchored path: scan occurrences of `"key"`, and for
250
+ * each, check whether the value that follows its `:` matches `target` (or a
251
+ * normalized candidate) before accepting it. */
252
+ function jsonKeyPrefixByKey(
253
+ text: string,
254
+ target: string,
255
+ key: string,
256
+ ): { prefix: string, close: string } | undefined {
257
+ const keyToken = `"${key}"`
258
+ let from = 0
259
+ for(;;) {
260
+ const keyIdx = text.indexOf(keyToken, from)
261
+ if(keyIdx < 0) {
262
+ return undefined
263
+ }
264
+
265
+ from = keyIdx + keyToken.length
266
+ let i = from
267
+ while(i < text.length && /\s/.test(text[i] ?? '')) {
268
+ i++
269
+ }
270
+
271
+ if(text[i] !== ':') {
272
+ continue
273
+ }
274
+
275
+ i++
276
+ while(i < text.length && /\s/.test(text[i] ?? '')) {
277
+ i++
278
+ }
279
+
280
+ const matched = matchJsonValueAt(text, i, target)
281
+ if(matched) {
282
+ return {
283
+ prefix: text.slice(keyIdx, matched.valueStart),
284
+ close: matched.close,
285
+ }
286
+ }
287
+ }
288
+ }
289
+
290
+ /** Whether the JSON value starting at `pos` in `text` — quoted string or a
291
+ * raw number/bool/null token — equals `target` (or a normalized candidate).
292
+ * Returns the position right after any opening quote (matching `firstIndex`'s
293
+ * convention) and the delimiter to close the match template with. */
294
+ function matchJsonValueAt(
295
+ text: string,
296
+ pos: number,
297
+ target: string,
298
+ ): { valueStart: number, close: string } | undefined {
299
+ const candidates = [target, ...normalizeCandidates(target)]
300
+ if(text[pos] === '"') {
301
+ let j = pos + 1
302
+ while(j < text.length && text[j] !== '"') {
303
+ j += text[j] === '\\' ? 2 : 1
304
+ }
305
+
306
+ const raw = text.slice(pos + 1, j)
307
+ return candidates.includes(raw)
308
+ ? { valueStart: pos + 1, close: '"' }
309
+ : undefined
310
+ }
311
+
312
+ let j = pos
313
+ while(j < text.length && !/[,}\]\s]/.test(text[j] ?? '')) {
314
+ j++
315
+ }
316
+
317
+ const raw = text.slice(pos, j)
318
+ return candidates.includes(raw) ? { valueStart: pos, close: '' } : undefined
319
+ }
320
+
230
321
  function findJsonPath(body: string, target: string): string | undefined {
231
322
  let parsed: unknown
232
323
  try {
@@ -320,7 +411,9 @@ function htmlMatcher(
320
411
  const scriptJson = scriptJsonContent(revealSlice)
321
412
  if(scriptJson) {
322
413
  const jsonPath = findJsonPath(scriptJson, target)
323
- const kp = jsonPath ? jsonKeyPrefix(scriptJson, target) : undefined
414
+ const kp = jsonPath
415
+ ? jsonKeyPrefix(scriptJson, target, lastPathKey(jsonPath))
416
+ : undefined
324
417
  if(jsonPath && kp) {
325
418
  return keyValueResult(
326
419
  kp.prefix, kp.close, target, paramName, valueClass,
@@ -550,7 +643,7 @@ function contentMatcher(
550
643
  return {
551
644
  match: {
552
645
  type: 'contains',
553
- value: buildContextualTemplate(body, target, paramName),
646
+ value: buildContextualTemplate(body, target, paramName, valueClass),
554
647
  },
555
648
  redaction,
556
649
  paramValues: { [paramName]: target },
@@ -577,18 +670,63 @@ function contentMatcher(
577
670
  * must CONTAIN the match template (buildContextualTemplate), so the trailing
578
671
  * delimiter is kept unless it's whitespace (which the template also drops).
579
672
  */
673
+ /** Indices where `cand` occurs in `body` as a WHOLE match of `valueClass` —
674
+ * i.e. not a substring inside a longer run of the same class (the digit "5"
675
+ * inside "354.85" is never a whole `\d+` match; "354" and "85" are). Only
676
+ * meaningful for a structural (non-free-form) class. */
677
+ function wholeTokenIndices(
678
+ body: string,
679
+ cand: string,
680
+ valueClass: string,
681
+ ): number[] {
682
+ const indices: number[] = []
683
+ let re: RegExp
684
+ try {
685
+ re = new RegExp(valueClass, 'g')
686
+ } catch{
687
+ return indices
688
+ }
689
+
690
+ let m: RegExpExecArray | null
691
+ while((m = re.exec(body))) {
692
+ if(m[0] === cand) {
693
+ indices.push(m.index)
694
+ }
695
+
696
+ if(re.lastIndex === m.index) {
697
+ re.lastIndex++
698
+ }
699
+ }
700
+
701
+ return indices
702
+ }
703
+
580
704
  function buildContentRegex(
581
705
  body: string,
582
706
  target: string,
583
707
  paramName: string,
584
708
  valueClass: string,
585
709
  ): string | undefined {
710
+ const structural = inferValueClass(target) !== undefined
586
711
  for(const cand of normalizeCandidates(target)) {
587
- const idx = body.indexOf(cand)
712
+ const occurrences = structural
713
+ ? wholeTokenIndices(body, cand, valueClass)
714
+ : undefined
715
+ const idx = occurrences ? occurrences[0] ?? -1 : body.indexOf(cand)
588
716
  if(idx < 0) {
589
717
  continue
590
718
  }
591
719
 
720
+ if(occurrences && occurrences.length > 1
721
+ && cand.replace(/[^A-Za-z0-9]/g, '').length < 4) {
722
+ throw new Error(
723
+ `target value "${target}" occurs ${occurrences.length} times as a `
724
+ + 'standalone token with no JSON key or HTML anchor to '
725
+ + "disambiguate — capture a request where it's unique, or use a "
726
+ + 'longer/more specific target.',
727
+ )
728
+ }
729
+
592
730
  const endIdx = idx + cand.length
593
731
  const rawRight = findRightContext(body, endIdx)
594
732
  const right = /^\s/.test(rawRight) ? '' : escapeRegexFlexWs(rawRight)
@@ -624,8 +762,10 @@ function buildContextualTemplate(
624
762
  body: string,
625
763
  target: string,
626
764
  paramName: string,
765
+ valueClass: string,
627
766
  ): string {
628
- const idx = body.indexOf(target)
767
+ const idx = wholeTokenIndices(body, target, valueClass)[0]
768
+ ?? body.indexOf(target)
629
769
  if(idx < 0) {
630
770
  return `{{${paramName}}}`
631
771
  }
@@ -69,14 +69,16 @@ export interface CreateVersionOptions {
69
69
  jsUserScripts?: string
70
70
  /** Requests the injection script may fire, as `RequestSelectionTemplate`s. */
71
71
  allowedJsRequests?: AllowedJsRequest[]
72
- /** In-app interception options, mapped verbatim to
73
- * `webSettings.clientOptions.inapp.interceptorOptions`. `interceptorType`
74
- * is required by the backend whenever the object is present.
75
- * `isDocumentRequestReplayEnabled` is the INVERSE of old-devtools'
76
- * `disableRequestReplay` (default false). */
72
+ /** In-app interception options, mapped to
73
+ * `webSettings.clientOptions.inapp.interceptorOptions` — ONLY when this
74
+ * is explicitly passed (builder's schema has no `NONE` interceptorType;
75
+ * omitting `inapp` entirely IS the "no interception" signal, so omitting
76
+ * this must omit `inapp`, not default it to HAWKEYE). When it IS passed,
77
+ * `isDocumentRequestReplayEnabled` is still ALWAYS force-disabled
78
+ * regardless of what's passed here — see `providerToCreateVersionRequest`;
79
+ * there is no author-facing way to enable it. */
77
80
  interceptorOptions?: {
78
81
  interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP'
79
- isDocumentRequestReplayEnabled?: boolean
80
82
  interceptorSettings?: string
81
83
  }
82
84
  }
@@ -240,14 +242,27 @@ export function providerToCreateVersionRequest(
240
242
  geoLocation: pickGeoLocation(providers),
241
243
  clientOptions: {
242
244
  portal: { useProxy: true },
245
+ // Builder's schema has NO "NONE" interceptorType — the real
246
+ // no-interception signal is OMITTING `inapp` entirely (it's not
247
+ // `required` on `InAppClientOptions`). So `inapp` is only built at
248
+ // ALL when the author explicitly opts into a real interceptorType;
249
+ // omitting `interceptorOptions` must omit `inapp` too, or every
250
+ // standard (non-interception) provider would incorrectly publish
251
+ // as HAWKEYE. Within that block, `isDocumentRequestReplayEnabled`
252
+ // is still force-disabled — see `CreateVersionOptions
253
+ // .interceptorOptions`'s doc comment; there is no way to enable it.
243
254
  ...(interceptorOptions
244
255
  ? {
245
256
  inapp: {
246
257
  interceptorOptions: {
247
- // openapi `default: false` — the generated type
248
- // requires the key even when the author omits it.
258
+ interceptorType: interceptorOptions.interceptorType,
249
259
  isDocumentRequestReplayEnabled: false,
250
- ...interceptorOptions,
260
+ ...(interceptorOptions.interceptorSettings
261
+ ? {
262
+ interceptorSettings:
263
+ interceptorOptions.interceptorSettings,
264
+ }
265
+ : {}),
251
266
  },
252
267
  },
253
268
  }