@phnx-labs/agents-cli 1.22.47 → 1.22.49
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/CHANGELOG.md +401 -17
- package/README.md +66 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +206 -32
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/exec.js +52 -15
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +2 -8
- package/dist/lib/config-keys.js +2 -13
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +67 -13
- package/dist/lib/exec.js +85 -29
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +11 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +19 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -1,8 +1,55 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { execFileSync } from 'child_process';
|
|
4
|
-
import { getProfileRuntimeDir, getBrowserRuntimeDir,
|
|
4
|
+
import { getProfileRuntimeDir, getBrowserRuntimeDir, getEndpointPresets, listProfiles, getConfiguredDefaultProfileName, isProfileLaunchableHere, deleteProfile, DEFAULT_BROWSER_PROFILE_NAME, LEGACY_DEFAULT_BROWSER_PROFILE_NAME, } from './profiles.js';
|
|
5
5
|
import { keyBelongsToProfile } from './types.js';
|
|
6
|
+
import { declaringDevices, profileKind } from './registry.js';
|
|
7
|
+
import { machineId } from '../machine-id.js';
|
|
8
|
+
/**
|
|
9
|
+
* Detect an identity-bearing profile whose stored endpoint is loopback, viewed
|
|
10
|
+
* from a machine that is NOT the declaring device.
|
|
11
|
+
*
|
|
12
|
+
* That is the original `comet-local` bug: five real logins on the declaring
|
|
13
|
+
* box, a bare logged-out chromium answering to the same name on a worker
|
|
14
|
+
* because `cdp://localhost:N` is evaluated on the machine running the command.
|
|
15
|
+
* The daemon now tunnels that shape (`resolve-target.ts`); doctor still flags
|
|
16
|
+
* it so a worker does not report a green local binary/port for someone else's
|
|
17
|
+
* browser. Kind comes from the registry (exactly one declaring device), never
|
|
18
|
+
* from a stored field.
|
|
19
|
+
*/
|
|
20
|
+
export function identityLoopbackMismatch(profile) {
|
|
21
|
+
if (profileKind(profile.name) !== 'identity')
|
|
22
|
+
return { misfiled: false };
|
|
23
|
+
const declaringDevice = declaringDevices(profile.name)[0];
|
|
24
|
+
if (!declaringDevice || declaringDevice === machineId())
|
|
25
|
+
return { misfiled: false };
|
|
26
|
+
const presets = getEndpointPresets(profile);
|
|
27
|
+
const chosen = profile.defaultEndpoint && presets[profile.defaultEndpoint]
|
|
28
|
+
? profile.defaultEndpoint
|
|
29
|
+
: Object.keys(presets)[0];
|
|
30
|
+
if (!chosen)
|
|
31
|
+
return { misfiled: false };
|
|
32
|
+
const target = presets[chosen].target;
|
|
33
|
+
if (!target.startsWith('cdp://'))
|
|
34
|
+
return { misfiled: false };
|
|
35
|
+
let host;
|
|
36
|
+
try {
|
|
37
|
+
host = new URL(target).hostname;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { misfiled: false };
|
|
41
|
+
}
|
|
42
|
+
if (host !== 'localhost' && host !== '127.0.0.1' && host !== '::1' && host !== '[::1]') {
|
|
43
|
+
return { misfiled: false };
|
|
44
|
+
}
|
|
45
|
+
const here = machineId();
|
|
46
|
+
return {
|
|
47
|
+
misfiled: true,
|
|
48
|
+
why: `identity-bearing profile declared on ${declaringDevice}, but "${target}" is a loopback endpoint — ` +
|
|
49
|
+
`on ${here} that address is this device's own browser, not ${declaringDevice}'s. ` +
|
|
50
|
+
`Diagnose the real browser on ${declaringDevice}: agents browser profiles doctor ${profile.name}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
6
53
|
const PID_FILE = 'pid';
|
|
7
54
|
const PORT_FILE = 'port';
|
|
8
55
|
const COMMAND_FILE = 'command';
|
|
@@ -96,7 +143,7 @@ export function clearProfileRuntime(profileName) {
|
|
|
96
143
|
}
|
|
97
144
|
/**
|
|
98
145
|
* Recursively remove the whole profile cache (chrome-data, tasks.json,
|
|
99
|
-
* everything). Used by `profiles
|
|
146
|
+
* everything). Used by `profiles remove` so an old profile name doesn't
|
|
100
147
|
* leak its history into a freshly-recreated one.
|
|
101
148
|
*/
|
|
102
149
|
export function removeProfileCache(profileName) {
|
|
@@ -298,8 +345,6 @@ export const PRUNE_REASON_TEXT = {
|
|
|
298
345
|
* - **the auto-detected profile** (`auto-chrome`, or the `default` an older
|
|
299
346
|
* build wrote) — auto-regenerated by `ensureDefaultBrowserProfile`, so
|
|
300
347
|
* pruning it is pure churn.
|
|
301
|
-
* - **fleet-synced** — deleting one removes it from EVERY machine, so it takes
|
|
302
|
-
* the explicit `includeFleet` opt-in.
|
|
303
348
|
*
|
|
304
349
|
* Known limitation: `BrowserProfileConfig` records no creation time, so a
|
|
305
350
|
* profile created seconds ago and not yet started is indistinguishable from an
|
|
@@ -312,20 +357,15 @@ export function planProfilePrune(profiles, opts = {}) {
|
|
|
312
357
|
const keep = (why, misfiled = false) => {
|
|
313
358
|
plan.kept.push(misfiled ? { name, scope, why, misfiled } : { name, scope, why });
|
|
314
359
|
};
|
|
315
|
-
// Checked FIRST
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
// move; an intentional delete is still available as `profiles delete`.
|
|
360
|
+
// Checked FIRST. A misfiled identity profile's defining symptom is that the
|
|
361
|
+
// browser is absent on whatever box you are standing on — pruning it would
|
|
362
|
+
// delete the declaring device's entry (or throw, for a peer declaration).
|
|
363
|
+
// The repair is to declare it only on the machine that owns the browser;
|
|
364
|
+
// an intentional delete is still available as `profiles remove`.
|
|
321
365
|
if (misfiledWhy) {
|
|
322
366
|
keep(`MISFILED — ${misfiledWhy}`, true);
|
|
323
367
|
continue;
|
|
324
368
|
}
|
|
325
|
-
if (scope === 'fleet' && !opts.includeFleet) {
|
|
326
|
-
keep('fleet-synced (pass --fleet to include it)');
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
369
|
if (name === DEFAULT_BROWSER_PROFILE_NAME || name === LEGACY_DEFAULT_BROWSER_PROFILE_NAME) {
|
|
330
370
|
keep('the auto-detected default profile');
|
|
331
371
|
continue;
|
|
@@ -355,17 +395,44 @@ export function planProfilePrune(profiles, opts = {}) {
|
|
|
355
395
|
* Build the prune plan from live config. Split from {@link planProfilePrune} so
|
|
356
396
|
* the decision rules stay unit-testable without a configured machine.
|
|
357
397
|
*/
|
|
358
|
-
export async function buildProfilePrunePlan(
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
398
|
+
export async function buildProfilePrunePlan() {
|
|
399
|
+
const current = machineId();
|
|
400
|
+
const profiles = await listProfiles();
|
|
401
|
+
const declaredHere = [];
|
|
402
|
+
const peerKept = [];
|
|
403
|
+
for (const profile of profiles) {
|
|
404
|
+
const scope = profile.devices.length > 1 ? 'fungible' : 'identity';
|
|
405
|
+
const misfiled = identityLoopbackMismatch(profile);
|
|
406
|
+
if (profile.devices.includes(current)) {
|
|
407
|
+
declaredHere.push({
|
|
408
|
+
name: profile.name,
|
|
409
|
+
scope,
|
|
410
|
+
launchableHere: isProfileLaunchableHere(profile),
|
|
411
|
+
misfiledWhy: misfiled.misfiled ? misfiled.why : undefined,
|
|
412
|
+
});
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (misfiled.misfiled) {
|
|
416
|
+
peerKept.push({
|
|
417
|
+
name: profile.name,
|
|
418
|
+
scope,
|
|
419
|
+
why: `MISFILED — ${misfiled.why}`,
|
|
420
|
+
misfiled: true,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
peerKept.push({
|
|
425
|
+
name: profile.name,
|
|
426
|
+
scope,
|
|
427
|
+
why: `declared on ${profile.devices.join(', ')}, not this device`,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const plan = planProfilePrune(declaredHere, {
|
|
432
|
+
configuredDefault: getConfiguredDefaultProfileName(),
|
|
433
|
+
});
|
|
434
|
+
plan.kept.push(...peerKept);
|
|
435
|
+
return plan;
|
|
369
436
|
}
|
|
370
437
|
/**
|
|
371
438
|
* Execute a prune plan: drop each candidate's config entry and wipe its cache
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CDPClient } from './cdp.js';
|
|
2
|
+
import { type DeviceProbe } from './resolve-target.js';
|
|
2
3
|
import { type ResolvedDomainSkill } from './domain-skills.js';
|
|
3
4
|
import { type Task, type TabInfo, type ProfileStatus, type BrowserType, type HistoricalTask, type ReapResult, type ProfileName, type ConnectionKey } from './types.js';
|
|
4
5
|
import { type ReapOptions } from './hygiene.js';
|
|
@@ -136,7 +137,8 @@ export declare class BrowserService {
|
|
|
136
137
|
* Live browsers, keyed by {@link ConnectionKey} — NOT by profile name. The
|
|
137
138
|
* branded key type is what makes `connections.get(someProfileName)` (the
|
|
138
139
|
* RUSH-2709 miss that made `status --profile <name>` return empty for a
|
|
139
|
-
* running `<name
|
|
140
|
+
* running `<name>@<device>`) fail to compile. Keys are `<profile>@<device>`
|
|
141
|
+
* (legacy leftover `<profile>@endpoint-N` dirs are still recognized).
|
|
140
142
|
*/
|
|
141
143
|
private connections;
|
|
142
144
|
private forkingProfiles;
|
|
@@ -165,6 +167,8 @@ export declare class BrowserService {
|
|
|
165
167
|
fleetRemote?: boolean;
|
|
166
168
|
/** Explicit human label (`--title`). */
|
|
167
169
|
title?: string;
|
|
170
|
+
/** Injected reachability probe — production uses ssh; tests pass a fake. */
|
|
171
|
+
probe?: DeviceProbe;
|
|
168
172
|
}): Promise<{
|
|
169
173
|
task: string;
|
|
170
174
|
name: string;
|
|
@@ -172,8 +176,12 @@ export declare class BrowserService {
|
|
|
172
176
|
windowId?: string;
|
|
173
177
|
/** BARE profile name — what the caller asked for, never the runtime key. */
|
|
174
178
|
profile: ProfileName;
|
|
175
|
-
/** Runtime key the task actually landed on (`<profile>@<
|
|
179
|
+
/** Runtime key the task actually landed on (`<profile>@<device>`). */
|
|
176
180
|
key: ConnectionKey;
|
|
181
|
+
/** Device the daemon connected to. */
|
|
182
|
+
device: string;
|
|
183
|
+
/** Set when the daemon picked a remote declaring device. */
|
|
184
|
+
picked?: string;
|
|
177
185
|
skill?: ResolvedDomainSkill;
|
|
178
186
|
}>;
|
|
179
187
|
/**
|
|
@@ -240,7 +248,7 @@ export declare class BrowserService {
|
|
|
240
248
|
/**
|
|
241
249
|
* Close tasks whose owning agent session is gone, or that have sat untouched
|
|
242
250
|
* past the idle window. The entry point the daemon's periodic tick and
|
|
243
|
-
* `agents browser
|
|
251
|
+
* `agents browser prune` both call; the policy lives in `hygiene.ts`.
|
|
244
252
|
*/
|
|
245
253
|
reapAbandoned(opts?: ReapOptions): Promise<ReapResult>;
|
|
246
254
|
stop(taskName: string): Promise<{
|
|
@@ -361,8 +369,9 @@ export declare class BrowserService {
|
|
|
361
369
|
* `profileName` is a {@link ProfileName} — a BARE name, never a runtime key.
|
|
362
370
|
* Every candidate is selected by the one rule ({@link keyBelongsToProfile}),
|
|
363
371
|
* and a scoped query that finds no live connection falls through to disk, so
|
|
364
|
-
* `status --profile comet-local` reports the LIVE `comet-local
|
|
365
|
-
* instead of the empty list
|
|
372
|
+
* `status --profile comet-local` reports the LIVE `comet-local@<device>`
|
|
373
|
+
* (and leftover `comet-local@endpoint-0` dirs) instead of the empty list
|
|
374
|
+
* it used to return (RUSH-2709).
|
|
366
375
|
*/
|
|
367
376
|
status(profileRef?: ProfileName | ConnectionKey): Promise<ProfileStatus[]>;
|
|
368
377
|
/**
|
|
@@ -438,10 +447,9 @@ export declare class BrowserService {
|
|
|
438
447
|
* already resolved the endpoint and built the `effectiveProfile` with
|
|
439
448
|
* the per-endpoint binary/targetFilter overrides applied; we just use it.
|
|
440
449
|
*
|
|
441
|
-
* `key` is the runtime key (`<profile>@<
|
|
442
|
-
* lookup uses
|
|
443
|
-
*
|
|
444
|
-
* because `effectiveProfile.name` stays the bare, user-facing name (RUSH-2709).
|
|
450
|
+
* `key` is the runtime key (`<profile>@<device>`) and is what every on-disk
|
|
451
|
+
* lookup uses. It is a SEPARATE argument because `effectiveProfile.name`
|
|
452
|
+
* stays the bare, user-facing name (RUSH-2709).
|
|
445
453
|
*/
|
|
446
454
|
private connectProfile;
|
|
447
455
|
private connectEndpoint;
|
|
@@ -486,6 +494,8 @@ export declare class BrowserService {
|
|
|
486
494
|
task: Task;
|
|
487
495
|
key: ConnectionKey;
|
|
488
496
|
created: boolean;
|
|
497
|
+
picked?: string;
|
|
498
|
+
device?: string;
|
|
489
499
|
} | null>;
|
|
490
500
|
private listTasksForCaller;
|
|
491
501
|
private currentUrlHint;
|
|
@@ -550,6 +560,12 @@ export declare class BrowserService {
|
|
|
550
560
|
* of killing the daemon.
|
|
551
561
|
*/
|
|
552
562
|
private reuseHealthyConnection;
|
|
563
|
+
/**
|
|
564
|
+
* Reuse a live connection for this profile: the new `<name>@<device>` key
|
|
565
|
+
* first, then any leftover `<name>@endpoint-N` key still in the map so a
|
|
566
|
+
* browser started before the key collapse is not abandoned.
|
|
567
|
+
*/
|
|
568
|
+
private reuseProfileConnection;
|
|
553
569
|
/** Connect a profile at `target`, register it under `key`, and arm downloads. */
|
|
554
570
|
private openConnection;
|
|
555
571
|
/**
|
|
@@ -568,10 +584,13 @@ export declare class BrowserService {
|
|
|
568
584
|
endpointName?: string;
|
|
569
585
|
fleetRemote?: boolean;
|
|
570
586
|
actor?: string;
|
|
587
|
+
probe?: DeviceProbe;
|
|
571
588
|
}): Promise<{
|
|
572
589
|
profile: ProfileName;
|
|
573
590
|
key: ConnectionKey;
|
|
574
591
|
tabId: string;
|
|
592
|
+
device: string;
|
|
593
|
+
picked?: string;
|
|
575
594
|
}>;
|
|
576
595
|
getHistory(limit?: number): Promise<HistoricalTask[]>;
|
|
577
596
|
}
|
|
@@ -7,9 +7,10 @@ import { killChrome, getRunningChromeInfo, launchBrowser, allocatePort } from '.
|
|
|
7
7
|
import { assertRemoteControlAllowedForRequest } from './remote-control.js';
|
|
8
8
|
import { connectLocal } from './drivers/local.js';
|
|
9
9
|
import { connectSSH, shellQuote } from './drivers/ssh.js';
|
|
10
|
+
import { adoptLegacyRuntimeIfLocal, isLegacyEndpointKey, resolveBrowserTarget, shouldForkProfile, } from './resolve-target.js';
|
|
10
11
|
import { clearProfileRuntime, listProfileCacheDirs, readProfileRuntimeMeta, isProcessAlive } from './runtime-state.js';
|
|
11
12
|
import { resolveDomainSkill } from './domain-skills.js';
|
|
12
|
-
import { generateTaskId, generateShortId,
|
|
13
|
+
import { generateTaskId, generateShortId, asConnectionKey, parseConnectionKey, keyBelongsToProfile, } from './types.js';
|
|
13
14
|
import { reapAbandonedTasks, resolveLiveIdentities, taskOwnerIsGone, } from './hygiene.js';
|
|
14
15
|
import { taskMatchesCaller } from './caller-identity.js';
|
|
15
16
|
import { getRefs, resolveRefToCoords, describeRefs, healRef } from './refs.js';
|
|
@@ -298,7 +299,8 @@ export class BrowserService {
|
|
|
298
299
|
* Live browsers, keyed by {@link ConnectionKey} — NOT by profile name. The
|
|
299
300
|
* branded key type is what makes `connections.get(someProfileName)` (the
|
|
300
301
|
* RUSH-2709 miss that made `status --profile <name>` return empty for a
|
|
301
|
-
* running `<name
|
|
302
|
+
* running `<name>@<device>`) fail to compile. Keys are `<profile>@<device>`
|
|
303
|
+
* (legacy leftover `<profile>@endpoint-N` dirs are still recognized).
|
|
302
304
|
*/
|
|
303
305
|
connections = new Map();
|
|
304
306
|
forkingProfiles = new Set();
|
|
@@ -318,26 +320,16 @@ export class BrowserService {
|
|
|
318
320
|
// page verbs that create a browser implicitly (navigate, click, screenshot,
|
|
319
321
|
// …) able to open a browser on a machine whose owner never opted in.
|
|
320
322
|
assertRemoteControlAllowedForRequest(opts.fleetRemote, { actor: opts.actor });
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// here (pre-RUSH-2709) is what leaked `comet-local@endpoint-0` into every
|
|
332
|
-
// listing and made `status --profile comet-local` miss its own browser.
|
|
333
|
-
// The key travels as its own argument instead.
|
|
334
|
-
const resolved = resolveEndpoint(profile, opts.endpointName);
|
|
335
|
-
const composite = connectionKey(profileName, resolved.name);
|
|
336
|
-
const effectiveProfile = {
|
|
337
|
-
...profile,
|
|
338
|
-
binary: resolved.binary,
|
|
339
|
-
targetFilter: resolved.targetFilter,
|
|
340
|
-
};
|
|
323
|
+
// Registry decides WHERE. Three outcomes, no fourth: local if this
|
|
324
|
+
// device declares the name; tunnel to a declaring device otherwise;
|
|
325
|
+
// fail loud if nobody does. Never auto-create a local browser under a
|
|
326
|
+
// name that means a logged-in browser somewhere else.
|
|
327
|
+
const routed = resolveBrowserTarget(profileName, {
|
|
328
|
+
endpointName: opts.endpointName,
|
|
329
|
+
probe: opts.probe,
|
|
330
|
+
});
|
|
331
|
+
const composite = routed.key;
|
|
332
|
+
const effectiveProfile = routed.profile;
|
|
341
333
|
const taskId = generateTaskId();
|
|
342
334
|
let taskName;
|
|
343
335
|
if (opts.taskName) {
|
|
@@ -351,9 +343,12 @@ export class BrowserService {
|
|
|
351
343
|
taskName = taskId;
|
|
352
344
|
}
|
|
353
345
|
const taskLabel = deriveTaskLabel({ title: opts.title, url: opts.url });
|
|
354
|
-
|
|
355
|
-
let
|
|
356
|
-
|
|
346
|
+
const reused = await this.reuseProfileConnection(profileName, composite, routed.local);
|
|
347
|
+
let conn = reused?.conn;
|
|
348
|
+
let effectiveKey = reused?.key ?? composite;
|
|
349
|
+
// Fork is a local Electron chrome-data mint. Never fork a tunnelled
|
|
350
|
+
// connection — that would launch a logged-out browser on THIS box.
|
|
351
|
+
if (conn && routed.local && shouldForkProfile(routed.kind, conn)) {
|
|
357
352
|
if (this.forkingProfiles.has(composite)) {
|
|
358
353
|
while (this.forkingProfiles.has(composite)) {
|
|
359
354
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -380,9 +375,14 @@ export class BrowserService {
|
|
|
380
375
|
}
|
|
381
376
|
}
|
|
382
377
|
else if (!conn) {
|
|
383
|
-
|
|
378
|
+
adoptLegacyRuntimeIfLocal(routed.local, profileName, composite, getBrowserRuntimeDir());
|
|
379
|
+
conn = await this.openConnection(effectiveProfile, routed.target, composite, profileName, { persistRemote: !routed.local });
|
|
380
|
+
effectiveKey = composite;
|
|
384
381
|
}
|
|
385
|
-
if (
|
|
382
|
+
if (!conn) {
|
|
383
|
+
throw new Error(`Could not connect to profile "${profileName}"`);
|
|
384
|
+
}
|
|
385
|
+
if (!conn.profile)
|
|
386
386
|
conn.profile = profileName;
|
|
387
387
|
// Browsers launch with --no-startup-window (session-cookie persistence,
|
|
388
388
|
// see launchBrowser), so a bare `start` with no --url would otherwise
|
|
@@ -503,7 +503,16 @@ export class BrowserService {
|
|
|
503
503
|
if (resolved)
|
|
504
504
|
skill = resolved;
|
|
505
505
|
}
|
|
506
|
-
return {
|
|
506
|
+
return {
|
|
507
|
+
task: taskId,
|
|
508
|
+
name: taskName,
|
|
509
|
+
tabId,
|
|
510
|
+
profile: profileName,
|
|
511
|
+
key: effectiveKey,
|
|
512
|
+
device: routed.device,
|
|
513
|
+
picked: routed.picked,
|
|
514
|
+
skill,
|
|
515
|
+
};
|
|
507
516
|
}
|
|
508
517
|
/**
|
|
509
518
|
* Reclaim a live page already showing `url` from an ABANDONED task, instead
|
|
@@ -656,7 +665,7 @@ export class BrowserService {
|
|
|
656
665
|
/**
|
|
657
666
|
* Close tasks whose owning agent session is gone, or that have sat untouched
|
|
658
667
|
* past the idle window. The entry point the daemon's periodic tick and
|
|
659
|
-
* `agents browser
|
|
668
|
+
* `agents browser prune` both call; the policy lives in `hygiene.ts`.
|
|
660
669
|
*/
|
|
661
670
|
async reapAbandoned(opts = {}) {
|
|
662
671
|
return reapAbandonedTasks(this, opts);
|
|
@@ -736,7 +745,7 @@ export class BrowserService {
|
|
|
736
745
|
return this.stop(taskName);
|
|
737
746
|
}
|
|
738
747
|
async stopProfile(profileRef) {
|
|
739
|
-
// Connections are keyed by the runtime key `<profile>@<
|
|
748
|
+
// Connections are keyed by the runtime key `<profile>@<device>` (see
|
|
740
749
|
// start()) while callers pass the bare profile name (or, occasionally, an
|
|
741
750
|
// exact key). A plain `connections.get(profileRef)` therefore missed every
|
|
742
751
|
// real remote connection, so `cleanup()` never ran — leaving the SSH tunnel
|
|
@@ -1517,8 +1526,9 @@ export class BrowserService {
|
|
|
1517
1526
|
* `profileName` is a {@link ProfileName} — a BARE name, never a runtime key.
|
|
1518
1527
|
* Every candidate is selected by the one rule ({@link keyBelongsToProfile}),
|
|
1519
1528
|
* and a scoped query that finds no live connection falls through to disk, so
|
|
1520
|
-
* `status --profile comet-local` reports the LIVE `comet-local
|
|
1521
|
-
* instead of the empty list
|
|
1529
|
+
* `status --profile comet-local` reports the LIVE `comet-local@<device>`
|
|
1530
|
+
* (and leftover `comet-local@endpoint-0` dirs) instead of the empty list
|
|
1531
|
+
* it used to return (RUSH-2709).
|
|
1522
1532
|
*/
|
|
1523
1533
|
async status(profileRef) {
|
|
1524
1534
|
// Reconnect live browsers after a daemon restart so status is not empty
|
|
@@ -2052,14 +2062,24 @@ export class BrowserService {
|
|
|
2052
2062
|
* already resolved the endpoint and built the `effectiveProfile` with
|
|
2053
2063
|
* the per-endpoint binary/targetFilter overrides applied; we just use it.
|
|
2054
2064
|
*
|
|
2055
|
-
* `key` is the runtime key (`<profile>@<
|
|
2056
|
-
* lookup uses
|
|
2057
|
-
*
|
|
2058
|
-
* because `effectiveProfile.name` stays the bare, user-facing name (RUSH-2709).
|
|
2065
|
+
* `key` is the runtime key (`<profile>@<device>`) and is what every on-disk
|
|
2066
|
+
* lookup uses. It is a SEPARATE argument because `effectiveProfile.name`
|
|
2067
|
+
* stays the bare, user-facing name (RUSH-2709).
|
|
2059
2068
|
*/
|
|
2060
|
-
async connectProfile(effectiveProfile, target, key) {
|
|
2069
|
+
async connectProfile(effectiveProfile, target, key, opts = {}) {
|
|
2061
2070
|
const existingInfo = getRunningChromeInfo(key);
|
|
2062
|
-
|
|
2071
|
+
const tunnelled = opts.persistRemote || target.startsWith('ssh:');
|
|
2072
|
+
if (tunnelled) {
|
|
2073
|
+
// A leftover local chrome-data / pid file under this key is the
|
|
2074
|
+
// pre-T2 logged-out browser on THIS box. Attaching localhost CDP
|
|
2075
|
+
// here is the original bug. Only a recorded SSH tunnel is ours to
|
|
2076
|
+
// reuse, and connectSSH already does that via isOwnTunnel.
|
|
2077
|
+
const meta = readProfileRuntimeMeta(key);
|
|
2078
|
+
if (meta && meta.kind !== 'tunnel') {
|
|
2079
|
+
clearProfileRuntime(key);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
else if (existingInfo) {
|
|
2063
2083
|
try {
|
|
2064
2084
|
const { wsUrl, browser } = await discoverBrowserWsUrl(existingInfo.port, 'localhost', effectiveProfile.name);
|
|
2065
2085
|
verifyBrowserIdentity(browser, effectiveProfile.browser, existingInfo.port);
|
|
@@ -2088,13 +2108,13 @@ export class BrowserService {
|
|
|
2088
2108
|
clearProfileRuntime(key);
|
|
2089
2109
|
}
|
|
2090
2110
|
}
|
|
2091
|
-
const conn = await this.connectEndpoint(effectiveProfile, target, key);
|
|
2111
|
+
const conn = await this.connectEndpoint(effectiveProfile, target, key, opts);
|
|
2092
2112
|
if (!conn) {
|
|
2093
2113
|
throw new Error(`Could not connect to endpoint ${target} for profile "${effectiveProfile.name}"`);
|
|
2094
2114
|
}
|
|
2095
2115
|
return conn;
|
|
2096
2116
|
}
|
|
2097
|
-
async connectEndpoint(profile, endpoint, key) {
|
|
2117
|
+
async connectEndpoint(profile, endpoint, key, opts = {}) {
|
|
2098
2118
|
const url = new URL(endpoint);
|
|
2099
2119
|
if (url.protocol === 'cdp:') {
|
|
2100
2120
|
const conn = await connectLocal(endpoint, profile, key);
|
|
@@ -2111,7 +2131,9 @@ export class BrowserService {
|
|
|
2111
2131
|
};
|
|
2112
2132
|
}
|
|
2113
2133
|
if (url.protocol === 'ssh:') {
|
|
2114
|
-
const conn = await connectSSH(endpoint, profile, key
|
|
2134
|
+
const conn = await connectSSH(endpoint, profile, key, {
|
|
2135
|
+
persistRemote: opts.persistRemote,
|
|
2136
|
+
});
|
|
2115
2137
|
await this.enableDomains(conn.cdp);
|
|
2116
2138
|
return {
|
|
2117
2139
|
cdp: conn.cdp,
|
|
@@ -2265,6 +2287,15 @@ export class BrowserService {
|
|
|
2265
2287
|
* - no handle, zero, !createIfMissing → return null (caller reports "nothing to close")
|
|
2266
2288
|
*/
|
|
2267
2289
|
async resolveOrCreateTask(opts) {
|
|
2290
|
+
// Consent gate — the one chokepoint every drive/mutate verb passes through.
|
|
2291
|
+
// ipc.ts routes each PAGE_RESOLVE_VERB and CLOSE_VERB here via bindTask, so
|
|
2292
|
+
// gating at the top refuses a fleet-remote ATTACH (an explicit --task, or a
|
|
2293
|
+
// lone identity match) exactly as it already refused an implicit CREATE.
|
|
2294
|
+
// Gating only the create branch below left tab-add/navigate/done on an
|
|
2295
|
+
// already-running browser ungated, so a remote caller could drive the
|
|
2296
|
+
// owner's authenticated profile with consent off (RUSH-3064). Read the
|
|
2297
|
+
// per-request marker, never the daemon's env — see remote-control.ts.
|
|
2298
|
+
assertRemoteControlAllowedForRequest(opts.fleetRemote, { actor: opts.actor });
|
|
2268
2299
|
if (opts.task) {
|
|
2269
2300
|
const found = await this.findTask(opts.task, opts.profile);
|
|
2270
2301
|
return { ...found, created: false };
|
|
@@ -2298,10 +2329,10 @@ export class BrowserService {
|
|
|
2298
2329
|
// Zero matches for this caller.
|
|
2299
2330
|
if (!opts.createIfMissing)
|
|
2300
2331
|
return null;
|
|
2301
|
-
//
|
|
2302
|
-
//
|
|
2303
|
-
//
|
|
2304
|
-
|
|
2332
|
+
// The top-of-function consent gate already refused a fleet-remote create
|
|
2333
|
+
// here — before ensureDefaultBrowserProfile() below can probe local browser
|
|
2334
|
+
// installs and persist an `auto-chrome` profile — so a refused request never
|
|
2335
|
+
// leaves state behind on the target machine.
|
|
2305
2336
|
// Implicit start on the default / named profile.
|
|
2306
2337
|
let profileName = opts.profile;
|
|
2307
2338
|
if (!profileName) {
|
|
@@ -2317,7 +2348,12 @@ export class BrowserService {
|
|
|
2317
2348
|
sessionId: opts.sessionId,
|
|
2318
2349
|
title: opts.title,
|
|
2319
2350
|
});
|
|
2320
|
-
return {
|
|
2351
|
+
return {
|
|
2352
|
+
...(await this.findTask(started.name, started.profile)),
|
|
2353
|
+
created: true,
|
|
2354
|
+
picked: started.picked,
|
|
2355
|
+
device: started.device,
|
|
2356
|
+
};
|
|
2321
2357
|
}
|
|
2322
2358
|
listTasksForCaller(caller) {
|
|
2323
2359
|
const out = [];
|
|
@@ -2458,7 +2494,15 @@ export class BrowserService {
|
|
|
2458
2494
|
return null;
|
|
2459
2495
|
let resolved;
|
|
2460
2496
|
try {
|
|
2461
|
-
|
|
2497
|
+
// Legacy keys encode the preset (`endpoint-0`). New keys encode the
|
|
2498
|
+
// declaring device, which is not a preset name — resolve via registry.
|
|
2499
|
+
if (endpointFromKey && /^endpoint-\d+$/.test(endpointFromKey)) {
|
|
2500
|
+
resolved = resolveEndpoint(profile, endpointFromKey);
|
|
2501
|
+
}
|
|
2502
|
+
else {
|
|
2503
|
+
const routed = resolveBrowserTarget(bare);
|
|
2504
|
+
resolved = { name: routed.device, target: routed.target };
|
|
2505
|
+
}
|
|
2462
2506
|
}
|
|
2463
2507
|
catch {
|
|
2464
2508
|
return null;
|
|
@@ -2851,9 +2895,35 @@ export class BrowserService {
|
|
|
2851
2895
|
this.connections.delete(key);
|
|
2852
2896
|
return undefined;
|
|
2853
2897
|
}
|
|
2898
|
+
/**
|
|
2899
|
+
* Reuse a live connection for this profile: the new `<name>@<device>` key
|
|
2900
|
+
* first, then any leftover `<name>@endpoint-N` key still in the map so a
|
|
2901
|
+
* browser started before the key collapse is not abandoned.
|
|
2902
|
+
*/
|
|
2903
|
+
async reuseProfileConnection(profileName, preferred, local) {
|
|
2904
|
+
const preferredConn = await this.reuseHealthyConnection(preferred);
|
|
2905
|
+
if (preferredConn)
|
|
2906
|
+
return { conn: preferredConn, key: preferred };
|
|
2907
|
+
// Leftover `@endpoint-N` connections are THIS machine's pre-T2 local
|
|
2908
|
+
// browser. Reusing them on a tunnelled resolve is the original bug.
|
|
2909
|
+
if (!local)
|
|
2910
|
+
return undefined;
|
|
2911
|
+
for (const key of [...this.connections.keys()]) {
|
|
2912
|
+
if (key === preferred)
|
|
2913
|
+
continue;
|
|
2914
|
+
if (!isLegacyEndpointKey(key, profileName))
|
|
2915
|
+
continue;
|
|
2916
|
+
if (parseConnectionKey(key).fork !== undefined)
|
|
2917
|
+
continue;
|
|
2918
|
+
const conn = await this.reuseHealthyConnection(key);
|
|
2919
|
+
if (conn)
|
|
2920
|
+
return { conn, key };
|
|
2921
|
+
}
|
|
2922
|
+
return undefined;
|
|
2923
|
+
}
|
|
2854
2924
|
/** Connect a profile at `target`, register it under `key`, and arm downloads. */
|
|
2855
|
-
async openConnection(profile, target, key, profileName) {
|
|
2856
|
-
const conn = await this.connectProfile(profile, target, key);
|
|
2925
|
+
async openConnection(profile, target, key, profileName, opts = {}) {
|
|
2926
|
+
const conn = await this.connectProfile(profile, target, key, opts);
|
|
2857
2927
|
conn.key = key;
|
|
2858
2928
|
conn.profile = profileName;
|
|
2859
2929
|
this.connections.set(key, conn);
|
|
@@ -2876,22 +2946,29 @@ export class BrowserService {
|
|
|
2876
2946
|
// Same consent gate as start(): this opens a browser, so a fleet-remote
|
|
2877
2947
|
// caller needs the target machine's opt-in.
|
|
2878
2948
|
assertRemoteControlAllowedForRequest(opts.fleetRemote, { actor: opts.actor });
|
|
2879
|
-
const
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
const key =
|
|
2884
|
-
const effectiveProfile =
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2949
|
+
const routed = resolveBrowserTarget(profileName, {
|
|
2950
|
+
endpointName: opts.endpointName,
|
|
2951
|
+
probe: opts.probe,
|
|
2952
|
+
});
|
|
2953
|
+
const key = routed.key;
|
|
2954
|
+
const effectiveProfile = routed.profile;
|
|
2955
|
+
const reused = await this.reuseProfileConnection(profileName, key, routed.local);
|
|
2956
|
+
let conn = reused?.conn;
|
|
2957
|
+
const effectiveKey = reused?.key ?? key;
|
|
2958
|
+
if (!conn) {
|
|
2959
|
+
adoptLegacyRuntimeIfLocal(routed.local, profileName, key, getBrowserRuntimeDir());
|
|
2960
|
+
conn = await this.openConnection(effectiveProfile, routed.target, key, profileName, { persistRemote: !routed.local });
|
|
2961
|
+
}
|
|
2891
2962
|
// createPageTarget refuses Arc with an actionable error rather than crashing it.
|
|
2892
2963
|
const created = await this.createPageTarget(conn, { url });
|
|
2893
2964
|
this.invalidateTargetCache(conn);
|
|
2894
|
-
return {
|
|
2965
|
+
return {
|
|
2966
|
+
profile: profileName,
|
|
2967
|
+
key: effectiveKey,
|
|
2968
|
+
tabId: created.targetId,
|
|
2969
|
+
device: routed.device,
|
|
2970
|
+
picked: routed.picked,
|
|
2971
|
+
};
|
|
2895
2972
|
}
|
|
2896
2973
|
async getHistory(limit = 10) {
|
|
2897
2974
|
const historyFile = path.join(getBrowserRuntimeDir(), 'history.json');
|