@phnx-labs/agents-cli 1.22.116 → 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 +247 -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.d.ts +18 -0
- package/dist/commands/exec.js +104 -12
- 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-term.d.ts +4 -3
- package/dist/commands/setup-term.js +5 -4
- package/dist/commands/setup.js +11 -11
- 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/hosts/remote-cmd.js +1 -0
- 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/placement.d.ts +2 -0
- package/dist/lib/placement.js +5 -0
- 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 +8 -12
- 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,184 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
|
-
const execFileAsync = promisify(execFile);
|
|
7
|
-
let resolvedFfmpeg;
|
|
8
|
-
function canonicalDirs(platform) {
|
|
9
|
-
if (platform === 'darwin')
|
|
10
|
-
return ['/opt/homebrew/bin', '/usr/local/bin'];
|
|
11
|
-
if (platform === 'linux')
|
|
12
|
-
return ['/usr/local/bin', '/usr/bin', '/snap/bin'];
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
function pathCandidates(env, platform, platformDirs) {
|
|
16
|
-
const names = platform === 'win32' ? ['ffmpeg.exe'] : ['ffmpeg'];
|
|
17
|
-
const dirs = [...new Set([...(env.PATH ?? '').split(path.delimiter).filter(Boolean), ...platformDirs])];
|
|
18
|
-
return dirs.flatMap((dir) => names.map((name) => path.join(dir, name)));
|
|
19
|
-
}
|
|
20
|
-
function managedCandidates(home, platform) {
|
|
21
|
-
const name = platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg';
|
|
22
|
-
return [
|
|
23
|
-
path.join(home, '.agents', '.cache', 'bin', name),
|
|
24
|
-
path.join(home, '.agents', '.cache', 'ffmpeg', 'bin', name),
|
|
25
|
-
path.join(home, '.agents', '.cache', 'ffmpeg', 'node_modules', 'ffmpeg-static', name),
|
|
26
|
-
];
|
|
27
|
-
}
|
|
28
|
-
async function isUsable(binary) {
|
|
29
|
-
try {
|
|
30
|
-
await execFileAsync(binary, ['-version'], { timeout: 10_000, windowsHide: true });
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function firstUsable(candidates) {
|
|
38
|
-
for (const candidate of candidates) {
|
|
39
|
-
if (fs.existsSync(candidate) && await isUsable(candidate))
|
|
40
|
-
return candidate;
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
function commandOnPath(name, env, platform, platformDirs) {
|
|
45
|
-
const names = platform === 'win32' ? [`${name}.exe`, `${name}.cmd`, name] : [name];
|
|
46
|
-
const dirs = [...new Set([...(env.PATH ?? '').split(path.delimiter).filter(Boolean), ...platformDirs])];
|
|
47
|
-
for (const dir of dirs) {
|
|
48
|
-
for (const candidate of names.map((entry) => path.join(dir, entry))) {
|
|
49
|
-
try {
|
|
50
|
-
fs.accessSync(candidate, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
|
|
51
|
-
return candidate;
|
|
52
|
-
}
|
|
53
|
-
catch { /* keep looking */ }
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
export function ffmpegManualCommand(platform, osRelease = '') {
|
|
59
|
-
if (platform === 'darwin')
|
|
60
|
-
return 'brew install ffmpeg';
|
|
61
|
-
if (platform === 'win32')
|
|
62
|
-
return 'winget install Gyan.FFmpeg';
|
|
63
|
-
if (/\b(ID|ID_LIKE)=(?:"[^"]*\b)?(?:ubuntu|debian)\b/m.test(osRelease)) {
|
|
64
|
-
return 'sudo apt-get update && sudo apt-get install -y ffmpeg';
|
|
65
|
-
}
|
|
66
|
-
return 'install ffmpeg with your system package manager';
|
|
67
|
-
}
|
|
68
|
-
function readOsRelease() {
|
|
69
|
-
try {
|
|
70
|
-
return fs.readFileSync('/etc/os-release', 'utf8');
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return '';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async function installManagedFfmpeg(home, env, platform, platformDirs) {
|
|
77
|
-
const npm = commandOnPath('npm', env, platform, platformDirs);
|
|
78
|
-
if (!npm)
|
|
79
|
-
return undefined;
|
|
80
|
-
const prefix = path.join(home, '.agents', '.cache', 'ffmpeg');
|
|
81
|
-
await fs.promises.mkdir(prefix, { recursive: true });
|
|
82
|
-
await execFileAsync(npm, [
|
|
83
|
-
'install', '--prefix', prefix, '--no-save', '--no-audit', '--no-fund',
|
|
84
|
-
'ffmpeg-static@5.2.0',
|
|
85
|
-
], { env, timeout: 10 * 60_000, windowsHide: true });
|
|
86
|
-
return firstUsable(managedCandidates(home, platform));
|
|
87
|
-
}
|
|
88
|
-
function installAttempt(platform, env, osRelease, platformDirs) {
|
|
89
|
-
if (platform === 'darwin') {
|
|
90
|
-
const brew = commandOnPath('brew', env, platform, platformDirs);
|
|
91
|
-
return brew ? [{ command: brew, args: ['install', 'ffmpeg'] }] : undefined;
|
|
92
|
-
}
|
|
93
|
-
if (platform === 'win32') {
|
|
94
|
-
const winget = commandOnPath('winget', env, platform, platformDirs);
|
|
95
|
-
return winget ? [{ command: winget, args: ['install', '--id', 'Gyan.FFmpeg', '--exact', '--accept-source-agreements', '--accept-package-agreements'] }] : undefined;
|
|
96
|
-
}
|
|
97
|
-
if (platform !== 'linux')
|
|
98
|
-
return undefined;
|
|
99
|
-
if (/\b(ID|ID_LIKE)=(?:"[^"]*\b)?(?:ubuntu|debian)\b/m.test(osRelease)) {
|
|
100
|
-
const apt = commandOnPath('apt-get', env, platform, platformDirs);
|
|
101
|
-
if (!apt)
|
|
102
|
-
return undefined;
|
|
103
|
-
if (typeof process.getuid === 'function' && process.getuid() === 0) {
|
|
104
|
-
return [
|
|
105
|
-
{ command: apt, args: ['update'] },
|
|
106
|
-
{ command: apt, args: ['install', '-y', 'ffmpeg'] },
|
|
107
|
-
];
|
|
108
|
-
}
|
|
109
|
-
const sudo = commandOnPath('sudo', env, platform, platformDirs);
|
|
110
|
-
return sudo ? [
|
|
111
|
-
{ command: sudo, args: ['-n', apt, 'update'] },
|
|
112
|
-
{ command: sudo, args: ['-n', apt, 'install', '-y', 'ffmpeg'] },
|
|
113
|
-
] : undefined;
|
|
114
|
-
}
|
|
115
|
-
const managers = [
|
|
116
|
-
['apk', ['add', 'ffmpeg']],
|
|
117
|
-
['dnf', ['install', '-y', 'ffmpeg']],
|
|
118
|
-
['yum', ['install', '-y', 'ffmpeg']],
|
|
119
|
-
['pacman', ['-S', '--noconfirm', 'ffmpeg']],
|
|
120
|
-
['zypper', ['--non-interactive', 'install', 'ffmpeg']],
|
|
121
|
-
];
|
|
122
|
-
for (const [name, args] of managers) {
|
|
123
|
-
const command = commandOnPath(name, env, platform, platformDirs);
|
|
124
|
-
if (command)
|
|
125
|
-
return [{ command, args }];
|
|
126
|
-
}
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
export async function resolveFfmpeg(options = {}) {
|
|
130
|
-
if (resolvedFfmpeg)
|
|
131
|
-
return resolvedFfmpeg;
|
|
132
|
-
resolvedFfmpeg = (async () => {
|
|
133
|
-
const env = options.env ?? process.env;
|
|
134
|
-
const home = options.home ?? os.homedir();
|
|
135
|
-
const platform = options.platform ?? os.platform();
|
|
136
|
-
const osRelease = options.osRelease ?? readOsRelease();
|
|
137
|
-
const executableDirs = options.executableDirs ?? canonicalDirs(platform);
|
|
138
|
-
const candidates = [...pathCandidates(env, platform, executableDirs), ...managedCandidates(home, platform)];
|
|
139
|
-
const existing = await firstUsable(candidates);
|
|
140
|
-
if (existing)
|
|
141
|
-
return existing;
|
|
142
|
-
const attempt = installAttempt(platform, env, osRelease, executableDirs);
|
|
143
|
-
let failure = 'no supported automatic installer was found';
|
|
144
|
-
if (attempt) {
|
|
145
|
-
try {
|
|
146
|
-
for (const step of attempt) {
|
|
147
|
-
await execFileAsync(step.command, step.args, {
|
|
148
|
-
env,
|
|
149
|
-
timeout: 10 * 60_000,
|
|
150
|
-
windowsHide: true,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
const installed = await firstUsable([...pathCandidates(env, platform, executableDirs), ...managedCandidates(home, platform)]);
|
|
154
|
-
if (installed)
|
|
155
|
-
return installed;
|
|
156
|
-
failure = `${path.basename(attempt.at(-1).command)} completed but ffmpeg is still unavailable`;
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
failure = `${path.basename(attempt.at(-1).command)} failed: ${error.message}`;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
const managed = await installManagedFfmpeg(home, env, platform, executableDirs);
|
|
164
|
-
if (managed)
|
|
165
|
-
return managed;
|
|
166
|
-
failure += '; managed npm installation was unavailable';
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
failure += `; managed npm installation failed: ${error.message}`;
|
|
170
|
-
}
|
|
171
|
-
const manual = ffmpegManualCommand(platform, osRelease);
|
|
172
|
-
throw new Error(`ffmpeg is required for browser recording and automatic installation failed (${failure}). Run \`${manual}\`, then retry.`);
|
|
173
|
-
})();
|
|
174
|
-
try {
|
|
175
|
-
return await resolvedFfmpeg;
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
resolvedFfmpeg = undefined;
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
export function resetFfmpegResolverForTest() {
|
|
183
|
-
resolvedFfmpeg = undefined;
|
|
184
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
interface FirefoxIniProfile {
|
|
2
|
-
/** `Name=` of the `[ProfileN]` section. */
|
|
3
|
-
name: string;
|
|
4
|
-
/** Absolute profile directory (`Path=` resolved against the ini's dir when `IsRelative=1`). */
|
|
5
|
-
dir: string;
|
|
6
|
-
/** `Default=1` on the entry (a legacy marker; `[Install*]` sections carry the real one). */
|
|
7
|
-
isDefault: boolean;
|
|
8
|
-
/** The `profiles.ini` this entry came from. */
|
|
9
|
-
iniPath: string;
|
|
10
|
-
}
|
|
11
|
-
type FirefoxDiscoveryResult = {
|
|
12
|
-
ok: true;
|
|
13
|
-
profiles: FirefoxIniProfile[];
|
|
14
|
-
iniPaths: string[];
|
|
15
|
-
} | {
|
|
16
|
-
ok: false;
|
|
17
|
-
kind: 'not-installed' | 'invalid';
|
|
18
|
-
reason: string;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Every directory a `profiles.ini` may live in on this machine. Ubuntu's snap
|
|
22
|
-
* Firefox keeps its whole `.mozilla` under `~/snap/firefox/common/`, a deb or
|
|
23
|
-
* tarball Firefox uses `~/.mozilla/firefox/` — a box can carry both, so both
|
|
24
|
-
* are read. `AGENTS_FIREFOX_DIRS` (path-separator list) points tests and
|
|
25
|
-
* non-default installs elsewhere.
|
|
26
|
-
*/
|
|
27
|
-
export declare function firefoxProfileRoots(): string[];
|
|
28
|
-
/**
|
|
29
|
-
* Parse one `profiles.ini`. Only `[Profile<n>]` sections are profiles;
|
|
30
|
-
* `[General]` and `[Install<hash>]` (which record the per-install default) are
|
|
31
|
-
* skipped. A `[Profile]` section that lacks `Name` or `Path` is a malformed
|
|
32
|
-
* file, not a profile to guess at.
|
|
33
|
-
*/
|
|
34
|
-
export declare function parseProfilesIni(iniPath: string): FirefoxIniProfile[] | {
|
|
35
|
-
error: string;
|
|
36
|
-
};
|
|
37
|
-
export declare function discoverFirefoxProfilesAt(roots: string[]): FirefoxDiscoveryResult;
|
|
38
|
-
export declare function discoverFirefoxProfiles(): FirefoxDiscoveryResult;
|
|
39
|
-
/** One discovered Firefox profile as an agents-cli profile row. */
|
|
40
|
-
interface FirefoxDiscoveredProfile {
|
|
41
|
-
/** The agents-cli profile name: `firefox-<name-slug>`. */
|
|
42
|
-
name: string;
|
|
43
|
-
profileName: string;
|
|
44
|
-
profileDir: string;
|
|
45
|
-
iniPath: string;
|
|
46
|
-
isDefault: boolean;
|
|
47
|
-
/** The local WebDriver BiDi port this profile is pinned to. */
|
|
48
|
-
port: number;
|
|
49
|
-
}
|
|
50
|
-
/** Firefox BiDi ports live in their own range so they never collide with the 9222-9399 CDP profiles. */
|
|
51
|
-
export declare const FIREFOX_PORT_RANGE: Readonly<{
|
|
52
|
-
base: 9600;
|
|
53
|
-
size: 100;
|
|
54
|
-
}>;
|
|
55
|
-
/**
|
|
56
|
-
* The port a discovered Firefox profile listens on: derived from its directory,
|
|
57
|
-
* so the relaunch hint in an attach error is the same string every time and a
|
|
58
|
-
* daemon restart reattaches to the Firefox it launched earlier. Collisions
|
|
59
|
-
* between two discovered profiles resolve by linear probing within the range.
|
|
60
|
-
*/
|
|
61
|
-
export declare function firefoxProfilePort(profileDir: string, taken?: Set<number>): number;
|
|
62
|
-
/**
|
|
63
|
-
* Flatten discovery into one agents-cli profile per `profiles.ini` entry, named
|
|
64
|
-
* `firefox-<name>` (`firefox-default`, `firefox-default-release`, …). Two
|
|
65
|
-
* entries with the same name (one per ini, say) get the first six hex
|
|
66
|
-
* characters of their directory hash appended so the names stay distinct.
|
|
67
|
-
*/
|
|
68
|
-
export declare function firefoxDiscoveredProfiles(result: FirefoxDiscoveryResult): FirefoxDiscoveredProfile[];
|
|
69
|
-
export {};
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Read-only Firefox profile discovery from `profiles.ini` (PHNX-4043).
|
|
3
|
-
*
|
|
4
|
-
* One agents-cli profile per `[ProfileN]` entry, named `firefox-<name-slug>`
|
|
5
|
-
* and pinned to that entry's directory. The profile already carries its
|
|
6
|
-
* cookies and logins, so it IS the browser profile agents pick with
|
|
7
|
-
* `--profile` — the same shape Arc Space discovery uses (`arc-discovery.ts`).
|
|
8
|
-
*/
|
|
9
|
-
import * as fs from 'node:fs';
|
|
10
|
-
import * as os from 'node:os';
|
|
11
|
-
import * as path from 'node:path';
|
|
12
|
-
/**
|
|
13
|
-
* Every directory a `profiles.ini` may live in on this machine. Ubuntu's snap
|
|
14
|
-
* Firefox keeps its whole `.mozilla` under `~/snap/firefox/common/`, a deb or
|
|
15
|
-
* tarball Firefox uses `~/.mozilla/firefox/` — a box can carry both, so both
|
|
16
|
-
* are read. `AGENTS_FIREFOX_DIRS` (path-separator list) points tests and
|
|
17
|
-
* non-default installs elsewhere.
|
|
18
|
-
*/
|
|
19
|
-
export function firefoxProfileRoots() {
|
|
20
|
-
const override = process.env.AGENTS_FIREFOX_DIRS;
|
|
21
|
-
if (override)
|
|
22
|
-
return override.split(path.delimiter).filter(Boolean).map((p) => path.resolve(p));
|
|
23
|
-
const home = os.homedir();
|
|
24
|
-
switch (process.platform) {
|
|
25
|
-
case 'darwin':
|
|
26
|
-
return [path.join(home, 'Library', 'Application Support', 'Firefox')];
|
|
27
|
-
case 'win32':
|
|
28
|
-
return [path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Mozilla', 'Firefox')];
|
|
29
|
-
default:
|
|
30
|
-
return [
|
|
31
|
-
path.join(home, '.mozilla', 'firefox'),
|
|
32
|
-
path.join(home, 'snap', 'firefox', 'common', '.mozilla', 'firefox'),
|
|
33
|
-
];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Parse one `profiles.ini`. Only `[Profile<n>]` sections are profiles;
|
|
38
|
-
* `[General]` and `[Install<hash>]` (which record the per-install default) are
|
|
39
|
-
* skipped. A `[Profile]` section that lacks `Name` or `Path` is a malformed
|
|
40
|
-
* file, not a profile to guess at.
|
|
41
|
-
*/
|
|
42
|
-
export function parseProfilesIni(iniPath) {
|
|
43
|
-
let text;
|
|
44
|
-
try {
|
|
45
|
-
text = fs.readFileSync(iniPath, 'utf8');
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
return { error: `Cannot read ${iniPath}: ${error instanceof Error ? error.message : String(error)}` };
|
|
49
|
-
}
|
|
50
|
-
const root = path.dirname(iniPath);
|
|
51
|
-
const sections = [];
|
|
52
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
53
|
-
const line = raw.trim();
|
|
54
|
-
if (!line || line.startsWith(';') || line.startsWith('#'))
|
|
55
|
-
continue;
|
|
56
|
-
const header = line.match(/^\[(.+)\]$/);
|
|
57
|
-
if (header) {
|
|
58
|
-
sections.push({ header: header[1], values: {} });
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const eq = line.indexOf('=');
|
|
62
|
-
if (eq === -1 || sections.length === 0) {
|
|
63
|
-
return { error: `${iniPath} has a malformed line: ${JSON.stringify(raw)}` };
|
|
64
|
-
}
|
|
65
|
-
sections[sections.length - 1].values[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
|
|
66
|
-
}
|
|
67
|
-
const profiles = [];
|
|
68
|
-
for (const section of sections) {
|
|
69
|
-
if (!/^Profile\d+$/.test(section.header))
|
|
70
|
-
continue;
|
|
71
|
-
const name = section.values.Name;
|
|
72
|
-
const rel = section.values.Path;
|
|
73
|
-
if (!name || !rel) {
|
|
74
|
-
return { error: `${iniPath} [${section.header}] has no Name or Path` };
|
|
75
|
-
}
|
|
76
|
-
const isRelative = section.values.IsRelative !== '0';
|
|
77
|
-
const dir = isRelative ? path.resolve(root, rel) : path.resolve(rel);
|
|
78
|
-
profiles.push({ name, dir, isDefault: section.values.Default === '1', iniPath });
|
|
79
|
-
}
|
|
80
|
-
return profiles;
|
|
81
|
-
}
|
|
82
|
-
export function discoverFirefoxProfilesAt(roots) {
|
|
83
|
-
const iniPaths = roots.map((root) => path.join(root, 'profiles.ini')).filter((p) => fs.existsSync(p));
|
|
84
|
-
if (iniPaths.length === 0) {
|
|
85
|
-
return {
|
|
86
|
-
ok: false,
|
|
87
|
-
kind: 'not-installed',
|
|
88
|
-
reason: `No Firefox profiles.ini under ${roots.join(', ')}`,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
const profiles = [];
|
|
92
|
-
for (const iniPath of iniPaths) {
|
|
93
|
-
const parsed = parseProfilesIni(iniPath);
|
|
94
|
-
if ('error' in parsed)
|
|
95
|
-
return { ok: false, kind: 'invalid', reason: parsed.error };
|
|
96
|
-
profiles.push(...parsed);
|
|
97
|
-
}
|
|
98
|
-
if (profiles.length === 0) {
|
|
99
|
-
return { ok: false, kind: 'invalid', reason: `No [Profile] entries in ${iniPaths.join(', ')}` };
|
|
100
|
-
}
|
|
101
|
-
return { ok: true, profiles, iniPaths };
|
|
102
|
-
}
|
|
103
|
-
export function discoverFirefoxProfiles() {
|
|
104
|
-
return discoverFirefoxProfilesAt(firefoxProfileRoots());
|
|
105
|
-
}
|
|
106
|
-
function slugify(value) {
|
|
107
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
108
|
-
}
|
|
109
|
-
/** FNV-1a over the path, folded into the range. Stable across runs and daemons. */
|
|
110
|
-
function stableHash(value) {
|
|
111
|
-
let hash = 0x811c9dc5;
|
|
112
|
-
for (let i = 0; i < value.length; i++) {
|
|
113
|
-
hash ^= value.charCodeAt(i);
|
|
114
|
-
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
115
|
-
}
|
|
116
|
-
return hash;
|
|
117
|
-
}
|
|
118
|
-
/** Firefox BiDi ports live in their own range so they never collide with the 9222-9399 CDP profiles. */
|
|
119
|
-
export const FIREFOX_PORT_RANGE = Object.freeze({ base: 9600, size: 100 });
|
|
120
|
-
/**
|
|
121
|
-
* The port a discovered Firefox profile listens on: derived from its directory,
|
|
122
|
-
* so the relaunch hint in an attach error is the same string every time and a
|
|
123
|
-
* daemon restart reattaches to the Firefox it launched earlier. Collisions
|
|
124
|
-
* between two discovered profiles resolve by linear probing within the range.
|
|
125
|
-
*/
|
|
126
|
-
export function firefoxProfilePort(profileDir, taken = new Set()) {
|
|
127
|
-
const { base, size } = FIREFOX_PORT_RANGE;
|
|
128
|
-
let port = base + (stableHash(profileDir) % size);
|
|
129
|
-
for (let i = 0; i < size && taken.has(port); i++) {
|
|
130
|
-
port = base + ((port - base + 1) % size);
|
|
131
|
-
}
|
|
132
|
-
return port;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Flatten discovery into one agents-cli profile per `profiles.ini` entry, named
|
|
136
|
-
* `firefox-<name>` (`firefox-default`, `firefox-default-release`, …). Two
|
|
137
|
-
* entries with the same name (one per ini, say) get the first six hex
|
|
138
|
-
* characters of their directory hash appended so the names stay distinct.
|
|
139
|
-
*/
|
|
140
|
-
export function firefoxDiscoveredProfiles(result) {
|
|
141
|
-
if (!result.ok)
|
|
142
|
-
return [];
|
|
143
|
-
const taken = new Set();
|
|
144
|
-
const flat = result.profiles.map((profile) => {
|
|
145
|
-
const port = firefoxProfilePort(profile.dir, taken);
|
|
146
|
-
taken.add(port);
|
|
147
|
-
return {
|
|
148
|
-
name: `firefox-${slugify(profile.name) || 'profile'}`,
|
|
149
|
-
profileName: profile.name,
|
|
150
|
-
profileDir: profile.dir,
|
|
151
|
-
iniPath: profile.iniPath,
|
|
152
|
-
isDefault: profile.isDefault,
|
|
153
|
-
port,
|
|
154
|
-
};
|
|
155
|
-
});
|
|
156
|
-
const counts = new Map();
|
|
157
|
-
for (const entry of flat)
|
|
158
|
-
counts.set(entry.name, (counts.get(entry.name) ?? 0) + 1);
|
|
159
|
-
return flat.map((entry) => (counts.get(entry.name) ?? 0) > 1
|
|
160
|
-
? { ...entry, name: `${entry.name}-${stableHash(entry.profileDir).toString(16).padStart(8, '0').slice(0, 6)}` }
|
|
161
|
-
: entry);
|
|
162
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type { NetworkRequest } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Minimal HAR 1.2 shapes. We only produce what our request buffer can back —
|
|
4
|
-
* fields the buffer doesn't populate (headers, cookies, bodies, timings) are
|
|
5
|
-
* emitted with the spec-mandated empty defaults or `-1` sentinels so validators
|
|
6
|
-
* still accept the document.
|
|
7
|
-
*
|
|
8
|
-
* Spec: http://www.softwareishard.com/blog/har-12-spec/
|
|
9
|
-
*/
|
|
10
|
-
interface HarLog {
|
|
11
|
-
version: '1.2';
|
|
12
|
-
creator: {
|
|
13
|
-
name: string;
|
|
14
|
-
version: string;
|
|
15
|
-
};
|
|
16
|
-
pages: HarPage[];
|
|
17
|
-
entries: HarEntry[];
|
|
18
|
-
}
|
|
19
|
-
interface HarPage {
|
|
20
|
-
startedDateTime: string;
|
|
21
|
-
id: string;
|
|
22
|
-
title: string;
|
|
23
|
-
pageTimings: {
|
|
24
|
-
onContentLoad: number;
|
|
25
|
-
onLoad: number;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
interface HarEntry {
|
|
29
|
-
startedDateTime: string;
|
|
30
|
-
time: number;
|
|
31
|
-
request: HarRequest;
|
|
32
|
-
response: HarResponse;
|
|
33
|
-
cache: Record<string, never>;
|
|
34
|
-
timings: HarTimings;
|
|
35
|
-
}
|
|
36
|
-
interface HarRequest {
|
|
37
|
-
method: string;
|
|
38
|
-
url: string;
|
|
39
|
-
httpVersion: string;
|
|
40
|
-
cookies: never[];
|
|
41
|
-
headers: never[];
|
|
42
|
-
queryString: Array<{
|
|
43
|
-
name: string;
|
|
44
|
-
value: string;
|
|
45
|
-
}>;
|
|
46
|
-
headersSize: number;
|
|
47
|
-
bodySize: number;
|
|
48
|
-
}
|
|
49
|
-
interface HarResponse {
|
|
50
|
-
status: number;
|
|
51
|
-
statusText: string;
|
|
52
|
-
httpVersion: string;
|
|
53
|
-
cookies: never[];
|
|
54
|
-
headers: never[];
|
|
55
|
-
content: {
|
|
56
|
-
size: number;
|
|
57
|
-
mimeType: string;
|
|
58
|
-
text?: string;
|
|
59
|
-
};
|
|
60
|
-
redirectURL: string;
|
|
61
|
-
headersSize: number;
|
|
62
|
-
bodySize: number;
|
|
63
|
-
}
|
|
64
|
-
interface HarTimings {
|
|
65
|
-
send: number;
|
|
66
|
-
wait: number;
|
|
67
|
-
receive: number;
|
|
68
|
-
}
|
|
69
|
-
interface BuildHarOptions {
|
|
70
|
-
creatorName?: string;
|
|
71
|
-
creatorVersion?: string;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Serialize a buffered `NetworkRequest[]` into a HAR 1.2 document.
|
|
75
|
-
*
|
|
76
|
-
* The buffer only records `url`, `method`, `status`, `mimeType`, `timestamp`.
|
|
77
|
-
* Everything downstream — cookies, headers, bodies, per-phase timings — is
|
|
78
|
-
* unknown and must be emitted as the spec's "unknown" sentinels: `-1` for
|
|
79
|
-
* sizes / timings, empty arrays for cookies / headers. HAR validators accept
|
|
80
|
-
* this shape (browsers do the same for opaque cross-origin responses).
|
|
81
|
-
*/
|
|
82
|
-
export declare function buildHar(requests: NetworkRequest[], opts?: BuildHarOptions): {
|
|
83
|
-
log: HarLog;
|
|
84
|
-
};
|
|
85
|
-
export {};
|
package/dist/lib/browser/har.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Serialize a buffered `NetworkRequest[]` into a HAR 1.2 document.
|
|
3
|
-
*
|
|
4
|
-
* The buffer only records `url`, `method`, `status`, `mimeType`, `timestamp`.
|
|
5
|
-
* Everything downstream — cookies, headers, bodies, per-phase timings — is
|
|
6
|
-
* unknown and must be emitted as the spec's "unknown" sentinels: `-1` for
|
|
7
|
-
* sizes / timings, empty arrays for cookies / headers. HAR validators accept
|
|
8
|
-
* this shape (browsers do the same for opaque cross-origin responses).
|
|
9
|
-
*/
|
|
10
|
-
export function buildHar(requests, opts = {}) {
|
|
11
|
-
const creator = {
|
|
12
|
-
name: opts.creatorName ?? 'agents-cli',
|
|
13
|
-
version: opts.creatorVersion ?? '',
|
|
14
|
-
};
|
|
15
|
-
const startedAt = requests.length > 0 ? Math.min(...requests.map((r) => r.timestamp)) : Date.now();
|
|
16
|
-
const page = {
|
|
17
|
-
startedDateTime: new Date(startedAt).toISOString(),
|
|
18
|
-
id: 'page_1',
|
|
19
|
-
title: 'agents browser session',
|
|
20
|
-
pageTimings: { onContentLoad: -1, onLoad: -1 },
|
|
21
|
-
};
|
|
22
|
-
const entries = requests.map((r) => {
|
|
23
|
-
const queryString = extractQueryString(r.url);
|
|
24
|
-
const mimeType = r.mimeType ?? '';
|
|
25
|
-
return {
|
|
26
|
-
startedDateTime: new Date(r.timestamp).toISOString(),
|
|
27
|
-
time: -1,
|
|
28
|
-
request: {
|
|
29
|
-
method: r.method,
|
|
30
|
-
url: r.url,
|
|
31
|
-
httpVersion: 'HTTP/1.1',
|
|
32
|
-
cookies: [],
|
|
33
|
-
headers: [],
|
|
34
|
-
queryString,
|
|
35
|
-
headersSize: -1,
|
|
36
|
-
bodySize: -1,
|
|
37
|
-
},
|
|
38
|
-
response: {
|
|
39
|
-
status: r.status ?? 0,
|
|
40
|
-
statusText: '',
|
|
41
|
-
httpVersion: 'HTTP/1.1',
|
|
42
|
-
cookies: [],
|
|
43
|
-
headers: [],
|
|
44
|
-
content: { size: -1, mimeType },
|
|
45
|
-
redirectURL: '',
|
|
46
|
-
headersSize: -1,
|
|
47
|
-
bodySize: -1,
|
|
48
|
-
},
|
|
49
|
-
cache: {},
|
|
50
|
-
timings: { send: -1, wait: -1, receive: -1 },
|
|
51
|
-
};
|
|
52
|
-
});
|
|
53
|
-
return {
|
|
54
|
-
log: {
|
|
55
|
-
version: '1.2',
|
|
56
|
-
creator,
|
|
57
|
-
pages: [page],
|
|
58
|
-
entries,
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Pull `?a=1&b=2` off a URL into HAR's `queryString` array. Returns `[]` when
|
|
64
|
-
* the URL has no query or fails to parse (opaque URLs like `data:` shouldn't
|
|
65
|
-
* abort the whole HAR).
|
|
66
|
-
*/
|
|
67
|
-
function extractQueryString(url) {
|
|
68
|
-
try {
|
|
69
|
-
const u = new URL(url);
|
|
70
|
-
const out = [];
|
|
71
|
-
u.searchParams.forEach((value, name) => out.push({ name, value }));
|
|
72
|
-
return out;
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { type PidSessionEntry } from '../session/pid-registry.js';
|
|
2
|
-
import { type ReapResult, type Task } from './types.js';
|
|
3
|
-
/**
|
|
4
|
-
* The slice of `BrowserService` the reaper needs. Structural, so a test drives
|
|
5
|
-
* it with a stub and `BrowserService` satisfies it without an import — which
|
|
6
|
-
* also keeps this module off the service's import cycle.
|
|
7
|
-
*/
|
|
8
|
-
export interface ReapableService {
|
|
9
|
-
listTasks(): Array<{
|
|
10
|
-
profile: string;
|
|
11
|
-
task: Task;
|
|
12
|
-
}>;
|
|
13
|
-
recordStatus(taskName: string): Promise<{
|
|
14
|
-
recording: boolean;
|
|
15
|
-
}>;
|
|
16
|
-
stop(taskName: string): Promise<{
|
|
17
|
-
ok: boolean;
|
|
18
|
-
profile?: string;
|
|
19
|
-
}>;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Injectable liveness sources. Same shape as `feed-post.ts`
|
|
23
|
-
* (`input.listEntries ?? listPidSessionEntries`, feed-post.ts:119) — the
|
|
24
|
-
* defaults are the real registry and the real process table, so a test can
|
|
25
|
-
* substitute them without mocking a module.
|
|
26
|
-
*/
|
|
27
|
-
export interface ReapDeps {
|
|
28
|
-
listEntries?: () => PidSessionEntry[];
|
|
29
|
-
pidAlive?: (pid: number, startedAtMs?: number) => boolean;
|
|
30
|
-
sessionIdOfPid?: (pid: number) => string | undefined;
|
|
31
|
-
sessionLiveOnProcessTable?: (sessionId: string) => Promise<boolean>;
|
|
32
|
-
}
|
|
33
|
-
export interface ReapOptions {
|
|
34
|
-
/**
|
|
35
|
-
* Idle window in ms. Default {@link DEFAULT_IDLE_MS}.
|
|
36
|
-
*
|
|
37
|
-
* `null` is the explicit "idle reaping is off" signal
|
|
38
|
-
* (`browser.task-idle-minutes=0`) — session-dead reaping below is
|
|
39
|
-
* unaffected. A caller-supplied `0` is a DIFFERENT thing and still throws
|
|
40
|
-
* (see the guard below): it is far likelier to be a bug (a value that
|
|
41
|
-
* survived `??` unset) than an intentional "reap everything immediately."
|
|
42
|
-
*/
|
|
43
|
-
idleMs?: number | null;
|
|
44
|
-
/** Clock, injectable for tests. Default `Date.now()`. */
|
|
45
|
-
now?: number;
|
|
46
|
-
/** Report what would be closed without closing anything. */
|
|
47
|
-
dryRun?: boolean;
|
|
48
|
-
deps?: ReapDeps;
|
|
49
|
-
}
|
|
50
|
-
interface LiveIdentities {
|
|
51
|
-
sessions: Set<string>;
|
|
52
|
-
launches: Set<string>;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Session and launch ids belonging to a process that is alive right now.
|
|
56
|
-
*
|
|
57
|
-
* Built from the per-pid launch registry, filtered to live pids —
|
|
58
|
-
* `isPidAlive(pid, startedAtMs)` rather than a bare existence check, so a pid
|
|
59
|
-
* the OS recycled onto an unrelated process does not read as a live agent.
|
|
60
|
-
* An entry with no recorded `sessionId` still contributes one when the live
|
|
61
|
-
* process carries `--session-id` on its argv, which is the RUSH-2384 recovery
|
|
62
|
-
* path the registry itself documents (pid-registry.ts:102-107).
|
|
63
|
-
*/
|
|
64
|
-
export declare function resolveLiveIdentities(deps?: ReapDeps): LiveIdentities;
|
|
65
|
-
/**
|
|
66
|
-
* True when the task's owner is PROVABLY gone. The bar is proof, not absence of
|
|
67
|
-
* evidence, because being wrong here closes a working agent's tabs.
|
|
68
|
-
*
|
|
69
|
-
* Only a `sessionId` can carry that proof. It has two independent sources — the
|
|
70
|
-
* per-pid launch registry, and a live process carrying `--session-id <id>` in
|
|
71
|
-
* its argv — so a session the registry missed is still caught by the process
|
|
72
|
-
* table. The registry misses constantly: a wrapper pid exits, a prune sweeps
|
|
73
|
-
* the entry, or the agent was never launched via `agents run`
|
|
74
|
-
* (pid-registry.ts:102-107, RUSH-2384).
|
|
75
|
-
*
|
|
76
|
-
* A `launchId` has NO second source — the registry is its only witness, and
|
|
77
|
-
* that witness is the unreliable one. So a task carrying only a `launchId` is
|
|
78
|
-
* never session-reaped, exactly like a task carrying no identity at all; both
|
|
79
|
-
* fall through to the idle rule. This is not a corner case: `launchId` is
|
|
80
|
-
* minted for every run (`exec.ts` `resolveLaunchId`) while `AGENT_SESSION_ID`
|
|
81
|
-
* is Claude-only and skipped on resume, so treating a missing registry entry as
|
|
82
|
-
* proof of death would close the tabs of every live codex/droid/grok run whose
|
|
83
|
-
* launch pid had already exited.
|
|
84
|
-
*
|
|
85
|
-
* A live `launchId` still RESCUES a task whose `sessionId` looks dead — proof of
|
|
86
|
-
* life needs only one witness, unlike proof of death.
|
|
87
|
-
*/
|
|
88
|
-
export declare function taskOwnerIsGone(task: Task, live: LiveIdentities, deps?: ReapDeps): Promise<boolean>;
|
|
89
|
-
/**
|
|
90
|
-
* Stop every task whose owner is gone or that has sat untouched past `idleMs`.
|
|
91
|
-
*
|
|
92
|
-
* Returns what it closed and how many it left alone. A task that is mid-
|
|
93
|
-
* recording is always left alone: reaping it would truncate a capture the user
|
|
94
|
-
* asked for, and an in-flight recording is itself proof the task is in use.
|
|
95
|
-
*/
|
|
96
|
-
export declare function reapAbandonedTasks(service: ReapableService, opts?: ReapOptions): Promise<ReapResult>;
|
|
97
|
-
export {};
|