@parall/daemon 1.36.0 → 1.37.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/bundle/manifest.json +11 -11
- package/bundle/parall-browser-pod.js +384 -32
- package/bundle/parall-claude-agent.js +426 -51
- package/bundle/parall-codex-agent.js +425 -49
- package/bundle/parall-daemon.js +33336 -31520
- package/dist/browser-pod.d.ts +7 -0
- package/dist/browser-pod.d.ts.map +1 -1
- package/dist/browser-pod.js +73 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/clip-runtime/browser-daemon-env.d.ts +29 -0
- package/dist/clip-runtime/browser-daemon-env.d.ts.map +1 -0
- package/dist/clip-runtime/browser-daemon-env.js +70 -0
- package/dist/clip-runtime/browser-profile-manager.d.ts +34 -5
- package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
- package/dist/clip-runtime/browser-profile-manager.js +166 -24
- package/dist/clip-runtime/browser-profile-pool.d.ts +250 -0
- package/dist/clip-runtime/browser-profile-pool.d.ts.map +1 -0
- package/dist/clip-runtime/browser-profile-pool.js +581 -0
- package/dist/clip-runtime/index.d.ts +2 -1
- package/dist/clip-runtime/index.d.ts.map +1 -1
- package/dist/clip-runtime/index.js +2 -1
- package/dist/clip-runtime/process-manager.d.ts +5 -3
- package/dist/clip-runtime/process-manager.d.ts.map +1 -1
- package/dist/clip-runtime/subprocess.d.ts +14 -0
- package/dist/clip-runtime/subprocess.d.ts.map +1 -1
- package/dist/clip-runtime/subprocess.js +49 -0
- package/dist/config.d.ts +14 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -26
- package/dist/daemon-main.d.ts +8 -0
- package/dist/daemon-main.d.ts.map +1 -0
- package/dist/daemon-main.js +165 -0
- package/dist/daemon-paths.d.ts +14 -0
- package/dist/daemon-paths.d.ts.map +1 -0
- package/dist/daemon-paths.js +26 -0
- package/dist/daemon-update-mode.d.ts +8 -0
- package/dist/daemon-update-mode.d.ts.map +1 -0
- package/dist/daemon-update-mode.js +18 -0
- package/dist/index.js +41 -167
- package/dist/runtime-bin-resolver.d.ts +4 -0
- package/dist/runtime-bin-resolver.d.ts.map +1 -1
- package/dist/runtime-bin-resolver.js +40 -7
- package/dist/runtime-detector.d.ts +30 -0
- package/dist/runtime-detector.d.ts.map +1 -0
- package/dist/runtime-detector.js +100 -0
- package/dist/supervisor.d.ts +64 -2
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +629 -67
- package/dist/update-health-gate.d.ts +66 -0
- package/dist/update-health-gate.d.ts.map +1 -0
- package/dist/update-health-gate.js +93 -0
- package/dist/updater-manifest.d.ts +2 -1
- package/dist/updater-manifest.d.ts.map +1 -1
- package/dist/updater-manifest.js +38 -7
- package/dist/updater.d.ts +13 -2
- package/dist/updater.d.ts.map +1 -1
- package/dist/updater.js +126 -17
- package/package.json +6 -6
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { BrowserProfileManager, } from './browser-profile-manager.js';
|
|
4
|
+
/**
|
|
5
|
+
* BrowserProfilePool — true per-profile isolation for the BYOC daemon supervisor.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS EXISTS. A single bb-browser/Chromium shared across profiles isolates
|
|
8
|
+
* only at the bb-browser "account" (CDP BrowserContext) level. That is real for
|
|
9
|
+
* cookies/storage, but bb-browser's COMMAND routing is account-blind: any command
|
|
10
|
+
* issued without an explicit tabId falls through to a single GLOBAL current tab
|
|
11
|
+
* (cdp.currentTargetId, else targets[0]) regardless of the `account` field
|
|
12
|
+
* (bb-browser-pro daemon.js ensurePageTarget). So when the global current tab
|
|
13
|
+
* belongs to profile A, a command meant for profile B executes in A's context —
|
|
14
|
+
* e.g. logging into a second account overwrites the first, and two profiles can
|
|
15
|
+
* never hold two different sessions at once (reproduced on a real Chrome).
|
|
16
|
+
*
|
|
17
|
+
* THE FIX. Give every profile its OWN bb-browser daemon + Chromium + on-disk home
|
|
18
|
+
* (`<baseHomeDir>/<profileId>`). Each Chromium then hosts exactly one profile, so
|
|
19
|
+
* there is no cross-profile global tab to leak into — isolation is enforced by the
|
|
20
|
+
* OS process + user-data-dir boundary, not by remembering to scope every command.
|
|
21
|
+
* The pool owns one {@link BrowserProfileManager} per profile and routes by
|
|
22
|
+
* profileId; an idle (stopped) profile holds no Chromium, so cost scales with the
|
|
23
|
+
* number of CONCURRENTLY-OPEN profiles, not the number that ever existed.
|
|
24
|
+
*
|
|
25
|
+
* NOT used by the hosted browser pod (`browser-pod.ts`): a pod serves exactly one
|
|
26
|
+
* profile, so it keeps using a single BrowserProfileManager directly (and its
|
|
27
|
+
* BrowserStateStore snapshots that manager's flat homeDir to S3 — the pool's
|
|
28
|
+
* per-profile nesting would break that). The pool is supervisor-only.
|
|
29
|
+
*
|
|
30
|
+
* OWNERSHIP. The pool is a per-profile runtime REGISTRY + the per-profile mutex
|
|
31
|
+
* (create / stop / list-active / invoke / viewer, serialized via `enqueue()`). The
|
|
32
|
+
* DESIRED-STATE coordinator is the supervisor: the server (clip-service) is the
|
|
33
|
+
* SSOT for which profiles should be live, and `DaemonSupervisor.reconcileBrowserProfiles`
|
|
34
|
+
* converges the local registry to it (open/ensure desired, stop the rest via
|
|
35
|
+
* `activeProfileIds()`). The pool keeps one bit of lifecycle policy — the `stopped`
|
|
36
|
+
* fence — because invoke (hub channel) and viewer funnel through it and must reject
|
|
37
|
+
* a stale command without a server round-trip.
|
|
38
|
+
*/
|
|
39
|
+
export class BrowserProfilePool {
|
|
40
|
+
opts;
|
|
41
|
+
managers = new Map();
|
|
42
|
+
// Per-profile op queue. Hub browser invokes arrive through ClipProcessManager,
|
|
43
|
+
// NOT the supervisor's per-profile lifecycle queue, so without this an invoke
|
|
44
|
+
// could interleave with a stop/reset that is disposing the same profile's
|
|
45
|
+
// runtime — racing a reset's wipe. Every op for a profileId runs serially;
|
|
46
|
+
// different profiles stay parallel.
|
|
47
|
+
opChains = new Map();
|
|
48
|
+
// Desired-state fence: profiles explicitly stopped/reset (desired = down).
|
|
49
|
+
// Serialization alone orders ops but does not stop a LATE invoke (authorized
|
|
50
|
+
// before the stop, arriving after it on the independent hub channel) from
|
|
51
|
+
// re-spawning a Chromium for a stopped profile and flipping it back to
|
|
52
|
+
// running. An invoke OR viewer command for a fenced profile is rejected until
|
|
53
|
+
// an explicit open/ensureRuntime (lifecycle/reconcile) revives it — a stale
|
|
54
|
+
// viewer command must not resurrect a profile the server already stopped. The
|
|
55
|
+
// full reconcile also fences profiles the server no longer lists as live.
|
|
56
|
+
stopped = new Set();
|
|
57
|
+
// Monotonic per-profile counter bumped every time the profile is fenced (stop /
|
|
58
|
+
// reset / reconcile release / pre-fence). A revive (open / ensureRuntime)
|
|
59
|
+
// captures this at ENQUEUE time and, when it finally runs, refuses to clear the
|
|
60
|
+
// fence if a newer stop was observed since — so a stale open/ensure queued
|
|
61
|
+
// before a stop (e.g. a reconnect reconcile racing a stop) can't un-fence the
|
|
62
|
+
// profile and let a late hub invoke spawn a transient Chromium. Last-write-wins.
|
|
63
|
+
stopSeq = new Map();
|
|
64
|
+
// Last lifecycle_generation observed for a profile (from a lifecycle event / reconcile
|
|
65
|
+
// snapshot). A hub invoke CAPTURES this at the pool entry (enqueue) so its `running`
|
|
66
|
+
// report carries the generation it was issued under — a later stop bumps the server's
|
|
67
|
+
// generation, so that captured (older) generation is then rejected as stale (PR #1650).
|
|
68
|
+
generations = new Map();
|
|
69
|
+
// The generation of the op CURRENTLY running for a profile, set at op start (the value
|
|
70
|
+
// captured at enqueue) and read by the manager-report wrapper so manager-driven status
|
|
71
|
+
// reports carry it. Per-profile ops are serialized, so this is stable for the op's
|
|
72
|
+
// duration — it is NOT a live read of `generations` (which a concurrent stop would move).
|
|
73
|
+
opGeneration = new Map();
|
|
74
|
+
// Set by stop() and NEVER reset (stop() is terminal — the supervisor discards
|
|
75
|
+
// the pool afterward). manager() refuses to create/return a runtime while true,
|
|
76
|
+
// so no queued or late op can spawn a manager that escapes teardown (the maps
|
|
77
|
+
// are cleared mid-stop and a queued op's closure may run after stop() returns).
|
|
78
|
+
stopping = false;
|
|
79
|
+
constructor(opts) {
|
|
80
|
+
this.opts = opts;
|
|
81
|
+
mkdirSync(this.opts.baseHomeDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
// All per-profile entrypoints go through enqueue() — it both serializes against
|
|
84
|
+
// concurrent lifecycle ops and turns a synchronous reject from manager() (e.g.
|
|
85
|
+
// an unsafe profile id) into a rejected promise, honoring the BrowserInvoker
|
|
86
|
+
// `Promise<…>` contract.
|
|
87
|
+
/** Inbound hub browser invoke for a profile — routed to that profile's own daemon. */
|
|
88
|
+
invoke(request) {
|
|
89
|
+
// Capture the profile's generation at the POOL ENTRY (enqueue), not when the op
|
|
90
|
+
// runs: if a stop arrives while this invoke waits in the queue, its captured
|
|
91
|
+
// (older) generation makes the server reject the `running` report as stale.
|
|
92
|
+
const generation = this.generations.get(request.profileId);
|
|
93
|
+
return this.enqueue(request.profileId, () => this.runWithGeneration(request.profileId, generation, () => {
|
|
94
|
+
// Reject a late invoke for a profile that was explicitly stopped/reset — do
|
|
95
|
+
// not resurrect it (see the `stopped` fence). An explicit open / ensureRuntime
|
|
96
|
+
// / viewer takeover clears the fence first. NOTE: a hub invoke reaches the pool
|
|
97
|
+
// directly while a lifecycle open may still be in the supervisor queue, so an
|
|
98
|
+
// invoke racing an in-flight open can hit the fence and be rejected here even
|
|
99
|
+
// though that open will clear it on its next turn — the rejection is transient
|
|
100
|
+
// (the hub retries next cycle), not a lost command.
|
|
101
|
+
if (this.stopped.has(request.profileId)) {
|
|
102
|
+
throw new Error(`browser profile ${request.profileId} is stopped; open it before invoking`);
|
|
103
|
+
}
|
|
104
|
+
// Pin account === profileId at the pool boundary. Each profile has its OWN
|
|
105
|
+
// Chromium hosting exactly one bb-browser account (the profileId), so a
|
|
106
|
+
// mismatched `account` in the request must never reach the manager — it would
|
|
107
|
+
// address a non-existent account and bb-browser's account-blind routing would
|
|
108
|
+
// fall through to the global current tab (the very leak this pool fixes).
|
|
109
|
+
return this.manager(request.profileId).invoke({ ...request, account: request.profileId });
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
// Set opGeneration for the duration of a serialized op so manager-driven status
|
|
113
|
+
// reports carry the captured generation, then clear it. `gen` is captured at enqueue
|
|
114
|
+
// (invoke) or passed from the lifecycle event / reconcile snapshot (open/ensure/stop/
|
|
115
|
+
// reset). Per-profile ops are serialized, so opGeneration is stable for the op.
|
|
116
|
+
async runWithGeneration(profileId, gen, fn) {
|
|
117
|
+
if (gen !== undefined)
|
|
118
|
+
this.opGeneration.set(profileId, gen);
|
|
119
|
+
try {
|
|
120
|
+
return await fn();
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
if (gen !== undefined)
|
|
124
|
+
this.opGeneration.delete(profileId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Reconcile-time: ensure the profile's runtime (account + tab) is live. If
|
|
129
|
+
* `sinceSeq` (the fence generation observed when this op was enqueued) is given
|
|
130
|
+
* and a newer stop has been observed since, skip — a stale reconcile/open queued
|
|
131
|
+
* before a stop must not un-fence and revive a profile about to be torn down
|
|
132
|
+
* (the pending stop wins; last-write-wins).
|
|
133
|
+
*/
|
|
134
|
+
ensureRuntime(profileId, sinceSeq, generation, opts = {}) {
|
|
135
|
+
return this.enqueue(profileId, () => this.runWithGeneration(profileId, generation, async () => {
|
|
136
|
+
if (this.isStaleRevive(profileId, sinceSeq))
|
|
137
|
+
return;
|
|
138
|
+
if (generation !== undefined)
|
|
139
|
+
this.generations.set(profileId, generation);
|
|
140
|
+
this.stopped.delete(profileId); // reconcile = server desires it running
|
|
141
|
+
// forceStatusReport: pending-row reconnect recovery must re-ACK `running` even if
|
|
142
|
+
// the dedup marker already says running (else the server stays stuck at pending).
|
|
143
|
+
await this.manager(profileId).ensureRuntime(profileId, opts);
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
/** Lifecycle `open` — open (or reuse) the profile's start tab. `sinceSeq`: see ensureRuntime. */
|
|
147
|
+
openProfile(profileId, startUrl, sinceSeq, generation) {
|
|
148
|
+
return this.enqueue(profileId, () => this.runWithGeneration(profileId, generation, async () => {
|
|
149
|
+
if (this.isStaleRevive(profileId, sinceSeq))
|
|
150
|
+
return;
|
|
151
|
+
if (generation !== undefined)
|
|
152
|
+
this.generations.set(profileId, generation);
|
|
153
|
+
this.stopped.delete(profileId); // explicit open revives a stopped profile
|
|
154
|
+
await this.manager(profileId).openProfile(profileId, startUrl);
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
/** True if a newer stop was observed after this revive captured `sinceSeq`. */
|
|
158
|
+
isStaleRevive(profileId, sinceSeq) {
|
|
159
|
+
if (sinceSeq === undefined || this.stopSeqOf(profileId) === sinceSeq)
|
|
160
|
+
return false;
|
|
161
|
+
this.opts.log.info(`browser profile ${profileId}: skipping stale revive (newer stop observed since enqueue)`);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/** Live-viewer control command for a profile — routed to its own daemon/streamer. */
|
|
165
|
+
handleViewerCommand(profileId, sessionId, command, input, turn) {
|
|
166
|
+
return this.enqueue(profileId, () => {
|
|
167
|
+
// Reject viewer commands on a fenced (stopped/reset) profile. A stale
|
|
168
|
+
// command — queued to the machine before the stop, delivered after — is
|
|
169
|
+
// indistinguishable from a fresh takeover here, and the server has already
|
|
170
|
+
// moved the profile to stopped; reviving it would resurrect a Chromium with
|
|
171
|
+
// the server still reading stopped. A real takeover goes through an explicit
|
|
172
|
+
// lifecycle reopen (open), which clears the fence and updates server status.
|
|
173
|
+
if (this.stopped.has(profileId)) {
|
|
174
|
+
throw new Error(`browser profile ${profileId} is stopped; reopen it before viewing`);
|
|
175
|
+
}
|
|
176
|
+
return this.manager(profileId).handleViewerCommand(profileId, sessionId, command, input, turn);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Lifecycle `stop` (explicit, owner-driven) — stop the profile's viewer + tear
|
|
181
|
+
* down its bb-browser daemon and Chromium to free the resources (an idle profile
|
|
182
|
+
* must not keep a browser running). Confirms `stopped` back to the server: the
|
|
183
|
+
* stop API already set the server row to `stopped`, and this report keeps the
|
|
184
|
+
* daemon authoritative over the actual runtime state (the no-runtime case still
|
|
185
|
+
* confirms). For reconcile-driven cleanup of profiles the server no longer owns
|
|
186
|
+
* here, use {@link releaseRuntime} — it must NOT report.
|
|
187
|
+
*/
|
|
188
|
+
stopProfile(profileId, generation) {
|
|
189
|
+
return this.enqueue(profileId, () => this.runWithGeneration(profileId, generation, async () => {
|
|
190
|
+
// Already fenced by the WS pre-fence (fence() bumped the generation for this
|
|
191
|
+
// event); only ensure the fence here — do NOT bump again, or a newer open
|
|
192
|
+
// that captured its seq AFTER the pre-fence would look stale and be dropped.
|
|
193
|
+
this.stopped.add(profileId);
|
|
194
|
+
if (generation !== undefined)
|
|
195
|
+
this.generations.set(profileId, generation);
|
|
196
|
+
const manager = this.managers.get(profileId);
|
|
197
|
+
if (!manager) {
|
|
198
|
+
// Direct report (no manager wrapper to inject the generation) — pass it.
|
|
199
|
+
this.opts.reportStatus?.(profileId, 'stopped', undefined, generation);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await manager.stopProfile(profileId); // reports `stopped` (via wrapper), kills viewer
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await this.dispose(profileId, manager); // release the bb-browser daemon + Chromium
|
|
207
|
+
}
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Reconcile teardown (negative convergence) — release a local runtime the server
|
|
212
|
+
* no longer lists as live on this machine (stopped / deleted / reassigned).
|
|
213
|
+
* Fences + disposes like a stop, but does NOT report status: here the server is
|
|
214
|
+
* the desired-state SSOT, so a daemon-side `stopped` push is semantically wrong
|
|
215
|
+
* (for a profile reassigned to another machine it isn't ours to report — the
|
|
216
|
+
* server's machine-binding check rejects it as 404 anyway) and only emits
|
|
217
|
+
* spurious calls. Local-only cleanup.
|
|
218
|
+
*/
|
|
219
|
+
releaseRuntime(profileId) {
|
|
220
|
+
return this.enqueue(profileId, async () => {
|
|
221
|
+
// Pre-fenced by the reconcile at enqueue (fence() bumped the generation);
|
|
222
|
+
// only ensure the fence here — don't bump, or a reopen requested after the
|
|
223
|
+
// reconcile decision but before this runs would be dropped as stale.
|
|
224
|
+
this.stopped.add(profileId);
|
|
225
|
+
const manager = this.managers.get(profileId);
|
|
226
|
+
if (manager)
|
|
227
|
+
await this.dispose(profileId, manager);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Synchronously fence a profile (reject late invoke/viewer until an explicit
|
|
232
|
+
* reopen) WITHOUT tearing anything down. The supervisor calls this the moment a
|
|
233
|
+
* stop/reset lifecycle event is received, before the queued stop/reset op runs:
|
|
234
|
+
* a hub invoke — which bypasses the supervisor queue and hits {@link invoke}
|
|
235
|
+
* directly — authorized just before the server row flipped to `stopped` could
|
|
236
|
+
* otherwise arrive in that window and spawn a transient Chromium the stop then
|
|
237
|
+
* immediately tears down. Fencing now rejects it instead. An in-flight invoke
|
|
238
|
+
* already past the fence check is unaffected (the queued stop serializes behind
|
|
239
|
+
* it); open/ensure clears the fence. Idempotent; no-op once stopping.
|
|
240
|
+
*/
|
|
241
|
+
fence(profileId) {
|
|
242
|
+
if (this.stopping)
|
|
243
|
+
return;
|
|
244
|
+
this.stopped.add(profileId);
|
|
245
|
+
// SINGLE generation-bump point. Every path that decides to stop/reset/release
|
|
246
|
+
// a profile calls fence() the moment it OBSERVES the intent (WS event receipt /
|
|
247
|
+
// reconcile enqueue) — synchronously, before queuing the teardown op — so a
|
|
248
|
+
// revive (open/ensureRuntime) that captures the generation afterward sees the
|
|
249
|
+
// new value and isn't dropped as stale. Queued teardown ops only ensure the
|
|
250
|
+
// fence (stopped.add); they never bump, or a reopen requested between observe
|
|
251
|
+
// and execute would be stranded at stopped.
|
|
252
|
+
this.stopSeq.set(profileId, (this.stopSeq.get(profileId) ?? 0) + 1);
|
|
253
|
+
}
|
|
254
|
+
/** The current fence generation for a profile — captured by a revive at enqueue. */
|
|
255
|
+
stopSeqOf(profileId) {
|
|
256
|
+
return this.stopSeq.get(profileId) ?? 0;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Lifecycle `reset` — wipe the profile's persisted state (cookies, localStorage,
|
|
260
|
+
* IndexedDB, cache) for a clean slate. Removes BOTH the per-profile home AND any
|
|
261
|
+
* unmigrated legacy flat cookie file — a reset done before the profile's first
|
|
262
|
+
* post-upgrade open would otherwise leave the legacy file in place, and the lazy
|
|
263
|
+
* migration would re-import the old login on the next open, silently undoing the
|
|
264
|
+
* reset. Done AFTER the daemon is stopped so Chromium has released its files.
|
|
265
|
+
* Isolated by construction — it can never touch another profile's state.
|
|
266
|
+
* Fail-closed: if the wipe fails (e.g. Chromium still holds a file, or a
|
|
267
|
+
* permission error), report `error` and throw rather than report `stopped` —
|
|
268
|
+
* a reset that left state on disk must not be marked clean, since the next open
|
|
269
|
+
* could restore the old login. Reports `stopped` only on a confirmed wipe.
|
|
270
|
+
*/
|
|
271
|
+
resetProfile(profileId, generation, resetGeneration) {
|
|
272
|
+
return this.enqueue(profileId, () => this.runWithGeneration(profileId, generation, async () => {
|
|
273
|
+
if (generation !== undefined)
|
|
274
|
+
this.generations.set(profileId, generation);
|
|
275
|
+
try {
|
|
276
|
+
// Wipe + record the applied reset_generation (so a reconnect won't re-wipe).
|
|
277
|
+
await this.doResetWipe(profileId, resetGeneration);
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
281
|
+
this.opts.reportStatus?.(profileId, 'error', message, generation);
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
this.opts.reportStatus?.(profileId, 'stopped', undefined, generation);
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Apply a reset the daemon hasn't yet (caught on reconnect when the server's
|
|
289
|
+
* reset_generation is newer than this daemon's recorded one). Same wipe as
|
|
290
|
+
* resetProfile but WITHOUT a status report — the caller drives desired state (an
|
|
291
|
+
* open/ensure reports running for a still-desired profile; a stopped profile is
|
|
292
|
+
* left stopped). Must run BEFORE any open/ensure for the profile so a reopen starts
|
|
293
|
+
* from clean state, never the old cookies (the supervisor enqueues it first).
|
|
294
|
+
*/
|
|
295
|
+
applyResetWipe(profileId, resetGeneration) {
|
|
296
|
+
return this.enqueue(profileId, () => this.doResetWipe(profileId, resetGeneration));
|
|
297
|
+
}
|
|
298
|
+
// Core reset wipe (runs inside an enqueue closure): fence, dispose the runtime, wipe
|
|
299
|
+
// the home, THEN record the applied reset_generation — strictly in that order, so the
|
|
300
|
+
// sidecar is written only after a successful wipe (a crash in between just re-wipes,
|
|
301
|
+
// idempotent). removeProfileState throws on failure → reset_generation NOT recorded.
|
|
302
|
+
async doResetWipe(profileId, resetGeneration) {
|
|
303
|
+
this.stopped.add(profileId); // fence late invokes until an explicit reopen
|
|
304
|
+
const manager = this.managers.get(profileId);
|
|
305
|
+
if (manager)
|
|
306
|
+
await this.dispose(profileId, manager);
|
|
307
|
+
this.removeProfileState(profileId);
|
|
308
|
+
if (resetGeneration !== undefined)
|
|
309
|
+
this.writeResetGeneration(profileId, resetGeneration);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Reconnect cleanup for a profile the server no longer lists AT ALL (deleted or
|
|
313
|
+
* reassigned away): dispose any runtime and wipe the per-profile home — the old host
|
|
314
|
+
* must not keep its cookies/storage. NO status report (the server is the SSOT and the
|
|
315
|
+
* row is gone there); fences too. Idempotent (a wiped home simply isn't found again).
|
|
316
|
+
*/
|
|
317
|
+
wipeAbsent(profileId) {
|
|
318
|
+
return this.enqueue(profileId, async () => {
|
|
319
|
+
this.stopped.add(profileId);
|
|
320
|
+
const manager = this.managers.get(profileId);
|
|
321
|
+
if (manager)
|
|
322
|
+
await this.dispose(profileId, manager);
|
|
323
|
+
this.removeProfileState(profileId);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Tear down every profile's daemon (supervisor shutdown). TERMINAL: the
|
|
328
|
+
* supervisor discards the pool after this, so `stopping` stays set forever —
|
|
329
|
+
* manager() refuses any further op. Drain outstanding per-profile chains BEFORE
|
|
330
|
+
* clearing the maps, so a queued op whose `prev` settles only after stop()
|
|
331
|
+
* returns can't slip through manager() and recreate a runtime (orphaning a
|
|
332
|
+
* Chromium past shutdown); `stopping` is the backstop if one still races in.
|
|
333
|
+
*/
|
|
334
|
+
async stop() {
|
|
335
|
+
this.stopping = true;
|
|
336
|
+
await Promise.allSettled([...this.opChains.values()]);
|
|
337
|
+
const managers = [...this.managers.values()];
|
|
338
|
+
this.managers.clear();
|
|
339
|
+
this.opChains.clear();
|
|
340
|
+
this.stopped.clear();
|
|
341
|
+
// allSettled (not all): one manager's stop() throwing must not skip tearing
|
|
342
|
+
// down the rest — every Chromium has to be released. (Bounded: each stop()
|
|
343
|
+
// awaits waitForChildExit, which has a hard cap.)
|
|
344
|
+
await Promise.allSettled(managers.map((m) => m.stop()));
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Registry view: profile ids that currently hold a live local runtime. The
|
|
348
|
+
* supervisor (the desired-state coordinator) diffs this against the server's
|
|
349
|
+
* desired-live set on full reconcile and stops the extras via stopProfile() —
|
|
350
|
+
* the convergence policy lives there, not here; the pool only owns runtime
|
|
351
|
+
* lifecycle mechanics.
|
|
352
|
+
*/
|
|
353
|
+
activeProfileIds() {
|
|
354
|
+
return [...this.managers.keys()];
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Run `op` after every prior operation for this profile settles — a per-profile
|
|
358
|
+
* serialization so invoke / open / stop / reset / viewer never interleave for
|
|
359
|
+
* one profile (different profiles still run in parallel). Bounded: bb-browser
|
|
360
|
+
* commands time out (~30s), so a stop never waits forever behind a hung invoke.
|
|
361
|
+
* Also normalizes a synchronous throw from `op` into a rejected promise.
|
|
362
|
+
*
|
|
363
|
+
* QUEUE CONTRACT. This is the FINAL per-profile mutex — EVERY pool entrypoint
|
|
364
|
+
* (invoke, open, ensure, stop, reset, viewer) goes through it, so it alone
|
|
365
|
+
* guarantees per-profile mutual exclusion. The supervisor's separate
|
|
366
|
+
* `enqueueBrowserProfileOp` is a higher-level ORDERING layer for the ops it
|
|
367
|
+
* originates (WS lifecycle/viewer events + reconcile), not a second mutex. Hub
|
|
368
|
+
* invokes arrive from the clip runtime and do NOT pass through the supervisor
|
|
369
|
+
* queue — only this one. So: do not assume "all profile ops share one queue";
|
|
370
|
+
* they share THIS one.
|
|
371
|
+
*/
|
|
372
|
+
enqueue(profileId, op) {
|
|
373
|
+
// Reject new work once teardown has begun, so a stop-time invoke/open/viewer
|
|
374
|
+
// never queues or spawns a runtime (the manager() guard backstops any op
|
|
375
|
+
// already queued whose closure runs after the maps are cleared).
|
|
376
|
+
if (this.stopping) {
|
|
377
|
+
return Promise.reject(new Error('browser profile pool is stopping'));
|
|
378
|
+
}
|
|
379
|
+
const prev = this.opChains.get(profileId) ?? Promise.resolve();
|
|
380
|
+
const run = prev.then(op, op); // run op whether the prior op fulfilled or rejected
|
|
381
|
+
this.opChains.set(profileId, run);
|
|
382
|
+
void run
|
|
383
|
+
.catch(() => { })
|
|
384
|
+
.finally(() => {
|
|
385
|
+
if (this.opChains.get(profileId) === run)
|
|
386
|
+
this.opChains.delete(profileId);
|
|
387
|
+
});
|
|
388
|
+
return run;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get (or lazily create) the dedicated manager for a profile. Synchronous so the
|
|
392
|
+
* get-or-create is atomic — the bb-browser daemon itself starts lazily on first
|
|
393
|
+
* command, not here.
|
|
394
|
+
*/
|
|
395
|
+
manager(profileId) {
|
|
396
|
+
if (!profileId)
|
|
397
|
+
throw new Error('browser profile id is required');
|
|
398
|
+
// During teardown, refuse any op so it can't spawn/use a manager that would
|
|
399
|
+
// escape stop() (which clears the maps mid-flight). See `stopping`.
|
|
400
|
+
if (this.stopping)
|
|
401
|
+
throw new Error('browser profile pool is stopping');
|
|
402
|
+
let manager = this.managers.get(profileId);
|
|
403
|
+
if (manager)
|
|
404
|
+
return manager;
|
|
405
|
+
this.migrateFlatProfileState(profileId);
|
|
406
|
+
const create = this.opts.createManager ?? ((opts) => new BrowserProfileManager(opts));
|
|
407
|
+
manager = create({
|
|
408
|
+
homeDir: this.profileHome(profileId),
|
|
409
|
+
log: this.opts.log,
|
|
410
|
+
// The manager reports with 3 args; inject the current op's generation so the
|
|
411
|
+
// server can fence stale reports. opGeneration is set for the duration of each
|
|
412
|
+
// serialized op (see runWithGeneration), so this reads THIS op's captured
|
|
413
|
+
// generation, not a live value a concurrent stop could have moved.
|
|
414
|
+
reportStatus: (id, status, errorMsg) => this.opts.reportStatus?.(id, status, errorMsg, this.opGeneration.get(id)),
|
|
415
|
+
// Thread the per-profile outbound proxy resolver through to this profile's own
|
|
416
|
+
// manager, so each isolated Chromium binds its own egress proxy at
|
|
417
|
+
// account_create (the resolver is keyed by profileId === account).
|
|
418
|
+
resolveProxy: this.opts.resolveProxy,
|
|
419
|
+
});
|
|
420
|
+
this.managers.set(profileId, manager);
|
|
421
|
+
return manager;
|
|
422
|
+
}
|
|
423
|
+
/** Stop a manager and drop it from the map (identity-checked so a concurrent
|
|
424
|
+
* recreate during the async stop is never evicted). */
|
|
425
|
+
async dispose(profileId, manager) {
|
|
426
|
+
try {
|
|
427
|
+
await manager.stop();
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
if (this.managers.get(profileId) === manager)
|
|
431
|
+
this.managers.delete(profileId);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
profileHome(profileId) {
|
|
435
|
+
return path.join(this.opts.baseHomeDir, sanitizeProfileId(profileId));
|
|
436
|
+
}
|
|
437
|
+
/** Legacy shared-layout cookie file for a profile (pre per-profile homes). */
|
|
438
|
+
legacyAccountFile(profileId) {
|
|
439
|
+
return path.join(this.opts.baseHomeDir, 'accounts', `${sanitizeProfileId(profileId)}.json`);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Remove every on-disk trace of a profile: its per-profile home AND any
|
|
443
|
+
* unmigrated legacy flat cookie file — otherwise a later open would re-import
|
|
444
|
+
* the legacy cookies (see resetProfile). Throws if any target could not be
|
|
445
|
+
* removed so the caller can fail the reset closed rather than report a clean
|
|
446
|
+
* slate while state survives on disk.
|
|
447
|
+
*/
|
|
448
|
+
removeProfileState(profileId) {
|
|
449
|
+
const failures = [];
|
|
450
|
+
for (const target of [this.profileHome(profileId), this.legacyAccountFile(profileId)]) {
|
|
451
|
+
try {
|
|
452
|
+
rmSync(target, { recursive: true, force: true });
|
|
453
|
+
}
|
|
454
|
+
catch (err) {
|
|
455
|
+
failures.push(`${target}: ${String(err)}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (failures.length > 0) {
|
|
459
|
+
throw new Error(`could not remove profile state for ${profileId} — ${failures.join('; ')}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// --- per-profile reset sidecar: the last reset_generation this daemon APPLIED ---
|
|
463
|
+
// A tiny JSON file inside the profile home, so removeProfileState() clears it with
|
|
464
|
+
// the rest of the state. On reconnect the reconcile compares the server's
|
|
465
|
+
// reset_generation to this value: a newer server value means a reset was missed
|
|
466
|
+
// while offline → wipe. Missing/corrupt reads as 0 (so any server reset wins).
|
|
467
|
+
metaFile(profileId) {
|
|
468
|
+
return path.join(this.profileHome(profileId), '.parall-profile-meta.json');
|
|
469
|
+
}
|
|
470
|
+
/** The reset_generation this daemon has applied for the profile (0 if unknown). */
|
|
471
|
+
appliedResetGeneration(profileId) {
|
|
472
|
+
try {
|
|
473
|
+
const parsed = JSON.parse(readFileSync(this.metaFile(profileId), 'utf8'));
|
|
474
|
+
const gen = parsed.reset_generation;
|
|
475
|
+
return typeof gen === 'number' && Number.isFinite(gen) ? gen : 0;
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return 0; // missing or corrupt → treat as never-applied (re-wipe if server is newer)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Record the applied reset_generation atomically (temp file + rename). Called ONLY
|
|
482
|
+
// after a successful wipe, so a crash before the rename just leaves the daemon to
|
|
483
|
+
// re-wipe on the next reconcile (idempotent) rather than skipping a real reset.
|
|
484
|
+
writeResetGeneration(profileId, generation) {
|
|
485
|
+
const home = this.profileHome(profileId);
|
|
486
|
+
mkdirSync(home, { recursive: true });
|
|
487
|
+
const tmp = path.join(home, `.parall-profile-meta.json.tmp-${process.pid}`);
|
|
488
|
+
writeFileSync(tmp, JSON.stringify({ reset_generation: generation }));
|
|
489
|
+
renameSync(tmp, this.metaFile(profileId));
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Profile ids that have ANY local state on this host — the reconcile's inventory for
|
|
493
|
+
* finding deleted/reassigned profiles whose residue must be wiped. Union of the two
|
|
494
|
+
* storage layouts this pool owns:
|
|
495
|
+
* - per-profile home dirs `<baseHomeDir>/brp_*`
|
|
496
|
+
* - legacy flat account files `<baseHomeDir>/accounts/brp_*.json` (pre per-profile
|
|
497
|
+
* homes; a profile deleted before its first post-upgrade reopen has ONLY this file
|
|
498
|
+
* and no home dir, so home-only enumeration would miss its login residue)
|
|
499
|
+
* Only names passing the strict brp_ check are returned, and the two layouts are
|
|
500
|
+
* deduped, so reconnect cleanup never touches the `accounts/` dir itself, a hidden
|
|
501
|
+
* dir, `legacy.json`, or any non-profile entry. removeProfileState wipes both layouts.
|
|
502
|
+
*/
|
|
503
|
+
localStateProfileIds() {
|
|
504
|
+
const ids = new Set();
|
|
505
|
+
try {
|
|
506
|
+
for (const e of readdirSync(this.opts.baseHomeDir, { withFileTypes: true })) {
|
|
507
|
+
if (e.isDirectory() && isSafeProfileId(e.name))
|
|
508
|
+
ids.add(e.name);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
// baseHomeDir missing → no per-profile home state
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const accountsDir = path.join(this.opts.baseHomeDir, 'accounts');
|
|
516
|
+
for (const e of readdirSync(accountsDir, { withFileTypes: true })) {
|
|
517
|
+
if (!e.isFile() || !e.name.endsWith('.json'))
|
|
518
|
+
continue;
|
|
519
|
+
const id = e.name.slice(0, -'.json'.length);
|
|
520
|
+
if (isSafeProfileId(id))
|
|
521
|
+
ids.add(id);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// no legacy accounts/ dir → no flat account state
|
|
526
|
+
}
|
|
527
|
+
return [...ids];
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* One-time, lazy migration from the legacy shared layout. Before per-profile
|
|
531
|
+
* homes, all profiles shared one bb-browser home and bb-browser persisted each
|
|
532
|
+
* profile's cookies to `<baseHomeDir>/accounts/<profileId>.json` (account name =
|
|
533
|
+
* profileId). Move that file into the profile's own `accounts/` dir on first use
|
|
534
|
+
* so existing cookie logins survive the upgrade instead of forcing a re-login.
|
|
535
|
+
* Only cookies are migrated — the shared `chrome-data/` was a single comingled
|
|
536
|
+
* Chromium profile that cannot be split per profile, and bb-browser never
|
|
537
|
+
* persisted localStorage/IndexedDB across restarts anyway.
|
|
538
|
+
*/
|
|
539
|
+
migrateFlatProfileState(profileId) {
|
|
540
|
+
const legacy = this.legacyAccountFile(profileId);
|
|
541
|
+
if (!existsSync(legacy))
|
|
542
|
+
return;
|
|
543
|
+
const destDir = path.join(this.profileHome(profileId), 'accounts');
|
|
544
|
+
const dest = path.join(destDir, path.basename(legacy));
|
|
545
|
+
if (existsSync(dest))
|
|
546
|
+
return; // already migrated (or a fresh per-profile file exists)
|
|
547
|
+
try {
|
|
548
|
+
mkdirSync(destDir, { recursive: true });
|
|
549
|
+
renameSync(legacy, dest);
|
|
550
|
+
this.opts.log.info(`[bb-browser] migrated cookie state for ${profileId} to per-profile home`);
|
|
551
|
+
}
|
|
552
|
+
catch (err) {
|
|
553
|
+
// error-level (not warn): a visible data-loss event — the profile's
|
|
554
|
+
// pre-upgrade login is lost and it starts fresh (re-login required). Stays
|
|
555
|
+
// fail-open (the profile still works); we do NOT reportStatus('error'), which
|
|
556
|
+
// has no "running-but-degraded" state and would wrongly block a working
|
|
557
|
+
// profile — the operator-visible signal is this log.
|
|
558
|
+
this.opts.log.error(`[bb-browser] cookie-state migration FAILED for ${profileId}; profile will start without its prior login (re-login required): ${String(err)}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Validate a profile id as the real browser-profile form (`brp_` + nanoid,
|
|
564
|
+
* alphabet `[A-Za-z0-9_-]`) and return it unchanged. Require the `brp_` prefix, not
|
|
565
|
+
* just a safe charset: a bare safe name like `accounts` would make
|
|
566
|
+
* `profileHome('accounts')` collide with the legacy flat `<baseHomeDir>/accounts`
|
|
567
|
+
* cookie dir, and folding (e.g. `a/b` → `a_b`) could alias two distinct ids onto
|
|
568
|
+
* one home while `managers` keys them separately — both silently collapse the very
|
|
569
|
+
* isolation this pool exists to enforce. Anything else is **rejected, not
|
|
570
|
+
* rewritten** (empty / `.` / `..` / path separators / reserved names all fail).
|
|
571
|
+
*/
|
|
572
|
+
/** True for a server-generated browser profile id (`brp_` + nanoid alphabet). */
|
|
573
|
+
function isSafeProfileId(id) {
|
|
574
|
+
return /^brp_[A-Za-z0-9_-]+$/.test(id);
|
|
575
|
+
}
|
|
576
|
+
function sanitizeProfileId(profileId) {
|
|
577
|
+
if (!isSafeProfileId(profileId)) {
|
|
578
|
+
throw new Error(`unsafe browser profile id: ${JSON.stringify(profileId)}`);
|
|
579
|
+
}
|
|
580
|
+
return profileId;
|
|
581
|
+
}
|
|
@@ -4,6 +4,7 @@ export { type IpcMessage, type IpcManifest, type ListClipInfo, type ListCommandI
|
|
|
4
4
|
export { type ClipConfig, type ManifestCache, type CommandDetail, type ClipJson, } from './manifest.js';
|
|
5
5
|
export { ClipProvider, type ClipProviderOptions } from './clip-provider.js';
|
|
6
6
|
export { HubClient, HubCommandError, type HubClientOptions, type ClipBinding, } from './hub-client.js';
|
|
7
|
-
export { BrowserProfileManager } from './browser-profile-manager.js';
|
|
7
|
+
export { BrowserProfileManager, type BrowserInvoker, type BrowserInvokeRequest, type BrowserProxyConfig, } from './browser-profile-manager.js';
|
|
8
|
+
export { BrowserProfilePool, type BrowserProfilePoolOptions } from './browser-profile-pool.js';
|
|
8
9
|
export { installClip, removeClip, parseSource, type InstallOptions, type InstallResult, } from './clip-installer.js';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,SAAS,EACT,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,SAAS,EACT,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
|
@@ -3,5 +3,6 @@ export { ClipProcess, ClipCommandError, } from './process.js';
|
|
|
3
3
|
export { MessageType, NdjsonReader, NdjsonWriter, } from './ipc.js';
|
|
4
4
|
export { ClipProvider } from './clip-provider.js';
|
|
5
5
|
export { HubClient, HubCommandError, } from './hub-client.js';
|
|
6
|
-
export { BrowserProfileManager } from './browser-profile-manager.js';
|
|
6
|
+
export { BrowserProfileManager, } from './browser-profile-manager.js';
|
|
7
|
+
export { BrowserProfilePool } from './browser-profile-pool.js';
|
|
7
8
|
export { installClip, removeClip, parseSource, } from './clip-installer.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ClipInvokeContext, type ClipProcessStatus } from './process.js';
|
|
2
2
|
import type { ClipConfig, ManifestCache } from './manifest.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { BrowserInvoker } from './browser-profile-manager.js';
|
|
4
4
|
import { type HubClient } from './hub-client.js';
|
|
5
5
|
export interface ClipProcessManagerOptions {
|
|
6
6
|
bunPath?: string;
|
|
@@ -10,9 +10,11 @@ export interface ClipProcessManagerOptions {
|
|
|
10
10
|
* Host-side browser runtime. Present only on daemons that can host a
|
|
11
11
|
* BrowserProfile; drives inbound hub browser InvokeCommands. The execution
|
|
12
12
|
* side never calls this directly — nested browser invokes go out through the
|
|
13
|
-
* hub (see hubClient).
|
|
13
|
+
* hub (see hubClient). Typed as the narrow {@link BrowserInvoker} so either a
|
|
14
|
+
* single BrowserProfileManager (hosted browser pod) or a BrowserProfilePool
|
|
15
|
+
* (BYOC supervisor, one bb-browser per profile) can back it.
|
|
14
16
|
*/
|
|
15
|
-
browserProfileManager?:
|
|
17
|
+
browserProfileManager?: BrowserInvoker;
|
|
16
18
|
/**
|
|
17
19
|
* Lazily-supplied hub client used by the execution side to resolve a
|
|
18
20
|
* dependency binding (GetBindings) and forward the dependency invoke
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAEvB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/process-manager.ts"],"names":[],"mappings":"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"}
|