@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.
- package/CHANGELOG.md +33 -1
- package/README.md +102 -9
- package/dist/browser-session-state.d.ts +2 -11
- package/dist/browser-session-state.d.ts.map +1 -1
- package/dist/browser-session-state.js +0 -4
- package/dist/commands/act.d.ts.map +1 -1
- package/dist/commands/act.js +14 -5
- package/dist/commands/attach.d.ts +1 -3
- package/dist/commands/attach.d.ts.map +1 -1
- package/dist/commands/attach.js +0 -2
- package/dist/commands/browser-status.d.ts +0 -2
- package/dist/commands/browser-status.d.ts.map +1 -1
- package/dist/commands/browser-status.js +1 -7
- package/dist/commands/interaction-kernel.d.ts +1 -1
- package/dist/commands/interaction-kernel.d.ts.map +1 -1
- package/dist/commands/interaction-kernel.js +1 -1
- package/dist/commands/launch.d.ts +0 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +0 -4
- package/dist/commands/observe-accessibility.d.ts.map +1 -1
- package/dist/commands/observe-accessibility.js +36 -2
- package/dist/commands/observe-inventory.d.ts +49 -7
- package/dist/commands/observe-inventory.d.ts.map +1 -1
- package/dist/commands/observe-inventory.js +807 -96
- package/dist/commands/observe-persistence.d.ts.map +1 -1
- package/dist/commands/observe-persistence.js +49 -6
- package/dist/commands/observe-projection.d.ts +6 -2
- package/dist/commands/observe-projection.d.ts.map +1 -1
- package/dist/commands/observe-projection.js +251 -27
- package/dist/commands/observe-semantics.d.ts +1 -0
- package/dist/commands/observe-semantics.d.ts.map +1 -1
- package/dist/commands/observe-semantics.js +541 -135
- package/dist/commands/observe-signals.d.ts +4 -4
- package/dist/commands/observe-signals.d.ts.map +1 -1
- package/dist/commands/observe-signals.js +2 -2
- package/dist/commands/observe-surfaces.d.ts +2 -1
- package/dist/commands/observe-surfaces.d.ts.map +1 -1
- package/dist/commands/observe-surfaces.js +143 -45
- package/dist/commands/observe.d.ts +5 -1
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +15 -11
- package/dist/commands/semantic-observe.d.ts.map +1 -1
- package/dist/commands/semantic-observe.js +43 -0
- package/dist/library.d.ts +2 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +2 -1
- package/dist/match-resolve-fill.d.ts +196 -0
- package/dist/match-resolve-fill.d.ts.map +1 -0
- package/dist/match-resolve-fill.js +700 -0
- package/dist/match-resolve-fill.test-support.d.ts +34 -0
- package/dist/match-resolve-fill.test-support.d.ts.map +1 -0
- package/dist/match-resolve-fill.test-support.js +81 -0
- package/dist/runtime-protected-state.d.ts.map +1 -1
- package/dist/runtime-protected-state.js +12 -0
- package/dist/runtime-state.d.ts +6 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +6 -0
- package/dist/secrets/form-matcher.d.ts.map +1 -1
- package/dist/secrets/form-matcher.js +76 -27
- package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -1
- package/dist/secrets/protected-exact-value-redaction.js +6 -0
- package/dist/secrets/protected-fill.js +3 -3
- package/dist/session.d.ts +3 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -2
- package/dist/solver/browser-launcher.d.ts.map +1 -1
- package/dist/solver/browser-launcher.js +2 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/docs/README.md +28 -11
- package/docs/api-reference.md +311 -19
- package/docs/assistive-runtime.md +41 -16
- package/docs/getting-started.md +45 -1
- package/docs/integration-checklist.md +32 -3
- package/docs/match-resolve-fill.md +699 -0
- package/docs/protected-fill.md +373 -91
- package/docs/testing.md +147 -15
- package/docs/troubleshooting.md +5 -0
- package/examples/README.md +7 -0
- package/examples/match-resolve-fill.ts +107 -0
- package/package.json +4 -2
package/docs/protected-fill.md
CHANGED
|
@@ -1,60 +1,69 @@
|
|
|
1
1
|
# AgentBrowse Protected Fill Guide
|
|
2
2
|
|
|
3
|
-
Protected fill
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
35
|
+
## When Normal `act(..., 'fill', value)` Is Enough
|
|
26
36
|
|
|
27
|
-
Use ordinary `act(
|
|
37
|
+
Use ordinary `act(targetRef, 'fill', value)` when:
|
|
28
38
|
|
|
29
|
-
- the value is not sensitive
|
|
30
|
-
- you are filling one
|
|
31
|
-
|
|
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
|
-
##
|
|
44
|
+
## Mental Model
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
Protected fill is a contract between three inputs:
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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(
|
|
77
|
+
const fillableForm = observeResult.fillableForms.find(
|
|
78
|
+
(form) => form.purpose === 'payment_card'
|
|
79
|
+
);
|
|
69
80
|
if (!fillableForm) {
|
|
70
|
-
throw new Error('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
The currently surfaced purposes are:
|
|
281
|
+
## Top-Level Result
|
|
93
282
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
113
|
-
`fillableForm.fields`. Always build `protectedValues` from that list rather
|
|
114
|
-
than assuming defaults.
|
|
302
|
+
## Execution Kinds
|
|
115
303
|
|
|
116
|
-
|
|
304
|
+
`execution.kind` tells you what happened during the fill itself.
|
|
117
305
|
|
|
118
|
-
|
|
306
|
+
### `kind: 'success'`
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
{ kind: 'success'; filledFields: Array<{ fieldKey; targetRef }> }
|
|
310
|
+
```
|
|
119
311
|
|
|
120
|
-
|
|
312
|
+
All declared fields applied. `filledFields` lists what was touched.
|
|
121
313
|
|
|
122
|
-
|
|
123
|
-
- approval flow
|
|
124
|
-
- policy decisions
|
|
125
|
-
- claims or grants
|
|
314
|
+
### `kind: 'binding_stale'`
|
|
126
315
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
2. a `fillableForm` produced by `observe(...)`
|
|
133
|
-
3. the sensitive values from your own trusted source
|
|
329
|
+
### `kind: 'validation_failed'`
|
|
134
330
|
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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.
|