browser-broker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +173 -0
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/RELEASES.md +97 -0
- package/dist/package.json +58 -0
- package/dist/src/adapter/conformance/case.js +1 -0
- package/dist/src/adapter/conformance/cases.js +429 -0
- package/dist/src/adapter/conformance/discovery.js +156 -0
- package/dist/src/adapter/conformance/driver.js +1 -0
- package/dist/src/adapter/conformance/drivers.js +36 -0
- package/dist/src/adapter/conformance/run.js +224 -0
- package/dist/src/adapter/conformance/service-subject.js +165 -0
- package/dist/src/adapter/contract.js +24 -0
- package/dist/src/adapter/operations.js +114 -0
- package/dist/src/adapter/service-seam.js +1 -0
- package/dist/src/artifacts/names.js +229 -0
- package/dist/src/artifacts/store.js +174 -0
- package/dist/src/bin/broker-tool.js +63 -0
- package/dist/src/bin/broker.js +111 -0
- package/dist/src/browser/adoption.js +143 -0
- package/dist/src/browser/automation-probe.js +113 -0
- package/dist/src/browser/conformance/case.js +1 -0
- package/dist/src/browser/conformance/cases.js +192 -0
- package/dist/src/browser/conformance/run.js +102 -0
- package/dist/src/browser/conformance/subjects.js +19 -0
- package/dist/src/browser/discovery.js +226 -0
- package/dist/src/browser/driver.js +195 -0
- package/dist/src/browser/fake.js +585 -0
- package/dist/src/browser/launch.js +504 -0
- package/dist/src/browser/real.js +1425 -0
- package/dist/src/browser/setup.js +161 -0
- package/dist/src/capture/accounting.js +59 -0
- package/dist/src/capture/image.js +112 -0
- package/dist/src/capture/ladder.js +72 -0
- package/dist/src/capture/legibility.js +195 -0
- package/dist/src/capture/pipeline.js +153 -0
- package/dist/src/capture/tiers.js +166 -0
- package/dist/src/cli/adapter.js +233 -0
- package/dist/src/cli/commands.js +270 -0
- package/dist/src/cli/conformance-driver.js +119 -0
- package/dist/src/cli/diffs.js +122 -0
- package/dist/src/cli/image.js +274 -0
- package/dist/src/cli/index.js +895 -0
- package/dist/src/cli/login-command.js +401 -0
- package/dist/src/cli/operations-commands.js +186 -0
- package/dist/src/cli/reconcile-command.js +137 -0
- package/dist/src/cli/sign-in.js +134 -0
- package/dist/src/cli/telemetry.js +222 -0
- package/dist/src/config/environment.js +446 -0
- package/dist/src/diff/artifact-path.js +77 -0
- package/dist/src/diff/crops.js +102 -0
- package/dist/src/diff/geometry.js +122 -0
- package/dist/src/diff/image.js +132 -0
- package/dist/src/diff/mask.js +46 -0
- package/dist/src/diff/regions.js +263 -0
- package/dist/src/diff/settings.js +135 -0
- package/dist/src/doctor/checks.js +588 -0
- package/dist/src/doctor/report.js +152 -0
- package/dist/src/doctor/session.js +161 -0
- package/dist/src/errors.js +36 -0
- package/dist/src/feedback/read.js +119 -0
- package/dist/src/feedback/record.js +199 -0
- package/dist/src/operations/addresses.js +175 -0
- package/dist/src/operations/derive.js +109 -0
- package/dist/src/operations/ledger.js +194 -0
- package/dist/src/operations/status.js +197 -0
- package/dist/src/operations/telemetry.js +280 -0
- package/dist/src/report/document.js +419 -0
- package/dist/src/report/escape.js +68 -0
- package/dist/src/report/snapshot.js +97 -0
- package/dist/src/service/arbitration.js +537 -0
- package/dist/src/service/artifacts.js +85 -0
- package/dist/src/service/bridge.js +577 -0
- package/dist/src/service/broker.js +120 -0
- package/dist/src/service/browser-session.js +269 -0
- package/dist/src/service/capacity.js +62 -0
- package/dist/src/service/capture-seam.js +83 -0
- package/dist/src/service/capture-store.js +91 -0
- package/dist/src/service/comparison-store.js +101 -0
- package/dist/src/service/comparison.js +173 -0
- package/dist/src/service/events.js +93 -0
- package/dist/src/service/keys.js +68 -0
- package/dist/src/service/leases.js +147 -0
- package/dist/src/service/nudge.js +66 -0
- package/dist/src/service/operations/claim.js +692 -0
- package/dist/src/service/operations/give-back.js +131 -0
- package/dist/src/service/operations/pages.js +771 -0
- package/dist/src/service/operations/sign-in.js +915 -0
- package/dist/src/service/operations/status.js +62 -0
- package/dist/src/service/ownership.js +93 -0
- package/dist/src/service/pages.js +616 -0
- package/dist/src/service/pending-seeds.js +20 -0
- package/dist/src/service/queue.js +233 -0
- package/dist/src/service/reconcile.js +220 -0
- package/dist/src/service/refusals.js +262 -0
- package/dist/src/service/runtime.js +131 -0
- package/dist/src/service/signin-recovery.js +148 -0
- package/dist/src/service/storage-seed.js +239 -0
- package/dist/src/service/tabs.js +123 -0
- package/dist/src/store/budget.js +99 -0
- package/dist/src/store/location.js +42 -0
- package/dist/src/store/network-path.js +182 -0
- package/dist/src/store/network-volume.js +92 -0
- package/dist/src/store/open.js +226 -0
- package/dist/src/store/schema/step-001-initial.js +523 -0
- package/dist/src/store/schema/step-002-tab-budget.js +53 -0
- package/dist/src/store/schema/step-003-queue-order.js +110 -0
- package/dist/src/store/schema/step-004-tab-never-opened.js +100 -0
- package/dist/src/store/schema/step-005-storage-seed-event.js +90 -0
- package/dist/src/store/schema/step-006-signin-events.js +104 -0
- package/dist/src/store/schema/step-007-signin-without-process.js +92 -0
- package/dist/src/store/schema/step-008-signin-owner.js +76 -0
- package/dist/src/store/schema/step-009-named-browsers.js +138 -0
- package/dist/src/store/schema/step-010-signin-request.js +135 -0
- package/dist/src/store/schema/step.js +172 -0
- package/dist/src/store/schema/steps.js +58 -0
- package/dist/src/store/transaction.js +37 -0
- package/dist/src/tool/adapter.js +90 -0
- package/dist/src/tool/conformance-driver.js +184 -0
- package/dist/src/tool/protocol.js +310 -0
- package/dist/src/tool/session.js +351 -0
- package/dist/src/tool/tools.js +310 -0
- package/package.json +58 -0
package/.env.example
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Browser Broker — the configuration registry.
|
|
2
|
+
#
|
|
3
|
+
# This file is DOCUMENTATION. Nothing loads it, and no code path reads a file
|
|
4
|
+
# for configuration: configuration is the process environment, full stop.
|
|
5
|
+
# Copy a line into your own environment if you want to change a value.
|
|
6
|
+
#
|
|
7
|
+
# Every variable has a working default, so a fresh install runs with NOTHING
|
|
8
|
+
# set. A variable that is set and cannot be read as its type refuses the spawn
|
|
9
|
+
# and names the variable — falling back to the default silently would run a
|
|
10
|
+
# configuration nobody chose, with nothing to notice it by.
|
|
11
|
+
#
|
|
12
|
+
# The values below are PLACEHOLDERS showing the shape, never real paths.
|
|
13
|
+
#
|
|
14
|
+
# This file lists the variables the store needs before it can open. The rest
|
|
15
|
+
# of the registry lands with the row that owns it, along with the test that
|
|
16
|
+
# walks the declared variables and asserts this file lists every one.
|
|
17
|
+
|
|
18
|
+
# Where the database file lives.
|
|
19
|
+
# Default: a directory of the service's own under the per-user application-data
|
|
20
|
+
# location the platform defines — which differs per operating system
|
|
21
|
+
# and is computed, never written down.
|
|
22
|
+
# Refused at startup if it resolves to a network location: the write-ahead log
|
|
23
|
+
# coordinates through a shared-memory index that requires every process using
|
|
24
|
+
# the database to be on one host, and a mapped network drive is indistinguishable
|
|
25
|
+
# from a local one by its path alone.
|
|
26
|
+
# BROKER_DB=/path/to/broker.db
|
|
27
|
+
|
|
28
|
+
# Where captures, snapshots and crops are written.
|
|
29
|
+
# Default: as above, in a directory of the service's own.
|
|
30
|
+
# BROKER_ARTIFACTS_ROOT=/path/to/artefacts
|
|
31
|
+
|
|
32
|
+
# Where the browser profiles live. Each browser launches against this plus its
|
|
33
|
+
# own name, explicitly and mandatorily — never a default profile location.
|
|
34
|
+
# Default: as above, in a directory of the service's own.
|
|
35
|
+
# BROKER_PROFILE_ROOT=/path/to/profiles
|
|
36
|
+
|
|
37
|
+
# ── The comparison feature's five numbers ────────────────────────────────
|
|
38
|
+
#
|
|
39
|
+
# A diff is an optional argument on a capture: the caller names which earlier
|
|
40
|
+
# capture to compare against, and with no such argument there is no diff and
|
|
41
|
+
# these are never read. Three of the five are copied onto every comparison
|
|
42
|
+
# record, because all three are mutable and all three decided the output — so
|
|
43
|
+
# what an earlier call did, under the numbers in force at the time, stays
|
|
44
|
+
# answerable after any of them moves.
|
|
45
|
+
|
|
46
|
+
# How different two pixels must be before either counts as changed, from 0 to 1;
|
|
47
|
+
# smaller is more sensitive.
|
|
48
|
+
# Default: 0.1 — the comparison library's own default, which is a better
|
|
49
|
+
# starting position than a number invented here precisely because it is
|
|
50
|
+
# not one.
|
|
51
|
+
# Worth knowing before tuning: at this default a change fainter than roughly
|
|
52
|
+
# grey 225 on white is not reported at all.
|
|
53
|
+
# BROKER_DIFF_COLOUR_TOLERANCE=0.1
|
|
54
|
+
|
|
55
|
+
# The smallest region reported, in SQUARE PIXELS OF AREA.
|
|
56
|
+
# Default: 64
|
|
57
|
+
# Area, with a thin-line allowance — never the shorter side. A filter on the
|
|
58
|
+
# shorter side discards a one-pixel line across a wide page, which is a border,
|
|
59
|
+
# an underline, a focus ring or a rule: the changes most worth catching.
|
|
60
|
+
# BROKER_DIFF_MINIMUM_REGION_AREA=64
|
|
61
|
+
|
|
62
|
+
# How many regions come back before the result is truncated, smallest first.
|
|
63
|
+
# The result says when it was truncated; a shortened list that did not say so
|
|
64
|
+
# would be a lie about completeness.
|
|
65
|
+
# Default: 12
|
|
66
|
+
# BROKER_DIFF_MAXIMUM_REGIONS=12
|
|
67
|
+
|
|
68
|
+
# How far apart two changed areas stay separate regions, in pixels.
|
|
69
|
+
# Default: 8
|
|
70
|
+
# BROKER_DIFF_REGION_MERGE_DISTANCE=8
|
|
71
|
+
|
|
72
|
+
# Context around a crop, in pixels. A tight box with nothing around it can be
|
|
73
|
+
# genuinely unidentifiable.
|
|
74
|
+
# Default: 16
|
|
75
|
+
# BROKER_DIFF_CROP_PADDING=16
|
|
76
|
+
|
|
77
|
+
# The total tab budget across ALL browsers, and — since a lease is a tab —
|
|
78
|
+
# the same number as the maximum count of live leases. No per-browser cap: the
|
|
79
|
+
# scarce thing is page processes and one costs the same in any browser. The
|
|
80
|
+
# keeper tab in each browser is not counted.
|
|
81
|
+
# Provisional: reasoned from roughly 50-150 MB per idle page process plus two
|
|
82
|
+
# browser processes, so fifteen is one to two gigabytes.
|
|
83
|
+
# THIS IS THE ONE VALUE ALSO WRITTEN TO THE STORE. Several processes arbitrate
|
|
84
|
+
# against it at the same moment, so the first process to open the store records
|
|
85
|
+
# the value it believes and every later process compares. On a disagreement the
|
|
86
|
+
# later process refuses to start and names both numbers — neither value is
|
|
87
|
+
# adopted and neither is overwritten.
|
|
88
|
+
# Default: 15
|
|
89
|
+
# BROKER_TAB_BUDGET=15
|
|
90
|
+
|
|
91
|
+
# How long an active lease lives without a call, in seconds.
|
|
92
|
+
# Deliberately NOT given the agreement check the budget gets: two processes
|
|
93
|
+
# disagreeing here expires something early or late, which is degraded behaviour
|
|
94
|
+
# rather than a broken invariant.
|
|
95
|
+
# Default: 600 (ten minutes)
|
|
96
|
+
# BROKER_LEASE_SECONDS=600
|
|
97
|
+
|
|
98
|
+
# How long a place in the queue lives without a call, in seconds.
|
|
99
|
+
# Deliberately EQUAL to the lease lifetime above. Polling is renewing, so a
|
|
100
|
+
# queued caller holds exactly the instrument an active holder does; and under
|
|
101
|
+
# strict first-in-first-out a queue place held longer blocks everyone behind it,
|
|
102
|
+
# so a generous queued lifetime is the harsher setting rather than the kinder one.
|
|
103
|
+
# Default: 600 (ten minutes)
|
|
104
|
+
# BROKER_QUEUE_SECONDS=600
|
|
105
|
+
|
|
106
|
+
# How long a launch-race loser waits for the winner's browser to accept a
|
|
107
|
+
# connection before declaring the launch failed, in seconds.
|
|
108
|
+
# The loser polls the winner's discovery record — the same liveness-and-identity
|
|
109
|
+
# check every attach uses — rather than sleeping for this whole duration; this is
|
|
110
|
+
# only the bound on how long that poll runs before giving up.
|
|
111
|
+
# Default: 30
|
|
112
|
+
# BROKER_LAUNCH_READINESS_TIMEOUT_SECONDS=30
|
|
113
|
+
|
|
114
|
+
# ── The browsers, and what they are called ─────────────────────────
|
|
115
|
+
#
|
|
116
|
+
# Two lists split by kind, rather than one list with a per-entry flag. Which
|
|
117
|
+
# list a name is written in IS its kind, so there is nothing for a name and
|
|
118
|
+
# its kind to disagree about, and nothing needs encoding.
|
|
119
|
+
#
|
|
120
|
+
# A name is what a caller claims by, the name of that browser's profile
|
|
121
|
+
# directory, and its key in the store. Lower-case letters, digits and hyphens,
|
|
122
|
+
# starting with a letter.
|
|
123
|
+
#
|
|
124
|
+
# Each of these refuses the spawn and NAMES THE OFFENDING ENTRY: a duplicate
|
|
125
|
+
# within one list, the same name in both lists, more names than the cap, or a
|
|
126
|
+
# name that is not a usable word.
|
|
127
|
+
#
|
|
128
|
+
# ⚠ READ THIS BEFORE SETTING ALL SIX. The tab budget above does not bound
|
|
129
|
+
# what these cost, because it counts TABS and each browser is a process
|
|
130
|
+
# BEFORE it holds a single tab. So BROKER_TAB_BUDGET=15 with six browsers is
|
|
131
|
+
# six browser processes, plus up to fifteen tabs, plus six keeper tabs that
|
|
132
|
+
# are not counted against the budget at all — six processes and up to
|
|
133
|
+
# twenty-one pages. Reasoned from roughly 50-150 MB per idle page process,
|
|
134
|
+
# that is meaningfully more than the same budget across two browsers, and the
|
|
135
|
+
# budget will not tell you: it is doing its job, and its job is tabs.
|
|
136
|
+
|
|
137
|
+
# The persistent, signed-in browsers, comma-separated, at most 3.
|
|
138
|
+
# THE FIRST ENTRY IS WHAT AN UNSTATED `browser` RESOLVES TO on browser_claim.
|
|
139
|
+
# Each holds a sign-in a person put there by hand, and each is a separate
|
|
140
|
+
# identity — which is the reason for more than one: tabs within a single
|
|
141
|
+
# browser share its cookie jar, so two identities at once means two browsers.
|
|
142
|
+
# Default: regular
|
|
143
|
+
# BROKER_REGULAR_BROWSERS=regular
|
|
144
|
+
|
|
145
|
+
# The ephemeral, signed-in-to-nothing browsers, comma-separated, at most 3.
|
|
146
|
+
# Capped separately rather than sharing a total with the list above, because
|
|
147
|
+
# the two kinds are not interchangeable: one shared total could be spent
|
|
148
|
+
# entirely on signed-in browsers, leaving no clean-room browser at all, and
|
|
149
|
+
# clean-room is the one that cannot be substituted for.
|
|
150
|
+
# Default: private
|
|
151
|
+
# BROKER_PRIVATE_BROWSERS=private
|
|
152
|
+
|
|
153
|
+
# Both caps are refusals, not truncations: naming a fourth browser in either
|
|
154
|
+
# list stops the service at startup and says which variable was wrong, rather
|
|
155
|
+
# than quietly running the first three. `broker doctor` reports on every
|
|
156
|
+
# browser named in both lists above, so a browser added here gains its own
|
|
157
|
+
# discovery and keeper-tab lines in the health report.
|
|
158
|
+
|
|
159
|
+
# Which browser binary the signed-in browsers launch.
|
|
160
|
+
# Options: chrome, brave, msedge — all Chromium over the same remote-debugging
|
|
161
|
+
# protocol, which is why the choice is a path rather than a driver.
|
|
162
|
+
# One engine per kind, never per browser: an engine per entry would put an
|
|
163
|
+
# attribute back on the entry, which is what the two lists exist to avoid.
|
|
164
|
+
# Default: msedge
|
|
165
|
+
# BROKER_REGULAR_BROWSER_ENGINE=msedge
|
|
166
|
+
|
|
167
|
+
# Which browser binary the clean-room browsers launch. MAY DIFFER from the
|
|
168
|
+
# engine above: a person signs into the signed-in browsers by hand, so which
|
|
169
|
+
# binary those are can follow what they already use, while nobody signs into
|
|
170
|
+
# a clean-room browser at all.
|
|
171
|
+
# Options: chrome, brave, msedge
|
|
172
|
+
# Default: msedge
|
|
173
|
+
# BROKER_PRIVATE_BROWSER_ENGINE=msedge
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zaida-3dO
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Browser Broker
|
|
2
|
+
|
|
3
|
+
**Several agents, two browsers, no collisions.**
|
|
4
|
+
|
|
5
|
+
Browser Broker brokers access to a small, fixed set of real browsers. A caller asks for a lease; it
|
|
6
|
+
gets back a secret key and **one tab** it exclusively owns. Every later call carries that key, and the
|
|
7
|
+
service uses it to route the call, to enforce what that caller may touch, and to account for what it
|
|
8
|
+
costs. There is nothing running in the background: the service is started by its caller and exits
|
|
9
|
+
with it, and the browsers outlive any one of them.
|
|
10
|
+
|
|
11
|
+
## What it gives you
|
|
12
|
+
|
|
13
|
+
- **A hard ceiling on browser processes.** Concurrency is expressed in tabs inside a fixed set of
|
|
14
|
+
browsers, so process count is bounded by configuration rather than by how many clients connect. A
|
|
15
|
+
lease is exactly one tab, so the budget, the pool bound and the number of live leases are one
|
|
16
|
+
integer that cannot disagree with itself. Need two tabs, claim twice.
|
|
17
|
+
- **Leases, with a queue.** When capacity is full a caller is queued rather than refused, and told
|
|
18
|
+
its position and when to check back.
|
|
19
|
+
- **Reclamation from callers that die.** Every key carries a time to live that any call renews, so a
|
|
20
|
+
client that vanishes mid-work returns its capacity on its own. This is the failure a client-side
|
|
21
|
+
convention cannot cover, because the client that should clean up is the one that is gone. Nothing
|
|
22
|
+
expires on a timer: every arbitration call first expires whatever has lapsed across the whole
|
|
23
|
+
store, then answers from the reconciled state.
|
|
24
|
+
- **A shared signed-in profile that no single caller can destroy.** Nothing browser-scoped is
|
|
25
|
+
exposed. A caller can close its own tab; it cannot close a browser, and no operation reaches a
|
|
26
|
+
browser outside the ones the service is configured to run.
|
|
27
|
+
- **References, not payloads.** Screenshots and page snapshots are written to disk and returned as a
|
|
28
|
+
path with its dimensions and size. An agent opens one only when it genuinely needs to look, so a
|
|
29
|
+
capture is paid for once instead of on every subsequent turn.
|
|
30
|
+
- **A capture policy applied by the thing that takes the capture.** Screenshots come back at a low
|
|
31
|
+
resolution unless you ask for more, and asking for the most expensive tier costs a stated reason in
|
|
32
|
+
free text. Nothing is ever refused — going over budget warns loudly and names the cheaper way to
|
|
33
|
+
get the same answer.
|
|
34
|
+
- **Changed-region review.** A capture can name an earlier capture to compare against, and the
|
|
35
|
+
regions that actually moved come back as crops — so a repeat review looks at what changed instead
|
|
36
|
+
of at everything. There is no canonical picture to bless first: a capture is a capture with an
|
|
37
|
+
identifier, and the caller says which one it means. If that image is missing, the full screenshot
|
|
38
|
+
comes back with an explanation rather than a refusal.
|
|
39
|
+
- **Nothing to configure before it runs.** Every value is an environment variable with a working
|
|
40
|
+
default, so a fresh install runs with nothing set, and `.env.example` documents the whole set. The
|
|
41
|
+
one value several processes must agree on — the tab budget — is written into the store by the first
|
|
42
|
+
process to open it, and a later process whose environment disagrees refuses to start and names both
|
|
43
|
+
numbers.
|
|
44
|
+
|
|
45
|
+
## Surfaces
|
|
46
|
+
|
|
47
|
+
The rules live in one service layer, and every surface is a thin adapter over it: **twelve tools**,
|
|
48
|
+
served by the caller's own spawned process, and a `broker` command line that runs the same logic in
|
|
49
|
+
the process you typed it in. A shared conformance suite asserts that the same operation and the same
|
|
50
|
+
refusal happen on both.
|
|
51
|
+
|
|
52
|
+
**Nothing is served over a socket.** The operations view is a self-contained HTML file that a command
|
|
53
|
+
generates and a person opens from disk — a snapshot, labelled with the moment it was taken, which does
|
|
54
|
+
not refresh. It is generated from inside a live session, so it reads each tab's address from the
|
|
55
|
+
browser itself; a browser that does not answer within a timeout renders as unreachable rather than
|
|
56
|
+
hanging the report.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
**Installation is the whole of deployment.** There is no image to pull, no daemon to register and no
|
|
61
|
+
service to keep running: the process is started by whatever calls it and exits with it. So getting it
|
|
62
|
+
working is an install and one more fetch below — there is no step after that.
|
|
63
|
+
|
|
64
|
+
You need **Node 22.18 or newer**.
|
|
65
|
+
|
|
66
|
+
### From the registry
|
|
67
|
+
|
|
68
|
+
The package ships compiled JavaScript, so nothing is built on your machine:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx browser-broker doctor
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A client that spawns the tool surface names the same package, and npm revalidates the version on
|
|
75
|
+
every run — so a published release arrives without anything being pulled or rebuilt by hand:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"browser-broker": { "command": "npx", "args": ["-y", "browser-broker"] }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Weigh that against spawning a checkout directly: `npx` costs a registry round-trip on every spawn,
|
|
86
|
+
which is several seconds against a fraction of one for a path on disk. A machine that develops this
|
|
87
|
+
service is better off pointing at its own tree; a machine that only *uses* it is better off here.
|
|
88
|
+
|
|
89
|
+
### From a checkout
|
|
90
|
+
|
|
91
|
+
For working on the service itself. The sources are TypeScript and run through the runtime's own type
|
|
92
|
+
stripping, so there is no build step in the development path:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone https://github.com/Zaida-3dO/browser-broker.git
|
|
96
|
+
cd browser-broker
|
|
97
|
+
npm install
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That compiles the one runtime dependency's native binding, which is *not* the only part of the install
|
|
101
|
+
that does real work: this repository depends on `playwright-core`, not the full `playwright`
|
|
102
|
+
distribution, precisely because the browser binary is spawned by this service, detached and by path,
|
|
103
|
+
rather than downloaded and managed by the package. `playwright-core` does not fetch a browser on
|
|
104
|
+
install, so a checkout that has never had one fetched by some other tooling has none, and `broker
|
|
105
|
+
doctor`'s automation check will genuinely fail with exit code 11 until you run:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx playwright-core install chromium
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Run this once per machine, before the first `broker doctor`. It is the same install mechanism the
|
|
112
|
+
full `playwright` package would run automatically on `npm install`; `playwright-core` just does not
|
|
113
|
+
run it for you. Then run the broker itself:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
node src/bin/broker.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The first run creates the store, brings its schema up to the version the build expects, prints where
|
|
120
|
+
the file is, and exits:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
store: <the resolved store location>
|
|
124
|
+
schema: stepped from version 0 to version 1 (1 step(s) applied)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Run it again and it says the schema is already where it should be. **Every spawn does this**, not
|
|
128
|
+
just the first — with no long-lived process, there is no other moment at which it could happen, and a
|
|
129
|
+
caller that has upgraded and one that has not may both start within the same minute.
|
|
130
|
+
|
|
131
|
+
To get the command on your path as `broker`, link the package from the checkout:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm link # then: broker --help
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Configuring it
|
|
138
|
+
|
|
139
|
+
**Nothing needs setting.** Every value is an environment variable with a working default, so the
|
|
140
|
+
install above runs as-is; [`.env.example`](.env.example) documents the whole set, with placeholders
|
|
141
|
+
rather than real values. Nothing reads that file — configuration is the process environment.
|
|
142
|
+
|
|
143
|
+
The default store location is a directory of the service's own under the per-user application-data
|
|
144
|
+
location your platform defines. It is computed rather than written down anywhere, because writing one
|
|
145
|
+
down would name one machine. To put it somewhere else, set `BROKER_DB`:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
BROKER_DB=/some/writable/path/broker.db node src/bin/broker.ts
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**The browsers are configuration too.** Two lists, at most three names each, defaulting to one of
|
|
152
|
+
each — `regular` and `private`. A name is what a caller claims by and what its profile directory is
|
|
153
|
+
called, and a caller that names no browser gets the first signed-in one:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
BROKER_REGULAR_BROWSERS=regular,checkout # persistent, signed in, at most 3
|
|
157
|
+
BROKER_PRIVATE_BROWSERS=private # ephemeral, at most 3
|
|
158
|
+
BROKER_REGULAR_BROWSER_ENGINE=msedge # chrome | brave | msedge
|
|
159
|
+
BROKER_PRIVATE_BROWSER_ENGINE=msedge # may differ from the line above
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Two signed-in browsers is how two identities are exercised at once: tabs within one browser share
|
|
163
|
+
its cookie jar, so they are isolated from other browsers and not from each other. **Note that each
|
|
164
|
+
browser is a process before it holds a single tab**, which the tab budget does not count —
|
|
165
|
+
`.env.example` gives the arithmetic beside the variables.
|
|
166
|
+
|
|
167
|
+
A variable that is set but cannot be read as its type **refuses the spawn and names the variable**,
|
|
168
|
+
rather than quietly falling back to the default — a configuration nobody chose is worse than a
|
|
169
|
+
refusal nobody missed. For a list, the refusal names the offending **entry**: a duplicate within one
|
|
170
|
+
list, a name in both lists, more names than the cap, or a name that is not a usable word. A store location that resolves to a network share is refused for the same
|
|
171
|
+
reason it has to be: the write-ahead log coordinates through shared memory that requires every
|
|
172
|
+
process using the file to sit on one host.
|
|
173
|
+
|
|
174
|
+
### Pointing a client at the tools
|
|
175
|
+
|
|
176
|
+
The twelve tools are served over standard input and output by `src/bin/broker-tool.ts`, which speaks
|
|
177
|
+
[the Model Context Protocol](https://modelcontextprotocol.io/specification/2025-06-18) — revision
|
|
178
|
+
`2025-06-18`, with `2025-03-26` accepted for a client that asks for it. A client spawns that file,
|
|
179
|
+
opens with `initialize`, and the twelve tools are listed to it.
|
|
180
|
+
|
|
181
|
+
Most clients read a JSON file naming the servers they may spawn. The block is the same shape in all
|
|
182
|
+
of them; put it in whichever file yours reads — commonly `.mcp.json` in a project, or the client's
|
|
183
|
+
own configuration:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"mcpServers": {
|
|
188
|
+
"browser-broker": {
|
|
189
|
+
"command": "node",
|
|
190
|
+
"args": ["/absolute/path/to/browser-broker/src/bin/broker-tool.ts"]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**The path has to be absolute**, because the client chooses the working directory it spawns from and
|
|
197
|
+
it is rarely the checkout. Nothing else is required: there is no port to configure, no token to
|
|
198
|
+
issue, and no process to have started first — the client starts it, and it exits when the client
|
|
199
|
+
closes the pipe.
|
|
200
|
+
|
|
201
|
+
To point it at a store other than the default, add the environment to the same block:
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"mcpServers": {
|
|
206
|
+
"browser-broker": {
|
|
207
|
+
"command": "node",
|
|
208
|
+
"args": ["/absolute/path/to/browser-broker/src/bin/broker-tool.ts"],
|
|
209
|
+
"env": { "BROKER_DB": "/some/writable/path/broker.db" }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Every client sharing a store sees the same leases, which is the point of the store being a file: the
|
|
216
|
+
capacity being arbitrated is one set of browsers, and two clients that could not see each other's
|
|
217
|
+
claims would both think the whole of it was free.
|
|
218
|
+
|
|
219
|
+
**To check the wiring without a client**, speak the handshake by hand. This writes three messages and
|
|
220
|
+
reads two back — the notification is the one that draws no reply:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
printf '%s\n' \
|
|
224
|
+
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
|
|
225
|
+
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
|
|
226
|
+
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
|
227
|
+
| node src/bin/broker-tool.ts
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The first response carries the negotiated `protocolVersion`, the server's `capabilities` and its
|
|
231
|
+
`serverInfo`; the second message is a notification and is deliberately not answered; the third lists
|
|
232
|
+
the twelve tools. A client that gets that far will work.
|
|
233
|
+
|
|
234
|
+
## First run
|
|
235
|
+
|
|
236
|
+
There is **one step a person performs by hand**, and it happens once: signing the shared browser in.
|
|
237
|
+
Everything else — creating the profiles, starting browsers, adopting them, keeping them alive — the
|
|
238
|
+
service does for itself. This is the whole of it, from a clean clone:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
git clone https://github.com/Zaida-3dO/browser-broker.git
|
|
242
|
+
cd browser-broker
|
|
243
|
+
npm install
|
|
244
|
+
|
|
245
|
+
node src/bin/broker.ts init # create the store and both browser profiles
|
|
246
|
+
node src/bin/broker.ts login # open a browser and sign in, by hand
|
|
247
|
+
node src/bin/broker.ts doctor # confirm the sign-in took
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**`broker init`** creates the store, steps its schema, and establishes a profile directory for each
|
|
251
|
+
browser. It reports each one as `created` or `found`, which is the distinction worth reading: a
|
|
252
|
+
profile reported as `created` on a machine where you expected a sign-in is the earliest possible
|
|
253
|
+
warning that you are about to be asked to sign in again. It never recreates or clears a profile that
|
|
254
|
+
is already there.
|
|
255
|
+
|
|
256
|
+
**`broker login`** opens the shared browser, headed, against that profile and hands it to you:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
A browser window is open for you to sign in.
|
|
260
|
+
|
|
261
|
+
1. Switch to the browser window that just opened. It is the regular browser,
|
|
262
|
+
running against the profile at regular under the configured
|
|
263
|
+
profile root — which is the profile every caller will share.
|
|
264
|
+
2. Go to whichever site you want this service to be signed in to, and sign in
|
|
265
|
+
normally. This is a real browser and a real sign-in: what you type goes to
|
|
266
|
+
that site exactly as it would in your own browser.
|
|
267
|
+
3. Close the window when you are done. Closing it is what ends this step.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Sign in to whatever you want the service to have access to, then **close the window** — that is what
|
|
271
|
+
ends the step. While it is open the browser is not serving callers: anything that asks for it is told
|
|
272
|
+
a person is signing in and to try again shortly, and anything already waiting in the queue keeps its
|
|
273
|
+
place and its timer. A sign-in is a pause, not a cancellation.
|
|
274
|
+
|
|
275
|
+
Two things it will refuse, both on purpose:
|
|
276
|
+
|
|
277
|
+
- **A browser with live work on it.** If a caller holds a tab there, signing in would mean driving the
|
|
278
|
+
window by hand underneath somebody's work. It names the leases holding it; waiting is enough,
|
|
279
|
+
because every lease expires on its own if its holder stops calling in.
|
|
280
|
+
- **The private browser.** Its profile is discarded when it exits, so a sign-in there would appear to
|
|
281
|
+
work and leave you signed into nothing.
|
|
282
|
+
|
|
283
|
+
**Nothing records what you type.** The sign-in is written into the browser's own profile directory by
|
|
284
|
+
the browser itself. This service never sees a credential and stores nothing about one anywhere —
|
|
285
|
+
which is also why there is no way to copy a sign-in between machines: the profile *is* the identity.
|
|
286
|
+
|
|
287
|
+
**`broker doctor`** then tells you whether it took, without opening a browser:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
[ok ] The regular browser’s profile carries a sign-in
|
|
291
|
+
The profile holds 1 stored cookie(s), so a session was established and written down.
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Before you have signed in, the same line reads:
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
[-- ] The regular browser’s profile carries a sign-in
|
|
298
|
+
The profile has a cookie store and it holds no cookies. That is what a profile nobody has
|
|
299
|
+
signed into looks like — though a site that keeps its session only in local storage would
|
|
300
|
+
look the same, so this is the absence of evidence rather than evidence of absence.
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**It never reports this as a failure**, and it will say `unknown` rather than guess. A profile with no
|
|
304
|
+
session is the ordinary state of every installation until somebody signs in, and a check that went red
|
|
305
|
+
on a working machine is one people learn to ignore. It also cannot see everything: it reads the
|
|
306
|
+
browser's stored cookies, so a site that keeps its session somewhere else is invisible to it, and a
|
|
307
|
+
browser that is still running has not necessarily written its cookies down yet — in that case it says
|
|
308
|
+
so and tells you to close the browser and ask again.
|
|
309
|
+
|
|
310
|
+
### Checking an install
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npm run check:install
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This spawns the executable as a real process against a temporary store, and asserts it creates the
|
|
317
|
+
file, steps the schema to the version the build expects, answers a command and **exits**. It is what
|
|
318
|
+
continuous integration runs on a clean hosted runner, and it is the check that stands in for the one
|
|
319
|
+
an image build would have given: proof that the thing actually starts.
|
|
320
|
+
|
|
321
|
+
Run everything the pipeline runs with `npm run check`.
|
|
322
|
+
|
|
323
|
+
### Recovering from leaked browsers, on Windows
|
|
324
|
+
|
|
325
|
+
<!-- external-ref-ok-next-line: this repository's own first-party PowerShell tool, named for the reader who needs to find it -->
|
|
326
|
+
The emergency sweep in `scripts/reap-broker-browsers.ps1` reports leaked broker browsers by default
|
|
327
|
+
and does not touch them; pass `-Execute` to terminate what it found, and add `-PruneDirs
|
|
328
|
+
-OlderThanHours <n>` to also clear stale profile directories older than that many hours. Not part of
|
|
329
|
+
the pipeline: a browser this project launches is spawned `detached: true` by design (see the header
|
|
330
|
+
of `src/browser/launch.ts`) so it survives the process that started it, and every known way that
|
|
331
|
+
leak could happen has since been fixed at its source — see the script's own header for the list.
|
|
332
|
+
Keep it installed anyway as insurance against whatever the next one turns out to be, and as the tool
|
|
333
|
+
to run if a machine is already in the frozen state a leak like this can cause. Matches processes by
|
|
334
|
+
profile-directory command line, never by image name, so it cannot touch an unrelated Chrome window
|
|
335
|
+
or this repository's own Playwright MCP tooling; see the script's own SAFETY section.
|
|
336
|
+
|
|
337
|
+
## Status
|
|
338
|
+
|
|
339
|
+
Under construction — the store, the executable and the pipeline are in place, the arbitration
|
|
340
|
+
surface is being built, and the one manual step is wired: `broker login` hands a person the shared
|
|
341
|
+
browser and `broker doctor` reports whether the sign-in took. Read [`docs/ROLLOUT.md`](docs/ROLLOUT.md) for taking it from installed to sole route in an order that
|
|
342
|
+
never leaves traffic unarbitrated, [`docs/plans/PLAN.md`](docs/plans/PLAN.md) for how it works,
|
|
343
|
+
[`docs/plans/DECISIONS.md`](docs/plans/DECISIONS.md) for why it is shaped this way, and
|
|
344
|
+
[`docs/plans/MILESTONES.md`](docs/plans/MILESTONES.md) for the work queue, and
|
|
345
|
+
[`RELEASES.md`](RELEASES.md) for what changes between versions — in particular for defaults that
|
|
346
|
+
move, which change an installation that has taken no action.
|
|
347
|
+
|
|
348
|
+
## Releasing
|
|
349
|
+
|
|
350
|
+
The development path runs the TypeScript sources directly; the published package cannot, because
|
|
351
|
+
**Node refuses to strip types from any file under a `node_modules` path** and an installed package is
|
|
352
|
+
exactly that. No flag overrides it. So a release compiles to `dist/` and the manifest's `bin` entries
|
|
353
|
+
name the emitted JavaScript.
|
|
354
|
+
|
|
355
|
+
`prepack` runs the build, so `npm publish` and `npm pack` compile on their own — there is no way to
|
|
356
|
+
publish a stale `dist/`, and no build step to remember.
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
npm version patch # or minor / major — writes the tag and the commit
|
|
360
|
+
npm publish # prepack builds, then the tarball goes up
|
|
361
|
+
git push --follow-tags
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`npm run check:package` asserts what a published tarball owes: every `bin` target is emitted
|
|
365
|
+
JavaScript rather than a TypeScript source, the whole built tree is included, and the tarball carries
|
|
366
|
+
no tests or plans. It runs in CI. Worth knowing if you touch the `files` field: **npm ships `bin`
|
|
367
|
+
targets whatever `files` says**, so a tarball can contain both executables and none of the modules
|
|
368
|
+
they import — which installs cleanly and dies on first run. That is the case the check exists for.
|
|
369
|
+
|
|
370
|
+
## Licence
|
|
371
|
+
|
|
372
|
+
**MIT** — see [`LICENSE`](LICENSE), and `docs/plans/DECISIONS.md` §13e for the reasoning. A public
|
|
373
|
+
repository without a licence file grants no rights to anyone, so the decision alone was never enough;
|
|
374
|
+
the file carries it and `package.json` declares it.
|
package/RELEASES.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Release notes
|
|
2
|
+
|
|
3
|
+
**What changes between versions, and specifically what changes *behaviour* on an installation that
|
|
4
|
+
sets nothing.**
|
|
5
|
+
|
|
6
|
+
That second half is the reason this file exists rather than the commit log being enough. A new
|
|
7
|
+
setting with a neutral default is not news: an installation that does not set it behaves exactly as
|
|
8
|
+
before. **A changed default is news**, because it moves an installation that has taken no action and
|
|
9
|
+
made no decision. `docs/plans/DECISIONS.md` §6.3 puts a changed default here rather than in a quiet
|
|
10
|
+
edit for that reason.
|
|
11
|
+
|
|
12
|
+
Entries are newest first. Each names what moved, what an installation has to do about it, and what
|
|
13
|
+
happens if it does nothing.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Unreleased
|
|
18
|
+
|
|
19
|
+
### The package is published, and it ships compiled JavaScript
|
|
20
|
+
|
|
21
|
+
**What moved.** The service is installable from the registry as `browser-broker`, so a caller can
|
|
22
|
+
spawn it without a checkout. The manifest's `bin` entries now name emitted JavaScript under `dist/`
|
|
23
|
+
rather than the TypeScript sources.
|
|
24
|
+
|
|
25
|
+
**Why the build exists**, given that the development path deliberately has none: **Node refuses to
|
|
26
|
+
strip types from any file under a `node_modules` path**, and an installed package is a directory
|
|
27
|
+
under `node_modules`. A manifest whose `bin` named a `.ts` file would install cleanly and then fail
|
|
28
|
+
on the machine of whoever installed it. There is no flag that changes this. The compiler therefore
|
|
29
|
+
runs once per release rather than on every machine that consumes the package, and `erasableSyntaxOnly`
|
|
30
|
+
stays on so the sources still run unbuilt — the two paths execute the same dialect.
|
|
31
|
+
|
|
32
|
+
**What an installation has to do.** Nothing. A checkout is unaffected: `node src/bin/broker.ts` still
|
|
33
|
+
runs the sources with no build. An installation that would rather not track a checkout can point at
|
|
34
|
+
the package instead, and npm revalidates the version on every spawn:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{ "command": "npx", "args": ["-y", "browser-broker"] }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Worth weighing before switching:** `npx` performs a registry round-trip on every spawn, costing
|
|
41
|
+
seconds where a path on disk costs a fraction of one. It buys an upgrade path, not speed.
|
|
42
|
+
|
|
43
|
+
**One surface changes what it reports.** The tool handshake's `serverInfo.version` was the literal
|
|
44
|
+
`0.0.0` while the package was unversioned, and now reads the manifest — so a client logging it sees
|
|
45
|
+
the released version rather than a placeholder.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### ⚠ Behaviour change: the default browser engine is Edge
|
|
49
|
+
|
|
50
|
+
**What moved.** A browser launched by this service uses **`msedge`** by default. The previous
|
|
51
|
+
behaviour was to launch whatever `chromium.executablePath()` resolved to — the Chromium build the
|
|
52
|
+
automation library had fetched.
|
|
53
|
+
|
|
54
|
+
**Why.** Edge is present on every Windows machine, so a fresh install runs with nothing set, with no
|
|
55
|
+
separate browser download step. That is `DECISIONS.md` §6.1's *"a fresh install runs with nothing
|
|
56
|
+
set"* applied to the one prerequisite `npm install` genuinely could not cover.
|
|
57
|
+
|
|
58
|
+
**What an installation has to do.** Nothing, if Edge is acceptable. To keep the prior behaviour, or
|
|
59
|
+
to pick a different browser, set the engine per kind:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
BROKER_REGULAR_BROWSER_ENGINE=chrome # chrome | brave | msedge
|
|
63
|
+
BROKER_PRIVATE_BROWSER_ENGINE=chrome # may differ from the line above
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**If it does nothing:** browsers launch under Edge. **Profiles are per browser and are not shared
|
|
67
|
+
between engines**, so an installation whose signed-in profile was established under a different
|
|
68
|
+
binary will find that browser signed out, and a person will be asked to sign in once more with
|
|
69
|
+
`broker login`. Nothing is destroyed — `setup.profile_never_destroyed` still holds, and the earlier
|
|
70
|
+
profile directory is left exactly where it is.
|
|
71
|
+
|
|
72
|
+
### Browsers are a configured list, and `browser` on a claim is optional
|
|
73
|
+
|
|
74
|
+
**What moved.** Two things, both reversals of recorded decisions — the argument for each is in
|
|
75
|
+
`DECISIONS.md` §13i:
|
|
76
|
+
|
|
77
|
+
- **The fixed pair of browsers becomes a bounded list per kind**, at most three each, named in
|
|
78
|
+
configuration. A name is what a caller claims by and what its profile directory is called.
|
|
79
|
+
- **`browser` on `browser_claim` becomes optional.** Unstated resolves to the first signed-in
|
|
80
|
+
browser; `regular` or `private` resolves to the first of that kind; a configured name resolves to
|
|
81
|
+
that browser exactly.
|
|
82
|
+
|
|
83
|
+
**What an installation has to do.** Nothing. The defaults name one browser of each kind, `regular`
|
|
84
|
+
and `private`, which is the pair that existed before — so an installation that sets nothing has the
|
|
85
|
+
same two browsers under the same two names, and a caller that states `browser` explicitly is
|
|
86
|
+
unaffected.
|
|
87
|
+
|
|
88
|
+
**Worth reading before configuring more than the default two:** the tab budget counts *tabs*, and
|
|
89
|
+
each browser costs a process **before it holds a single tab**. `BROKER_TAB_BUDGET=15` with six
|
|
90
|
+
browsers is six browser processes, plus up to fifteen tabs, plus six keeper tabs that are not
|
|
91
|
+
counted against the budget at all. `.env.example` states this beside the variables.
|
|
92
|
+
|
|
93
|
+
**One schema step.** The store gains a `kind` column on `browsers` and drops the check constraint
|
|
94
|
+
that limited a browser's name to two literals. It is applied on the next spawn, like every step, and
|
|
95
|
+
the two existing rows are backfilled to their own kinds. **A store stepped by this build is not
|
|
96
|
+
readable by an earlier one**, which is the ordinary direction — a build refuses a store newer than
|
|
97
|
+
itself rather than downgrading it.
|