@parall/daemon 1.45.0 → 1.46.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 +15 -15
- package/bundle/parall-browser-pod.js +29726 -375
- package/bundle/parall-channel-exec.js +2 -0
- package/bundle/parall-claude-agent.js +26091 -273
- package/bundle/parall-codex-agent.js +26650 -331
- package/bundle/parall-daemon.js +31384 -1998
- package/bundle/parall-openclaw-agent.js +1 -0
- package/dist/browser-pod.d.ts +23 -1
- package/dist/browser-pod.d.ts.map +1 -1
- package/dist/browser-pod.js +104 -34
- package/dist/browser-profile-reconcile.d.ts +21 -0
- package/dist/browser-profile-reconcile.d.ts.map +1 -0
- package/dist/browser-profile-reconcile.js +188 -0
- package/dist/clip-runtime/browser-cdp.d.ts +40 -0
- package/dist/clip-runtime/browser-cdp.d.ts.map +1 -0
- package/dist/clip-runtime/browser-cdp.js +218 -0
- package/dist/clip-runtime/browser-profile-manager.d.ts +97 -24
- package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
- package/dist/clip-runtime/browser-profile-manager.js +316 -182
- package/dist/clip-runtime/browser-profile-pool.d.ts +3 -0
- package/dist/clip-runtime/browser-profile-pool.d.ts.map +1 -1
- package/dist/clip-runtime/browser-profile-pool.js +18 -1
- package/dist/clip-runtime/browser-proxy-reconcile.d.ts +77 -0
- package/dist/clip-runtime/browser-proxy-reconcile.d.ts.map +1 -0
- package/dist/clip-runtime/browser-proxy-reconcile.js +139 -0
- package/dist/clip-runtime/browser-proxy-state.d.ts +55 -0
- package/dist/clip-runtime/browser-proxy-state.d.ts.map +1 -0
- package/dist/clip-runtime/browser-proxy-state.js +149 -0
- package/dist/clip-runtime/browser-quiescence.d.ts +71 -0
- package/dist/clip-runtime/browser-quiescence.d.ts.map +1 -0
- package/dist/clip-runtime/browser-quiescence.js +136 -0
- package/dist/clip-runtime/browser-readiness.d.ts +64 -0
- package/dist/clip-runtime/browser-readiness.d.ts.map +1 -0
- package/dist/clip-runtime/browser-readiness.js +161 -0
- package/dist/clip-runtime/browser-state-store.d.ts +13 -2
- package/dist/clip-runtime/browser-state-store.d.ts.map +1 -1
- package/dist/clip-runtime/browser-state-store.js +15 -6
- package/dist/clip-runtime/browser-target-registry.d.ts +143 -0
- package/dist/clip-runtime/browser-target-registry.d.ts.map +1 -0
- package/dist/clip-runtime/browser-target-registry.js +297 -0
- package/dist/clip-runtime/browser-viewer-streamer.d.ts +13 -14
- package/dist/clip-runtime/browser-viewer-streamer.d.ts.map +1 -1
- package/dist/clip-runtime/browser-viewer-streamer.js +11 -63
- package/dist/daemon-main.d.ts.map +1 -1
- package/dist/daemon-main.js +3 -1
- package/dist/supervisor.d.ts +6 -0
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +48 -187
- package/package.json +8 -6
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { sleep } from './subprocess.js';
|
|
2
|
+
/**
|
|
3
|
+
* BrowserTargetRegistry — binds every browser command to a page target OWNED by
|
|
4
|
+
* the profile's bb-browser account, so no command can fall through to
|
|
5
|
+
* bb-browser's account-blind global routing.
|
|
6
|
+
*
|
|
7
|
+
* WHY. bb-browser-pro 0.15.0 resolves a tab-less command via
|
|
8
|
+
* `ensurePageTarget(undefined)` → global `currentTargetId` → `targets[0]`
|
|
9
|
+
* (verified in its daemon source; `request.account` is never consulted). Chrome
|
|
10
|
+
* is launched with a literal `about:blank` positional tab in the DEFAULT
|
|
11
|
+
* browser context — no account cookies, no account proxy — and nothing ever
|
|
12
|
+
* closes it, so it IS `targets[0]` on a fresh Chromium. Worse, the daemon's own
|
|
13
|
+
* tab-less `tab_list` warm-up is dispatched through `ensurePageTarget` too and
|
|
14
|
+
* pins the global `currentTargetId` to that blank page. Any agent command sent
|
|
15
|
+
* without an explicit `tabId` (navigate/get/click/screenshot/eval/...) then
|
|
16
|
+
* executes against a cookie-less, proxy-less page — including real-IP egress
|
|
17
|
+
* for proxy-configured profiles. The registry closes this by resolving an
|
|
18
|
+
* OWNED target (per `tab_list`'s per-tab `account` attribution) and stamping
|
|
19
|
+
* its full CDP targetId on every tab-addressed command.
|
|
20
|
+
*
|
|
21
|
+
* Ownership ground truth is bb-browser's own `tab_list` attribution (targetId →
|
|
22
|
+
* account, derived from its BrowserContext bookkeeping), so a recreated tab —
|
|
23
|
+
* `tab_new {account}` routes through `createTabInContext` → `ensureContext` —
|
|
24
|
+
* always lands in the profile's BrowserContext with its cookies re-injected,
|
|
25
|
+
* never the default context.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Thrown when a command's target cannot be bound to a profile-owned page: a
|
|
29
|
+
* caller-supplied ref that is not the profile's own tab (foreign tab, numeric
|
|
30
|
+
* global index, stale id), or an owned page that cannot be (re)created. The
|
|
31
|
+
* command is NEVER retargeted at another page — fail closed.
|
|
32
|
+
*/
|
|
33
|
+
export class BrowserTargetError extends Error {
|
|
34
|
+
code;
|
|
35
|
+
constructor(message, code) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.name = 'BrowserTargetError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class BrowserTargetRegistry {
|
|
42
|
+
host;
|
|
43
|
+
opts;
|
|
44
|
+
// Deliberately STATELESS: ownership is recomputed from bb-browser's live
|
|
45
|
+
// `tab_list` attribution on every resolve. There is no cached binding to go
|
|
46
|
+
// stale, nothing to invalidate on restart/reset, and a resolve can never
|
|
47
|
+
// return a tab that isn't owned RIGHT NOW.
|
|
48
|
+
constructor(host, opts = {}) {
|
|
49
|
+
this.host = host;
|
|
50
|
+
this.opts = opts;
|
|
51
|
+
}
|
|
52
|
+
/** `tab_list` rows owned by the account (per-tab `account` attribution). */
|
|
53
|
+
async ownedTabs(account) {
|
|
54
|
+
const list = await this.host.sendCommand({ method: 'tab_list', account });
|
|
55
|
+
const rows = Array.isArray(list.tabs)
|
|
56
|
+
? list.tabs
|
|
57
|
+
: [];
|
|
58
|
+
const owned = [];
|
|
59
|
+
for (const row of rows) {
|
|
60
|
+
if (row?.account !== account)
|
|
61
|
+
continue;
|
|
62
|
+
const targetId = typeof row.tabId === 'string' && row.tabId ? row.tabId : undefined;
|
|
63
|
+
if (!targetId)
|
|
64
|
+
continue;
|
|
65
|
+
owned.push({
|
|
66
|
+
targetId,
|
|
67
|
+
shortId: row.tab === undefined ? undefined : String(row.tab),
|
|
68
|
+
url: typeof row.url === 'string' ? row.url : undefined,
|
|
69
|
+
active: row.active === true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return owned;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the full CDP targetId a command must run against.
|
|
76
|
+
*
|
|
77
|
+
* With `ref` (caller-supplied `tabId`/`tab`): it must be one of the account's
|
|
78
|
+
* OWN tabs (full targetId or short id) — anything else throws
|
|
79
|
+
* BrowserTargetError. Numeric refs are rejected outright: bb-browser resolves
|
|
80
|
+
* bare numbers as indices into the GLOBAL page-target array, which can address
|
|
81
|
+
* another context's page.
|
|
82
|
+
*
|
|
83
|
+
* Without `ref`: the owned tab Chrome marks active, else the first owned
|
|
84
|
+
* tab, else a fresh `about:blank` created INSIDE the account's
|
|
85
|
+
* BrowserContext (never the default context). Stateless — derived from the
|
|
86
|
+
* live tab_list every time.
|
|
87
|
+
*/
|
|
88
|
+
async resolveCommandTarget(account, ref) {
|
|
89
|
+
if (ref !== undefined && ref !== null && ref !== '') {
|
|
90
|
+
return this.resolveExplicitRef(account, ref);
|
|
91
|
+
}
|
|
92
|
+
const owned = await this.ownedTabs(account);
|
|
93
|
+
const pick = owned.find((t) => t.active) ?? owned[0];
|
|
94
|
+
if (pick)
|
|
95
|
+
return pick.targetId;
|
|
96
|
+
return this.createOwnedTab(account);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate + canonicalize a caller-supplied tab ref against owned tabs only.
|
|
100
|
+
* The ref is matched as a string against owned full/short ids and NEVER
|
|
101
|
+
* forwarded raw — so bb-browser's numeric-index fallback (which indexes into
|
|
102
|
+
* the GLOBAL page array, any context) is unreachable.
|
|
103
|
+
*/
|
|
104
|
+
async resolveExplicitRef(account, ref) {
|
|
105
|
+
const wanted = String(ref);
|
|
106
|
+
const owned = await this.ownedTabs(account);
|
|
107
|
+
const match = owned.find((t) => t.targetId === wanted || t.shortId === wanted);
|
|
108
|
+
if (!match) {
|
|
109
|
+
const indexHint = /^\d+$/.test(wanted)
|
|
110
|
+
? ' (numeric tab indices are not supported — pass the tab id returned by tab_list/tab_new)'
|
|
111
|
+
: '';
|
|
112
|
+
throw new BrowserTargetError(`tab ${wanted} does not belong to browser profile ${account} — commands are bound to the profile's own pages${indexHint}`, 'BROWSER_TARGET_FOREIGN');
|
|
113
|
+
}
|
|
114
|
+
return match.targetId;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Ensure the account owns at least one page tab, returning its targetId. A
|
|
118
|
+
* fresh account (or one whose pages were all closed) gets an `about:blank`
|
|
119
|
+
* tab created in ITS BrowserContext via `tab_new {account}` — bb-browser's
|
|
120
|
+
* `createTabInContext` re-activates the context (cookies re-injected) when it
|
|
121
|
+
* was disposed, so recovery never lands in the default context.
|
|
122
|
+
*/
|
|
123
|
+
async ensureOwnedTab(account) {
|
|
124
|
+
const owned = await this.ownedTabs(account);
|
|
125
|
+
const existing = owned.find((t) => t.active) ?? owned[0];
|
|
126
|
+
if (existing)
|
|
127
|
+
return existing.targetId;
|
|
128
|
+
return this.createOwnedTab(account);
|
|
129
|
+
}
|
|
130
|
+
async createOwnedTab(account) {
|
|
131
|
+
const created = await this.host.sendCommand({
|
|
132
|
+
method: 'tab_new',
|
|
133
|
+
account,
|
|
134
|
+
url: 'about:blank',
|
|
135
|
+
});
|
|
136
|
+
const targetId = typeof created.tabId === 'string' && created.tabId ? created.tabId : undefined;
|
|
137
|
+
if (targetId)
|
|
138
|
+
return targetId;
|
|
139
|
+
// tab_new responded without a usable tabId — re-derive from ownership rather
|
|
140
|
+
// than guess (never fall back to an unowned page).
|
|
141
|
+
const owned = await this.ownedTabs(account);
|
|
142
|
+
const pick = owned.find((t) => t.active) ?? owned[0];
|
|
143
|
+
if (!pick) {
|
|
144
|
+
throw new BrowserTargetError(`could not create a page for browser profile ${account} in its own context`, 'BROWSER_TARGET_UNAVAILABLE');
|
|
145
|
+
}
|
|
146
|
+
return pick.targetId;
|
|
147
|
+
}
|
|
148
|
+
/** Owned tab currently on `host` (www-tolerant), optionally only a specific ref. */
|
|
149
|
+
async findOwnedTabOnHost(account, host, onlyTargetId) {
|
|
150
|
+
const owned = await this.ownedTabs(account);
|
|
151
|
+
for (const tab of owned) {
|
|
152
|
+
if (onlyTargetId !== undefined && tab.targetId !== onlyTargetId)
|
|
153
|
+
continue;
|
|
154
|
+
if (!tab.url)
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
if (hostsMatch(new URL(tab.url).host, host))
|
|
158
|
+
return tab.targetId;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* non-URL tab (about:blank etc.) — skip */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
/** Whether any owned tab is already on `url` (same resource, http→https tolerant). */
|
|
167
|
+
async findOwnedTabMatchingUrl(account, url) {
|
|
168
|
+
const expected = comparableUrl(url);
|
|
169
|
+
if (!expected)
|
|
170
|
+
return undefined;
|
|
171
|
+
const owned = await this.ownedTabs(account);
|
|
172
|
+
for (const tab of owned) {
|
|
173
|
+
if (!tab.url)
|
|
174
|
+
continue;
|
|
175
|
+
const actual = comparableUrl(tab.url);
|
|
176
|
+
if (!actual)
|
|
177
|
+
continue;
|
|
178
|
+
const sameResource = hostsMatch(actual.host, expected.host) && actual.target === expected.target;
|
|
179
|
+
const sameProtocol = actual.protocol === expected.protocol;
|
|
180
|
+
const redirectedUpgrade = expected.protocol === 'http:' && actual.protocol === 'https:';
|
|
181
|
+
if (sameResource && (sameProtocol || redirectedUpgrade))
|
|
182
|
+
return tab.targetId;
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Account-scoped tab-by-domain resolution for `eval {domain}` (bb-browser's
|
|
188
|
+
* own `resolveTabByDomain` is account-blind — the reason this exists). Two
|
|
189
|
+
* properties the bare `tab_new` + eval approach lacked, both caught on the
|
|
190
|
+
* staging closed-loop E2E (2026-06-04):
|
|
191
|
+
*
|
|
192
|
+
* 1. Reuse: an existing account tab already on the domain is reused instead
|
|
193
|
+
* of opening a new tab per eval (upstream reuses matching tabs too).
|
|
194
|
+
* 2. Load wait: after creating a tab, wait for the navigation to commit
|
|
195
|
+
* before eval — upstream waits (~10s poll + settle); without it the clip
|
|
196
|
+
* script races `about:blank` and relative fetches fail
|
|
197
|
+
* ("Failed to parse URL from /hot.json").
|
|
198
|
+
*/
|
|
199
|
+
async resolveDomainTab(account, domain) {
|
|
200
|
+
const url = normalizeDomainUrl(domain);
|
|
201
|
+
let host = '';
|
|
202
|
+
try {
|
|
203
|
+
host = new URL(url).host;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
/* fall through — tab_new with whatever bb-browser makes of it */
|
|
207
|
+
}
|
|
208
|
+
if (host) {
|
|
209
|
+
const existing = await this.findOwnedTabOnHost(account, host);
|
|
210
|
+
if (existing !== undefined)
|
|
211
|
+
return existing;
|
|
212
|
+
}
|
|
213
|
+
const tab = await this.host.sendCommand({ method: 'tab_new', url, account });
|
|
214
|
+
const targetId = typeof tab.tabId === 'string' && tab.tabId ? tab.tabId : undefined;
|
|
215
|
+
if (targetId === undefined || !host)
|
|
216
|
+
return targetId;
|
|
217
|
+
// Wait for the created tab to actually reach the domain (navigation
|
|
218
|
+
// committed). Mirrors upstream resolveTabByDomain's bounded wait + settle.
|
|
219
|
+
let committed = false;
|
|
220
|
+
const deadline = Date.now() + (this.opts.commitTimeoutMs ?? 10_000);
|
|
221
|
+
while (Date.now() < deadline) {
|
|
222
|
+
if ((await this.findOwnedTabOnHost(account, host, targetId)) !== undefined) {
|
|
223
|
+
committed = true;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
await sleep(this.opts.commitPollMs ?? 300);
|
|
227
|
+
}
|
|
228
|
+
if (!committed) {
|
|
229
|
+
// The tab never confirmed it reached the domain within the deadline — its
|
|
230
|
+
// document is still about:blank / a partial load. Returning it would let a
|
|
231
|
+
// subsequent eval run on the WRONG page. CLOSE it so it can't linger as an
|
|
232
|
+
// owned tab a later ref-less resolve would pick, then fail closed.
|
|
233
|
+
await this.closeTabBestEffort(account, targetId);
|
|
234
|
+
throw new BrowserTargetError(`domain ${domain} page did not commit navigation in browser profile ${account}`, 'BROWSER_TARGET_UNAVAILABLE');
|
|
235
|
+
}
|
|
236
|
+
await sleep(this.opts.commitSettleMs ?? 750); // post-commit settle (upstream uses a flat 2s)
|
|
237
|
+
return targetId;
|
|
238
|
+
}
|
|
239
|
+
/** Close a tab we created but could not commit, so it does not linger as a
|
|
240
|
+
* residual owned tab. Best-effort — a close failure (already gone / rejected)
|
|
241
|
+
* is swallowed. bb-browser's close command is `close` (→ Target.closeTarget). */
|
|
242
|
+
async closeTabBestEffort(account, targetId) {
|
|
243
|
+
try {
|
|
244
|
+
await this.host.sendCommand({ method: 'close', tabId: targetId, account });
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
/* the tab may already be gone / bb-browser rejected the close — ignore */
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Commands that take no page target at all — everything else is tab-addressed
|
|
253
|
+
* and MUST carry an owned tabId. `tab_new`, `site_info`, and the `site_*` listing
|
|
254
|
+
* commands resolve/create their page inside the account's context upstream
|
|
255
|
+
* (createTabInContext / account-scoped adapter execution), so they pass `account`
|
|
256
|
+
* instead of a pinned tab. NOTE `tab_list` IS dispatched through bb-browser's
|
|
257
|
+
* `ensurePageTarget` (its global-current-tab side effect is harmless once every
|
|
258
|
+
* real command pins its own target). `site_run` is deliberately NOT tab-less:
|
|
259
|
+
* the manager resolves the adapter's domain via site_info and pins an owned tab
|
|
260
|
+
* (or fails closed for a domain-less adapter), closing the old global-current-tab
|
|
261
|
+
* fallthrough — see BrowserProfileManager.buildCommandRequest.
|
|
262
|
+
*/
|
|
263
|
+
export const TABLESS_COMMANDS = new Set([
|
|
264
|
+
'account_list',
|
|
265
|
+
'account_create',
|
|
266
|
+
'account_delete',
|
|
267
|
+
'account_info',
|
|
268
|
+
'tab_list',
|
|
269
|
+
'tab_new',
|
|
270
|
+
'site_list',
|
|
271
|
+
'site_search',
|
|
272
|
+
'site_info',
|
|
273
|
+
]);
|
|
274
|
+
/** Host comparison tolerant of a `www.` prefix on either side. */
|
|
275
|
+
export function hostsMatch(a, b) {
|
|
276
|
+
const norm = (h) => h.toLowerCase().replace(/^www\./, '');
|
|
277
|
+
return norm(a) === norm(b);
|
|
278
|
+
}
|
|
279
|
+
export function normalizeDomainUrl(domain) {
|
|
280
|
+
const trimmed = domain.trim();
|
|
281
|
+
if (!trimmed)
|
|
282
|
+
return 'about:blank';
|
|
283
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
284
|
+
}
|
|
285
|
+
export function comparableUrl(url) {
|
|
286
|
+
try {
|
|
287
|
+
const parsed = new URL(url);
|
|
288
|
+
return {
|
|
289
|
+
protocol: parsed.protocol,
|
|
290
|
+
host: parsed.host,
|
|
291
|
+
target: `${parsed.pathname}${parsed.search}${parsed.hash}`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BrowserTargetRegistry } from './browser-target-registry.js';
|
|
1
2
|
/** Optional managed TURN relay config carried on a `stream.start` command. */
|
|
2
3
|
export interface ViewerTurnConfig {
|
|
3
4
|
url: string;
|
|
@@ -21,13 +22,19 @@ export interface ViewerStreamerHost {
|
|
|
21
22
|
method: string;
|
|
22
23
|
account?: string;
|
|
23
24
|
}): Promise<Record<string, unknown>>;
|
|
24
|
-
|
|
25
|
+
/** First-use preparation (desired-proxy reconcile + readiness), shared with the
|
|
26
|
+
* agent-invoke path — the viewer's first stream/nav on a hydrated account must
|
|
27
|
+
* reconcile the proxy + verify readiness too, not just confirm the account
|
|
28
|
+
* exists. Idempotent: the steady path after first use is cheap. */
|
|
29
|
+
prepareForUse(account: string): Promise<void>;
|
|
25
30
|
withDaemonRecovery<T>(operation: () => Promise<T>, label: string): Promise<T>;
|
|
26
31
|
/** cdpHost/cdpPort from the bb-browser daemon `GET /status`. */
|
|
27
32
|
cdpEndpoint(): Promise<{
|
|
28
33
|
host: string;
|
|
29
34
|
port: number;
|
|
30
35
|
}>;
|
|
36
|
+
/** Shared owned-target resolution (same registry the agent-invoke path pins with). */
|
|
37
|
+
targets: BrowserTargetRegistry;
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
33
40
|
* bb-viewer's /command control plane is UNAUTHENTICATED, so on the shared agents
|
|
@@ -165,25 +172,17 @@ export declare class BrowserViewerStreamer {
|
|
|
165
172
|
private resolveAccountPageCdpUrl;
|
|
166
173
|
/**
|
|
167
174
|
* Full CDP targetId of the profile account's active page tab (the owned tab
|
|
168
|
-
* Chrome marks active, else the first owned tab
|
|
169
|
-
*
|
|
170
|
-
* path
|
|
175
|
+
* Chrome marks active, else the first owned tab; a tab-less account gets an
|
|
176
|
+
* `about:blank` created inside its own context first). Shared with the
|
|
177
|
+
* agent-invoke path via BrowserTargetRegistry — one ownership implementation.
|
|
171
178
|
*/
|
|
172
179
|
private accountActivePageTargetId;
|
|
173
|
-
/**
|
|
174
|
-
* Full CDP targetId (`tabId`) of the account's active page tab: the owned tab
|
|
175
|
-
* Chrome marks `active` when it belongs to this account, else the first owned
|
|
176
|
-
* tab (the global active flag is account-blind — another profile's tab may
|
|
177
|
-
* hold it, which must not leak here).
|
|
178
|
-
*/
|
|
179
|
-
private firstAccountPageTargetId;
|
|
180
180
|
/**
|
|
181
181
|
* Full CDP targetId for a specific account tab, addressed by the bb-browser
|
|
182
|
-
* short tab id (`tab`) or full target id (`tabId`).
|
|
182
|
+
* short tab id (`tab`) or full target id (`tabId`). Throws on tabs the
|
|
183
|
+
* profile does not own (BrowserTargetError). Used by stream.switch.
|
|
183
184
|
*/
|
|
184
185
|
private accountTabTargetId;
|
|
185
|
-
/** `tab_list {account}` rows, typed to the fields we read (tabId/tab/account/active). */
|
|
186
|
-
private accountTabs;
|
|
187
186
|
/**
|
|
188
187
|
* Spawn a bb-viewer streamer for the profile and wait until healthy. Binary
|
|
189
188
|
* is PRLL_BB_VIEWER_BIN (our image sets it to /usr/local/bin/bb-viewer),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-viewer-streamer.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/browser-viewer-streamer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"browser-viewer-streamer.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/browser-viewer-streamer.ts"],"names":[],"mappings":"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"}
|
|
@@ -332,7 +332,7 @@ export class BrowserViewerStreamer {
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
return this.host.withDaemonRecovery(async () => {
|
|
335
|
-
await this.host.
|
|
335
|
+
await this.host.prepareForUse(profileId);
|
|
336
336
|
const request = { ...(input ?? {}) };
|
|
337
337
|
if (command === 'open' || command === 'tab_new') {
|
|
338
338
|
assertViewerNavigableUrl(request.url);
|
|
@@ -375,7 +375,7 @@ export class BrowserViewerStreamer {
|
|
|
375
375
|
* is the per-tab attribution we filter on (reused by findAccountTabOnHost).
|
|
376
376
|
*/
|
|
377
377
|
async resolveAccountPageCdpUrl(profileId, tabRef) {
|
|
378
|
-
await this.host.
|
|
378
|
+
await this.host.prepareForUse(profileId);
|
|
379
379
|
const { host, port } = await this.host.cdpEndpoint();
|
|
380
380
|
const targetId = tabRef !== undefined
|
|
381
381
|
? await this.accountTabTargetId(profileId, tabRef)
|
|
@@ -384,72 +384,20 @@ export class BrowserViewerStreamer {
|
|
|
384
384
|
}
|
|
385
385
|
/**
|
|
386
386
|
* Full CDP targetId of the profile account's active page tab (the owned tab
|
|
387
|
-
* Chrome marks active, else the first owned tab
|
|
388
|
-
*
|
|
389
|
-
* path
|
|
387
|
+
* Chrome marks active, else the first owned tab; a tab-less account gets an
|
|
388
|
+
* `about:blank` created inside its own context first). Shared with the
|
|
389
|
+
* agent-invoke path via BrowserTargetRegistry — one ownership implementation.
|
|
390
390
|
*/
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (targetId !== undefined)
|
|
394
|
-
return targetId;
|
|
395
|
-
await this.host.sendBrowserCommand({
|
|
396
|
-
method: 'tab_new',
|
|
397
|
-
account: profileId,
|
|
398
|
-
url: 'about:blank',
|
|
399
|
-
});
|
|
400
|
-
targetId = await this.firstAccountPageTargetId(profileId);
|
|
401
|
-
if (targetId === undefined) {
|
|
402
|
-
throw new Error(`no page tab found for profile account ${profileId}`);
|
|
403
|
-
}
|
|
404
|
-
return targetId;
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Full CDP targetId (`tabId`) of the account's active page tab: the owned tab
|
|
408
|
-
* Chrome marks `active` when it belongs to this account, else the first owned
|
|
409
|
-
* tab (the global active flag is account-blind — another profile's tab may
|
|
410
|
-
* hold it, which must not leak here).
|
|
411
|
-
*/
|
|
412
|
-
async firstAccountPageTargetId(profileId) {
|
|
413
|
-
const tabs = await this.accountTabs(profileId);
|
|
414
|
-
let firstOwned;
|
|
415
|
-
for (const t of tabs) {
|
|
416
|
-
if (t.account !== profileId)
|
|
417
|
-
continue;
|
|
418
|
-
const tabId = t.tabId;
|
|
419
|
-
if (typeof tabId !== 'string' || !tabId)
|
|
420
|
-
continue;
|
|
421
|
-
if (t.active === true)
|
|
422
|
-
return tabId;
|
|
423
|
-
firstOwned ??= tabId;
|
|
424
|
-
}
|
|
425
|
-
return firstOwned;
|
|
391
|
+
accountActivePageTargetId(profileId) {
|
|
392
|
+
return this.host.targets.resolveCommandTarget(profileId);
|
|
426
393
|
}
|
|
427
394
|
/**
|
|
428
395
|
* Full CDP targetId for a specific account tab, addressed by the bb-browser
|
|
429
|
-
* short tab id (`tab`) or full target id (`tabId`).
|
|
396
|
+
* short tab id (`tab`) or full target id (`tabId`). Throws on tabs the
|
|
397
|
+
* profile does not own (BrowserTargetError). Used by stream.switch.
|
|
430
398
|
*/
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const tabs = await this.accountTabs(profileId);
|
|
434
|
-
for (const t of tabs) {
|
|
435
|
-
if (t.account !== profileId)
|
|
436
|
-
continue;
|
|
437
|
-
const tabId = typeof t.tabId === 'string' ? t.tabId : undefined;
|
|
438
|
-
const shortTab = t.tab === undefined ? undefined : String(t.tab);
|
|
439
|
-
if ((tabId && tabId === ref) || (shortTab && shortTab === ref)) {
|
|
440
|
-
if (!tabId)
|
|
441
|
-
break;
|
|
442
|
-
return tabId;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
throw new Error(`tab ${ref} not found for profile account ${profileId}`);
|
|
446
|
-
}
|
|
447
|
-
/** `tab_list {account}` rows, typed to the fields we read (tabId/tab/account/active). */
|
|
448
|
-
async accountTabs(profileId) {
|
|
449
|
-
const list = await this.host.sendBrowserCommand({ method: 'tab_list', account: profileId });
|
|
450
|
-
return Array.isArray(list.tabs)
|
|
451
|
-
? list.tabs
|
|
452
|
-
: [];
|
|
399
|
+
accountTabTargetId(profileId, tabRef) {
|
|
400
|
+
return this.host.targets.resolveCommandTarget(profileId, tabRef);
|
|
453
401
|
}
|
|
454
402
|
// ---- bb-viewer streamer lifecycle ----
|
|
455
403
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-main.d.ts","sourceRoot":"","sources":["../src/daemon-main.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"daemon-main.d.ts","sourceRoot":"","sources":["../src/daemon-main.ts"],"names":[],"mappings":";AA0BA,MAAM,MAAM,oBAAoB,GAAG;IACjC,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AA8KF,wBAAsB,cAAc,CAClC,SAAS,EAAE,oBAAoB,EAC/B,IAAI,WAAwB,GAC3B,OAAO,CAAC,IAAI,CAAC,CASf"}
|
package/dist/daemon-main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createLogger, createOtelLogger, initAgentTelemetry, } from '@parall/agent-core';
|
|
2
|
+
import { configureHttpKeepAlive, createLogger, createOtelLogger, initAgentTelemetry, } from '@parall/agent-core';
|
|
3
3
|
import { ParallClient } from '@parall/sdk';
|
|
4
4
|
import { runCLI } from './cli.js';
|
|
5
5
|
import { resolveBundleDir, resolveClaudeDaemonConfig, resolveWsUrl, } from './config.js';
|
|
@@ -75,6 +75,8 @@ async function runForever(config, client, log, signal, updater, gate) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
async function main(bootstrap) {
|
|
78
|
+
// Before any fetch: long-lived HTTP connections for control-plane calls.
|
|
79
|
+
configureHttpKeepAlive();
|
|
78
80
|
const config = resolveClaudeDaemonConfig(process.env);
|
|
79
81
|
const telemetry = await initAgentTelemetry('parall-daemon', 'daemon');
|
|
80
82
|
log = createOtelLogger('daemon', 'daemon');
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -81,6 +81,12 @@ export declare class DaemonSupervisor {
|
|
|
81
81
|
* proxy-configured profile (the caller reports `error` and retries next tick).
|
|
82
82
|
*/
|
|
83
83
|
private resolveBrowserProfileProxy;
|
|
84
|
+
/** Proxy readiness probe endpoint for BYOC profiles (browser-readiness.ts): an
|
|
85
|
+
* explicit override, else the api-server's Parall-owned public `/generate_204`.
|
|
86
|
+
* The probe navigates a sacrificial tab THROUGH the profile's proxy, so the
|
|
87
|
+
* target must be public + proxy-resolvable — `config.apiUrl` is the public API
|
|
88
|
+
* host for BYOC. Falls back to a well-known public 204 if apiUrl is unusable. */
|
|
89
|
+
private resolveBrowserProxyProbeUrl;
|
|
84
90
|
/**
|
|
85
91
|
* Detects a legacy flat state layout (no agents/ subdir) and migrates it
|
|
86
92
|
* into the per-agent directory for the owning agent. Ownership is determined
|
package/dist/supervisor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyC,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,EAgBL,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
|