@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BrowserProfileConfig } from '../types.js';
|
|
2
|
+
export interface ProfileDeclaration {
|
|
3
|
+
device: string;
|
|
4
|
+
config: BrowserProfileConfig;
|
|
5
|
+
}
|
|
6
|
+
/** Leftover central `browser:` map from before per-device declarations. Empty when none remain. */
|
|
7
|
+
export declare function centralBrowserProfiles(): Record<string, BrowserProfileConfig>;
|
|
8
|
+
export interface CentralClaimResult {
|
|
9
|
+
claimed: string[];
|
|
10
|
+
skipped: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fold leftover central `browser:` entries into THIS device's declaration file.
|
|
14
|
+
*
|
|
15
|
+
* Never called implicitly, and that is the point. Every device can read the
|
|
16
|
+
* central map, so an implicit claim races: whichever box happens to read first
|
|
17
|
+
* claims the name, {@link profileKind} then reports it `identity`, and the
|
|
18
|
+
* daemon tunnels to that box — which for a fleet-wide `cdp://localhost:*`
|
|
19
|
+
* profile is a logged-out headless browser wearing a credentialed browser's
|
|
20
|
+
* name. That is the exact bug this module exists to remove, and an implicit
|
|
21
|
+
* migration would write it to disk as a stored fact.
|
|
22
|
+
*
|
|
23
|
+
* The claim is an explicit operator action (`agents browser profiles claim`),
|
|
24
|
+
* run on the machine that actually owns the browser. `canHostHere` is supplied
|
|
25
|
+
* by the command layer so this module stays a leaf — it must not import
|
|
26
|
+
* `isProfileLaunchableHere` (that would cycle through chrome.ts → profiles.ts).
|
|
27
|
+
* Only profiles this machine can host are claimed; the rest stay central,
|
|
28
|
+
* undeclared, and fail loudly on resolve.
|
|
29
|
+
*/
|
|
30
|
+
export declare function migrateCentralBrowserProfiles(canHostHere: (config: BrowserProfileConfig) => boolean, name?: string): CentralClaimResult;
|
|
31
|
+
/**
|
|
32
|
+
* Every profile any device declares, keyed by name to all declaring devices.
|
|
33
|
+
* Reads every `devices/<name>/agents.yaml`; declarations never overwrite.
|
|
34
|
+
*/
|
|
35
|
+
export declare function profileRegistry(): Map<string, ProfileDeclaration[]>;
|
|
36
|
+
/** Devices declaring `name`, empty when nobody does. */
|
|
37
|
+
export declare function declaringDevices(name: string): string[];
|
|
38
|
+
/** Exactly one declaring device is identity-bearing; several are fungible. */
|
|
39
|
+
export declare function profileKind(name: string): 'identity' | 'fungible' | null;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import * as yaml from 'yaml';
|
|
5
|
+
import { getUserAgentsDir, readMeta, updateMeta } from '../state.js';
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
/** Leftover central `browser:` map from before per-device declarations. Empty when none remain. */
|
|
10
|
+
export function centralBrowserProfiles() {
|
|
11
|
+
const central = readMeta().browser;
|
|
12
|
+
if (!central || Object.keys(central).length === 0)
|
|
13
|
+
return {};
|
|
14
|
+
return { ...central };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fold leftover central `browser:` entries into THIS device's declaration file.
|
|
18
|
+
*
|
|
19
|
+
* Never called implicitly, and that is the point. Every device can read the
|
|
20
|
+
* central map, so an implicit claim races: whichever box happens to read first
|
|
21
|
+
* claims the name, {@link profileKind} then reports it `identity`, and the
|
|
22
|
+
* daemon tunnels to that box — which for a fleet-wide `cdp://localhost:*`
|
|
23
|
+
* profile is a logged-out headless browser wearing a credentialed browser's
|
|
24
|
+
* name. That is the exact bug this module exists to remove, and an implicit
|
|
25
|
+
* migration would write it to disk as a stored fact.
|
|
26
|
+
*
|
|
27
|
+
* The claim is an explicit operator action (`agents browser profiles claim`),
|
|
28
|
+
* run on the machine that actually owns the browser. `canHostHere` is supplied
|
|
29
|
+
* by the command layer so this module stays a leaf — it must not import
|
|
30
|
+
* `isProfileLaunchableHere` (that would cycle through chrome.ts → profiles.ts).
|
|
31
|
+
* Only profiles this machine can host are claimed; the rest stay central,
|
|
32
|
+
* undeclared, and fail loudly on resolve.
|
|
33
|
+
*/
|
|
34
|
+
export function migrateCentralBrowserProfiles(canHostHere, name) {
|
|
35
|
+
const meta = readMeta();
|
|
36
|
+
const central = meta.browser;
|
|
37
|
+
if (!central || Object.keys(central).length === 0) {
|
|
38
|
+
if (name) {
|
|
39
|
+
throw new Error(`No leftover central browser profile named "${name}".`);
|
|
40
|
+
}
|
|
41
|
+
return { claimed: [], skipped: [] };
|
|
42
|
+
}
|
|
43
|
+
const local = meta.deviceBrowser ?? {};
|
|
44
|
+
if (name) {
|
|
45
|
+
const config = central[name];
|
|
46
|
+
if (!config) {
|
|
47
|
+
throw new Error(local[name]
|
|
48
|
+
? `Browser profile "${name}" is already declared on this device.`
|
|
49
|
+
: `No leftover central browser profile named "${name}".`);
|
|
50
|
+
}
|
|
51
|
+
if (!canHostHere(config)) {
|
|
52
|
+
throw new Error(`Cannot claim browser profile "${name}": this machine cannot host it ` +
|
|
53
|
+
`(its browser/binary isn't installed here). Run this command on the machine that has that browser.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const toClaim = {};
|
|
57
|
+
const skipped = [];
|
|
58
|
+
for (const [profileName, config] of Object.entries(central)) {
|
|
59
|
+
if (name && profileName !== name)
|
|
60
|
+
continue;
|
|
61
|
+
if (!canHostHere(config)) {
|
|
62
|
+
skipped.push(profileName);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const existing = local[profileName];
|
|
66
|
+
if (existing && !isDeepStrictEqual(existing, config)) {
|
|
67
|
+
throw new Error(`Cannot migrate browser profile "${profileName}": central agents.yaml and this device's ` +
|
|
68
|
+
`agents.yaml declare different configurations. Resolve the duplicate before retrying.`);
|
|
69
|
+
}
|
|
70
|
+
toClaim[profileName] = config;
|
|
71
|
+
}
|
|
72
|
+
const claimed = Object.keys(toClaim).sort();
|
|
73
|
+
skipped.sort();
|
|
74
|
+
if (claimed.length === 0)
|
|
75
|
+
return { claimed, skipped };
|
|
76
|
+
updateMeta((current) => {
|
|
77
|
+
const legacy = current;
|
|
78
|
+
const remaining = {};
|
|
79
|
+
for (const [profileName, config] of Object.entries(legacy.browser ?? {})) {
|
|
80
|
+
if (!(profileName in toClaim))
|
|
81
|
+
remaining[profileName] = config;
|
|
82
|
+
}
|
|
83
|
+
const { browser: _removed, ...withoutCentralBrowser } = legacy;
|
|
84
|
+
return {
|
|
85
|
+
...withoutCentralBrowser,
|
|
86
|
+
...(Object.keys(remaining).length > 0 ? { browser: remaining } : {}),
|
|
87
|
+
deviceBrowser: { ...current.deviceBrowser, ...toClaim },
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
return { claimed, skipped };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Every profile any device declares, keyed by name to all declaring devices.
|
|
94
|
+
* Reads every `devices/<name>/agents.yaml`; declarations never overwrite.
|
|
95
|
+
*/
|
|
96
|
+
export function profileRegistry() {
|
|
97
|
+
const registry = new Map();
|
|
98
|
+
const devicesDir = path.join(getUserAgentsDir(), 'devices');
|
|
99
|
+
if (!fs.existsSync(devicesDir))
|
|
100
|
+
return registry;
|
|
101
|
+
const devices = fs
|
|
102
|
+
.readdirSync(devicesDir, { withFileTypes: true })
|
|
103
|
+
.filter((entry) => entry.isDirectory())
|
|
104
|
+
.map((entry) => entry.name)
|
|
105
|
+
.sort();
|
|
106
|
+
for (const device of devices) {
|
|
107
|
+
const file = path.join(devicesDir, device, 'agents.yaml');
|
|
108
|
+
if (!fs.existsSync(file))
|
|
109
|
+
continue;
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = yaml.parse(fs.readFileSync(file, 'utf8'));
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw new Error(`Cannot read browser declarations from ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
116
|
+
}
|
|
117
|
+
if (parsed == null)
|
|
118
|
+
continue;
|
|
119
|
+
if (!isRecord(parsed)) {
|
|
120
|
+
throw new Error(`Cannot read browser declarations from ${file}: document root must be a map.`);
|
|
121
|
+
}
|
|
122
|
+
const browser = parsed.browser;
|
|
123
|
+
if (browser == null)
|
|
124
|
+
continue;
|
|
125
|
+
if (!isRecord(browser)) {
|
|
126
|
+
throw new Error(`Cannot read browser declarations from ${file}: browser must be a map.`);
|
|
127
|
+
}
|
|
128
|
+
for (const [name, config] of Object.entries(browser)) {
|
|
129
|
+
if (!isRecord(config)) {
|
|
130
|
+
throw new Error(`Cannot read browser declaration "${name}" from ${file}: profile must be a map.`);
|
|
131
|
+
}
|
|
132
|
+
const declarations = registry.get(name) ?? [];
|
|
133
|
+
declarations.push({ device, config: config });
|
|
134
|
+
registry.set(name, declarations);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return registry;
|
|
138
|
+
}
|
|
139
|
+
/** Devices declaring `name`, empty when nobody does. */
|
|
140
|
+
export function declaringDevices(name) {
|
|
141
|
+
return (profileRegistry().get(name) ?? []).map((declaration) => declaration.device);
|
|
142
|
+
}
|
|
143
|
+
/** Exactly one declaring device is identity-bearing; several are fungible. */
|
|
144
|
+
export function profileKind(name) {
|
|
145
|
+
const count = profileRegistry().get(name)?.length ?? 0;
|
|
146
|
+
if (count === 0)
|
|
147
|
+
return null;
|
|
148
|
+
return count === 1 ? 'identity' : 'fungible';
|
|
149
|
+
}
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
* drives. Read-only queries are not gated.
|
|
13
13
|
*
|
|
14
14
|
* The authoritative gate is {@link assertRemoteControlAllowedForRequest}, called
|
|
15
|
-
* inside the browser daemon at the
|
|
16
|
-
*
|
|
17
|
-
* to live there because ~18
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* inside the browser daemon at the top of `resolveOrCreateTask` — the one
|
|
16
|
+
* chokepoint every task-scoped verb resolves through — plus `BrowserService.start`
|
|
17
|
+
* for the task-less `browser start` command. It has to live there because ~18
|
|
18
|
+
* page verbs (`navigate`, `click`, `screenshot`, `tab-add`, …) launch OR attach
|
|
19
|
+
* to a browser implicitly, and gating only the `browser start` command left every
|
|
20
|
+
* one of them ungated; gating only the create branch of `resolveOrCreateTask` left
|
|
21
|
+
* the attach paths ungated (RUSH-3064). {@link assertRemoteControlAllowed} remains
|
|
22
|
+
* as a fast-fail CLI-side check so a refused `start` never auto-creates a profile.
|
|
21
23
|
*/
|
|
22
24
|
/** Env marker set on every remote `agents` invocation by `buildRemoteAgentsInvocation`. */
|
|
23
25
|
export declare const FLEET_REMOTE_ENV = "AGENTS_FLEET_REMOTE";
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
* drives. Read-only queries are not gated.
|
|
13
13
|
*
|
|
14
14
|
* The authoritative gate is {@link assertRemoteControlAllowedForRequest}, called
|
|
15
|
-
* inside the browser daemon at the
|
|
16
|
-
*
|
|
17
|
-
* to live there because ~18
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* inside the browser daemon at the top of `resolveOrCreateTask` — the one
|
|
16
|
+
* chokepoint every task-scoped verb resolves through — plus `BrowserService.start`
|
|
17
|
+
* for the task-less `browser start` command. It has to live there because ~18
|
|
18
|
+
* page verbs (`navigate`, `click`, `screenshot`, `tab-add`, …) launch OR attach
|
|
19
|
+
* to a browser implicitly, and gating only the `browser start` command left every
|
|
20
|
+
* one of them ungated; gating only the create branch of `resolveOrCreateTask` left
|
|
21
|
+
* the attach paths ungated (RUSH-3064). {@link assertRemoteControlAllowed} remains
|
|
22
|
+
* as a fast-fail CLI-side check so a refused `start` never auto-creates a profile.
|
|
21
23
|
*/
|
|
22
24
|
import { getConfigValue } from '../device-config.js';
|
|
23
25
|
/** Env marker set on every remote `agents` invocation by `buildRemoteAgentsInvocation`. */
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type ProfileDeclaration } from './registry.js';
|
|
2
|
+
import { type BrowserProfile, type ConnectionKey } from './types.js';
|
|
3
|
+
import type { BrowserProfileConfig } from '../types.js';
|
|
4
|
+
export type DeviceProbeResult = {
|
|
5
|
+
reachable: true;
|
|
6
|
+
os?: string;
|
|
7
|
+
} | {
|
|
8
|
+
reachable: false;
|
|
9
|
+
reason: string;
|
|
10
|
+
};
|
|
11
|
+
export type DeviceProbe = (device: string) => DeviceProbeResult;
|
|
12
|
+
export interface ResolvedBrowserTarget {
|
|
13
|
+
name: string;
|
|
14
|
+
kind: 'identity' | 'fungible';
|
|
15
|
+
/** Device the daemon will talk to. */
|
|
16
|
+
device: string;
|
|
17
|
+
local: boolean;
|
|
18
|
+
key: ConnectionKey;
|
|
19
|
+
/** `cdp:` locally, `ssh:` when tunnelling to a declaring device. */
|
|
20
|
+
target: string;
|
|
21
|
+
profile: BrowserProfile;
|
|
22
|
+
/**
|
|
23
|
+
* Human line when the daemon picked a remote device. Command output
|
|
24
|
+
* surfaces this so the caller knows WHERE the browser actually lives.
|
|
25
|
+
*/
|
|
26
|
+
picked?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Fork only fungible Electron profiles; identity-bearing ones share one connection. */
|
|
29
|
+
export declare function shouldForkProfile(kind: 'identity' | 'fungible', conn: {
|
|
30
|
+
electron?: boolean;
|
|
31
|
+
tasks: {
|
|
32
|
+
size: number;
|
|
33
|
+
};
|
|
34
|
+
}): boolean;
|
|
35
|
+
export declare function profileFromDeclaration(name: string, declaration: ProfileDeclaration): BrowserProfile;
|
|
36
|
+
/** Runtime key: profile name plus the resolved device. Drops `@endpoint-N`. */
|
|
37
|
+
export declare function profileConnectionKey(profileName: string, device: string): ConnectionKey;
|
|
38
|
+
/** Declared names that look like `query` — used in the undeclared error. */
|
|
39
|
+
export declare function similarProfileNames(query: string, names: string[]): string[];
|
|
40
|
+
export declare function undeclaredProfileError(name: string): Error;
|
|
41
|
+
export declare function unreachableDeclaringDevicesError(name: string, devices: string[], failures: string[]): Error;
|
|
42
|
+
export declare function sshEndpointForDeclaration(device: string, config: BrowserProfileConfig, endpointName?: string, os?: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Decide where `name` connects. Never claims a central leftover. Never
|
|
45
|
+
* falls back to a local browser when the name is declared elsewhere.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveBrowserTarget(name: string, opts?: {
|
|
48
|
+
endpointName?: string;
|
|
49
|
+
here?: string;
|
|
50
|
+
probe?: DeviceProbe;
|
|
51
|
+
}): ResolvedBrowserTarget;
|
|
52
|
+
/**
|
|
53
|
+
* Rename a leftover `<name>@endpoint-N` runtime dir to the new
|
|
54
|
+
* `<name>@<device>` key so logins in chrome-data are not orphaned.
|
|
55
|
+
*
|
|
56
|
+
* Only when exactly one legacy dir exists and the destination does not —
|
|
57
|
+
* several endpoint dirs stay put so a multi-endpoint profile is not merged.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Adopt a leftover `@endpoint-N` runtime dir onto the new key ONLY for a
|
|
61
|
+
* local connect. Doing this for a tunnel key would rename this machine's
|
|
62
|
+
* pre-T2 logged-out chrome-data onto `comet-local@zion`, and connectProfile
|
|
63
|
+
* would then attach localhost CDP instead of tunnelling.
|
|
64
|
+
*/
|
|
65
|
+
export declare function adoptLegacyRuntimeIfLocal(local: boolean, profileName: string, newKey: ConnectionKey, runtimeRoot: string): void;
|
|
66
|
+
export declare function migrateLegacyRuntimeDir(profileName: string, newKey: ConnectionKey, runtimeRoot: string): void;
|
|
67
|
+
/** True when a live key is a leftover `@endpoint-N` key of `profileName`. */
|
|
68
|
+
export declare function isLegacyEndpointKey(key: string, profileName: string): boolean;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry-driven browser target resolution.
|
|
3
|
+
*
|
|
4
|
+
* The profile KIND is not a field: it falls out of how many devices declare
|
|
5
|
+
* the name ({@link profileKind}). This module answers where a name should
|
|
6
|
+
* connect, and never claims a leftover central profile (that race is T1).
|
|
7
|
+
*
|
|
8
|
+
* Three outcomes, no fourth:
|
|
9
|
+
* 1. THIS device declares the name → connect locally.
|
|
10
|
+
* 2. Only OTHER devices declare it → tunnel to a reachable declaring device.
|
|
11
|
+
* 3. Nobody declares it → fail loud. Never auto-create a local browser
|
|
12
|
+
* under a name that means a logged-in browser somewhere else.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
import { machineId } from '../machine-id.js';
|
|
17
|
+
import { centralBrowserProfiles, declaringDevices, profileKind, profileRegistry, } from './registry.js';
|
|
18
|
+
import { parseEndpointUrl, resolveEndpoint } from './profiles.js';
|
|
19
|
+
import { connectionKey, parseConnectionKey, } from './types.js';
|
|
20
|
+
import { probeHost } from '../hosts/ready.js';
|
|
21
|
+
/** Fork only fungible Electron profiles; identity-bearing ones share one connection. */
|
|
22
|
+
export function shouldForkProfile(kind, conn) {
|
|
23
|
+
if (kind === 'identity')
|
|
24
|
+
return false;
|
|
25
|
+
return Boolean(conn.electron && conn.tasks.size > 0);
|
|
26
|
+
}
|
|
27
|
+
export function profileFromDeclaration(name, declaration) {
|
|
28
|
+
const config = declaration.config;
|
|
29
|
+
return {
|
|
30
|
+
name,
|
|
31
|
+
description: config.description,
|
|
32
|
+
browser: config.browser,
|
|
33
|
+
binary: config.binary,
|
|
34
|
+
electron: config.electron,
|
|
35
|
+
targetFilter: config.targetFilter,
|
|
36
|
+
endpoints: config.endpoints,
|
|
37
|
+
defaultEndpoint: config.defaultEndpoint,
|
|
38
|
+
chrome: config.chrome,
|
|
39
|
+
secrets: config.secrets,
|
|
40
|
+
viewport: config.viewport,
|
|
41
|
+
logDir: config.logDir,
|
|
42
|
+
logHost: config.logHost,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Runtime key: profile name plus the resolved device. Drops `@endpoint-N`. */
|
|
46
|
+
export function profileConnectionKey(profileName, device) {
|
|
47
|
+
return connectionKey(profileName, device);
|
|
48
|
+
}
|
|
49
|
+
function levenshtein(a, b) {
|
|
50
|
+
if (a === b)
|
|
51
|
+
return 0;
|
|
52
|
+
if (a.length === 0)
|
|
53
|
+
return b.length;
|
|
54
|
+
if (b.length === 0)
|
|
55
|
+
return a.length;
|
|
56
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
57
|
+
for (let i = 0; i < a.length; i++) {
|
|
58
|
+
let prev = row[0];
|
|
59
|
+
row[0] = i + 1;
|
|
60
|
+
for (let j = 0; j < b.length; j++) {
|
|
61
|
+
const cur = row[j + 1];
|
|
62
|
+
const cost = a[i] === b[j] ? 0 : 1;
|
|
63
|
+
row[j + 1] = Math.min(row[j] + 1, row[j + 1] + 1, prev + cost);
|
|
64
|
+
prev = cur;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return row[b.length];
|
|
68
|
+
}
|
|
69
|
+
/** Declared names that look like `query` — used in the undeclared error. */
|
|
70
|
+
export function similarProfileNames(query, names) {
|
|
71
|
+
const q = query.toLowerCase();
|
|
72
|
+
const maxDist = Math.max(2, Math.floor(q.length / 4));
|
|
73
|
+
return names
|
|
74
|
+
.filter((name) => {
|
|
75
|
+
const x = name.toLowerCase();
|
|
76
|
+
if (x === q)
|
|
77
|
+
return false;
|
|
78
|
+
if (x.includes(q) || q.includes(x))
|
|
79
|
+
return Math.min(x.length, q.length) >= 3;
|
|
80
|
+
return levenshtein(x, q) <= maxDist;
|
|
81
|
+
})
|
|
82
|
+
.sort();
|
|
83
|
+
}
|
|
84
|
+
function lines(...parts) {
|
|
85
|
+
return parts.filter((part) => part != null && part !== '').join('\n');
|
|
86
|
+
}
|
|
87
|
+
export function undeclaredProfileError(name) {
|
|
88
|
+
const central = centralBrowserProfiles();
|
|
89
|
+
if (central[name]) {
|
|
90
|
+
return new Error(lines(`Browser profile "${name}" is not declared by any device.`, `It still lives in the central agents.yaml browser: map.`, `Claim it on the machine that hosts that browser with: agents browser profiles claim ${name}`));
|
|
91
|
+
}
|
|
92
|
+
const registry = profileRegistry();
|
|
93
|
+
const similar = similarProfileNames(name, [...registry.keys()]);
|
|
94
|
+
const similarLines = similar.map((candidate) => {
|
|
95
|
+
const devices = (registry.get(candidate) ?? []).map((entry) => entry.device);
|
|
96
|
+
return devices.length > 0
|
|
97
|
+
? ` ${candidate} (declared on ${devices.join(', ')})`
|
|
98
|
+
: ` ${candidate}`;
|
|
99
|
+
});
|
|
100
|
+
return new Error(lines(`Browser profile "${name}" is not declared by any device.`, ...(similarLines.length > 0
|
|
101
|
+
? ['Similar names:', ...similarLines]
|
|
102
|
+
: ['No device declares a similar name.']), `Create it on the machine that hosts the browser: agents browser profiles create ${name} --browser <chrome|comet|chromium|brave|edge|arc|custom>`, `Or, if it is a leftover central profile, claim it: agents browser profiles claim ${name}`));
|
|
103
|
+
}
|
|
104
|
+
export function unreachableDeclaringDevicesError(name, devices, failures) {
|
|
105
|
+
return new Error(lines(`Browser profile "${name}" is declared on ${devices.join(', ')}, but none of those devices are reachable.`, ...failures.map((line) => ` ${line}`), `A local browser will not be launched — that would be a logged-out copy of an identity-bearing profile.`));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build the SSH endpoint used to tunnel to a declaring device's own browser.
|
|
109
|
+
*
|
|
110
|
+
* A `cdp://localhost:N` declaration on that device is THIS machine's loopback
|
|
111
|
+
* if used as-is — that is the original bug. Rewrite it to `ssh://<device>?port=N`.
|
|
112
|
+
* An already-`ssh://` declaration is used as written.
|
|
113
|
+
*/
|
|
114
|
+
function isWindowsOs(os) {
|
|
115
|
+
if (!os)
|
|
116
|
+
return false;
|
|
117
|
+
const normalized = os.toLowerCase();
|
|
118
|
+
return (normalized.includes('windows') ||
|
|
119
|
+
normalized === 'win32' ||
|
|
120
|
+
normalized === 'win64' ||
|
|
121
|
+
normalized === 'windows_nt');
|
|
122
|
+
}
|
|
123
|
+
export function sshEndpointForDeclaration(device, config, endpointName, os) {
|
|
124
|
+
const profile = profileFromDeclaration('_', { device, config });
|
|
125
|
+
const resolved = resolveEndpoint(profile, endpointName);
|
|
126
|
+
if (resolved.target.startsWith('ssh:'))
|
|
127
|
+
return resolved.target;
|
|
128
|
+
const parsed = parseEndpointUrl(resolved.target);
|
|
129
|
+
const port = parsed?.port ?? 9222;
|
|
130
|
+
const osQuery = isWindowsOs(os) ? '&os=windows' : '';
|
|
131
|
+
return `ssh://${device}?port=${port}${osQuery}`;
|
|
132
|
+
}
|
|
133
|
+
function defaultProbe(device) {
|
|
134
|
+
try {
|
|
135
|
+
const result = probeHost(device);
|
|
136
|
+
if (result.reachable)
|
|
137
|
+
return { reachable: true, os: result.os };
|
|
138
|
+
return { reachable: false, reason: `ssh to ${device} failed` };
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
reachable: false,
|
|
143
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Decide where `name` connects. Never claims a central leftover. Never
|
|
149
|
+
* falls back to a local browser when the name is declared elsewhere.
|
|
150
|
+
*/
|
|
151
|
+
export function resolveBrowserTarget(name, opts = {}) {
|
|
152
|
+
const here = opts.here ?? machineId();
|
|
153
|
+
const kind = profileKind(name);
|
|
154
|
+
const declarations = profileRegistry().get(name) ?? [];
|
|
155
|
+
if (kind === null || declarations.length === 0) {
|
|
156
|
+
throw undeclaredProfileError(name);
|
|
157
|
+
}
|
|
158
|
+
const devices = declaringDevices(name);
|
|
159
|
+
const local = declarations.find((entry) => entry.device === here);
|
|
160
|
+
if (local) {
|
|
161
|
+
const profile = profileFromDeclaration(name, local);
|
|
162
|
+
const resolved = resolveEndpoint(profile, opts.endpointName);
|
|
163
|
+
return {
|
|
164
|
+
name,
|
|
165
|
+
kind,
|
|
166
|
+
device: here,
|
|
167
|
+
local: true,
|
|
168
|
+
key: profileConnectionKey(name, here),
|
|
169
|
+
target: resolved.target,
|
|
170
|
+
profile: {
|
|
171
|
+
...profile,
|
|
172
|
+
binary: resolved.binary,
|
|
173
|
+
targetFilter: resolved.targetFilter,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const probe = opts.probe ?? defaultProbe;
|
|
178
|
+
const candidates = [...declarations].sort((a, b) => a.device.localeCompare(b.device));
|
|
179
|
+
const failures = [];
|
|
180
|
+
for (const declaration of candidates) {
|
|
181
|
+
const result = probe(declaration.device);
|
|
182
|
+
if (!result.reachable) {
|
|
183
|
+
failures.push(`${declaration.device}: ${result.reason}`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const profile = profileFromDeclaration(name, declaration);
|
|
187
|
+
const resolved = resolveEndpoint(profile, opts.endpointName);
|
|
188
|
+
const target = sshEndpointForDeclaration(declaration.device, declaration.config, opts.endpointName, result.os);
|
|
189
|
+
const picked = candidates.length > 1
|
|
190
|
+
? `Using ${name} on ${declaration.device} (declared on ${devices.join(', ')})`
|
|
191
|
+
: `Using ${name} on ${declaration.device}`;
|
|
192
|
+
return {
|
|
193
|
+
name,
|
|
194
|
+
kind,
|
|
195
|
+
device: declaration.device,
|
|
196
|
+
local: false,
|
|
197
|
+
key: profileConnectionKey(name, declaration.device),
|
|
198
|
+
target,
|
|
199
|
+
profile: {
|
|
200
|
+
...profile,
|
|
201
|
+
binary: resolved.binary,
|
|
202
|
+
targetFilter: resolved.targetFilter,
|
|
203
|
+
},
|
|
204
|
+
picked,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
throw unreachableDeclaringDevicesError(name, devices, failures);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Rename a leftover `<name>@endpoint-N` runtime dir to the new
|
|
211
|
+
* `<name>@<device>` key so logins in chrome-data are not orphaned.
|
|
212
|
+
*
|
|
213
|
+
* Only when exactly one legacy dir exists and the destination does not —
|
|
214
|
+
* several endpoint dirs stay put so a multi-endpoint profile is not merged.
|
|
215
|
+
*/
|
|
216
|
+
/**
|
|
217
|
+
* Adopt a leftover `@endpoint-N` runtime dir onto the new key ONLY for a
|
|
218
|
+
* local connect. Doing this for a tunnel key would rename this machine's
|
|
219
|
+
* pre-T2 logged-out chrome-data onto `comet-local@zion`, and connectProfile
|
|
220
|
+
* would then attach localhost CDP instead of tunnelling.
|
|
221
|
+
*/
|
|
222
|
+
export function adoptLegacyRuntimeIfLocal(local, profileName, newKey, runtimeRoot) {
|
|
223
|
+
if (!local)
|
|
224
|
+
return;
|
|
225
|
+
migrateLegacyRuntimeDir(profileName, newKey, runtimeRoot);
|
|
226
|
+
}
|
|
227
|
+
export function migrateLegacyRuntimeDir(profileName, newKey, runtimeRoot) {
|
|
228
|
+
const dest = path.join(runtimeRoot, newKey);
|
|
229
|
+
if (fs.existsSync(dest))
|
|
230
|
+
return;
|
|
231
|
+
if (!fs.existsSync(runtimeRoot))
|
|
232
|
+
return;
|
|
233
|
+
const legacy = fs.readdirSync(runtimeRoot).filter((entry) => {
|
|
234
|
+
const parsed = parseConnectionKey(entry);
|
|
235
|
+
return (parsed.profile === profileName &&
|
|
236
|
+
parsed.endpoint !== undefined &&
|
|
237
|
+
/^endpoint-\d+$/.test(parsed.endpoint) &&
|
|
238
|
+
parsed.fork === undefined);
|
|
239
|
+
});
|
|
240
|
+
if (legacy.length !== 1)
|
|
241
|
+
return;
|
|
242
|
+
fs.renameSync(path.join(runtimeRoot, legacy[0]), dest);
|
|
243
|
+
}
|
|
244
|
+
/** True when a live key is a leftover `@endpoint-N` key of `profileName`. */
|
|
245
|
+
export function isLegacyEndpointKey(key, profileName) {
|
|
246
|
+
const parsed = parseConnectionKey(key);
|
|
247
|
+
return (parsed.profile === profileName &&
|
|
248
|
+
parsed.endpoint !== undefined &&
|
|
249
|
+
/^endpoint-\d+$/.test(parsed.endpoint));
|
|
250
|
+
}
|
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type BrowserProfileWithDeclarations } from './profiles.js';
|
|
2
2
|
import { type ProfileName } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* How many devices declare this profile — the shipped model's two kinds.
|
|
5
|
+
*
|
|
6
|
+
* NOT the deleted `scope: local | fleet`, which described where the config was
|
|
7
|
+
* stored. `fungible` means several devices declare the name and each uses its
|
|
8
|
+
* own browser; `identity` means exactly one does, so the name is a singular
|
|
9
|
+
* browser. Prune only ever sees profiles THIS device declares, so `fungible`
|
|
10
|
+
* here means "declared here and elsewhere too".
|
|
11
|
+
*/
|
|
12
|
+
type PruneProfileClass = 'identity' | 'fungible';
|
|
13
|
+
/**
|
|
14
|
+
* Detect an identity-bearing profile whose stored endpoint is loopback, viewed
|
|
15
|
+
* from a machine that is NOT the declaring device.
|
|
16
|
+
*
|
|
17
|
+
* That is the original `comet-local` bug: five real logins on the declaring
|
|
18
|
+
* box, a bare logged-out chromium answering to the same name on a worker
|
|
19
|
+
* because `cdp://localhost:N` is evaluated on the machine running the command.
|
|
20
|
+
* The daemon now tunnels that shape (`resolve-target.ts`); doctor still flags
|
|
21
|
+
* it so a worker does not report a green local binary/port for someone else's
|
|
22
|
+
* browser. Kind comes from the registry (exactly one declaring device), never
|
|
23
|
+
* from a stored field.
|
|
24
|
+
*/
|
|
25
|
+
export declare function identityLoopbackMismatch(profile: BrowserProfileWithDeclarations): {
|
|
26
|
+
misfiled: false;
|
|
27
|
+
} | {
|
|
28
|
+
misfiled: true;
|
|
29
|
+
why: string;
|
|
30
|
+
};
|
|
3
31
|
/**
|
|
4
32
|
* Per-profile runtime files we persist under
|
|
5
33
|
* `~/.agents/.cache/browser/<composite>/`:
|
|
@@ -51,7 +79,7 @@ export declare function readProfileRuntime(profileName: string): ProfileRuntime
|
|
|
51
79
|
export declare function clearProfileRuntime(profileName: string): void;
|
|
52
80
|
/**
|
|
53
81
|
* Recursively remove the whole profile cache (chrome-data, tasks.json,
|
|
54
|
-
* everything). Used by `profiles
|
|
82
|
+
* everything). Used by `profiles remove` so an old profile name doesn't
|
|
55
83
|
* leak its history into a freshly-recreated one.
|
|
56
84
|
*/
|
|
57
85
|
export declare function removeProfileCache(profileName: string): void;
|
|
@@ -135,7 +163,7 @@ export declare function isProfileInUse(profileName: string): boolean;
|
|
|
135
163
|
export type PruneReason = 'binary-missing' | 'never-used';
|
|
136
164
|
export interface PruneCandidate {
|
|
137
165
|
name: string;
|
|
138
|
-
scope:
|
|
166
|
+
scope: PruneProfileClass;
|
|
139
167
|
reason: PruneReason;
|
|
140
168
|
/** Cache dirs that would be removed along with the config entry. */
|
|
141
169
|
cacheDirs: string[];
|
|
@@ -146,13 +174,13 @@ export interface PrunePlan {
|
|
|
146
174
|
/** Profiles deliberately left alone, with the guard that kept them. */
|
|
147
175
|
kept: Array<{
|
|
148
176
|
name: string;
|
|
149
|
-
scope:
|
|
177
|
+
scope: PruneProfileClass;
|
|
150
178
|
why: string;
|
|
151
179
|
/**
|
|
152
|
-
* True for
|
|
153
|
-
* A structured flag, not a substring of
|
|
154
|
-
* and `--json` consumers branch on it,
|
|
155
|
-
* couple them to wording.
|
|
180
|
+
* True for an identity-bearing profile whose loopback endpoint only makes
|
|
181
|
+
* sense on the declaring device. A structured flag, not a substring of
|
|
182
|
+
* `why`: both the CLI's human output and `--json` consumers branch on it,
|
|
183
|
+
* and sniffing the reason text would couple them to wording.
|
|
156
184
|
*/
|
|
157
185
|
misfiled?: boolean;
|
|
158
186
|
}>;
|
|
@@ -177,8 +205,6 @@ export declare const PRUNE_REASON_TEXT: Record<PruneReason, string>;
|
|
|
177
205
|
* - **the auto-detected profile** (`auto-chrome`, or the `default` an older
|
|
178
206
|
* build wrote) — auto-regenerated by `ensureDefaultBrowserProfile`, so
|
|
179
207
|
* pruning it is pure churn.
|
|
180
|
-
* - **fleet-synced** — deleting one removes it from EVERY machine, so it takes
|
|
181
|
-
* the explicit `includeFleet` opt-in.
|
|
182
208
|
*
|
|
183
209
|
* Known limitation: `BrowserProfileConfig` records no creation time, so a
|
|
184
210
|
* profile created seconds ago and not yet started is indistinguishable from an
|
|
@@ -187,24 +213,22 @@ export declare const PRUNE_REASON_TEXT: Record<PruneReason, string>;
|
|
|
187
213
|
*/
|
|
188
214
|
export declare function planProfilePrune(profiles: Array<{
|
|
189
215
|
name: string;
|
|
190
|
-
scope:
|
|
216
|
+
scope: PruneProfileClass;
|
|
191
217
|
launchableHere: boolean;
|
|
192
|
-
/** Set when this
|
|
218
|
+
/** Set when this identity-bearing profile's endpoint only makes sense on one machine. */
|
|
193
219
|
misfiledWhy?: string;
|
|
194
220
|
}>, opts?: {
|
|
195
|
-
includeFleet?: boolean;
|
|
196
221
|
configuredDefault?: string;
|
|
197
222
|
}): PrunePlan;
|
|
198
223
|
/**
|
|
199
224
|
* Build the prune plan from live config. Split from {@link planProfilePrune} so
|
|
200
225
|
* the decision rules stay unit-testable without a configured machine.
|
|
201
226
|
*/
|
|
202
|
-
export declare function buildProfilePrunePlan(
|
|
203
|
-
includeFleet?: boolean;
|
|
204
|
-
}): Promise<PrunePlan>;
|
|
227
|
+
export declare function buildProfilePrunePlan(): Promise<PrunePlan>;
|
|
205
228
|
/**
|
|
206
229
|
* Execute a prune plan: drop each candidate's config entry and wipe its cache
|
|
207
230
|
* dirs. Returns the plan that was applied so the caller can report exactly what
|
|
208
231
|
* went, matching what `--dry-run` printed.
|
|
209
232
|
*/
|
|
210
233
|
export declare function pruneProfiles(plan: PrunePlan): Promise<PrunePlan>;
|
|
234
|
+
export {};
|