@mercuryo-ai/agentbrowse 0.2.61 → 0.2.63

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 (82) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +102 -9
  3. package/dist/browser-session-state.d.ts +2 -11
  4. package/dist/browser-session-state.d.ts.map +1 -1
  5. package/dist/browser-session-state.js +0 -4
  6. package/dist/commands/act.d.ts.map +1 -1
  7. package/dist/commands/act.js +14 -5
  8. package/dist/commands/attach.d.ts +1 -3
  9. package/dist/commands/attach.d.ts.map +1 -1
  10. package/dist/commands/attach.js +0 -2
  11. package/dist/commands/browser-status.d.ts +0 -2
  12. package/dist/commands/browser-status.d.ts.map +1 -1
  13. package/dist/commands/browser-status.js +1 -7
  14. package/dist/commands/interaction-kernel.d.ts +1 -1
  15. package/dist/commands/interaction-kernel.d.ts.map +1 -1
  16. package/dist/commands/interaction-kernel.js +1 -1
  17. package/dist/commands/launch.d.ts +0 -1
  18. package/dist/commands/launch.d.ts.map +1 -1
  19. package/dist/commands/launch.js +0 -4
  20. package/dist/commands/observe-accessibility.d.ts.map +1 -1
  21. package/dist/commands/observe-accessibility.js +36 -2
  22. package/dist/commands/observe-inventory.d.ts +49 -7
  23. package/dist/commands/observe-inventory.d.ts.map +1 -1
  24. package/dist/commands/observe-inventory.js +807 -96
  25. package/dist/commands/observe-persistence.d.ts.map +1 -1
  26. package/dist/commands/observe-persistence.js +49 -6
  27. package/dist/commands/observe-projection.d.ts +6 -2
  28. package/dist/commands/observe-projection.d.ts.map +1 -1
  29. package/dist/commands/observe-projection.js +251 -27
  30. package/dist/commands/observe-semantics.d.ts +1 -0
  31. package/dist/commands/observe-semantics.d.ts.map +1 -1
  32. package/dist/commands/observe-semantics.js +541 -135
  33. package/dist/commands/observe-signals.d.ts +4 -4
  34. package/dist/commands/observe-signals.d.ts.map +1 -1
  35. package/dist/commands/observe-signals.js +2 -2
  36. package/dist/commands/observe-surfaces.d.ts +2 -1
  37. package/dist/commands/observe-surfaces.d.ts.map +1 -1
  38. package/dist/commands/observe-surfaces.js +143 -45
  39. package/dist/commands/observe.d.ts +5 -1
  40. package/dist/commands/observe.d.ts.map +1 -1
  41. package/dist/commands/observe.js +15 -11
  42. package/dist/commands/semantic-observe.d.ts.map +1 -1
  43. package/dist/commands/semantic-observe.js +43 -0
  44. package/dist/library.d.ts +2 -1
  45. package/dist/library.d.ts.map +1 -1
  46. package/dist/library.js +2 -1
  47. package/dist/match-resolve-fill.d.ts +196 -0
  48. package/dist/match-resolve-fill.d.ts.map +1 -0
  49. package/dist/match-resolve-fill.js +700 -0
  50. package/dist/match-resolve-fill.test-support.d.ts +34 -0
  51. package/dist/match-resolve-fill.test-support.d.ts.map +1 -0
  52. package/dist/match-resolve-fill.test-support.js +81 -0
  53. package/dist/runtime-protected-state.d.ts.map +1 -1
  54. package/dist/runtime-protected-state.js +12 -0
  55. package/dist/runtime-state.d.ts +6 -0
  56. package/dist/runtime-state.d.ts.map +1 -1
  57. package/dist/runtime-state.js +6 -0
  58. package/dist/secrets/form-matcher.d.ts.map +1 -1
  59. package/dist/secrets/form-matcher.js +76 -27
  60. package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -1
  61. package/dist/secrets/protected-exact-value-redaction.js +6 -0
  62. package/dist/secrets/protected-fill.js +3 -3
  63. package/dist/session.d.ts +3 -3
  64. package/dist/session.d.ts.map +1 -1
  65. package/dist/session.js +2 -2
  66. package/dist/solver/browser-launcher.d.ts.map +1 -1
  67. package/dist/solver/browser-launcher.js +2 -1
  68. package/dist/testing.d.ts +1 -0
  69. package/dist/testing.d.ts.map +1 -1
  70. package/dist/testing.js +1 -0
  71. package/docs/README.md +28 -11
  72. package/docs/api-reference.md +311 -19
  73. package/docs/assistive-runtime.md +41 -16
  74. package/docs/getting-started.md +45 -1
  75. package/docs/integration-checklist.md +32 -3
  76. package/docs/match-resolve-fill.md +699 -0
  77. package/docs/protected-fill.md +373 -91
  78. package/docs/testing.md +147 -15
  79. package/docs/troubleshooting.md +5 -0
  80. package/examples/README.md +7 -0
  81. package/examples/match-resolve-fill.ts +107 -0
  82. package/package.json +4 -2
@@ -1,60 +1,69 @@
1
1
  # AgentBrowse Protected Fill Guide
2
2
 
3
- Protected fill solves a specific problem:
4
-
5
- You already have sensitive values from your own approval or secret-management
6
- flow, and you want AgentBrowse to apply those values to a previously observed
7
- form in a guarded way.
8
-
9
- Normal fill and protected fill solve different problems.
3
+ Protected fill applies caller-supplied sensitive values to a form AgentBrowse
4
+ already observed. It is a typed, guarded alternative to calling
5
+ `act(targetRef, 'fill', value)` by hand for every secret field.
6
+
7
+ You bring the values from your own approval or secret-management flow.
8
+ AgentBrowse handles the browser execution step: resolving targets, ordering
9
+ fields, validating that the form bindings still apply, and returning a
10
+ typed outcome you can branch on.
11
+
12
+ > **What "protected" means here.** The values passed into
13
+ > `fillProtectedForm(...)` travel from your code into the browser **without
14
+ > being placed in an LLM prompt**. The orchestrating model decides *when*
15
+ > to trigger the fill; it does not read the values. That is the sole
16
+ > security guarantee of this code path. It is **not** a sandbox, and it
17
+ > does not protect values from a compromised browser, host, or runtime —
18
+ > by the time the fill happens, the value is in the browser's memory. If
19
+ > you need the broader threat model (prompt leakage vs. compromised host,
20
+ > screenshots after fill, post-submit side channels), see
21
+ > [MagicPay SDK — Security Model](../../magicpay-sdk/docs/security-model.md).
10
22
 
11
23
  ## When To Use Protected Fill
12
24
 
13
- Use protected fill when all of these are true:
14
-
15
- - you already observed the page and have a form description
16
- - you already have the sensitive values in your own application
17
- - you want AgentBrowse to apply them through a typed execution path
25
+ Use `fillProtectedForm(...)` when all of these are true:
18
26
 
19
- Examples:
27
+ - `observe(...)` has already returned a `fillableForm` for the current page;
28
+ - you already have the sensitive values in your own application;
29
+ - you want AgentBrowse to apply them through a guarded execution path instead
30
+ of a loose sequence of per-field `act(...)` calls.
20
31
 
21
- - card number, expiry, CVV
22
- - password or one-time code
23
- - any field set that should not be treated like ordinary page text input
32
+ Typical cases: card entry, password entry, identity-document entry, one-time
33
+ code entry.
24
34
 
25
- ## When A Normal `act(..., 'fill', value)` Is Enough
35
+ ## When Normal `act(..., 'fill', value)` Is Enough
26
36
 
27
- Use ordinary `act(..., 'fill', value)` when:
37
+ Use ordinary `act(targetRef, 'fill', value)` when:
28
38
 
29
- - the value is not sensitive
30
- - you are filling one simple field directly
31
- - you do not need form-level guarded execution
39
+ - the value is not sensitive;
40
+ - you are filling one isolated field that is not part of an observed protected
41
+ form;
42
+ - you do not need stale-binding guards or field-policy handling.
32
43
 
33
- ## What Protected Fill Adds
44
+ ## Mental Model
34
45
 
35
- Compared with a normal fill action, protected fill works with:
46
+ Protected fill is a contract between three inputs:
36
47
 
37
- - a `fillableForm` returned by a previous `observe(...)` call;
38
- - structured `protectedValues` keyed by field meaning (e.g. `card_number`,
39
- `password`), not by raw DOM selector;
40
- - optional `fieldPolicies` that pin per-field behavior such as
41
- `strict: true` (abort if the expected target is missing) or
42
- `allowPartial: true` (apply what you can, skip the rest);
43
- - typed execution outcomes so your code can distinguish success from
44
- stale bindings, validation failure, and generic execution errors.
45
-
46
- It also validates that the form bindings returned by `observe(...)` still
47
- apply to the current DOM. If the page changed between observation and
48
- fill, protected fill fails with a stale-binding result instead of trying
49
- to fill a moved or removed target.
50
-
51
- ## Import
48
+ 1. **The `fillableForm` from `observe(...)`** — the shape of the protected
49
+ form AgentBrowse recognised. It lists every field AgentBrowse wants you
50
+ to fill, each tagged with a stable `fieldKey` and a `targetRef` it will
51
+ drive in the browser.
52
+ 2. **Your `protectedValues`** a plain object keyed by the `fieldKey`
53
+ values from the form. You look up each value in your own vault or
54
+ approval result and hand it in.
55
+ 3. **Optional `fieldPolicies`** per-field overrides that switch a field
56
+ from deterministic lookup to LLM-assisted resolution (see
57
+ [Split Fields And Assistive Runtime](#split-fields-and-assistive-runtime)).
52
58
 
53
- ```ts
54
- import { fillProtectedForm } from '@mercuryo-ai/agentbrowse/protected-fill';
55
- ```
59
+ The rule that matters most: **you build `protectedValues` from the field
60
+ keys that the form actually lists**, not from a hard-coded template. The
61
+ set of keys present in `fillableForm.fields` is the authoritative contract
62
+ for that page at that moment. A card page may surface only `pan` + `cvv`,
63
+ or the full five fields, or a combined month/year input — always match the
64
+ form.
56
65
 
57
- ## Example
66
+ ## Quick Example
58
67
 
59
68
  ```ts
60
69
  import { observe } from '@mercuryo-ai/agentbrowse';
@@ -65,81 +74,354 @@ if (!observeResult.success) {
65
74
  throw new Error(observeResult.reason ?? observeResult.message);
66
75
  }
67
76
 
68
- const fillableForm = observeResult.fillableForms.find((form) => form.purpose === 'payment_card');
77
+ const fillableForm = observeResult.fillableForms.find(
78
+ (form) => form.purpose === 'payment_card'
79
+ );
69
80
  if (!fillableForm) {
70
- throw new Error('Could not find a payment_card form.');
81
+ throw new Error('No payment_card form on this page.');
71
82
  }
72
83
 
84
+ // Build protectedValues from the keys the form actually declared.
85
+ const myVault: Partial<Record<string, string>> = {
86
+ cardholder: 'Jane Doe',
87
+ pan: '4111111111111111',
88
+ exp_month: '12',
89
+ exp_year: '2030',
90
+ cvv: '123',
91
+ };
92
+
93
+ const protectedValues = Object.fromEntries(
94
+ fillableForm.fields
95
+ .map((field) => [field.fieldKey, myVault[field.fieldKey]] as const)
96
+ .filter(([, value]) => typeof value === 'string' && value.length > 0)
97
+ );
98
+
73
99
  const result = await fillProtectedForm({
74
100
  session,
75
101
  fillableForm,
76
- protectedValues: {
77
- card_number: '4111111111111111',
78
- exp_month: '12',
79
- exp_year: '2030',
80
- cvv: '123',
81
- },
102
+ protectedValues,
82
103
  });
83
104
 
84
105
  if (!result.success) {
85
- throw new Error(result.reason ?? result.message);
106
+ // Browser-level failure (see "Top-Level Result" below).
107
+ throw new Error(`${result.error}: ${result.message}`);
108
+ }
109
+
110
+ // The fill was attempted. Branch on the execution kind to decide next step.
111
+ switch (result.execution.kind) {
112
+ case 'success':
113
+ // All fields applied.
114
+ break;
115
+ case 'binding_stale':
116
+ // Re-observe and retry (see "Stale Binding Recovery").
117
+ break;
118
+ case 'validation_failed':
119
+ // Page-level client validation rejected at least one field.
120
+ break;
121
+ case 'unexpected_error':
122
+ // See "Execution Kinds" for the full reason vocabulary.
123
+ break;
124
+ }
125
+ ```
126
+
127
+ ## Workflow
128
+
129
+ ### 1. Observe the page
130
+
131
+ `observe(session)` returns `fillableForms: PersistedFillableForm[]`. Each
132
+ entry describes one recognised protected form.
133
+
134
+ Relevant top-level fields on a form:
135
+
136
+ - `fillRef`, `pageRef`, `scopeRef?` — stable refs carried back into
137
+ `fillProtectedForm(...)`.
138
+ - `purpose` — `'login' | 'identity' | 'payment_card'`. Use this to pick the
139
+ form you want.
140
+ - `loginStep?` — `'full' | 'identifier' | 'password'`, only on login forms.
141
+ - `presence?` — `'present' | 'unknown' | 'absent'`. If `'absent'`, the form
142
+ is no longer on the page and you should re-observe.
143
+ - `fields` — the per-field contract (next section).
144
+
145
+ ### 2. Pick the form
146
+
147
+ Typical selection:
148
+
149
+ ```ts
150
+ const form = observeResult.fillableForms.find(
151
+ (f) => f.purpose === 'payment_card' && f.presence !== 'absent'
152
+ );
153
+ ```
154
+
155
+ If several forms match (for example, two login stages on a multi-step page),
156
+ branch on `loginStep` too.
157
+
158
+ ### 3. Build `protectedValues` from `fillableForm.fields`
159
+
160
+ Each entry in `fillableForm.fields` is a `FillableFormFieldBinding`:
161
+
162
+ | Field | Meaning |
163
+ | --- | --- |
164
+ | `fieldKey` | The stable key you use in `protectedValues` (see next section for the full list). |
165
+ | `targetRef` | The observed browser target AgentBrowse will drive. You do not pass this in `protectedValues` — AgentBrowse uses it internally. |
166
+ | `label?` | Human-readable label from the page, useful for diagnostics. |
167
+ | `required?` | Whether the page marks the field as required. |
168
+ | `valueHint?` | `'direct'` (default) or a split-field hint such as `'full_name.given'`. See [Split Fields And Assistive Runtime](#split-fields-and-assistive-runtime). |
169
+
170
+ The same `fieldKey` can appear more than once with different `valueHint`
171
+ values (for example, a page that splits `full_name` into two inputs will
172
+ emit two entries with the same `fieldKey` and different `valueHint`s).
173
+ You still pass `full_name` once in `protectedValues`; the runtime splits it.
174
+
175
+ ### 4. Call `fillProtectedForm(...)`
176
+
177
+ ```ts
178
+ const result = await fillProtectedForm({
179
+ session, // required — the sticky-owner browser session
180
+ fillableForm, // required — the form object from observe(...)
181
+ protectedValues, // required — keyed by fieldKey
182
+ fieldPolicies, // optional — see the fieldPolicies section
183
+ });
184
+ ```
185
+
186
+ Empty-string and missing values in `protectedValues` are dropped silently.
187
+ Keys that the form did not declare are ignored.
188
+
189
+ ### 5. Handle the result
190
+
191
+ See [Top-Level Result](#top-level-result) and [Execution Kinds](#execution-kinds)
192
+ for the full vocabulary.
193
+
194
+ ## Field Keys By Purpose
195
+
196
+ These are the standard `fieldKey` values AgentBrowse may put in
197
+ `fillableForm.fields` for each `purpose`. The form only lists the subset
198
+ that actually exists on the page.
199
+
200
+ ### `purpose: 'login'`
201
+
202
+ | fieldKey | Meaning |
203
+ | --- | --- |
204
+ | `username` | Username or email identifier. |
205
+ | `password` | Password or one-time code. |
206
+
207
+ ### `purpose: 'identity'`
208
+
209
+ | fieldKey | Meaning |
210
+ | --- | --- |
211
+ | `full_name` | Full legal name. May be split into given/family via `valueHint`. |
212
+ | `document_number` | Passport/ID/driver-license number. |
213
+ | `date_of_birth` | Date of birth. May be split into day/month/year via `valueHint`. |
214
+ | `nationality` | Nationality. |
215
+ | `issue_date` | Document issue date. |
216
+ | `expiry_date` | Document expiry date. |
217
+ | `issuing_country` | Country that issued the document. |
218
+
219
+ ### `purpose: 'payment_card'`
220
+
221
+ | fieldKey | Meaning |
222
+ | --- | --- |
223
+ | `cardholder` | Cardholder name as printed on the card. |
224
+ | `pan` | Primary account number (the long card number). |
225
+ | `exp_month` | Expiry month. Combined input `MM/YY` is handled automatically when the form declares both `exp_month` and `exp_year`. |
226
+ | `exp_year` | Expiry year. |
227
+ | `cvv` | Card verification value. |
228
+
229
+ There is no `wallet` purpose in the current type surface. If you need to
230
+ fill crypto wallet fields, use ordinary `act(...)` calls.
231
+
232
+ ## Split Fields And Assistive Runtime
233
+
234
+ Some pages split one logical value across multiple inputs: `full_name` into
235
+ separate first-name and family-name inputs, `date_of_birth` into three
236
+ selects (day / month / year). AgentBrowse emits these as multiple field
237
+ entries with the same `fieldKey` and different `valueHint` values.
238
+
239
+ Possible `valueHint` values:
240
+
241
+ - `'direct'` (default) — the whole stored value goes into the target as-is.
242
+ - `'full_name.given'`, `'full_name.family'` — one part of a split name.
243
+ - `'date_of_birth.day'`, `'date_of_birth.month'`, `'date_of_birth.year'` —
244
+ one part of a split date.
245
+
246
+ You still pass `full_name` and `date_of_birth` once each in
247
+ `protectedValues`. The runtime then needs to derive the per-input value
248
+ (e.g. the given name from "Jane Doe", or the month number from
249
+ `"1990-05-12"`).
250
+
251
+ - For `date_of_birth` splits, AgentBrowse normalises the date
252
+ deterministically and does not require an assistive runtime.
253
+ - For `full_name` splits and for any page-localised value (for example, a
254
+ `nationality` dropdown that needs "Россия" on a Russian page), AgentBrowse
255
+ needs an **assistive runtime** (LLM client). Without one, the call
256
+ returns `{ kind: 'unexpected_error', reason: 'assisted_value_resolution_failed' }`.
257
+
258
+ See [assistive-runtime.md](./assistive-runtime.md) for how to install the
259
+ runtime. If you know your target forms never split values, you can skip it.
260
+
261
+ ## `fieldPolicies`
262
+
263
+ `fieldPolicies` is an optional per-field override:
264
+
265
+ ```ts
266
+ fieldPolicies: {
267
+ full_name: 'llm_assisted', // force LLM resolution even if valueHint='direct'
268
+ date_of_birth: 'deterministic_only', // refuse LLM, fail if not deterministic
86
269
  }
87
270
  ```
88
271
 
89
- ### Form purpose values
272
+ - `'deterministic_only'` only use the stored value as-is (or deterministic
273
+ splits like dates). Fails with `deterministic_only_resolution_failed` if
274
+ an LLM would be required.
275
+ - `'llm_assisted'` — route the field through the assistive runtime even
276
+ when the stored value looks directly usable.
277
+
278
+ Most integrations do not set `fieldPolicies`. Use it when you have a reason
279
+ to pin a field to one mode (policy, cost control, test determinism).
90
280
 
91
- `fillableForm.purpose` identifies which kind of form AgentBrowse detected.
92
- The currently surfaced purposes are:
281
+ ## Top-Level Result
93
282
 
94
- - `login` username/password or email/password form.
95
- - `identity` — identity-verification fields (name, address, document
96
- details).
97
- - `payment_card` — credit/debit card entry.
98
- - `wallet` — crypto wallet address/chain fields.
283
+ `fillProtectedForm(...)` returns either a success wrapper or a browser-level
284
+ failure:
99
285
 
100
- ### Protected value keys
286
+ ```ts
287
+ type FillProtectedFormResult =
288
+ | { success: true; pageRef; url; title; execution }
289
+ | { success: false; error; message; reason };
290
+ ```
291
+
292
+ Browser-level `error` values:
101
293
 
102
- The keys in `protectedValues` are the standard field names chosen by the
103
- form detector, not raw DOM attributes. Typical keys per purpose:
294
+ | error | When | Action |
295
+ | --- | --- | --- |
296
+ | `browser_connection_failed` | The sticky-owner browser session could not be reached. | Re-check the session, possibly re-attach. |
297
+ | `page_resolution_failed` | `pageRef` from the form no longer maps to an open page. | Re-navigate and re-observe before retrying. |
104
298
 
105
- - `login`: `username`, `password` (or `email`, `password`).
106
- - `identity`: `given_name`, `family_name`, `date_of_birth`, `country`,
107
- `document_number`, etc.
108
- - `payment_card`: `card_number`, `cardholder_name`, `exp_month`,
109
- `exp_year`, `cvv`.
110
- - `wallet`: `address`, `chain`.
299
+ If `success` is `true`, the fill attempt reached the page. The detailed
300
+ outcome is in `execution`.
111
301
 
112
- The exact set of keys expected for a given `fillableForm` is listed in
113
- `fillableForm.fields`. Always build `protectedValues` from that list rather
114
- than assuming defaults.
302
+ ## Execution Kinds
115
303
 
116
- ## Where It Fits
304
+ `execution.kind` tells you what happened during the fill itself.
117
305
 
118
- Protected fill handles the browser execution step.
306
+ ### `kind: 'success'`
307
+
308
+ ```ts
309
+ { kind: 'success'; filledFields: Array<{ fieldKey; targetRef }> }
310
+ ```
119
311
 
120
- Applications usually pair it with their own:
312
+ All declared fields applied. `filledFields` lists what was touched.
121
313
 
122
- - secret storage
123
- - approval flow
124
- - policy decisions
125
- - claims or grants
314
+ ### `kind: 'binding_stale'`
126
315
 
127
- ## What You Need Before Calling It
316
+ ```ts
317
+ {
318
+ kind: 'binding_stale';
319
+ targetRef; fieldKeys;
320
+ reason: 'target_missing' | 'target_not_live' | 'page_signature_mismatch'
321
+ | 'dom_signature_mismatch' | 'locator_resolution_failed' | 'target_blocked';
322
+ attempts: string[];
323
+ }
324
+ ```
128
325
 
129
- Before protected fill, you usually need:
326
+ The form was observed earlier but the DOM has changed. See
327
+ [Stale Binding Recovery](#stale-binding-recovery) for the retry flow.
130
328
 
131
- 1. a launched browser `session`
132
- 2. a `fillableForm` produced by `observe(...)`
133
- 3. the sensitive values from your own trusted source
329
+ ### `kind: 'validation_failed'`
134
330
 
135
- ## Result Shape
331
+ ```ts
332
+ {
333
+ kind: 'validation_failed';
334
+ filledFields: Array<{ fieldKey; targetRef }>;
335
+ fieldErrors: Array<{
336
+ fieldKey; targetRef;
337
+ reason: 'client_validation_rejected' | 'value_not_applied';
338
+ validationTextRedacted?: true;
339
+ }>;
340
+ }
341
+ ```
136
342
 
137
- Protected fill returns a typed result that tells you whether the fill:
343
+ At least one field was rejected by client-side validation (HTML5 validity,
344
+ `aria-invalid`, inline error text, or the value simply did not land in the
345
+ control). `filledFields` lists what did apply before the failure.
138
346
 
139
- - succeeded
140
- - failed because bindings became stale
141
- - failed validation
142
- - failed for another execution reason
347
+ `validationTextRedacted: true` means there was a visible validation
348
+ message, but it is not echoed back — protected fields never surface raw
349
+ page text that may contain the submitted value.
350
+
351
+ ### `kind: 'unexpected_error'`
352
+
353
+ ```ts
354
+ { kind: 'unexpected_error'; reason: ... }
355
+ ```
356
+
357
+ Reasons:
358
+
359
+ | reason | Meaning | Action |
360
+ | --- | --- | --- |
361
+ | `missing_protected_value` | The form declared a `fieldKey` that was not present in `protectedValues`. | Check that your vault covers every key in `fillableForm.fields`. |
362
+ | `unsupported_protected_field_group` | The form grouped fields on one target in a shape protected fill cannot yet combine (only `exp_month + exp_year` is combinable today). | Fall back to ordinary `act(...)` for the odd target, or report the case. |
363
+ | `deterministic_only_resolution_failed` | A `'deterministic_only'` policy blocked LLM resolution for a value that required it. | Relax the policy or provide a pre-split value. |
364
+ | `assisted_value_resolution_failed` | The assistive runtime is missing or errored. | Install an assistive runtime (see [assistive-runtime.md](./assistive-runtime.md)). |
365
+ | `action_failed` | The browser command itself failed (navigation, detached frame, etc.). | Treat similarly to a stale binding: re-observe and retry. |
366
+ | `ambiguous_date_value` / `incomplete_date_value` / `invalid_date_value` | The `date_of_birth` value could not be normalised. | Check the stored format (ISO `YYYY-MM-DD` is safest). |
367
+
368
+ ## Stale Binding Recovery
369
+
370
+ A `binding_stale` result is not a hard error — it means the page moved on
371
+ since `observe(...)` ran. The recovery pattern is:
372
+
373
+ ```ts
374
+ if (!result.success) {
375
+ // browser/page problem — handle separately
376
+ } else if (result.execution.kind === 'binding_stale') {
377
+ // Re-observe the page and pick a fresh fillableForm before retrying.
378
+ const next = await observe(session);
379
+ // ... pick the form again, rebuild protectedValues, retry fill
380
+ }
381
+ ```
382
+
383
+ Do **not** reuse the old `fillableForm` after a stale result — its
384
+ `targetRef` / signature references are what went stale in the first place.
385
+
386
+ ## What Stays Outside Protected Fill
387
+
388
+ Protected fill does not own:
389
+
390
+ - secret storage — you bring values from your own vault;
391
+ - approval UX — you decide when to release secrets to this call;
392
+ - form submission — `fillProtectedForm(...)` only fills; the final click/
393
+ submit is a separate `act(...)` call or a provider-specific step.
394
+
395
+ ## Import
396
+
397
+ ```ts
398
+ import { fillProtectedForm } from '@mercuryo-ai/agentbrowse/protected-fill';
399
+ ```
143
400
 
144
- That makes it safer than treating every sensitive field as a raw one-off
145
- string fill.
401
+ `fillProtectedForm` is intentionally exported from the `/protected-fill`
402
+ subpath, not from the root `@mercuryo-ai/agentbrowse`. This keeps the
403
+ secret-handling surface separate from the general browsing surface in
404
+ callers that want to restrict imports.
405
+
406
+ ## Relation To `match / resolve / fill`
407
+
408
+ `fillProtectedForm(...)` is the low-level direct API. It assumes you
409
+ already have the protected values in hand (for example, an artifact
410
+ returned by a MagicPay `data.waitForResult(...)` call) and only does the
411
+ guarded browser apply step.
412
+
413
+ The `match`, `resolve`, and `fill` primitives from the root package
414
+ cover a wider pipeline: deciding which candidate value fits the form,
415
+ resolving values stored behind a vault or approval adapter, and then
416
+ applying them through the same protected-fill guardrails. In that flow,
417
+ `resolver.fill` — a capability on the same `AgentbrowseMatchResolver`
418
+ adapter that owns `resolve` — is the place where `fillProtectedForm(...)`
419
+ is typically invoked. See the
420
+ [Match / Resolve / Fill Guide](./match-resolve-fill.md) for the grouped
421
+ walk-through and the [API Reference](./api-reference.md) for the
422
+ resolver shape.
423
+
424
+ Use `fillProtectedForm(...)` directly when you already have the values
425
+ and do not need match/resolve orchestration on top. Use
426
+ `fill(session, form, plan, { resolver })` when you want one deterministic
427
+ surface that covers deciding, resolving, and applying.