@mercuryo-ai/agentbrowse 0.2.60 → 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 (105) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +132 -14
  3. package/dist/browser-session-state.d.ts +40 -10
  4. package/dist/browser-session-state.d.ts.map +1 -1
  5. package/dist/browser-session-state.js +63 -5
  6. package/dist/commands/act.d.ts.map +1 -1
  7. package/dist/commands/act.js +548 -535
  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 +5 -12
  11. package/dist/commands/browser-connection-failure.d.ts +9 -0
  12. package/dist/commands/browser-connection-failure.d.ts.map +1 -0
  13. package/dist/commands/browser-connection-failure.js +15 -0
  14. package/dist/commands/browser-status.d.ts +0 -2
  15. package/dist/commands/browser-status.d.ts.map +1 -1
  16. package/dist/commands/browser-status.js +27 -37
  17. package/dist/commands/close.d.ts.map +1 -1
  18. package/dist/commands/close.js +5 -0
  19. package/dist/commands/extract.d.ts.map +1 -1
  20. package/dist/commands/extract.js +147 -144
  21. package/dist/commands/interaction-kernel.d.ts +1 -1
  22. package/dist/commands/interaction-kernel.d.ts.map +1 -1
  23. package/dist/commands/interaction-kernel.js +1 -1
  24. package/dist/commands/launch.d.ts +0 -1
  25. package/dist/commands/launch.d.ts.map +1 -1
  26. package/dist/commands/launch.js +11 -12
  27. package/dist/commands/navigate.d.ts.map +1 -1
  28. package/dist/commands/navigate.js +79 -73
  29. package/dist/commands/observe-accessibility.d.ts.map +1 -1
  30. package/dist/commands/observe-accessibility.js +36 -2
  31. package/dist/commands/observe-inventory.d.ts +50 -7
  32. package/dist/commands/observe-inventory.d.ts.map +1 -1
  33. package/dist/commands/observe-inventory.js +822 -99
  34. package/dist/commands/observe-persistence.d.ts.map +1 -1
  35. package/dist/commands/observe-persistence.js +49 -6
  36. package/dist/commands/observe-projection.d.ts +6 -2
  37. package/dist/commands/observe-projection.d.ts.map +1 -1
  38. package/dist/commands/observe-projection.js +251 -27
  39. package/dist/commands/observe-semantics.d.ts +1 -0
  40. package/dist/commands/observe-semantics.d.ts.map +1 -1
  41. package/dist/commands/observe-semantics.js +541 -135
  42. package/dist/commands/observe-signals.d.ts +4 -4
  43. package/dist/commands/observe-signals.d.ts.map +1 -1
  44. package/dist/commands/observe-signals.js +2 -2
  45. package/dist/commands/observe-surfaces.d.ts +2 -1
  46. package/dist/commands/observe-surfaces.d.ts.map +1 -1
  47. package/dist/commands/observe-surfaces.js +143 -45
  48. package/dist/commands/observe.d.ts +5 -1
  49. package/dist/commands/observe.d.ts.map +1 -1
  50. package/dist/commands/observe.js +266 -274
  51. package/dist/commands/screenshot.d.ts.map +1 -1
  52. package/dist/commands/screenshot.js +50 -64
  53. package/dist/commands/semantic-observe.d.ts.map +1 -1
  54. package/dist/commands/semantic-observe.js +43 -0
  55. package/dist/library.d.ts +3 -1
  56. package/dist/library.d.ts.map +1 -1
  57. package/dist/library.js +3 -1
  58. package/dist/match-resolve-fill.d.ts +196 -0
  59. package/dist/match-resolve-fill.d.ts.map +1 -0
  60. package/dist/match-resolve-fill.js +700 -0
  61. package/dist/match-resolve-fill.test-support.d.ts +34 -0
  62. package/dist/match-resolve-fill.test-support.d.ts.map +1 -0
  63. package/dist/match-resolve-fill.test-support.js +81 -0
  64. package/dist/protected-fill.d.ts.map +1 -1
  65. package/dist/protected-fill.js +46 -7
  66. package/dist/runtime-protected-state.d.ts.map +1 -1
  67. package/dist/runtime-protected-state.js +12 -0
  68. package/dist/runtime-state.d.ts +6 -0
  69. package/dist/runtime-state.d.ts.map +1 -1
  70. package/dist/runtime-state.js +6 -0
  71. package/dist/secrets/form-matcher.d.ts.map +1 -1
  72. package/dist/secrets/form-matcher.js +76 -27
  73. package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -1
  74. package/dist/secrets/protected-exact-value-redaction.js +6 -0
  75. package/dist/secrets/protected-fill.js +3 -3
  76. package/dist/session.d.ts +3 -3
  77. package/dist/session.d.ts.map +1 -1
  78. package/dist/session.js +2 -2
  79. package/dist/solver/browser-launcher.d.ts.map +1 -1
  80. package/dist/solver/browser-launcher.js +2 -1
  81. package/dist/sticky-owner-host-entry.d.ts +2 -0
  82. package/dist/sticky-owner-host-entry.d.ts.map +1 -0
  83. package/dist/sticky-owner-host-entry.js +97 -0
  84. package/dist/sticky-owner.d.ts +15 -0
  85. package/dist/sticky-owner.d.ts.map +1 -0
  86. package/dist/sticky-owner.js +431 -0
  87. package/dist/testing.d.ts +1 -0
  88. package/dist/testing.d.ts.map +1 -1
  89. package/dist/testing.js +1 -0
  90. package/docs/README.md +28 -11
  91. package/docs/api-reference.md +311 -19
  92. package/docs/assistive-runtime.md +41 -16
  93. package/docs/configuration.md +36 -4
  94. package/docs/getting-started.md +73 -5
  95. package/docs/integration-checklist.md +32 -3
  96. package/docs/match-resolve-fill.md +699 -0
  97. package/docs/protected-fill.md +373 -91
  98. package/docs/testing.md +147 -15
  99. package/docs/troubleshooting.md +47 -6
  100. package/examples/README.md +7 -0
  101. package/examples/match-resolve-fill.ts +107 -0
  102. package/package.json +4 -2
  103. package/dist/protected-fill-browser.d.ts +0 -22
  104. package/dist/protected-fill-browser.d.ts.map +0 -1
  105. package/dist/protected-fill-browser.js +0 -52
@@ -97,21 +97,211 @@ Captures a screenshot of the current page.
97
97
 
98
98
  Closes the browser session.
99
99
 
100
- ## Stable Error Code Arrays
100
+ ### `match(subject, options)`
101
101
 
102
- The root package exports stable top-level error code arrays for command
103
- branching:
102
+ Decides which caller-supplied candidate value fits an observed target or
103
+ fillable form. Pure and local — does not call the network and does not
104
+ mutate browser state. See
105
+ [Match / Resolve / Fill Guide](./match-resolve-fill.md) for the full
106
+ mental model.
104
107
 
105
- - `ACT_ERROR_CODES`
106
- - `ATTACH_ERROR_CODES`
107
- - `CLOSE_ERROR_CODES`
108
- - `EXTRACT_ERROR_CODES`
109
- - `LAUNCH_ERROR_CODES`
110
- - `NAVIGATE_ERROR_CODES`
111
- - `OBSERVE_ERROR_CODES`
112
- - `SCREENSHOT_ERROR_CODES`
108
+ ```ts
109
+ function match(
110
+ subject: TargetDescriptor | ProtectedFillForm,
111
+ options: AgentbrowseMatchOptions,
112
+ ): Promise<AgentbrowseMatchResult>;
113
+
114
+ interface AgentbrowseMatchOptions {
115
+ from: AgentbrowseMatchSource;
116
+ host?: string;
117
+ protectedTargetRefs?: ReadonlySet<string>;
118
+ }
119
+ ```
120
+
121
+ ### `resolve(plan, options)`
122
+
123
+ Turns one or many `needs_resolution` plans into ready match results
124
+ through a caller-supplied adapter. `ready` results pass through
125
+ untouched. Overloaded for single plan and batch arrays.
126
+
127
+ ```ts
128
+ function resolve(
129
+ plan: AgentbrowseMatchResult,
130
+ options: AgentbrowseResolveOptions,
131
+ ): Promise<AgentbrowseMatchResult>;
132
+
133
+ function resolve(
134
+ plans: ReadonlyArray<AgentbrowseMatchResult>,
135
+ options: AgentbrowseResolveOptions,
136
+ ): Promise<AgentbrowseMatchResult[]>;
137
+
138
+ interface AgentbrowseResolveOptions {
139
+ with: AgentbrowseMatchResolver;
140
+ }
141
+ ```
142
+
143
+ ### `fill(session, subject, plan, options?)`
144
+
145
+ Applies a match result to the browser. Dereferences the opaque
146
+ value/artifact ref internally and hands off to the standard `act(...)`
147
+ path (single targets) or `resolver.fill(...)` (grouped protected forms).
148
+
149
+ ```ts
150
+ function fill(
151
+ session: BrowserCommandSession,
152
+ subject: Pick<TargetDescriptor, 'ref'> | ProtectedFillForm,
153
+ plan: AgentbrowseMatchResult,
154
+ options?: AgentbrowseFillOptions,
155
+ ): Promise<AgentbrowseFillResult>;
156
+
157
+ interface AgentbrowseFillOptions {
158
+ resolver?: AgentbrowseMatchResolver;
159
+ }
160
+ ```
161
+
162
+ `fill(...)` can run `resolve` inline when you pass a `resolver` for a
163
+ plan that still needs resolution — equivalent to the two-call form. See
164
+ [match-resolve-fill.md → Walk-through 3](./match-resolve-fill.md#walk-through-3--collapsing-to-one-call).
113
165
 
114
- These arrays back the exported `*ErrorCode` types.
166
+ ## Result Shape
167
+
168
+ All main commands share the same top-level pattern.
169
+
170
+ ```ts
171
+ // success
172
+ { success: true, ...commandSpecificFields }
173
+
174
+ // failure
175
+ {
176
+ success: false,
177
+ error: <ErrorCode>,
178
+ outcomeType: <OutcomeType>,
179
+ message: string,
180
+ reason: string,
181
+ ...commandSpecificFields,
182
+ }
183
+ ```
184
+
185
+ - `error` — stable top-level code from the per-command table below.
186
+ Branch on this, not on `reason` or `message`.
187
+ - `outcomeType` — stable outcome category (e.g. `binding_stale`,
188
+ `blocked`). Same vocabulary per command as the `*_OUTCOME_TYPES`
189
+ exports.
190
+ - `message` — human-readable short message.
191
+ - `reason` — detailed reason string. Usually a lower-level error code,
192
+ a truncation detail, or an explanation; see the sticky-owner note
193
+ below for one common special case.
194
+
195
+ The root package exports stable arrays backing the `*ErrorCode` and
196
+ `*OutcomeType` types for every command: `ACT_ERROR_CODES`,
197
+ `ACT_OUTCOME_TYPES`, `ATTACH_ERROR_CODES`, `CLOSE_ERROR_CODES`,
198
+ `EXTRACT_ERROR_CODES`, `EXTRACT_OUTCOME_TYPES`, `LAUNCH_ERROR_CODES`,
199
+ `NAVIGATE_ERROR_CODES`, `OBSERVE_ERROR_CODES`, `OBSERVE_OUTCOME_TYPES`,
200
+ `SCREENSHOT_ERROR_CODES`, `SCREENSHOT_OUTCOME_TYPES`.
201
+
202
+ ### Cross-command codes
203
+
204
+ Any command that drives an already-open browser session may surface:
205
+
206
+ | `error` | When | Action |
207
+ | --- | --- | --- |
208
+ | `browser_connection_failed` | AgentBrowse could not reach the browser. | Check `reason`. A common special value is `sticky_owner_unrecoverable` — the prior browser session is lost; launch or attach a fresh session before retrying. |
209
+
210
+ ### Error codes by command
211
+
212
+ #### `launch`
213
+
214
+ | `error` | When | Action |
215
+ | --- | --- | --- |
216
+ | `browser_launch_failed` | The managed browser could not be started. | Inspect `reason`/`message`; verify the host can run the browser. |
217
+
218
+ #### `attach`
219
+
220
+ | `error` | When | Action |
221
+ | --- | --- | --- |
222
+ | `browser_attach_failed` | Could not attach to the provided CDP URL. | Verify the CDP URL is reachable and exposes the protocol. |
223
+
224
+ #### `navigate`
225
+
226
+ | `error` | When | Action |
227
+ | --- | --- | --- |
228
+ | `browser_connection_failed` | See Cross-command codes. | — |
229
+ | `navigation_failed` | Navigation did not complete. | Retry, or re-observe to see current page state. |
230
+
231
+ #### `observe`
232
+
233
+ | `error` | When | Action |
234
+ | --- | --- | --- |
235
+ | `browser_connection_failed` | See Cross-command codes. | — |
236
+ | `observe_failed` | Page inspection failed (DOM or assistive runtime error). | Retry; if persistent, check the assistive runtime. |
237
+ | `protected_observe_blocked` | The page is under active protected exposure; observation is blocked. | Complete or cancel the protected step first. |
238
+
239
+ #### `act`
240
+
241
+ | `error` | When | Action |
242
+ | --- | --- | --- |
243
+ | `act_failed` | Generic action failure not covered by a specific code. | Check `reason`/`message`. |
244
+ | `action_not_allowed_for_target` | The requested action (`click`/`fill`/`type`/`select`/`press`) is not valid for this target kind. | Use an action compatible with the target's capability. |
245
+ | `browser_connection_failed` | See Cross-command codes. | — |
246
+ | `no_observable_progress` | The action ran but no DOM/UI change was detected within the wait window. | Re-observe; the target may need a different interaction. |
247
+ | `stale_target` | The target binding is stale at execution time. | Re-observe and rebind. |
248
+ | `stale_target_ref` | The passed `targetRef` no longer maps to an observed element. | Re-observe. |
249
+ | `target_disabled` | Element is present but disabled. | Wait for enablement or resolve the blocker. |
250
+ | `target_gated` | Element is gated behind an intermediate step. | Resolve the gating step first. |
251
+ | `target_not_actionable` | Element exists but cannot be actioned (visibility or position). | Scroll/expand the owning scope, then re-observe. |
252
+ | `target_readonly` | Element is read-only; `fill`/`type` are not allowed. | Use a different action or a different target. |
253
+ | `target_surface_inactive` | The owning scope is currently inactive (e.g. a closed modal). | Activate or expand the scope first. |
254
+ | `target_surface_not_live` | The owning scope is no longer live on the page. | Re-observe. |
255
+ | `target_surface_unavailable` | The owning scope is not currently available. | Re-observe after any UI transition. |
256
+ | `unknown_target_ref` | The `targetRef` was not issued by a prior `observe(...)`. | Never synthesise refs — pass back what `observe` returned. |
257
+ | `validation_blocked` | The target accepted the input, but page-level validation blocks continuation. | Resolve the validation error and re-observe. |
258
+
259
+ #### `extract`
260
+
261
+ | `error` | When | Action |
262
+ | --- | --- | --- |
263
+ | `browser_connection_failed` | See Cross-command codes. | — |
264
+ | `expired_extract_scope` | The scope ref expired (page moved past its lifetime). | Re-observe and use the new scope. |
265
+ | `extract_failed` | Extraction failed at runtime. | Check `reason`/`message` and retry. |
266
+ | `extract_output_truncated` | The assistive runtime returned a structured output that was cut off. | Raise `maxOutputTokens` in your adapter. |
267
+ | `invalid_extract_schema` | The passed schema is not a supported shape. | See [Extraction Schema Rules](#extraction-schema-rules). |
268
+ | `invalid_extract_scope` | The scope ref was provided but is not valid. | Verify the ref came from `observe(...)`. |
269
+ | `stale_extract_scope` | The scope ref is stale against the current DOM. | Re-observe. |
270
+ | `unknown_scope_ref` | The scope ref was not issued by a prior `observe(...)`. | Never synthesise refs. |
271
+
272
+ #### `screenshot`
273
+
274
+ | `error` | When | Action |
275
+ | --- | --- | --- |
276
+ | `browser_connection_failed` | See Cross-command codes. | — |
277
+ | `protected_screenshot_blocked` | The page is under active protected exposure; screenshot is blocked. | Complete or cancel the protected step first. |
278
+ | `screenshot_failed` | The screenshot attempt failed. | Check `reason`/`message`. |
279
+
280
+ #### `close`
281
+
282
+ | `error` | When | Action |
283
+ | --- | --- | --- |
284
+ | `browser_close_failed` | The browser session could not be closed cleanly. | Usually cosmetic; the process will eventually clean up. |
285
+
286
+ #### `fill`
287
+
288
+ Contract failures from `fill(...)` share a dedicated shape,
289
+ `AgentbrowseFillFailureResult`, with `failureSurface: 'contract'` and
290
+ `action: 'fill'`. Browser-level failures (stale refs, validation,
291
+ connection) flow through the underlying `ActResult` / `resolver.fill`
292
+ result instead — see `act` codes above and
293
+ [protected-fill.md](./protected-fill.md).
294
+
295
+ | `error` | When | Action |
296
+ | --- | --- | --- |
297
+ | `match_no_match` | `fill` received a `no_match` / `no_match_group` plan, or a subject-shape mismatch (field plan on a form, form plan on a field). | Do not retry with the same input. Re-observe or branch on the match result first. |
298
+ | `match_ambiguous` | `fill` received an `ambiguous` / `ambiguous_group` plan. | Ask the caller to disambiguate before filling. |
299
+ | `match_resolver_required` | Plan needs external resolution and no `resolver` was supplied, or the resolver is missing the required capability (`.resolve` for needs-resolution, `.fill` for grouped). | Provide a resolver adapter that implements the capability. |
300
+ | `match_value_unavailable` | Internal value accessor is gone. Typically means a `ready` result was serialized across a process boundary and the non-enumerable accessor was lost. | Run `resolve` → `fill` inside the same process, or ship the `needs_resolution` plan instead. |
301
+ | `match_artifact_unavailable` | Same failure mode as above, for grouped ready plans. | Same fix. |
302
+
303
+ See [Protected Fill](./protected-fill.md) for the separate error and
304
+ execution-kind vocabulary used by `fillProtectedForm(...)`.
115
305
 
116
306
  ## Error Classes
117
307
 
@@ -123,13 +313,6 @@ For code paths that want `instanceof` checks instead of string matching on
123
313
  - `AssistiveStructuredOutputTruncatedError` — thrown when the assistive
124
314
  runtime returns a structured output that was cut off mid-response.
125
315
 
126
- ## Core Result Shapes
127
-
128
- All main commands use the same top-level pattern:
129
-
130
- - success: `{ success: true, ... }`
131
- - failure: `{ success: false, error, outcomeType, message, reason, ... }`
132
-
133
316
  ## Observe Types
134
317
 
135
318
  ### `ObserveTarget`
@@ -197,6 +380,115 @@ Fields:
197
380
  - `framePath`
198
381
  - `source`
199
382
 
383
+ ## Match / Resolve / Fill Types
384
+
385
+ The exported types for the `match` / `resolve` / `fill` primitives.
386
+ Read [match-resolve-fill.md](./match-resolve-fill.md) for how they
387
+ compose in practice; this section is lookup-only.
388
+
389
+ ### Candidate sources
390
+
391
+ - `AgentbrowseMatchSource` — union of `Record<string, AgentbrowseMatchValue>`,
392
+ `ReadonlyArray<AgentbrowseMatchCandidate>`,
393
+ `AgentbrowseMatchStore`,
394
+ `ReadonlyArray<AgentbrowseGroupMatchCandidate>`, or
395
+ `AgentbrowseGroupMatchStore`. All of these are valid `options.from`
396
+ values for `match(...)`.
397
+ - `AgentbrowseMatchValue` — `string | number`.
398
+ - `AgentbrowseMatchApplicability` — `{ target: 'global' | 'host'; value?: string }`.
399
+ - `AgentbrowseMatchCandidate` — single-field candidate with optional
400
+ `candidateRef`, `value`, `type`, `label`, `semanticTags`,
401
+ `applicability`, `resolve` plan.
402
+ - `AgentbrowseGroupMatchCandidate` — grouped candidate with required
403
+ `fieldKeys` plus optional `candidateRef`, `itemRef`, `label`,
404
+ `confidence`, `applicability`, `resolve` plan, `artifact`.
405
+ - `AgentbrowseMatchStore` — `{ entries(), read(candidateRef) }`.
406
+ Opaque single-field store; values stay behind `read`.
407
+ - `AgentbrowseGroupMatchStore` — `{ entries(), readArtifact(candidateRef) }`.
408
+ Opaque grouped store; artifacts stay behind `readArtifact`.
409
+
410
+ ### Match result union
411
+
412
+ `AgentbrowseMatchResult` is a discriminated union over `kind`:
413
+
414
+ - `AgentbrowseReadyMatchResult` — `kind: 'ready'` (single target).
415
+ - `AgentbrowseNeedsResolutionMatchResult` — `kind: 'needs_resolution'`
416
+ with `plan: AgentbrowseResolutionPlan`.
417
+ - `AgentbrowseAmbiguousMatchResult` — `kind: 'ambiguous'` with
418
+ `candidates: string[]`.
419
+ - `AgentbrowseNoMatchResult` — `kind: 'no_match'` with a stable
420
+ `reason`: `'protected_target' | 'no_candidate' | 'scope_ineligible' | 'incompatible_shape' | 'low_confidence'`.
421
+ - `AgentbrowseReadyGroupMatchResult` — `kind: 'ready_group'`.
422
+ - `AgentbrowseNeedsResolutionGroupMatchResult` — `kind: 'needs_resolution_group'`
423
+ with `plan: AgentbrowseGroupResolutionPlan`.
424
+ - `AgentbrowseAmbiguousGroupMatchResult` — `kind: 'ambiguous_group'`.
425
+ - `AgentbrowseNoGroupMatchResult` — `kind: 'no_match_group'` with the
426
+ same `reason` vocabulary.
427
+
428
+ `AgentbrowseResolvableMatchResult` is the narrower union of
429
+ `'needs_resolution' | 'needs_resolution_group'` — what `resolve(...)`
430
+ actually hands to your adapter.
431
+
432
+ ### Resolution plans
433
+
434
+ - `AgentbrowseResolutionPlan` — `{ targetRef, candidateRef, fieldKey, type?, resolve }`.
435
+ - `AgentbrowseGroupResolutionPlan` — `{ fillRef, pageRef, scopeRef?, purpose, candidateRef, itemRef?, fieldKeys, resolve }`.
436
+ - `AgentbrowseMatchResolutionRequest` — `{ kind: string; key?: string; params?: Record<string, unknown> }`.
437
+ The `resolve` field on a candidate or plan. AgentBrowse does not
438
+ interpret `kind` or `key` — they are opaque to the core and meaningful
439
+ only to your adapter.
440
+
441
+ ### Resolved resources
442
+
443
+ - `AgentbrowseResolvedResource` — `AgentbrowseResolvedValueResource | AgentbrowseResolvedArtifactResource`.
444
+ - `AgentbrowseResolvedValueResource` — `{ kind: 'value'; value }`.
445
+ - `AgentbrowseResolvedArtifactResource` — `{ kind: 'artifact'; artifact; itemRef?; requestId?; resolutionPath?; claimedAt? }`.
446
+ - `AgentbrowseReadyGroupFillInput` — the shape `resolver.fill` receives:
447
+ `{ candidateRef, itemRef?, fieldKeys, artifact, requestId?, resolutionPath?, claimedAt? }`.
448
+
449
+ ### Resolver adapter
450
+
451
+ Two exported interfaces. The `{ resolver }` slot on `fill(...)` accepts
452
+ either via a union; `resolve(plan, { with })` only accepts the main
453
+ resolver.
454
+
455
+ ```ts
456
+ interface AgentbrowseMatchResolver {
457
+ resolve(plan): Promise<AgentbrowseResolvedResource>;
458
+ resolveBatch?(plans): Promise<ReadonlyArray<AgentbrowseResolvedResource>>;
459
+ fill?(session, subject, ready): Promise<Record<string, unknown> & { success: boolean }>;
460
+ }
461
+
462
+ interface AgentbrowseGroupFillHandler {
463
+ fill(session, subject, ready): Promise<Record<string, unknown> & { success: boolean }>;
464
+ }
465
+ ```
466
+
467
+ - `AgentbrowseMatchResolver` — full adapter. `resolve` is required;
468
+ `resolveBatch` and `fill` are optional. Used by `resolve(plan, { with })`
469
+ and by `fill(...)` for all plan kinds.
470
+ - `AgentbrowseGroupFillHandler` — narrow handler used only at the
471
+ `fill(...)` boundary when the plan is `ready_group` and no resolution
472
+ is needed. A handler is not accepted by `resolve(...)`.
473
+
474
+ At runtime, `fill(...)` picks the right capability via an internal type
475
+ guard (`hasResolveCapability` / `hasGroupFillCapability`). When a
476
+ capability the current plan needs is missing on the passed object,
477
+ `fill(...)` returns a typed `match_resolver_required` failure instead
478
+ of throwing.
479
+
480
+ See [match-resolve-fill.md → Resolver Interface](./match-resolve-fill.md#resolver-interface)
481
+ for examples of each shape.
482
+
483
+ ### Fill result
484
+
485
+ - `AgentbrowseFillResult` — `ActResult | AgentbrowseFillFailureResult | (Record<string, unknown> & { success: boolean })`.
486
+ Single-target fill returns `ActResult`; grouped fill returns whatever
487
+ your `resolver.fill` returned; contract failures return
488
+ `AgentbrowseFillFailureResult`.
489
+ - `AgentbrowseFillFailureResult` — `{ success: false; failureSurface: 'contract'; error; outcomeType; message; reason; targetRef?; fillRef?; action: 'fill' }`.
490
+ See the [`fill` error table](#fill) above for `error` codes.
491
+
200
492
  ## Ref Glossary
201
493
 
202
494
  - `ref`
@@ -58,6 +58,9 @@ this usually means:
58
58
 
59
59
  1. Convert the Zod schema to JSON Schema (e.g. with
60
60
  `@browserbasehq/stagehand`'s `toJsonSchema`, or your own helper).
61
+ `@browserbasehq/stagehand` is a direct dependency of this package, so
62
+ `toJsonSchema` is available without a separate install. Alternatives
63
+ like `zod-to-json-schema` work too.
61
64
  2. Pass it as `response_format: { type: 'json_schema', json_schema: { ... } }`.
62
65
  3. Parse `choices[0].message.content` and return `{ data, usage? }`.
63
66
 
@@ -99,6 +102,9 @@ by hand.
99
102
 
100
103
  ## Recommended Setup
101
104
 
105
+ The preferred path is a per-client runtime: pass your runtime into
106
+ `createAgentbrowseClient({ assistiveRuntime })` and reuse that client.
107
+
102
108
  ```ts
103
109
  import { createAgentbrowseClient } from '@mercuryo-ai/agentbrowse';
104
110
 
@@ -106,16 +112,38 @@ const client = createAgentbrowseClient({
106
112
  assistiveRuntime: createOpenAiCompatibleAssistiveRuntime({
107
113
  baseUrl: 'https://api.openai.com/v1',
108
114
  apiKey: process.env.OPENAI_API_KEY!,
109
- model: 'gpt-4.1-mini',
115
+ // Any OpenAI-compatible model that supports structured outputs.
116
+ model: process.env.OPENAI_MODEL ?? '<your-model>',
110
117
  }),
111
118
  });
112
119
  ```
113
120
 
114
121
  This pattern works well when:
115
122
 
116
- - your app is multi-tenant
117
- - you run parallel tests
118
- - different consumers in one process need different LLM settings
123
+ - your app is multi-tenant;
124
+ - you run parallel tests;
125
+ - different consumers in one process need different LLM settings.
126
+
127
+ ### Per-client vs global runtime
128
+
129
+ | Setup | When to use |
130
+ | --- | --- |
131
+ | `createAgentbrowseClient({ assistiveRuntime })` | Default. Keeps the runtime scoped to one client, works with multi-tenant and parallel scenarios. |
132
+ | `configureAgentbrowseAssistiveRuntime(runtime)` | Fallback for small scripts and single-tenant processes — sets one global runtime for the whole process. Not recommended when multiple consumers may coexist. |
133
+
134
+ Global-runtime shape:
135
+
136
+ ```ts
137
+ import { configureAgentbrowseAssistiveRuntime } from '@mercuryo-ai/agentbrowse';
138
+
139
+ configureAgentbrowseAssistiveRuntime(
140
+ createOpenAiCompatibleAssistiveRuntime({
141
+ baseUrl: 'https://api.openai.com/v1',
142
+ apiKey: process.env.OPENAI_API_KEY!,
143
+ model: process.env.OPENAI_MODEL ?? '<your-model>',
144
+ })
145
+ );
146
+ ```
119
147
 
120
148
  ## OpenAI-Compatible Helper Example
121
149
 
@@ -232,21 +260,18 @@ Examples:
232
260
  - OpenRouter base URL:
233
261
  `https://openrouter.ai/api/v1`
234
262
 
235
- ## Small Script Fallback
236
-
237
- For small scripts, you can also use:
238
-
239
- ```ts
240
- import { configureAgentbrowseAssistiveRuntime } from '@mercuryo-ai/agentbrowse';
241
- ```
242
-
243
- This is a convenience fallback, not the preferred embedded pattern.
244
-
245
263
  ## What Happens Without Assistive Runtime
246
264
 
247
- - `extract(...)` cannot run successfully
265
+ - `extract(...)` cannot run successfully.
248
266
  - `observe(session, goal)` still runs, but quality may be lower because
249
- AgentBrowse falls back to local heuristics instead of LLM-assisted ranking
267
+ AgentBrowse falls back to local heuristics instead of LLM-assisted
268
+ ranking.
269
+ - `fillProtectedForm(...)` returns
270
+ `{ kind: 'unexpected_error', reason: 'assisted_value_resolution_failed' }`
271
+ for fields that require LLM-assisted resolution (split `full_name` into
272
+ given/family, localised dropdown values like nationality on a
273
+ non-English page, or any field pinned to the `llm_assisted` policy).
274
+ See [Protected Fill Guide](./protected-fill.md#split-fields-and-assistive-runtime).
250
275
 
251
276
  ## Testing Runtime
252
277
 
@@ -15,6 +15,12 @@ Most applications can start with this mental model:
15
15
  2. keep the returned `session` in memory
16
16
  3. pass that `session` into later calls
17
17
 
18
+ Both bootstrap the same sticky-owner lifecycle. AgentBrowse may keep that
19
+ owner in-process or in an internal detached host, but consumers do not manage
20
+ that host directly. Detached hosts default to a 30 minute TTL and may be
21
+ recreated on the next browser command if the browser session itself is still
22
+ alive.
23
+
18
24
  You only need more configuration when you want one of these:
19
25
 
20
26
  - custom LLM integration
@@ -43,6 +49,10 @@ const attached = await attach(remoteCdpUrl, {
43
49
  The provider label is metadata only — AgentBrowse treats the connection as
44
50
  a generic CDP-attached browser session regardless of the label.
45
51
 
52
+ `attach(...)` creates the same sticky-owner metadata as `launch(...)`. Later
53
+ browser commands reuse that owner and only attempt a fresh root attach again
54
+ as a repair path after owner loss.
55
+
46
56
  ## Client Configuration
47
57
 
48
58
  ```ts
@@ -68,6 +78,9 @@ configuration is the cleaner embedded pattern.
68
78
  Persistence is optional. Use it when you want to restore a browser session
69
79
  after a process restart.
70
80
 
81
+ Persisted session files store browser identity plus versioned sticky-owner
82
+ metadata. They do not serialize a live Playwright connection.
83
+
71
84
  ### Default Store
72
85
 
73
86
  ```ts
@@ -76,12 +89,12 @@ import { loadBrowserSession, saveBrowserSession, status } from '@mercuryo-ai/age
76
89
  saveBrowserSession(session);
77
90
  const restored = loadBrowserSession();
78
91
 
79
- // Always check a restored session before using it the browser it points
80
- // at may already be gone.
92
+ // `null` means there is no usable persisted session. That includes
93
+ // incompatible reconnect-era records and incomplete owner metadata.
81
94
  if (restored) {
82
95
  const check = await status(restored);
83
- if (!check.success) {
84
- // The session is no longer reachable. Discard and relaunch.
96
+ if (!check.alive) {
97
+ // The session is no longer reachable. Discard and relaunch or re-attach.
85
98
  }
86
99
  }
87
100
  ```
@@ -90,6 +103,21 @@ Default path:
90
103
 
91
104
  `~/.agentbrowse/browse-session.json`
92
105
 
106
+ If the detached owner host is gone but the underlying browser session is still
107
+ alive, the first command after restore may repair ownership. If the browser is
108
+ gone, AgentBrowse fails closed and you should start a fresh session.
109
+
110
+ ### Sticky Owner TTL
111
+
112
+ Detached sticky-owner hosts use a bounded lifetime by default:
113
+
114
+ - default TTL: `30` minutes
115
+ - env override: `AGENTBROWSE_STICKY_OWNER_TTL_MS=<milliseconds>`
116
+
117
+ This TTL is a resource guard for the detached owner host, not for the browser
118
+ session itself. If the TTL expires and the browser is still reachable, the next
119
+ browser command may bootstrap a fresh owner and continue.
120
+
93
121
  ### Custom Store
94
122
 
95
123
  For embedded apps, prefer an explicit store root:
@@ -108,6 +136,10 @@ store.delete();
108
136
 
109
137
  This avoids hidden machine-level coupling to `~/.agentbrowse`.
110
138
 
139
+ `store.load()` follows the same contract as `loadBrowserSession()`: it returns
140
+ `null` for missing files, incompatible old records, or unusable sticky-owner
141
+ metadata.
142
+
111
143
  ## Proxy Configuration
112
144
 
113
145
  The clearest way to use a proxy is to pass it directly to `launch(...)`.
@@ -19,9 +19,18 @@ The normal flow is:
19
19
  5. `close(session)` ends the browser session
20
20
 
21
21
  The `session` is the key object in the whole API. It is the handle that keeps
22
- the browser connection and runtime state together between calls. A session
23
- stays valid while the underlying browser connection is live; call
24
- `status(session)` to check if you need to.
22
+ the browser connection, runtime state, and sticky-owner metadata together
23
+ between calls. Healthy commands reuse that sticky owner instead of issuing a
24
+ fresh root attach on every call. If you persist the session and restart your
25
+ process, the next command may repair the owner while the underlying browser is
26
+ still alive; otherwise the session fails closed and you start fresh. Detached
27
+ sticky owners also have a bounded lifetime, so an idle or expired owner may be
28
+ recreated on the next browser command while the underlying browser session is
29
+ still live.
30
+
31
+ The sticky owner may live in-process or in an internal detached host. That is
32
+ an implementation detail of AgentBrowse, not a daemon you manage separately.
33
+ Detached hosts default to a 30 minute TTL.
25
34
 
26
35
  Refs returned by `observe(...)` (target refs, scope refs, fill refs) are
27
36
  valid for the page state that produced them, not forever. Any of these
@@ -33,12 +42,14 @@ invalidates them:
33
42
 
34
43
  After any of the above, call `observe(...)` again and use the new refs.
35
44
 
36
- At a high level, AgentBrowse has three kinds of behavior:
45
+ At a high level, AgentBrowse has four kinds of behavior:
37
46
 
38
47
  - normal browser execution for `launch`, `navigate`, `observe`, `act`,
39
48
  `status`, `screenshot`, and `close`
40
49
  - assistive page understanding for `extract` and some goal-based
41
50
  `observe(session, goal)` calls
51
+ - deterministic field data-plane for deciding which caller-supplied
52
+ value belongs in which observed field (`match`, `resolve`, `fill`)
42
53
  - protected fill for applying sensitive values you already have through a
43
54
  guarded form execution path
44
55
 
@@ -116,6 +127,11 @@ Success result includes:
116
127
  - current `url`
117
128
  - current `title`
118
129
 
130
+ `attach(...)` bootstraps the same sticky-owner lifecycle as `launch(...)`.
131
+ After attach succeeds, later browser commands use that owner. A new provider-
132
+ level root attach is only attempted again as an explicit repair path after
133
+ owner loss.
134
+
119
135
  ### `observe(session, goal?)`
120
136
 
121
137
  Reads the current page and returns what AgentBrowse found.
@@ -141,6 +157,22 @@ serve different intents:
141
157
  - `observe(session)` is for general page inspection
142
158
  - `observe(session, goal)` is for a focused question
143
159
 
160
+ These examples share a shape that works well: each names one control,
161
+ optionally anchored to a surface. A useful goal looks like
162
+ `"find <target> in <surface>"`:
163
+
164
+ - one target — a single field, button, or grid cell
165
+ - one surface — the active form, the open datepicker, the visible banner
166
+ - one step — the goal describes what the next `act` will target,
167
+ not the rest of the plan
168
+
169
+ When the task takes several steps, run one `observe` per step:
170
+
171
+ 1. `observe(session, "find the date picker trigger in the top search form")`
172
+ 2. `act(session, trigger.ref, "click")`
173
+ 3. `observe(session, "find May 5, 2026 in the open calendar")`
174
+ 4. `act(session, cell.ref, "click")`
175
+
144
176
  ### `act(session, targetRef, action, value?)`
145
177
 
146
178
  Executes a browser action against a `targetRef` returned by `observe(...)`.
@@ -172,12 +204,42 @@ before calling it.
172
204
  Returns local browser/runtime diagnostics for an existing session.
173
205
 
174
206
  Use it when you want to know whether the browser is still reachable and what
175
- page AgentBrowse believes it is on.
207
+ page AgentBrowse believes it is on. After restoring a persisted session,
208
+ `status(session)` is the cheapest explicit health check before more expensive
209
+ workflows.
176
210
 
177
211
  ### `close(session)`
178
212
 
179
213
  Closes the browser session.
180
214
 
215
+ This also terminates the internal sticky owner. Repeated closes and already-
216
+ dead owner hosts are treated as idempotent.
217
+
218
+ ### `match` / `resolve` / `fill`
219
+
220
+ Three primitives for the «key–value pairs into an observed form» problem.
221
+ Instead of calling `act(session, ref, 'fill', value)` by hand, the
222
+ primitives let you hand a source of candidate values to `match(...)`,
223
+ resolve externally stored values through a caller-supplied adapter, and
224
+ apply the result to the browser deterministically — without the values
225
+ passing through LLM prompts or public result objects.
226
+
227
+ The typical shape is `match → (resolve) → fill`:
228
+
229
+ ```ts
230
+ import { match, resolve, fill } from '@mercuryo-ai/agentbrowse';
231
+
232
+ const matched = await match(emailTarget, {
233
+ from: { email: 'traveler@example.com' },
234
+ });
235
+ await fill(session, emailTarget, matched);
236
+ ```
237
+
238
+ See the dedicated [Match / Resolve / Fill Guide](./match-resolve-fill.md)
239
+ for the full mental model, walk-throughs (value in hand, external
240
+ lookup, batch, grouped protected forms), and the design rules (no raw
241
+ values in public results, stable resolved refs, adapter boundary).
242
+
181
243
  ## How To Handle Results
182
244
 
183
245
  All main commands use the same broad pattern:
@@ -233,6 +295,11 @@ If you want to restore a browser session between process runs, use:
233
295
  - `loadBrowserSession()`
234
296
  - `createBrowserSessionStore({ rootDir })`
235
297
 
298
+ Persisted session records now require restorable sticky-owner metadata.
299
+ Incompatible reconnect-era records are rejected at load time instead of being
300
+ auto-migrated. Treat `loadBrowserSession() === null` as "no usable session",
301
+ not as a recoverable partial state.
302
+
236
303
  See:
237
304
 
238
305
  - [Configuration Guide](./configuration.md)
@@ -240,6 +307,7 @@ See:
240
307
  ## Next Docs
241
308
 
242
309
  - [API Reference](./api-reference.md)
310
+ - [Match / Resolve / Fill Guide](./match-resolve-fill.md)
243
311
  - [Configuration Guide](./configuration.md)
244
312
  - [Assistive Runtime Guide](./assistive-runtime.md)
245
313
  - [Protected Fill Guide](./protected-fill.md)