browser-pilot-cli 0.1.5 → 0.2.1

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.
@@ -0,0 +1,965 @@
1
+ # Browser Pilot Platform Specification
2
+
3
+ Status: **Authoritative, approved for implementation**
4
+ Baseline: `0c5661ccb133b2c9feda15732892f66ce947f232`
5
+ Last updated: 2026-07-25
6
+
7
+ This document is the source of truth for turning Browser Pilot from a
8
+ single-agent CLI with global state into a user-level browser control runtime
9
+ that any local Agent can install or embed. The two execution plans in
10
+ `docs/plans/` derive from this specification.
11
+
12
+ ## Purpose and Reader
13
+
14
+ This specification is for Browser Pilot maintainers, Agent-product integrators,
15
+ security reviewers, and test authors. It defines observable behavior and
16
+ compatibility boundaries before implementation. Internal component names near
17
+ the end are implementation boundaries required to preserve those behaviors;
18
+ they are not third-party APIs.
19
+
20
+ ## Evidence and Assumptions
21
+
22
+ Evidence from the baseline repository:
23
+
24
+ - The npm package currently publishes CLI binaries but no library exports.
25
+ - High-level actions, output formatting, file writes, and process exits are
26
+ combined in `src/cli.ts`.
27
+ - `src/state.ts` persists one active target, CDP session, and frame context.
28
+ - `src/snapshot.ts` persists one target's refs in a global file.
29
+ - `src/daemon.ts` exposes arbitrary CDP and keeps auth, network, dialog, and
30
+ popup state globally.
31
+ - Existing fixtures prove the current browser operations are worth preserving.
32
+
33
+ Evidence from the browser-use comparison is recorded in
34
+ `docs/plans/browser-capability-evolution.md` at the pinned research commit.
35
+
36
+ Assumptions used by this specification:
37
+
38
+ - Supported Agent runtimes can spawn a local child process and read protected
39
+ local Artifact files.
40
+ - Browser Pilot and embedded Agents execute under the same OS user account.
41
+ - Chrome continues to require an explicit user action to enable or authorize
42
+ remote debugging.
43
+ - An Agent product can map its own active lifecycle to Workspace and Lease
44
+ without Broker knowledge of that product's domain nouns.
45
+
46
+ ## Decision Summary
47
+
48
+ - **DEC-1:** Browser Pilot is an Agent-neutral, per-OS-user Browser Broker. It
49
+ is not a Tenon component and does not model conversations, runs, tasks, or
50
+ workers.
51
+ - **DEC-2:** Root Chrome DevTools Protocol (CDP) is the only browser connector.
52
+ Browser extensions are never required or supported.
53
+ - **DEC-3:** The public product surface is one official CLI executable. It
54
+ supports human one-shot commands and a persistent `bridge --stdio` machine
55
+ mode. Native SDK and MCP surfaces are out of scope.
56
+ - **DEC-4:** Existing one-shot `bp` commands remain a first-class integration
57
+ path. Embedded products bundle the same executable and use the stdio bridge.
58
+ - **DEC-5:** The production machine interface exposes controlled browser tools,
59
+ not arbitrary CDP forwarding. Raw CDP is internal. JavaScript evaluation is
60
+ an explicit developer capability.
61
+ - **DEC-6:** Invoking Browser Pilot grants the Agent full control of all
62
+ eligible current and future tabs in the selected BrowserInstance. Browser
63
+ Pilot does not implement task-intent approval, selected-tab grants, or
64
+ per-action confirmation. An Agent host may remove operations when launching
65
+ the Broker, and owns any higher-level approval UX it wants.
66
+ - **DEC-7:** Browser profile state is shared because controlled targets run in
67
+ the user's browser profile. Target assignments, commands, refs, rules, and
68
+ artifacts are isolated by Broker objects.
69
+
70
+ ## Objective, Constraints, and Selected Target
71
+
72
+ - **OBJ-1:** Any local Agent that can execute a process must be able to control
73
+ the user's real browser reliably, while preserving the user's profiles,
74
+ logins, cookies, and installed extensions.
75
+ - **OBJ-2:** Multiple unrelated Agent products must be able to use Browser Pilot
76
+ concurrently without overwriting each other's target, frame, ref, auth, or
77
+ network state.
78
+ - **OBJ-3:** Agents that already install and call `bp` themselves must continue
79
+ to work after the platform migration.
80
+ - **Minimum acceptable outcome:** The compatibility requirements in this spec
81
+ all pass. A partial SDK, Tenon-only adapter, or globally shared target state
82
+ does not count.
83
+ - **Clean-slate answer:** A signed, self-contained executable with a local
84
+ Broker, versioned stdio protocol, complete browser control, strong runtime
85
+ isolation, and a conformance kit.
86
+ - **Selected brownfield target:** Evolve the current TypeScript/npm CLI into
87
+ that architecture without replacing proven CDP capabilities or breaking the
88
+ current command workflow.
89
+
90
+ ### Constraints
91
+
92
+ - **CON-1 hard:** No browser extension may be used.
93
+ - **CON-2 hard:** All eligible ordinary Chrome tabs in the selected
94
+ BrowserInstance are visible and controllable. DevTools, unsupported internal
95
+ pages, and Browser Pilot's own internal targets remain excluded.
96
+ - **CON-3 hard:** Browser Pilot must not depend on Agent-specific lifecycle
97
+ concepts or model-provider APIs.
98
+ - **CON-4 hard:** Production clients cannot invoke arbitrary root CDP methods.
99
+ - **CON-5 hard:** Sensitive browser data and media must not be emitted to logs
100
+ or world-readable files.
101
+ - **CON-6 legacy:** Existing `bp` commands, JSON output, npm installation, and
102
+ the current Agent skill must remain usable during migration.
103
+ - **CON-7 platform:** Without an extension, root CDP exposes the browser to any
104
+ process that can independently reach its debugging endpoint. Broker
105
+ isolation protects Broker clients from each other; it is not an OS sandbox.
106
+ - **CON-8 scope:** The universal integration target is local Agents that can
107
+ spawn a process and access local files. Hosted Agents without a local
108
+ execution bridge are not supported by CLI-only distribution.
109
+
110
+ ### Rejected Alternatives
111
+
112
+ - **OPT-1 MCP as the canonical interface:** Rejected because it creates a
113
+ second public product surface and runtime dependency. An Agent adapter may
114
+ translate the stdio bridge to MCP outside Browser Pilot.
115
+ - **OPT-2 Native SDKs:** Rejected because every language binding would create a
116
+ versioned compatibility obligation. The executable protocol is the SDK.
117
+ - **OPT-3 One-shot CLI only:** Rejected because it cannot efficiently deliver
118
+ events, cancellation, leases, or repeated tool calls to an embedded Agent.
119
+ - **OPT-4 Agent-specific embedding:** Rejected because Tenon and OpenClaw are
120
+ clients and conformance fixtures, not Broker domain concepts.
121
+
122
+ ### Revisit Triggers
123
+
124
+ Reconsider MCP or a remote protocol only if Browser Pilot must support Agents
125
+ that cannot spawn a local process. Reconsider SDKs only if the stdio protocol
126
+ cannot provide required performance or type safety after measured use.
127
+
128
+ ## Preserved and Changed Behavior
129
+
130
+ ### Preserved
131
+
132
+ - Agent-managed installation via npm and direct `bp` invocation.
133
+ - Chrome remote-debugging discovery and connection.
134
+ - A distinct visible Pilot window using the user's browser profile.
135
+ - AX snapshots and numbered refs.
136
+ - Navigation, click, type, keyboard, upload, tabs, frames, dialogs, HTTP auth,
137
+ screenshots, PDF, cookies, and network tooling.
138
+ - Human-readable terminal output and JSON output for scripts.
139
+ - The existing skill and Playwright fixture coverage.
140
+
141
+ ### Changed
142
+
143
+ - CLI handlers become presentation adapters over reusable Broker services.
144
+ - Global active target/session/frame state becomes isolated Workspace and Lease
145
+ state.
146
+ - The single refs file becomes observation-scoped Broker state.
147
+ - The daemon's arbitrary `/cdp` endpoint becomes private implementation wiring.
148
+ - Dialogs become explicit events and decisions; they are not auto-accepted.
149
+ - Popup adoption requires a verified owned opener chain.
150
+ - Existing user tabs and newly opened tabs are included automatically in the
151
+ Workspace inventory; invoking the tool is the authorization boundary.
152
+ - Auth, network rules, request journals, and artifacts are scoped and cleaned up
153
+ by the owning Workspace or Lease.
154
+ - Screenshots and PDFs become mode-restricted, owner-scoped Artifact results
155
+ with quotas and expiry. The human CLI may export an Artifact to a
156
+ caller-selected path.
157
+
158
+ ## Product Model
159
+
160
+ ### ClientPrincipal
161
+
162
+ A stable identity for an installed Agent product or direct CLI user. It owns
163
+ Workspaces and their transient resources. It is used for correlation and
164
+ isolation, not as proof that an Agent's semantic decision is correct. A
165
+ Principal is not a conversation or task.
166
+
167
+ ### ClientConnection
168
+
169
+ One live `bridge --stdio` process connection. It is authenticated by the local
170
+ Broker and associated with exactly one ClientPrincipal after initialization.
171
+
172
+ ### BrowserInstance
173
+
174
+ One running, discoverable Chromium-family browser process identified by a
175
+ process-stable identity, product, profile path, debugging endpoint, and
176
+ connection generation.
177
+
178
+ ### BrowserWorkspace
179
+
180
+ A logical browser working set owned by one ClientPrincipal. It owns one or more
181
+ ManagedTabSets and transient scoped state. Workspaces live in Broker memory by
182
+ default and are recreated after Broker restart; Agent adapters map their own
183
+ active lifecycle to this object.
184
+
185
+ ### ManagedTabSet
186
+
187
+ The logical collection of Broker-created tabs used for isolated Agent work. It
188
+ is normally backed by a dedicated visible Pilot window because Browser Pilot
189
+ does not use extension-only native tab-group APIs. New task navigation and
190
+ owned popups stay in this set by default. Workspace cleanup may close this set,
191
+ but never user-owned tabs outside it.
192
+
193
+ ### ControlLease
194
+
195
+ A time-bounded command session in a Workspace. It carries negotiated
196
+ capabilities, heartbeat expiry, and explicit target control assignments. Agent
197
+ adapters map their own active work lifecycle to this object.
198
+
199
+ ### BrowserControlPolicy
200
+
201
+ An optional launch-time reduction of Browser Pilot operations configured by
202
+ the Agent host, for example disabling `network.modify` or `tabs.close`. The
203
+ default policy allows every supported operation. This is tool exposure, not a
204
+ Browser Pilot approval lifecycle, and it cannot be expanded through a tool call
205
+ inside the running bridge.
206
+
207
+ ### ControlledTarget
208
+
209
+ A target addressable by a Workspace. Its origin is `managed`, `managed_popup`,
210
+ or `user_tab`, and it records an optional ManagedTabSet. A user tab retains user
211
+ ownership: releasing a Workspace removes Broker mappings without closing the
212
+ tab. Every result identifies its Workspace, Lease, opaque controlled-target ID,
213
+ URL, origin, and browser connection generation. Raw CDP target IDs are not a
214
+ public identifier.
215
+
216
+ ### Observation
217
+
218
+ An immutable, bounded view of a ControlledTarget at a point in time. Public element
219
+ handles are `observationId + ref`. Observations are never shared between
220
+ Workspaces.
221
+
222
+ ### AgentHint
223
+
224
+ A bounded, discriminated browser-state signal attached to an Observation or
225
+ event. A hint has a stable `code`, `source`, `confidence`, and
226
+ `recommendedAction`, plus code-specific public data. Hints report browser
227
+ evidence and a useful next inspection strategy; they never approve an action,
228
+ assert that the task succeeded, or replace fresh Observation and event state.
229
+
230
+ ### Command
231
+
232
+ A requested operation with a unique command ID, idempotency key, deadline,
233
+ capability requirement, and target assignment. Each target serializes its
234
+ commands through one actor.
235
+
236
+ ### Artifact
237
+
238
+ A Broker-managed binary result such as a screenshot, preview, PDF, or download.
239
+ Artifacts have metadata, sensitivity, ownership, size limits, retention state,
240
+ and expiry.
241
+
242
+ ### BrowserEvent
243
+
244
+ A typed, ordered event in a bounded per-Workspace journal. Events can be pushed
245
+ over stdio and replayed with a cursor. Every event carries the browser connection
246
+ generation of its source. Ordinary events from a retired generation are dropped;
247
+ Command terminal state and explicit reconnect cleanup remain replayable with
248
+ their historical generation.
249
+
250
+ ## Public Executable Surface
251
+
252
+ The official distribution exposes one executable under both `browser-pilot`
253
+ and the compatible `bp` alias.
254
+
255
+ ```text
256
+ browser-pilot
257
+ broker serve
258
+ bridge --stdio
259
+ tool list
260
+ tool call
261
+ artifact export|release
262
+ browsers|connect|disconnect|open|snapshot|click|type|keyboard|press
263
+ read|eval|upload|screenshot|pdf|cookies|frame|auth|tabs|tab|close|net
264
+ ```
265
+
266
+ Three distribution modes are equivalent:
267
+
268
+ 1. User or Agent global installation.
269
+ 2. Project-local installation and `npx browser-pilot` invocation.
270
+ 3. Product-bundled official executable launched by an Agent adapter.
271
+
272
+ Embedded products must not import `src/*`, depend on daemon socket details, or
273
+ parse human-readable output.
274
+
275
+ The npm distribution contains bundled JavaScript entry points for the CLI,
276
+ Broker, and managed-target janitor and requires Node 18 or newer. The native
277
+ distribution is one Node SEA executable containing the runtime and JavaScript
278
+ dependencies. It re-executes itself with reserved private role arguments for
279
+ Broker and janitor processes; it neither downloads a runtime nor falls back to
280
+ system Node. Both layouts derive executable installation identity from the
281
+ public entry point, so private child roles do not create false version
282
+ boundaries.
283
+
284
+ Native artifacts are built and verified on their target OS and architecture.
285
+ Each archive includes an actual-state signature descriptor, file sizes and
286
+ SHA-256 hashes, runtime/dependency licenses, and an archive checksum. macOS uses
287
+ Developer ID signing with hardened runtime and explicit JIT entitlements when
288
+ credentials exist, otherwise ad-hoc signing. Windows uses Authenticode when a
289
+ certificate exists, otherwise records unsigned state. Linux records unsigned
290
+ state. Build or release automation must fail on incomplete credentials and
291
+ must never describe an artifact as signed when signing did not run.
292
+
293
+ ### Broker Locator and Startup
294
+
295
+ The executable, not an embedding Host, resolves the per-user Broker endpoint.
296
+ On Unix it uses an owner-only domain socket and falls back to a deterministic
297
+ short owner-specific runtime directory when the state path would exceed
298
+ portable socket limits. On Windows it uses a named pipe derived from the OS
299
+ user identity. Persistent state defaults to the Browser Pilot user directory
300
+ (`%LOCALAPPDATA%\\Browser Pilot` on Windows). An explicit absolute
301
+ `BROWSER_PILOT_HOME` is the cross-platform installation-level isolation
302
+ override. It is never selected automatically.
303
+
304
+ State/runtime directories are mode `0700` and regular metadata files are mode
305
+ `0600` where POSIX modes apply. The socket is mode `0600`; Windows relies on the
306
+ creating user's process-token DACL for its local named pipe. Symlinked final
307
+ directories and metadata, foreign ownership, unexpected file types, and
308
+ overly-permissive metadata are rejected.
309
+
310
+ The versioned locator records PID, endpoint/transport, start time, Broker
311
+ process identity, service version, executable installation identity/path, and
312
+ the supported protocol range. A separate owner-only `broker-versions.json`
313
+ keeps only the current and immediately previous executable metadata for
314
+ diagnosis and explicit rollback; it does not preserve an executable or any
315
+ browser, target, Workspace, Lease, ref, credential, rule, or command state. An
316
+ atomic owner-recorded startup lock surrounds discovery and daemon launch. Every
317
+ contender health-checks before and after the lock; therefore exactly one
318
+ process starts the Broker and all others reuse it. Broker startup and discovery
319
+ are passive and do not open a browser WebSocket.
320
+ A lock whose owner is dead can be reclaimed; a live owner is never displaced
321
+ merely because startup is slow. During Broker startup, the daemon writes an
322
+ owner-only `starting` process record. Chrome authorization begins only after an
323
+ explicit `browser.connect`; all concurrent callers share its single in-flight
324
+ connection attempt. A dead
325
+ locator/socket is removed without signaling its recorded PID. If a ready PID is
326
+ alive but its endpoint is unresponsive, Browser Pilot returns structured
327
+ restart remediation and never silently kills or replaces the process.
328
+
329
+ ## Primary Flows
330
+
331
+ ### FLOW-1 Agent-managed one-shot use
332
+
333
+ An Agent installs Browser Pilot globally or in its project and runs `bp connect`.
334
+ The CLI starts or reuses the compatible per-user Broker, creates its
335
+ compatibility Workspace and Lease, then explicitly requests the browser
336
+ connection. Other browser commands require that connection and never trigger
337
+ authorization implicitly.
338
+ Existing and future eligible user tabs are available immediately. The command
339
+ returns the existing JSON shape during migration. Failures include a stable
340
+ machine code in addition to compatible human guidance. One-shot processes use
341
+ a fixed daemon-internal Connection, idempotently keyed Workspace, and renewable
342
+ Lease with a maximum five-minute TTL. Normal process exit leaves that transient
343
+ daemon-memory state available to the next command; expiry invalidates refs and
344
+ releases target control. No target, frame, session, Observation, ref, auth, or
345
+ network mapping is persisted to disk. `bp disconnect` explicitly releases the
346
+ compatibility Workspace and closes only its managed targets. It stops the
347
+ daemon only when called by the matching executable installation and no live
348
+ embedded Connection remains; otherwise it returns `protocol_incompatible` or
349
+ `broker_in_use` without replacing or terminating the Broker. JavaScript dialogs
350
+ remain pending and are handled explicitly with `bp dialogs` and `bp dialog`;
351
+ Workspace isolation prevents access to other clients' dialogs.
352
+
353
+ ### FLOW-2 Product-embedded use
354
+
355
+ An Agent product bundles or resolves the official executable and starts
356
+ `browser-pilot bridge --stdio`. It initializes, negotiates protocol and
357
+ capabilities, creates or resumes a Workspace, acquires a Lease, discovers tools,
358
+ calls `browser.connect` when the selected browser is not ready, and maps the
359
+ remaining tools into its own runtime. Bridge launch, initialization, discovery,
360
+ and Workspace creation stay passive. It calls tools, consumes ordered
361
+ events, converts Artifact files into native media content, heartbeats while
362
+ active, then releases the Lease. A process crash is recovered by Lease expiry.
363
+
364
+ ### FLOW-3 Browser disconnect and recovery
365
+
366
+ The Broker emits connection loss, stops dispatching target commands, and marks
367
+ commands whose effects are uncertain as `unknown_outcome`. It never reconnects
368
+ on a timer. A client explicitly calls `browser.connect` when recovery is
369
+ appropriate. After that succeeds, the Broker
370
+ creates a new browser connection generation, invalidates old CDP sessions,
371
+ ControlledTarget mappings, frames, and Observations, and emits structured recovery
372
+ state. The daemon rediscovers only the originally selected browser profile; it
373
+ does not silently switch to another running browser. Consumers rebuild inventory
374
+ with `browser.tabs.list`. Browser Pilot never replays a mutating command
375
+ automatically.
376
+
377
+ ### FLOW-4 Concurrent clients
378
+
379
+ Two unrelated Principals create separate Workspaces and Pilot targets. Their
380
+ actions may execute concurrently on different target actors. Any attempt to use
381
+ the other Principal's target, ref, rule, Artifact, or Workspace fails closed.
382
+ Releasing or crashing one client does not close or mutate the other's targets.
383
+
384
+ ### FLOW-5 Existing and future user tabs
385
+
386
+ When a Host exposes Browser Pilot to an Agent, that invocation authorizes the
387
+ Agent to use the selected BrowserInstance. `tabs.list` immediately returns the
388
+ Workspace's ManagedTabSets plus all eligible user tabs, tagged by origin. Tabs
389
+ opened later appear on the next inventory refresh. The Agent may switch to and
390
+ operate any listed tab, including a form the user opened before invoking it.
391
+
392
+ Browser Pilot does not ask the Agent to request access and does not display a
393
+ grant UI. If a product needs confirmation or restricted tool exposure, the
394
+ Agent host implements that policy before launching the bridge or maps it to a
395
+ launch-time BrowserControlPolicy. Releasing a Lease or Workspace removes
396
+ transient mappings and rules but leaves user tabs open. Closing a user tab
397
+ always requires an explicit target-specific command; bulk cleanup applies only
398
+ to ManagedTabSets.
399
+
400
+ ## Machine Protocol
401
+
402
+ `bridge --stdio` uses JSON-RPC 2.0 over newline-delimited UTF-8 JSON. Each line
403
+ contains one complete message. Stdout is protocol-only. Logs go to stderr and
404
+ must redact secrets. The bridge terminates on malformed framing after returning
405
+ a protocol error when possible.
406
+
407
+ Required methods:
408
+
409
+ ```text
410
+ initialize
411
+ tools/list
412
+ tools/call
413
+ workspaces/create|get|release
414
+ leases/create|heartbeat|release
415
+ commands/cancel|get
416
+ events/poll
417
+ artifacts/import|get|export|retain|release
418
+ shutdown
419
+ ```
420
+
421
+ Asynchronous events use best-effort `events/event` JSON-RPC notifications.
422
+ Polling with a cursor is the authoritative recovery path after reconnect,
423
+ notification loss, or backpressure. Notifications and responses may interleave.
424
+
425
+ ### Initialization
426
+
427
+ The client sends its product identity, instance identity, supported protocol
428
+ range, requested capabilities, launch mode, and optional protocol 1.1 transport
429
+ limits. The response includes:
430
+
431
+ - service and executable versions;
432
+ - selected protocol version;
433
+ - supported and granted capabilities;
434
+ - Broker process identity and connection ID;
435
+ - browser discovery and authorization state;
436
+ - transport and result size limits.
437
+
438
+ Initialization must fail with a structured incompatibility error when no
439
+ protocol overlap exists. A newer executable must not silently replace an
440
+ incompatible Broker that has live clients.
441
+
442
+ Protocol 1.0 clients use the service transport ceilings. Protocol 1.1 clients
443
+ may declare maximum message and result byte sizes; the Broker selects the lower
444
+ client/service value per Connection. Initialization itself uses fixed bootstrap
445
+ ceilings, and pipelined messages after initialize are not parsed until the
446
+ selected limits take effect. Artifact and journal capacities remain service
447
+ resource limits. Pending ordinary and out-of-band control calls have separate
448
+ bounds so overload cannot create an unbounded Promise queue or prevent dialog
449
+ and cancellation control from overtaking a blocked action.
450
+
451
+ ### Tool Contract
452
+
453
+ `tools/list` is the canonical machine-readable manifest. Every tool declares:
454
+
455
+ - stable name and description;
456
+ - JSON input schema;
457
+ - output schema and possible Artifact kinds;
458
+ - required capabilities;
459
+ - whether it may mutate browser state;
460
+ - cancellation and idempotency semantics;
461
+ - sensitivity classification.
462
+
463
+ The tool-level `sensitivity.input` and `sensitivity.output` arrays declare every
464
+ classification that may occur in that direction. Content-bearing input and
465
+ output schema nodes use `x-browser-pilot-sensitivity` to identify the specific
466
+ fields an adapter must taint when it maps values into model text, image, or file
467
+ content. The manifest validator rejects unknown, duplicate, or field-level
468
+ classifications missing from the tool-level declaration. Schema annotations do
469
+ not change the call or result value shape.
470
+
471
+ `tools/call` returns `{ command, result?, error? }`; each manifest output schema
472
+ describes the inner `result`. Callers may provide an opaque Command ID,
473
+ idempotency key, and relative deadline. `commands/get` and `commands/cancel`
474
+ accept a Command ID plus its Workspace ID when applicable. They authorize by
475
+ ClientPrincipal ownership so a replacement Connection can inspect known state
476
+ without inheriting the original Lease.
477
+
478
+ Action results include bounded typed evidence where Browser Pilot can observe
479
+ an effect. Ref clicks combine local control state and composed focus changes
480
+ with Broker-owned navigation, normalized document, dialog, and attributable
481
+ popup signals. `verified` means at least one supported effect was observed,
482
+ `mismatch` means an observable control did not reach its expected state, and
483
+ `unavailable` means dispatch completed without conclusive evidence. Evidence is
484
+ not a claim that the Agent's higher-level business goal succeeded.
485
+ Input evidence is discriminated as `type` or `keyboard`; press evidence uses the
486
+ focused backend node plus bounded control-state signatures and Broker-owned
487
+ signals. Upload evidence reads the selected file count and compares its browser
488
+ filename without returning the local source path.
489
+
490
+ Observation-producing tools also return `hints`. Observation-derived codes
491
+ cover explicit autocomplete surfaces, modal overlays, explicit filter controls,
492
+ and authentication surface transitions; action results may add a repeated
493
+ observable no-progress hint. Hint refs are numbered refs from that Observation
494
+ only. Page collection returns counts and refs, never page text, input values,
495
+ passwords, selectors, raw nodes, or CDP identifiers for this classification.
496
+ Blocked main-document responses and download lifecycle hints are event-derived.
497
+
498
+ `hints` is an additive schema field under protocol 1.0 and 1.1. The built-in
499
+ Browser tool implementation always returns the array, including an empty array;
500
+ the manifest leaves the field optional so compatible older executors remain
501
+ valid. Existing clients already ignore unknown response fields. No new
502
+ capability is negotiated because hints expose no operation and contain only
503
+ bounded derivatives of browser data already authorized by `observation.read`
504
+ or `event.read`.
505
+
506
+ Composite input actions pin the ControlledTarget, CDP session, selected frame,
507
+ loader, Document identity, and browser connection generation for the lifetime
508
+ of the action. After focus is established, `type` and `keyboard` also pin the
509
+ deep composed active element. Browser Pilot checks this identity before every
510
+ remaining semantic step, including select-all, deletion, each keyboard
511
+ character, and optional submit. A change before the first browser mutation is
512
+ a retryable `action_not_verified`; a change after any step was dispatched is
513
+ `unknown_outcome` with bounded `action`, `step`, `reason`, `dispatchedSteps`,
514
+ and `remainingStepsStopped` context. No remaining step is sent to the changed
515
+ page and the command is never replayed automatically.
516
+
517
+ The initial controlled tool families are browser discovery/status, Workspace
518
+ and Lease lifecycle, open/observe/read, click/type/keyboard/press,
519
+ capture/upload, tabs/frames, dialogs, auth/cookies, network, events, and
520
+ Artifacts. Raw CDP is never listed. `eval` requires `developer.eval`.
521
+
522
+ ## Access and Isolation Rules
523
+
524
+ - **BR-1:** A public command can address a ControlledTarget only through its
525
+ Workspace and a valid ControlLease.
526
+ - **BR-2:** Managed targets enter a Workspace through Broker creation and a
527
+ verified managed-popup opener chain. Eligible user tabs enter through target
528
+ inventory. Public commands use opaque ControlledTarget IDs; Agent-supplied
529
+ raw CDP identifiers are never accepted as public handles.
530
+ - **BR-3:** Closing or releasing a Workspace may close its ManagedTabSets, but
531
+ must not close user tabs merely because they were visible or controlled. It
532
+ affects only its own mappings, rules, credentials, journals, and Artifacts.
533
+ - **BR-4:** Network and auth configuration is not global. Precedence and cleanup
534
+ are deterministic within the owning Workspace.
535
+ - **BR-5:** Each physical browser target serializes state-changing commands.
536
+ Reads that depend on document state join that same actor even when separate
537
+ Workspaces address the target through different opaque ControlledTarget IDs.
538
+ - **BR-6:** A physical target has at most one controlling Lease at a time.
539
+ Multiple Workspaces may inventory it under different opaque IDs, but a
540
+ conflicting acquisition reports `target_busy` and never silently steals
541
+ control. The controlling Lease may call `browser.tabs.release` with its own
542
+ opaque target ID. The Broker retires its target session and scoped state
543
+ before a different Lease can acquire control with that Lease's own opaque ID.
544
+ No handoff call accepts a foreign Lease or target ID.
545
+ - **BR-7:** The Pilot window visibly identifies the controlling client without
546
+ injecting mutable content into the page DOM.
547
+ - **BR-21:** Exposing the CLI or bridge to an Agent is the product-level
548
+ browser-control authorization boundary. Chrome's own remote-debugging
549
+ authorization is requested only by explicit `bp connect`/`browser.connect`.
550
+ Browser Pilot does not infer task intent, ask for per-tab grants, or perform
551
+ per-action confirmation.
552
+ - **BR-22:** The default BrowserControlPolicy permits all supported operations.
553
+ A Host may remove operations at launch; a running Agent cannot expand that
554
+ fixed policy through the browser tool protocol.
555
+ - **BR-23:** Every Workspace can inventory the same eligible user tab under a
556
+ distinct opaque ID. The single controlling-Lease rule in **BR-6** resolves
557
+ concurrent mutation.
558
+ - **BR-24:** Releasing a Lease or Workspace removes its user-target mappings and
559
+ invalidates their Observations without closing those tabs.
560
+ - **BR-25:** Eligible popups created from user tabs appear as ordinary user tabs.
561
+ Managed popup inheritance remains restricted to a complete verified opener
562
+ chain rooted in the same ManagedTabSet.
563
+ - **BR-26:** `tabs.list` returns eligible current and future tabs, tagging each
564
+ with origin and ManagedTabSet membership. Browser Pilot internal targets,
565
+ DevTools, and unsupported browser-internal targets remain excluded.
566
+ - **BR-27:** Bulk cleanup and `close --all` default to the ManagedTabSet. No
567
+ bulk operation closes user tabs outside it. Each user tab requires an
568
+ explicit target-specific close command.
569
+
570
+ ## Command Reliability
571
+
572
+ Mutating commands follow this runtime state model:
573
+
574
+ ```text
575
+ accepted -> dispatched -> completed
576
+ \-> unknown_outcome
577
+ accepted -> cancelled
578
+ accepted -> expired
579
+ ```
580
+
581
+ - **BR-8:** Every command has a caller-provided or Broker-generated idempotency
582
+ key scoped to Principal and Workspace.
583
+ - **BR-9:** A duplicate completed command returns its recorded result. A
584
+ duplicate dispatched command returns its status and is not dispatched again.
585
+ - **BR-10:** If the Broker loses certainty after dispatch, it reports
586
+ `unknown_outcome`. Mutating commands are never automatically replayed.
587
+ - **BR-11:** Cancellation before dispatch prevents execution. Cancellation
588
+ after dispatch is best-effort and must not claim rollback.
589
+ - **BR-12:** Browser disconnect and explicit reconnect change the BrowserInstance
590
+ connection generation and invalidate sessions and observations. The generation
591
+ advances on successful restoration, not merely on an attempt. Discovery and
592
+ timers never initiate a browser connection.
593
+
594
+ Commands capture that generation when accepted. The Broker verifies it again
595
+ before browser dispatch and after the executor returns. A queued stale command
596
+ never reaches the browser. If a mutating command was already dispatched when the
597
+ generation changed, its terminal state is `unknown_outcome`; a stale read fails
598
+ with retryable `browser_disconnected` rather than returning old data.
599
+
600
+ The dispatch transition occurs immediately before the first browser mutation,
601
+ after argument, ref, target, and continuity preflight. Composite-action guards
602
+ preserve their structured interruption context when the terminal state is
603
+ `unknown_outcome`; callers must obtain fresh target/frame/Observation state
604
+ before deciding what remains to be done.
605
+
606
+ Normal bridge calls preserve dispatch order. `commands/get`, `commands/cancel`,
607
+ and dialog list/respond calls may bypass a pending `tools/call`; JSON-RPC clients
608
+ therefore correlate responses by ID rather than relying on response order. The
609
+ dialog exception prevents a paused browser command from blocking the explicit
610
+ response needed to release its JavaScript dialog.
611
+
612
+ Workspaces, Leases, target mappings, refs, command status, idempotency records,
613
+ and bounded event journals live in Broker memory by default. Broker restart
614
+ invalidates them; clients initialize again, list tabs again, and observe again.
615
+ Only installation identity, browser preference, Broker locator/version data,
616
+ and explicit user configuration may persist. DOM snapshots, refs, cookies,
617
+ passwords, network bodies, and transient control state never persist.
618
+
619
+ Broker-created targets cross one additional private ownership boundary. A
620
+ janitor child process is the sole owner of the root CDP WebSocket and proxies
621
+ all daemon CDP requests, results, and events over private IPC. The same process
622
+ creates and tracks only managed targets plus popup descendants with a verified
623
+ managed opener chain. Its parent pipe is the liveness signal: daemon EOF,
624
+ including `SIGKILL`, causes bounded descendant-first target cleanup. User
625
+ targets are never adopted merely because they share a browser window. Browser
626
+ disconnect clears that transient ownership before a new connection generation
627
+ is exposed. No janitor method or raw target ID is part of the CLI or stdio
628
+ protocol, and no ownership record is written to disk.
629
+
630
+ ## Observation and Ref Semantics
631
+
632
+ Observation v1 has these required public fields: `workspaceId`, `leaseId`,
633
+ `targetId`, `url`, `observationId`, `title`, `elements`, `truncated`, and
634
+ `truncationReasons`. Each element contains only `ref`, `role`, `name`, and the
635
+ optional `value` and `checked` fields. Built-in Observation-producing tools
636
+ also return `hints`; action tools additionally return discriminated `evidence`.
637
+ The latter two fields remain optional in the schema so older compatible
638
+ executors stay valid. Raw nodes, selectors, bounds, CDP identifiers, and the
639
+ internal identity below never appear in an Observation result.
640
+
641
+ The frozen v1 limits are 50 elements by default and 10,000 at most; 4,096
642
+ string code units for title and element name; 16,384 for URL; 65,536 for one
643
+ element value; 1,000,000 across collected Observation text; AX depth 128; and
644
+ 2 MiB of UTF-8 serialized Observation data. Broker storage retains at most
645
+ 2,048 Observations for five minutes each. Hitting any collection limit sets
646
+ `truncated: true` and includes one or more canonical reasons in deterministic
647
+ order: `element_limit`, `text_limit`, `depth_limit`, `byte_limit`.
648
+
649
+ The public identity of an element is:
650
+
651
+ ```text
652
+ workspaceId + observationId + ref
653
+ ```
654
+
655
+ Internal resolution includes at least:
656
+
657
+ ```text
658
+ browserProcessIdentity
659
+ browserConnectionGeneration
660
+ targetId
661
+ CDPSessionId
662
+ frameId
663
+ loaderId
664
+ backendNodeId
665
+ documentGeneration
666
+ ```
667
+
668
+ The Broker stores process, connection, target, Lease, CDP session, frame,
669
+ loader, and Document generation on the Observation; each ref adds its backend
670
+ node identity. Resolution verifies all of them before live-node revalidation.
671
+ Same-context failures expose one of these stable invalidation reasons when
672
+ known: `navigation`, `loader_replaced`, `document_replaced`, `frame_changed`,
673
+ `frame_detached`, `session_replaced`, `target_detached`,
674
+ `browser_reconnected`, `target_ineligible`, `target_closed`,
675
+ `control_released`, or `expired`.
676
+ Ownership mismatches remain an undifferentiated `stale_ref` so they do not
677
+ disclose another Workspace's state.
678
+
679
+ Adding `document_replaced` as an optional `stale_ref.context.reason` value is
680
+ additive under protocol 1.0 and 1.1: clients branch on the stable error code,
681
+ handle known reasons when useful, and treat unknown reasons as a request to
682
+ rebuild current Observation state. It adds no operation or capability.
683
+
684
+ - **BR-13:** Cross-Workspace, cross-target, cross-observation, and expired refs
685
+ return `stale_ref`; they never fall back to another target.
686
+ - **BR-14:** Navigation, loader or Document replacement, target detach, frame
687
+ detach, CDP session replacement, and browser reconnect hard-invalidate
688
+ affected refs.
689
+ - **BR-15:** Same-document DOM changes require live node revalidation. They do
690
+ not force blanket invalidation when the node remains resolvable and valid.
691
+ - **BR-16:** Observations are immutable and bounded by element count, text size,
692
+ depth, and byte limits, with explicit truncation metadata.
693
+ - **BR-28:** Agent hints are advisory, deterministic, bounded, and based only on
694
+ browser-verifiable signals. They never contain field values, credentials,
695
+ private file paths, raw CDP identities, or framework-specific prompt state.
696
+
697
+ ## Events
698
+
699
+ The event taxonomy includes navigation, document changed, target attached,
700
+ target detached, control acquired/released, popup, dialog, download, connection
701
+ lost, connection restored, network request, network response, command status,
702
+ observation invalidated, Lease expiry, and watchdog signals for stalled
703
+ navigation, selected-frame detach, unhandled dialogs, and repeated observable
704
+ no-progress actions.
705
+
706
+ Relevant event payloads carry the same `hints` representation: main-document
707
+ 403/429 responses use `access_blocked`; downloads use `download` with the public
708
+ Artifact ID only after completion; and the threshold no-progress event uses
709
+ `repeated_action`. Subresource failures do not produce a page-blocked hint.
710
+
711
+ Every event contains an event ID, monotonic Workspace sequence, timestamp,
712
+ Workspace ID, relevant Lease and target IDs, type, payload version, and
713
+ sensitivity. `workspaces/create|get` returns an explicit current `eventCursor`.
714
+ Consumers poll from their last fully processed cursor; a poll returns ordered
715
+ events, `nextCursor`, and `hasMore`. The Broker may compact old events and must
716
+ return `cursor_expired` with retained cursor bounds rather than silently skip
717
+ them. A consumer that loses its cursor rebuilds tab and Observation state, then
718
+ uses `workspaces/get` to establish a new baseline.
719
+
720
+ JavaScript dialogs enter explicit pending state and emit opened/closed events.
721
+ They remain paused until the user or an Agent issues `browser.dialogs.respond`;
722
+ Browser Pilot never auto-accepts them.
723
+
724
+ Watchdogs report browser state; they do not make task decisions. A navigation
725
+ that never becomes interactive emits `watchdog.navigation_stalled` and leaves
726
+ the dispatched command as `unknown_outcome`. Detaching the selected frame emits
727
+ `watchdog.frame_detached`, clears the frame selection, and invalidates its
728
+ Observation. A dialog still pending after the bounded threshold emits one
729
+ `watchdog.dialog_unhandled` event but remains pending. Three consecutive actions
730
+ with browser-observable mismatch or no effect emit one `watchdog.no_progress`
731
+ event per streak, scoped by Lease and target. Unobservable coordinate/canvas
732
+ actions do not increment the streak. Navigation, frame changes, verified
733
+ progress, session replacement, Lease release, and Workspace cleanup reset the
734
+ applicable transient state.
735
+
736
+ Watchdog payloads contain only bounded recovery metadata and opaque public IDs;
737
+ they never contain typed values, passwords, refs, raw CDP target/session/frame
738
+ IDs, or dialog prompt responses. Consumers inspect fresh browser state before
739
+ deciding whether to continue, retry, or ask the user.
740
+
741
+ ## Artifacts
742
+
743
+ Artifact descriptors contain an opaque ID, kind, MIME type, byte size, original
744
+ filename or dimensions when applicable, sensitivity, creation time, expiry, and
745
+ optional preview relationship. They do not expose internal storage paths as the
746
+ primary identity.
747
+
748
+ - **BR-17:** Broker storage directories are mode `0700`; files are mode `0600`
749
+ on platforms that support POSIX permissions.
750
+ - **BR-18:** Artifacts have per-item, per-Workspace, and global capacity limits,
751
+ TTL cleanup, retain/release, and explicit export to a client-owned path.
752
+ - **BR-19:** Binary bytes are not written as base64 to the stdio stream. The
753
+ local adapter reads an authorized Artifact or asks the CLI to export it, then
754
+ converts it to the Agent runtime's image/file content type.
755
+ - **BR-20:** Large or full-page screenshots return a model-sized preview plus
756
+ an optional original Artifact.
757
+
758
+ An adapter authorizes a local upload by calling `artifacts/import` with an
759
+ absolute path, Workspace, and active Lease. The Broker copies the file into
760
+ protected storage as a `user_file` / `upload_input` Artifact while preserving
761
+ its basename. `browser.upload` accepts only that Artifact kind and never treats
762
+ a screenshot, PDF, download, or arbitrary Broker-internal path as upload
763
+ authorization.
764
+
765
+ Downloads are event-produced Artifacts rather than a separate path-returning
766
+ tool. The implementation must configure download behavior on the controlled
767
+ target session only, stage bytes in a mode-`0700` per-session directory, keep
768
+ the CDP download GUID and staging path private, and ingest a completed file as a
769
+ Workspace-owned `download` Artifact before publishing the completed `download`
770
+ event. Lease/session/Workspace release removes partial staging files; failed,
771
+ cancelled, oversized, or detached downloads publish bounded metadata without a
772
+ local path. Browser-wide `Browser.setDownloadBehavior` on the user's default
773
+ context is forbidden because it would redirect downloads from unrelated user
774
+ tabs. If target-session isolation is unavailable in a Chrome version, Browser
775
+ Pilot reports download capture as unavailable and does not fall back globally.
776
+ The implementation applies per-download, per-Workspace staging, and global
777
+ staging bounds. An oversized or over-capacity download is cancelled by its
778
+ private GUID without changing download behavior for the browser's default
779
+ context. Stale staging directories from a prior Broker process are removed before
780
+ the first new download session is configured.
781
+
782
+ ## Browser Discovery and Setup
783
+
784
+ Discovery returns every supported local browser candidate with product,
785
+ channel, profile, process state, remote-debugging state, authorization state,
786
+ and structured remediation. It does not silently select the first filesystem
787
+ match when several viable instances exist.
788
+
789
+ The client may select an instance explicitly or use a deterministic persisted
790
+ preference. Setup that requires Chrome UI remains a user-visible action. Browser
791
+ Pilot cannot bypass Chrome authorization and must not simulate consent.
792
+
793
+ Browser Pilot does not add an access-approval page. Products that want an
794
+ approval UX own it in the Agent host before exposing the tool. Browser Pilot
795
+ still excludes its own setup/status pages and other internal targets from
796
+ Agent inventory; the same-user CDP limitation in **CON-7** remains.
797
+
798
+ ## Security and Sensitive Data
799
+
800
+ - Password inputs, cookies, auth credentials, network bodies, uploads,
801
+ downloads, page captures, and selected page text carry sensitivity metadata.
802
+ - Tool schemas mark selected text and page/element values as `browser_data`,
803
+ with `credential` added where a field may carry a password, cookie, header,
804
+ request body, response body, prompt response, or arbitrary eval value.
805
+ - Artifact descriptors and BrowserEvents carry runtime sensitivity because
806
+ their classification depends on the produced object or event. Their runtime
807
+ value takes precedence over a tool's possible schema classifications.
808
+ - Secrets are accepted through protected stdin or structured machine input, not
809
+ command-line arguments in recommended workflows.
810
+ - Audit records store metadata and hashes where possible, not secret values.
811
+ - Public errors do not include cookies, credentials, network bodies, DOM dumps,
812
+ or raw CDP payloads.
813
+ - The Broker socket/pipe, configuration, and Artifact storage are restricted to
814
+ the OS user.
815
+ - Browser Pilot documents that another same-user process may bypass it and use
816
+ Chrome's debugging endpoint directly.
817
+
818
+ ## Stable Error Model
819
+
820
+ Errors contain a stable code, human message, retryability, affected object IDs,
821
+ and optional structured remediation. Required codes include:
822
+
823
+ ```text
824
+ protocol_incompatible not_initialized capability_denied
825
+ browser_not_found browser_not_authorized browser_disconnected
826
+ broker_in_use
827
+ workspace_not_found lease_expired target_not_owned
828
+ target_busy stale_ref command_cancelled
829
+ action_not_verified command_expired unknown_outcome
830
+ artifact_not_found
831
+ artifact_expired cursor_expired result_too_large
832
+ invalid_argument internal_error
833
+ ```
834
+
835
+ Human CLI output may add prose. Machine clients branch only on codes and typed
836
+ fields, never on message text.
837
+
838
+ ## Compatibility and Versioning
839
+
840
+ - Protocol versions are independent of executable versions.
841
+ - Compatible embedded clients reuse the running Broker even when their host or
842
+ bundled executable versions differ. The one-shot compatibility CLI requires
843
+ the running Broker's exact executable version because its external JSON
844
+ contract is versioned separately from the bridge protocol.
845
+ - An incompatible executable never replaces a live Broker. A host either uses
846
+ a compatible executable or deliberately supplies a distinct
847
+ `BROWSER_PILOT_HOME`; Browser Pilot never creates a hidden version namespace.
848
+ - Broker shutdown is compare-and-stop: it requires the current Broker process
849
+ identity and matching executable installation identity, and refuses while
850
+ any embedded Connection remains live.
851
+ - Additive tool and field changes use capability negotiation and schema
852
+ evolution. Breaking semantics require a protocol-major change.
853
+ - Unknown response fields are ignored; unknown request fields are rejected only
854
+ when they change safety or semantics.
855
+ - Optional advisory output such as `hints` does not require a protocol-minor
856
+ version or capability when it adds no operation and only derives bounded data
857
+ already covered by an existing granted capability.
858
+ - The current CLI JSON shapes remain supported until a documented major CLI
859
+ release and migration period.
860
+ - A conformance suite validates any embedded adapter against the same executable
861
+ used by direct CLI users.
862
+
863
+ ## Functional Requirements and Acceptance
864
+
865
+ - **FR-1 Universal installation:** An Agent can install Browser Pilot globally
866
+ or locally and use existing one-shot commands.
867
+ - **AC-1:** Existing documented CLI workflows and fixture tests pass after
868
+ Broker migration without requiring an SDK, MCP server, or extension.
869
+ - **FR-2 Embedded integration:** A product can bundle the official executable,
870
+ initialize `bridge --stdio`, discover tools, execute them, receive events,
871
+ and release resources.
872
+ - **AC-2:** A reference adapter integrates using only executable invocation,
873
+ the published protocol, and Artifact files; it imports no Browser Pilot
874
+ source module.
875
+ - **FR-3 Multi-client isolation:** Concurrent clients have isolated control and
876
+ transient state.
877
+ - **AC-3:** Two clients can operate separate Pilot windows concurrently, and
878
+ refs, active targets, frames, auth, rules, and close operations cannot cross
879
+ Workspace boundaries.
880
+ - **FR-4 User-browser control:** An Agent can list and control all eligible
881
+ current and future tabs in the selected BrowserInstance, including tabs the
882
+ user opened before invoking it.
883
+ - **AC-4:** No access-request or grant step is required. Inventory combines
884
+ ManagedTabSets and user tabs behind Workspace-scoped opaque IDs. A Host may
885
+ remove operations at launch. Concurrent control reports `target_busy`,
886
+ release leaves user tabs open, and bulk cleanup closes only managed tabs.
887
+ - **FR-5 Crash behavior:** Client and Broker failures have deterministic
888
+ outcomes.
889
+ - **AC-5:** Expired Leases are reclaimed; commands lost after dispatch report
890
+ `unknown_outcome`; reconnect invalidates prior observations.
891
+ - **FR-6 Media delivery:** Agent products can deliver Browser Pilot screenshots
892
+ to multimodal models.
893
+ - **AC-6:** Capture returns a protected Artifact and preview metadata; a
894
+ reference adapter converts it to native image content without base64 on
895
+ stdout.
896
+ - **FR-7 Version safety:** User-installed and product-bundled clients coexist.
897
+ - **AC-7:** Compatible clients reuse the Broker; incompatible clients receive
898
+ `protocol_incompatible` or use a deliberately isolated Broker without
899
+ terminating live clients.
900
+ - **FR-8 Browser reliability:** Browser capability improvements do not alter the
901
+ public lifecycle and isolation contract.
902
+ - **AC-8:** Observation and action conformance tests pass against the public
903
+ tool interface, including frame, navigation, stale-ref, obstruction, and
904
+ input readback cases.
905
+
906
+ ## Non-Functional Requirements
907
+
908
+ - **NFR-1:** No protocol message or normal tool result exceeds negotiated size
909
+ limits; truncation is explicit.
910
+ - **NFR-2:** Broker restart invalidates transient Workspaces, Leases, target
911
+ assignments, refs, and commands. Clients rebuild state explicitly without
912
+ guessing or replaying mutations.
913
+ - **NFR-3:** Every command and security-sensitive lifecycle transition emits an
914
+ auditable event without storing secret payloads.
915
+ - **NFR-4:** Target command ordering is deterministic under concurrent clients.
916
+ - **NFR-5:** Resource cleanup is idempotent and bounded after normal release,
917
+ client crash, browser exit, and Broker restart.
918
+
919
+ ## Non-Goals
920
+
921
+ - Hosting browsers in the cloud or providing captcha-solving infrastructure.
922
+ - Controlling Firefox or WebKit in the current architecture.
923
+ - Providing OS-level isolation between processes owned by the same user.
924
+ - Defining whether an Agent's requested browser action matches user intent;
925
+ Agent hosts own semantic approval and confirmation policy.
926
+ - Modeling an Agent's conversation, run, task, worker, or model provider.
927
+ - Replacing URL Preview features inside embedding products.
928
+ - Making Browser Pilot an MCP server or publishing language SDKs.
929
+
930
+ ## Verification Strategy
931
+
932
+ 1. Protocol unit tests validate framing, negotiation, errors, schemas,
933
+ idempotency, cancellation, and cursor behavior.
934
+ 2. Broker integration tests create concurrent fake clients and assert complete
935
+ tab inventory, target assignment, Lease cleanup, crash recovery, and version
936
+ coexistence.
937
+ 3. Existing Playwright fixtures remain regression tests for human CLI behavior.
938
+ 4. Capability tests cover DOM/AX fusion, frames/OOPIF, stale refs, obstruction,
939
+ action verification, input readback, and document changes.
940
+ 5. Adapter conformance tests run against a language-neutral reference adapter
941
+ and at least two example Agent runtimes. No example-specific behavior enters
942
+ Broker production code.
943
+ 6. Browser-control tests prove that all eligible existing and future tabs are
944
+ included without grants, launch-time denied operations fail before CDP
945
+ dispatch, opaque IDs cannot cross Workspaces, and bulk cleanup never closes
946
+ user tabs.
947
+ 7. Distribution tests pack and install the npm package through global and local
948
+ layouts, launch local npx and product-owned absolute paths, and exercise
949
+ version output, bridge initialization, private Broker startup, and cleanup.
950
+ Native verification additionally checks manifest hashes, signature state,
951
+ private child roles, and archive checksums on each target platform.
952
+
953
+ ## Implementation Boundary
954
+
955
+ The target internal boundary is:
956
+
957
+ ```text
958
+ CLI formatter -----------\
959
+ stdio bridge -------------+--> BrowserPilotService --> Broker domain actors
960
+ conformance harness ------/ | --> CDP connector
961
+ +--> Artifact/Event stores
962
+ ```
963
+
964
+ `BrowserPilotService` is an internal architectural boundary, not a promised
965
+ Native SDK. CLI and bridge code may depend on it. Third-party products may not.