@phnx-labs/agents-cli 1.22.117 → 1.22.118
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 +235 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -1
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup.js +9 -9
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +6 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +0 -11
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/harness-auth-capabilities.js +0 -1
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.js +9 -7
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +12 -1
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +5 -10
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -1,584 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { execFileSync } from 'child_process';
|
|
4
|
-
import { getProfileRuntimeDir, getBrowserRuntimeDir, getEndpointPresets, listProfiles, getConfiguredDefaultProfileName, isProfileLaunchableHere, isProfileDeclaredHere, deleteProfile, DEFAULT_BROWSER_PROFILE_NAME, LEGACY_DEFAULT_BROWSER_PROFILE_NAME, } from './profiles.js';
|
|
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
|
-
}
|
|
53
|
-
const PID_FILE = 'pid';
|
|
54
|
-
const PORT_FILE = 'port';
|
|
55
|
-
const COMMAND_FILE = 'command';
|
|
56
|
-
const META_FILE = 'meta.json';
|
|
57
|
-
function readNumberFile(p) {
|
|
58
|
-
try {
|
|
59
|
-
const n = parseInt(fs.readFileSync(p, 'utf-8').trim(), 10);
|
|
60
|
-
return Number.isFinite(n) ? n : null;
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function readStringFile(p) {
|
|
67
|
-
try {
|
|
68
|
-
return fs.readFileSync(p, 'utf-8').trim() || null;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Save the runtime record atomically. We write the legacy one-value-per-
|
|
76
|
-
* file fields plus a JSON meta blob so future code can read either.
|
|
77
|
-
* The cache directory may not exist yet (first launch); we create it.
|
|
78
|
-
*/
|
|
79
|
-
export function writeProfileRuntime(profileName, runtime) {
|
|
80
|
-
const dir = getProfileRuntimeDir(profileName);
|
|
81
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
82
|
-
fs.writeFileSync(path.join(dir, PID_FILE), String(runtime.pid));
|
|
83
|
-
if (runtime.port !== undefined) {
|
|
84
|
-
fs.writeFileSync(path.join(dir, PORT_FILE), String(runtime.port));
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
try {
|
|
88
|
-
fs.unlinkSync(path.join(dir, PORT_FILE));
|
|
89
|
-
}
|
|
90
|
-
catch { /* not present */ }
|
|
91
|
-
}
|
|
92
|
-
if (runtime.command) {
|
|
93
|
-
fs.writeFileSync(path.join(dir, COMMAND_FILE), runtime.command);
|
|
94
|
-
}
|
|
95
|
-
const meta = {
|
|
96
|
-
...runtime,
|
|
97
|
-
daemonPid: runtime.daemonPid ?? process.pid,
|
|
98
|
-
spawnedAt: runtime.spawnedAt ?? Date.now(),
|
|
99
|
-
};
|
|
100
|
-
fs.writeFileSync(path.join(dir, META_FILE), JSON.stringify(meta));
|
|
101
|
-
}
|
|
102
|
-
/** Read just the JSON meta record. Returns null when absent or malformed. */
|
|
103
|
-
export function readProfileRuntimeMeta(profileName) {
|
|
104
|
-
const dir = getProfileRuntimeDir(profileName);
|
|
105
|
-
try {
|
|
106
|
-
const raw = fs.readFileSync(path.join(dir, META_FILE), 'utf-8');
|
|
107
|
-
const obj = JSON.parse(raw);
|
|
108
|
-
if (typeof obj !== 'object' || obj === null)
|
|
109
|
-
return null;
|
|
110
|
-
return obj;
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Read the runtime triple. Returns null when the files are missing OR when
|
|
118
|
-
* the recorded pid no longer points at the same process we launched —
|
|
119
|
-
* stale data is auto-cleaned to keep the next caller from acting on it.
|
|
120
|
-
*/
|
|
121
|
-
export function readProfileRuntime(profileName) {
|
|
122
|
-
const dir = getProfileRuntimeDir(profileName);
|
|
123
|
-
const pid = readNumberFile(path.join(dir, PID_FILE));
|
|
124
|
-
const port = readNumberFile(path.join(dir, PORT_FILE));
|
|
125
|
-
const command = readStringFile(path.join(dir, COMMAND_FILE)) ?? undefined;
|
|
126
|
-
if (pid === null || port === null)
|
|
127
|
-
return null;
|
|
128
|
-
if (!isProcessAlive(pid, command)) {
|
|
129
|
-
clearProfileRuntime(profileName);
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
return { pid, port, command };
|
|
133
|
-
}
|
|
134
|
-
/** Remove the pid/port/command/meta files. Leaves chrome-data + tasks.json intact. */
|
|
135
|
-
export function clearProfileRuntime(profileName) {
|
|
136
|
-
const dir = getProfileRuntimeDir(profileName);
|
|
137
|
-
for (const f of [PID_FILE, PORT_FILE, COMMAND_FILE, META_FILE]) {
|
|
138
|
-
try {
|
|
139
|
-
fs.unlinkSync(path.join(dir, f));
|
|
140
|
-
}
|
|
141
|
-
catch { /* not present */ }
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Recursively remove the whole profile cache (chrome-data, tasks.json,
|
|
146
|
-
* everything). Used by `profiles remove` so an old profile name doesn't
|
|
147
|
-
* leak its history into a freshly-recreated one.
|
|
148
|
-
*/
|
|
149
|
-
export function removeProfileCache(profileName) {
|
|
150
|
-
const dir = getProfileRuntimeDir(profileName);
|
|
151
|
-
try {
|
|
152
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
153
|
-
}
|
|
154
|
-
catch { /* gone */ }
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Find every cache directory belonging to a given profile. A runtime dir name
|
|
158
|
-
* IS a connection key (`<name>@<endpoint>[.<fork>]`), so a single agents-cli
|
|
159
|
-
* profile can have several side by side; this finds them all, plus the legacy
|
|
160
|
-
* non-composite dir from older builds.
|
|
161
|
-
*
|
|
162
|
-
* Membership is decided by {@link keyBelongsToProfile} — the same single rule
|
|
163
|
-
* `status`, `stopProfile`, and `findTask` use (RUSH-2709), so a fork dir is no
|
|
164
|
-
* longer missed here while being matched there.
|
|
165
|
-
*/
|
|
166
|
-
export function listProfileCacheDirs(profileName) {
|
|
167
|
-
const root = getBrowserRuntimeDir();
|
|
168
|
-
if (!fs.existsSync(root))
|
|
169
|
-
return [];
|
|
170
|
-
const matches = [];
|
|
171
|
-
for (const entry of fs.readdirSync(root)) {
|
|
172
|
-
if (keyBelongsToProfile(entry, profileName))
|
|
173
|
-
matches.push(path.join(root, entry));
|
|
174
|
-
}
|
|
175
|
-
return matches;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* `process.kill(pid, 0)` answers "is a process with this id alive?" — but
|
|
179
|
-
* pid reuse is real on long-uptime machines, and a stale cache pointing
|
|
180
|
-
* at a since-reassigned pid would happily call the imposter ours.
|
|
181
|
-
*
|
|
182
|
-
* Strategy: if we recorded the executable basename when we launched, ask
|
|
183
|
-
* `ps` what command the live pid is running and compare. No command on
|
|
184
|
-
* record means we fall back to the existence check (older cache entries
|
|
185
|
-
* or `pid:0` for "attached to an externally-launched browser").
|
|
186
|
-
*/
|
|
187
|
-
export function isProcessAlive(pid, expectedCommand) {
|
|
188
|
-
if (pid === 0)
|
|
189
|
-
return true;
|
|
190
|
-
try {
|
|
191
|
-
process.kill(pid, 0);
|
|
192
|
-
}
|
|
193
|
-
catch (err) {
|
|
194
|
-
if (err && err.code === 'EPERM') {
|
|
195
|
-
// exists but we can't signal it — count it as alive
|
|
196
|
-
return !expectedCommand || matchesCommand(pid, expectedCommand);
|
|
197
|
-
}
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
if (!expectedCommand)
|
|
201
|
-
return true;
|
|
202
|
-
return matchesCommand(pid, expectedCommand);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Read every profile cache directory and produce a structured snapshot.
|
|
206
|
-
* Works without the daemon — `agents browser ps` uses this to render a
|
|
207
|
-
* complete state view even when the IPC server is down. The caller can
|
|
208
|
-
* post-process to detect conflicts (e.g. two profiles with the same port,
|
|
209
|
-
* or a port someone else is listening on).
|
|
210
|
-
*/
|
|
211
|
-
export function listAllProfileSnapshots() {
|
|
212
|
-
const root = getBrowserRuntimeDir();
|
|
213
|
-
if (!fs.existsSync(root))
|
|
214
|
-
return [];
|
|
215
|
-
const out = [];
|
|
216
|
-
for (const name of fs.readdirSync(root).sort()) {
|
|
217
|
-
const dir = path.join(root, name);
|
|
218
|
-
let stat;
|
|
219
|
-
try {
|
|
220
|
-
stat = fs.statSync(dir);
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (!stat.isDirectory())
|
|
226
|
-
continue;
|
|
227
|
-
const meta = readProfileRuntimeMeta(name);
|
|
228
|
-
const taskCount = readTaskCount(dir);
|
|
229
|
-
const pidAlive = meta ? isProcessAlive(meta.pid, meta.command) : false;
|
|
230
|
-
const tunnelAlive = meta?.tunnelPid ? isProcessAlive(meta.tunnelPid, 'ssh') : false;
|
|
231
|
-
const daemonAlive = meta?.daemonPid ? isProcessAlive(meta.daemonPid) : false;
|
|
232
|
-
out.push({ name, dir, meta, pidAlive, tunnelAlive, daemonAlive, taskCount });
|
|
233
|
-
}
|
|
234
|
-
return out;
|
|
235
|
-
}
|
|
236
|
-
function readTaskCount(dir) {
|
|
237
|
-
try {
|
|
238
|
-
const raw = fs.readFileSync(path.join(dir, 'tasks.json'), 'utf-8');
|
|
239
|
-
const obj = JSON.parse(raw);
|
|
240
|
-
if (Array.isArray(obj))
|
|
241
|
-
return obj.length;
|
|
242
|
-
if (obj && typeof obj === 'object')
|
|
243
|
-
return Object.keys(obj).length;
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
return 0;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Reap browser + tunnel processes spawned by daemons that no longer exist.
|
|
252
|
-
* Call once on daemon startup. The idea: every process we spawn records
|
|
253
|
-
* its daemonPid in meta.json. If that daemon is dead (crashed, SIGKILL),
|
|
254
|
-
* its children were left rootless — kill them now so they don't hijack
|
|
255
|
-
* the next session's local ports.
|
|
256
|
-
*
|
|
257
|
-
* We're conservative: a record with no daemonPid (older builds) is left
|
|
258
|
-
* alone — we'd rather leak than wrongly kill a user-owned process that
|
|
259
|
-
* happens to share metadata.
|
|
260
|
-
*/
|
|
261
|
-
/**
|
|
262
|
-
* Clean up what a DEAD daemon genuinely left behind — which is not the browser.
|
|
263
|
-
*
|
|
264
|
-
* A live local browser outliving its daemon is the NORMAL case, not an orphan.
|
|
265
|
-
* `BrowserService.shutdown` deliberately closes only CDP and leaves the browser
|
|
266
|
-
* running, and on macOS the spawn is detached so the daemon's own positive-pid
|
|
267
|
-
* `killTree` reaches the daemon alone. This reaper used to read "the recorded
|
|
268
|
-
* `daemonPid` has exited" as "everything it recorded is garbage" and SIGTERM the
|
|
269
|
-
* browser — so replacing the daemon (a menubar activation, an upgrade, a crash
|
|
270
|
-
* restart) killed a browser the user was looking at, and `clearProfileRuntime`
|
|
271
|
-
* then removed the `port`/meta that {@link readProfileRuntime} needs, so even a
|
|
272
|
-
* survivor could not be re-attached and a second browser would launch beside it.
|
|
273
|
-
*
|
|
274
|
-
* The three cases are now decided independently:
|
|
275
|
-
*
|
|
276
|
-
* - **A live local browser/electron is preserved untouched** — not signalled, not
|
|
277
|
-
* cleared, and its `daemonPid` deliberately NOT rewritten. Ownership is adopted
|
|
278
|
-
* only after an identity-checked attach succeeds (`adoptProfileRuntimeOwner`,
|
|
279
|
-
* called from the reuse branch in `BrowserService.connectProfile`), so nothing
|
|
280
|
-
* claims a browser it has not proven it can talk to. Re-running this is a no-op.
|
|
281
|
-
* - **A dead browser record is cleared**, with no signal sent: there is nothing
|
|
282
|
-
* alive to signal, and the stale pid/port would otherwise mislead the next
|
|
283
|
-
* attach.
|
|
284
|
-
* - **A stale tunnel IS reaped.** A local `ssh -L` cannot outlive its purpose, so
|
|
285
|
-
* a dead owner does make it an orphan. This is the one thing the old code was
|
|
286
|
-
* right about; it just shared a code path with the browser.
|
|
287
|
-
*
|
|
288
|
-
* The accepted trade: a genuinely leaked browser from a long-dead daemon is no
|
|
289
|
-
* longer force-killed here. Idle/abandoned cleanup has its own owner in
|
|
290
|
-
* `browser/hygiene.ts` (session-dead + idle), which the daemon's periodic tick and
|
|
291
|
-
* `agents browser prune` both call — that is the right place for it, because it can
|
|
292
|
-
* tell "idle and abandoned" from "in use right now" and this function cannot.
|
|
293
|
-
*/
|
|
294
|
-
export function reapOrphanedProcesses() {
|
|
295
|
-
const root = getBrowserRuntimeDir();
|
|
296
|
-
if (!fs.existsSync(root))
|
|
297
|
-
return { reaped: 0, details: [] };
|
|
298
|
-
let reaped = 0;
|
|
299
|
-
const details = [];
|
|
300
|
-
for (const profileName of fs.readdirSync(root)) {
|
|
301
|
-
const meta = readProfileRuntimeMeta(profileName);
|
|
302
|
-
if (!meta)
|
|
303
|
-
continue;
|
|
304
|
-
if (!meta.daemonPid)
|
|
305
|
-
continue;
|
|
306
|
-
if (meta.daemonPid === process.pid)
|
|
307
|
-
continue;
|
|
308
|
-
// Owning daemon still alive — leave its kids alone.
|
|
309
|
-
if (isProcessAlive(meta.daemonPid))
|
|
310
|
-
continue;
|
|
311
|
-
// A local `ssh -L` whose owner is gone is genuinely orphaned: nothing is
|
|
312
|
-
// driving it and it cannot be re-adopted, so it is the one process to signal.
|
|
313
|
-
// Matched against the recorded command (or `ssh`) so a recycled pid is not
|
|
314
|
-
// mistaken for our tunnel.
|
|
315
|
-
if (meta.tunnelPid && meta.tunnelPid !== 0) {
|
|
316
|
-
const cmdOk = !meta.command
|
|
317
|
-
|| matchesCommand(meta.tunnelPid, meta.command)
|
|
318
|
-
|| matchesCommand(meta.tunnelPid, 'ssh');
|
|
319
|
-
if (cmdOk) {
|
|
320
|
-
try {
|
|
321
|
-
process.kill(meta.tunnelPid, 'SIGTERM');
|
|
322
|
-
reaped++;
|
|
323
|
-
details.push(`reaped tunnel ${meta.tunnelPid} (profile ${profileName})`);
|
|
324
|
-
}
|
|
325
|
-
catch { /* already gone */ }
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
// A tunnel-kind record has no local browser of its own, so clearing it is the
|
|
329
|
-
// whole cleanup. `pid` on such a record is the REMOTE browser (normally 0) and
|
|
330
|
-
// is never ours to signal.
|
|
331
|
-
if (meta.kind === 'tunnel') {
|
|
332
|
-
clearProfileRuntime(profileName);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
// `isProcessAlive` re-checks the recorded command, so a recycled pid does not
|
|
336
|
-
// read as our browser. A record with no pid at all is stale by definition.
|
|
337
|
-
const liveBrowser = !!meta.pid && meta.pid !== 0 && isProcessAlive(meta.pid, meta.command);
|
|
338
|
-
if (liveBrowser) {
|
|
339
|
-
details.push(`preserved live browser ${meta.pid} (profile ${profileName})`);
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
// Dead browser: clear the stale record. No signal — there is nothing there.
|
|
343
|
-
clearProfileRuntime(profileName);
|
|
344
|
-
if (meta.pid)
|
|
345
|
-
details.push(`cleared dead browser record ${meta.pid} (profile ${profileName})`);
|
|
346
|
-
}
|
|
347
|
-
return { reaped, details };
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Take ownership of a runtime record after an identity-checked attach succeeded.
|
|
351
|
-
*
|
|
352
|
-
* Called from the reuse branch in `BrowserService.connectProfile`, which has just
|
|
353
|
-
* probed `/json/version` and run `verifyBrowserIdentity` against the profile's
|
|
354
|
-
* expected browser family — so by this point the process on the recorded port is
|
|
355
|
-
* proven to be ours. Only then may a new service claim it.
|
|
356
|
-
*
|
|
357
|
-
* Rewrites `daemonPid` ALONE. Every other field — `pid`, `port`, `command`,
|
|
358
|
-
* `userDataDir`, `spawnedAt`, `kind`, `tunnelPid` — is the original launch's
|
|
359
|
-
* metadata and stays exactly as recorded: this is an ownership handover, not a
|
|
360
|
-
* re-registration, and rewriting the rest would lose the provenance the reaper and
|
|
361
|
-
* the hygiene pass both read. A record that has since disappeared is left alone
|
|
362
|
-
* rather than recreated from a partial view.
|
|
363
|
-
*/
|
|
364
|
-
export function adoptProfileRuntimeOwner(profileName, daemonPid = process.pid) {
|
|
365
|
-
const meta = readProfileRuntimeMeta(profileName);
|
|
366
|
-
if (!meta)
|
|
367
|
-
return false;
|
|
368
|
-
if (meta.daemonPid === daemonPid)
|
|
369
|
-
return true;
|
|
370
|
-
const dir = getProfileRuntimeDir(profileName);
|
|
371
|
-
try {
|
|
372
|
-
fs.writeFileSync(path.join(dir, META_FILE), JSON.stringify({ ...meta, daemonPid }));
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
// Losing the handover is not worth failing an otherwise good attach; the next
|
|
377
|
-
// startup simply preserves the browser again and re-attaches.
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* True when ANY cache dir belonging to this profile still has a live browser or
|
|
383
|
-
* SSH tunnel, or an open task. The composite naming (`<name>@<endpoint>`) means
|
|
384
|
-
* one profile can own several runtime dirs, so checking only
|
|
385
|
-
* `getProfileRuntimeDir(name)` would miss a running composite — and prune must
|
|
386
|
-
* never remove a profile that is in use.
|
|
387
|
-
*
|
|
388
|
-
* A recorded `pid: 0` means "attached to a browser someone else launched", so it
|
|
389
|
-
* is not our process to protect; an open task on that dir still counts as in use.
|
|
390
|
-
*/
|
|
391
|
-
export function isProfileInUse(profileName) {
|
|
392
|
-
for (const dir of listProfileCacheDirs(profileName)) {
|
|
393
|
-
const entry = path.basename(dir);
|
|
394
|
-
if (readTaskCount(dir) > 0)
|
|
395
|
-
return true;
|
|
396
|
-
const meta = readProfileRuntimeMeta(entry);
|
|
397
|
-
if (!meta)
|
|
398
|
-
continue;
|
|
399
|
-
if (meta.pid && isProcessAlive(meta.pid, meta.command))
|
|
400
|
-
return true;
|
|
401
|
-
if (meta.tunnelPid && isProcessAlive(meta.tunnelPid, 'ssh'))
|
|
402
|
-
return true;
|
|
403
|
-
}
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
export const PRUNE_REASON_TEXT = {
|
|
407
|
-
'binary-missing': 'its browser/binary is not installed on this machine',
|
|
408
|
-
'never-used': 'it has never been started (no runtime dir)',
|
|
409
|
-
};
|
|
410
|
-
/**
|
|
411
|
-
* Decide which browser profiles are dead, WITHOUT touching anything.
|
|
412
|
-
* `pruneProfiles` executes exactly this plan, so `--dry-run` and the real run
|
|
413
|
-
* can never disagree.
|
|
414
|
-
*
|
|
415
|
-
* Dead means one of {@link PruneReason}: the configured browser cannot launch
|
|
416
|
-
* here at all, or the profile has never been started so nothing on this disk
|
|
417
|
-
* refers to it. That second reason is what actually reclaims agent-minted
|
|
418
|
-
* throwaway profiles (RUSH-2716) — they are created for an installed browser, so
|
|
419
|
-
* only "never started" identifies them.
|
|
420
|
-
*
|
|
421
|
-
* Four guards, and each exists because removing that profile would be wrong
|
|
422
|
-
* rather than merely tidy:
|
|
423
|
-
* - **in use** — a live browser, SSH tunnel, or open task on any of its dirs.
|
|
424
|
-
* - **the configured default** — this machine resolves a bare
|
|
425
|
-
* `agents browser start` to it; removing it breaks that command.
|
|
426
|
-
* - **the auto-detected profile** (`auto-chrome`, or the `default` an older
|
|
427
|
-
* build wrote) — still recognized and resolved by
|
|
428
|
-
* `ensureDefaultBrowserProfile` as this machine's default; since PHNX-3296
|
|
429
|
-
* it is no longer re-created on demand, so pruning it forces the user
|
|
430
|
-
* back through `agents setup` to get a default browser again.
|
|
431
|
-
*
|
|
432
|
-
* Known limitation: `BrowserProfileConfig` records no creation time, so a
|
|
433
|
-
* profile created seconds ago and not yet started is indistinguishable from an
|
|
434
|
-
* abandoned one and is reported `never-used`. That is why the verb is explicit
|
|
435
|
-
* and `--dry-run` exists.
|
|
436
|
-
*/
|
|
437
|
-
export function planProfilePrune(profiles, opts = {}) {
|
|
438
|
-
const plan = { candidates: [], kept: [] };
|
|
439
|
-
for (const { name, scope, launchableHere, misfiledWhy } of profiles) {
|
|
440
|
-
const keep = (why, misfiled = false) => {
|
|
441
|
-
plan.kept.push(misfiled ? { name, scope, why, misfiled } : { name, scope, why });
|
|
442
|
-
};
|
|
443
|
-
// Checked FIRST. A misfiled identity profile's defining symptom is that the
|
|
444
|
-
// browser is absent on whatever box you are standing on — pruning it would
|
|
445
|
-
// delete the declaring device's entry (or throw, for a peer declaration).
|
|
446
|
-
// The repair is to declare it only on the machine that owns the browser;
|
|
447
|
-
// an intentional delete is still available as `profiles remove`.
|
|
448
|
-
if (misfiledWhy) {
|
|
449
|
-
keep(`MISFILED — ${misfiledWhy}`, true);
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
if (name === DEFAULT_BROWSER_PROFILE_NAME || name === LEGACY_DEFAULT_BROWSER_PROFILE_NAME) {
|
|
453
|
-
keep('the auto-detected default profile');
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
if (opts.configuredDefault && name === opts.configuredDefault) {
|
|
457
|
-
keep("this machine's configured default profile");
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
if (isProfileInUse(name)) {
|
|
461
|
-
keep('in use (live browser, tunnel, or open task)');
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
const cacheDirs = listProfileCacheDirs(name);
|
|
465
|
-
if (!launchableHere) {
|
|
466
|
-
plan.candidates.push({ name, scope, reason: 'binary-missing', cacheDirs });
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (cacheDirs.length === 0) {
|
|
470
|
-
plan.candidates.push({ name, scope, reason: 'never-used', cacheDirs });
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
keep('healthy (browser installed, has runtime state)');
|
|
474
|
-
}
|
|
475
|
-
return plan;
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Build the prune plan from live config. Split from {@link planProfilePrune} so
|
|
479
|
-
* the decision rules stay unit-testable without a configured machine.
|
|
480
|
-
*/
|
|
481
|
-
export async function buildProfilePrunePlan() {
|
|
482
|
-
const current = machineId();
|
|
483
|
-
const profiles = await listProfiles();
|
|
484
|
-
const declaredHere = [];
|
|
485
|
-
const peerKept = [];
|
|
486
|
-
for (const profile of profiles) {
|
|
487
|
-
const scope = profile.devices.length > 1 ? 'fungible' : 'identity';
|
|
488
|
-
const misfiled = identityLoopbackMismatch(profile);
|
|
489
|
-
if (profile.arc && !isProfileDeclaredHere(profile.name)) {
|
|
490
|
-
peerKept.push({
|
|
491
|
-
name: profile.name,
|
|
492
|
-
scope,
|
|
493
|
-
why: 'read-only native Arc discovery; no agents-cli alias to prune',
|
|
494
|
-
});
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
if (profile.devices.includes(current)) {
|
|
498
|
-
declaredHere.push({
|
|
499
|
-
name: profile.name,
|
|
500
|
-
scope,
|
|
501
|
-
launchableHere: isProfileLaunchableHere(profile),
|
|
502
|
-
misfiledWhy: misfiled.misfiled ? misfiled.why : undefined,
|
|
503
|
-
});
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (misfiled.misfiled) {
|
|
507
|
-
peerKept.push({
|
|
508
|
-
name: profile.name,
|
|
509
|
-
scope,
|
|
510
|
-
why: `MISFILED — ${misfiled.why}`,
|
|
511
|
-
misfiled: true,
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
peerKept.push({
|
|
516
|
-
name: profile.name,
|
|
517
|
-
scope,
|
|
518
|
-
why: `declared on ${profile.devices.join(', ')}, not this device`,
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
const plan = planProfilePrune(declaredHere, {
|
|
523
|
-
configuredDefault: getConfiguredDefaultProfileName(),
|
|
524
|
-
});
|
|
525
|
-
plan.kept.push(...peerKept);
|
|
526
|
-
return plan;
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Execute a prune plan: drop each candidate's config entry and wipe its cache
|
|
530
|
-
* dirs. Returns the plan that was applied so the caller can report exactly what
|
|
531
|
-
* went, matching what `--dry-run` printed.
|
|
532
|
-
*/
|
|
533
|
-
export async function pruneProfiles(plan) {
|
|
534
|
-
for (const candidate of plan.candidates) {
|
|
535
|
-
await deleteProfile(candidate.name);
|
|
536
|
-
for (const dir of candidate.cacheDirs) {
|
|
537
|
-
try {
|
|
538
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
539
|
-
}
|
|
540
|
-
catch { /* already gone */ }
|
|
541
|
-
}
|
|
542
|
-
removeProfileCache(candidate.name);
|
|
543
|
-
}
|
|
544
|
-
return plan;
|
|
545
|
-
}
|
|
546
|
-
function matchesCommand(pid, expectedCommand) {
|
|
547
|
-
const out = liveProcessCommand(pid);
|
|
548
|
-
if (!out)
|
|
549
|
-
return false;
|
|
550
|
-
// Match on the basename only — `/Applications/Comet.app/Contents/MacOS/Comet`
|
|
551
|
-
// vs the recorded `Comet`, vs `Google\ Chrome`, vs Windows `chrome.exe`.
|
|
552
|
-
// Case-insensitive.
|
|
553
|
-
const live = path.basename(out).toLowerCase();
|
|
554
|
-
const want = path.basename(expectedCommand).toLowerCase();
|
|
555
|
-
return live === want || live.startsWith(want) || want.startsWith(live);
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* The executable/image name the live `pid` is running, or null if it can't be
|
|
559
|
-
* determined. The process-listing API differs per OS: Windows has no `ps`, so
|
|
560
|
-
* we query `tasklist` (CSV image name in column 1); POSIX uses `ps -o comm=`.
|
|
561
|
-
*/
|
|
562
|
-
export function liveProcessCommand(pid) {
|
|
563
|
-
try {
|
|
564
|
-
if (process.platform === 'win32') {
|
|
565
|
-
const out = execFileSync('tasklist', ['/FI', `PID eq ${pid}`, '/NH', '/FO', 'CSV'], {
|
|
566
|
-
encoding: 'utf-8',
|
|
567
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
568
|
-
windowsHide: true,
|
|
569
|
-
}).trim();
|
|
570
|
-
// Rows look like: "node.exe","1234","Console","1","12,345 K"
|
|
571
|
-
// A no-match prints an "INFO: No tasks..." line that won't match the regex.
|
|
572
|
-
const m = out.match(/^"([^"]+)"/);
|
|
573
|
-
return m ? m[1] : null;
|
|
574
|
-
}
|
|
575
|
-
const out = execFileSync('ps', ['-p', String(pid), '-o', 'comm='], {
|
|
576
|
-
encoding: 'utf-8',
|
|
577
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
578
|
-
}).trim();
|
|
579
|
-
return out || null;
|
|
580
|
-
}
|
|
581
|
-
catch {
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse a `<bundle>/<KEY>` reference (optionally `secret:<bundle>/<KEY>`) used by
|
|
3
|
-
* `agents browser type --secret` to feed a credential from an `agents secrets`
|
|
4
|
-
* bundle into a page WITHOUT the value ever crossing stdout or the transcript.
|
|
5
|
-
* Returns null on a malformed ref.
|
|
6
|
-
*/
|
|
7
|
-
export declare function parseSecretRef(ref: string): {
|
|
8
|
-
bundle: string;
|
|
9
|
-
key: string;
|
|
10
|
-
} | null;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse a `<bundle>/<KEY>` reference (optionally `secret:<bundle>/<KEY>`) used by
|
|
3
|
-
* `agents browser type --secret` to feed a credential from an `agents secrets`
|
|
4
|
-
* bundle into a page WITHOUT the value ever crossing stdout or the transcript.
|
|
5
|
-
* Returns null on a malformed ref.
|
|
6
|
-
*/
|
|
7
|
-
export function parseSecretRef(ref) {
|
|
8
|
-
const body = ref.startsWith('secret:') ? ref.slice('secret:'.length) : ref;
|
|
9
|
-
const slash = body.indexOf('/');
|
|
10
|
-
// Need a non-empty bundle before the slash and a non-empty key after it.
|
|
11
|
-
if (slash <= 0 || slash >= body.length - 1)
|
|
12
|
-
return null;
|
|
13
|
-
return { bundle: body.slice(0, slash), key: body.slice(slash + 1) };
|
|
14
|
-
}
|