@phnx-labs/agents-cli 1.22.117 → 1.22.119

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.
Files changed (271) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/dist/bootstrap.js +4 -11
  3. package/dist/commands/accounts.js +7 -4
  4. package/dist/commands/browser.d.ts +52 -70
  5. package/dist/commands/browser.js +192 -3562
  6. package/dist/commands/daemon.d.ts +39 -2
  7. package/dist/commands/daemon.js +72 -73
  8. package/dist/commands/events.js +1 -1
  9. package/dist/commands/exec.js +53 -1
  10. package/dist/commands/projects.js +92 -7
  11. package/dist/commands/repo.js +13 -49
  12. package/dist/commands/route.js +5 -5
  13. package/dist/commands/routines.js +39 -12
  14. package/dist/commands/sessions-render.d.ts +1 -1
  15. package/dist/commands/sessions-render.js +0 -1
  16. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  17. package/dist/commands/sessions.test-fixture.js +0 -37
  18. package/dist/commands/setup-browser.d.ts +17 -12
  19. package/dist/commands/setup-browser.js +52 -87
  20. package/dist/commands/setup-preferences.d.ts +0 -17
  21. package/dist/commands/setup-preferences.js +18 -49
  22. package/dist/commands/setup.js +9 -9
  23. package/dist/commands/ssh.js +10 -1
  24. package/dist/commands/sync.js +6 -44
  25. package/dist/commands/teams.js +0 -1
  26. package/dist/commands/update.d.ts +19 -0
  27. package/dist/commands/update.js +34 -6
  28. package/dist/commands/view.d.ts +12 -1
  29. package/dist/commands/view.js +47 -2
  30. package/dist/index.js +107 -26
  31. package/dist/lib/account-capabilities.js +0 -2
  32. package/dist/lib/account-catalog.d.ts +38 -0
  33. package/dist/lib/account-catalog.js +110 -26
  34. package/dist/lib/account-provider-registry.js +1 -1
  35. package/dist/lib/account-registry.d.ts +1 -1
  36. package/dist/lib/account-registry.js +36 -2
  37. package/dist/lib/accounting/rotate.js +22 -8
  38. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  39. package/dist/lib/accounting/usage-ingest.js +115 -45
  40. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  41. package/dist/lib/accounting/usage-sync.js +227 -66
  42. package/dist/lib/accounting/usage.d.ts +16 -3
  43. package/dist/lib/accounting/usage.js +26 -8
  44. package/dist/lib/accounts/add.d.ts +3 -1
  45. package/dist/lib/accounts/add.js +24 -13
  46. package/dist/lib/accounts/slots.d.ts +8 -0
  47. package/dist/lib/accounts/slots.js +17 -0
  48. package/dist/lib/add-dir.js +0 -1
  49. package/dist/lib/agent-cli-commands.js +0 -1
  50. package/dist/lib/agent-spec/agents.d.ts +6 -7
  51. package/dist/lib/agent-spec/agents.js +7 -60
  52. package/dist/lib/auth-health.d.ts +69 -1
  53. package/dist/lib/auth-health.js +133 -4
  54. package/dist/lib/browser/context.d.ts +82 -0
  55. package/dist/lib/browser/context.js +88 -0
  56. package/dist/lib/browser/paths.d.ts +25 -0
  57. package/dist/lib/browser/paths.js +51 -0
  58. package/dist/lib/browser/record.d.ts +38 -0
  59. package/dist/lib/browser/record.js +57 -0
  60. package/dist/lib/browser/sessions-list.js +1 -2
  61. package/dist/lib/browser-client.d.ts +158 -0
  62. package/dist/lib/browser-client.js +234 -0
  63. package/dist/lib/channels/owner-sink.d.ts +1 -18
  64. package/dist/lib/channels/owner-sink.js +29 -82
  65. package/dist/lib/channels/providers/rush.d.ts +7 -7
  66. package/dist/lib/channels/providers/rush.js +127 -57
  67. package/dist/lib/claude-account-token.d.ts +7 -1
  68. package/dist/lib/claude-account-token.js +26 -4
  69. package/dist/lib/claude-statusline.d.ts +1 -1
  70. package/dist/lib/claude-statusline.js +15 -3
  71. package/dist/lib/commands.js +1 -1
  72. package/dist/lib/crabbox/lease.js +1 -1
  73. package/dist/lib/crabbox/runtimes.js +1 -3
  74. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  75. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  76. package/dist/lib/daemon/auth-sync-service.js +37 -43
  77. package/dist/lib/daemon/daemon.d.ts +25 -14
  78. package/dist/lib/daemon/daemon.js +92 -154
  79. package/dist/lib/daemon/runner.d.ts +2 -2
  80. package/dist/lib/daemon/runner.js +32 -9
  81. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  82. package/dist/lib/daemon/self-update-service.js +40 -36
  83. package/dist/lib/daemon/service.d.ts +12 -8
  84. package/dist/lib/daemon/supervisor.d.ts +65 -54
  85. package/dist/lib/daemon/supervisor.js +136 -171
  86. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  87. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  88. package/dist/lib/daemon/usage-sync-service.js +46 -78
  89. package/dist/lib/daemon-health.d.ts +22 -5
  90. package/dist/lib/daemon-health.js +47 -3
  91. package/dist/lib/daemon-services.d.ts +1 -1
  92. package/dist/lib/daemon-services.js +0 -10
  93. package/dist/lib/daemon-ticks.js +4 -2
  94. package/dist/lib/devices/doctor-findings.js +8 -11
  95. package/dist/lib/devices/harness-inventory.js +14 -5
  96. package/dist/lib/exec-account-home.js +5 -0
  97. package/dist/lib/exec.js +10 -13
  98. package/dist/lib/feed/events.js +0 -1
  99. package/dist/lib/feed/tool-activity.js +5 -2
  100. package/dist/lib/fleet-shared-state.d.ts +45 -0
  101. package/dist/lib/fleet-shared-state.js +78 -9
  102. package/dist/lib/fs-atomic.js +25 -0
  103. package/dist/lib/github/pr-mergeable.d.ts +4 -2
  104. package/dist/lib/github/pr-mergeable.js +5 -3
  105. package/dist/lib/github/project-prs.d.ts +58 -3
  106. package/dist/lib/github/project-prs.js +205 -7
  107. package/dist/lib/harness/adapters/cursor.js +14 -4
  108. package/dist/lib/harness-auth-capabilities.d.ts +4 -3
  109. package/dist/lib/harness-auth-capabilities.js +1 -2
  110. package/dist/lib/helper-versions.js +1 -1
  111. package/dist/lib/hooks/cache.d.ts +7 -0
  112. package/dist/lib/hooks/cache.js +27 -2
  113. package/dist/lib/hooks/install.js +7 -1
  114. package/dist/lib/hosts/ready.d.ts +38 -8
  115. package/dist/lib/hosts/ready.js +62 -14
  116. package/dist/lib/installations/active-check.d.ts +27 -0
  117. package/dist/lib/installations/active-check.js +61 -0
  118. package/dist/lib/installations/migrate.js +6 -3
  119. package/dist/lib/installations/shims.d.ts +1 -1
  120. package/dist/lib/installations/shims.js +32 -12
  121. package/dist/lib/installations/store.d.ts +9 -0
  122. package/dist/lib/installations/store.js +24 -0
  123. package/dist/lib/installations/update.js +21 -15
  124. package/dist/lib/installations/versions.js +15 -3
  125. package/dist/lib/manifest.js +1 -1
  126. package/dist/lib/models.js +0 -2
  127. package/dist/lib/open-url.js +28 -54
  128. package/dist/lib/profiles.js +0 -2
  129. package/dist/lib/projects.d.ts +16 -0
  130. package/dist/lib/projects.js +31 -0
  131. package/dist/lib/refresh.js +1 -1
  132. package/dist/lib/routine-readiness.d.ts +35 -0
  133. package/dist/lib/routine-readiness.js +74 -16
  134. package/dist/lib/rules/compile.js +6 -0
  135. package/dist/lib/sandbox.js +0 -2
  136. package/dist/lib/secrets-policy.d.ts +89 -11
  137. package/dist/lib/secrets-policy.js +175 -28
  138. package/dist/lib/session/active.d.ts +17 -6
  139. package/dist/lib/session/active.js +105 -49
  140. package/dist/lib/session/db.d.ts +1 -1
  141. package/dist/lib/session/db.js +65 -18
  142. package/dist/lib/session/discover.js +1 -283
  143. package/dist/lib/session/mirror.d.ts +4 -3
  144. package/dist/lib/session/mirror.js +4 -3
  145. package/dist/lib/session/parse.d.ts +0 -2
  146. package/dist/lib/session/parse.js +1 -138
  147. package/dist/lib/session/pid-registry.d.ts +6 -3
  148. package/dist/lib/session/pid-registry.js +18 -8
  149. package/dist/lib/session/remote/watch.d.ts +1 -1
  150. package/dist/lib/session/remote/watch.js +7 -3
  151. package/dist/lib/session/throughput.d.ts +1 -3
  152. package/dist/lib/session/throughput.js +0 -20
  153. package/dist/lib/session/types.d.ts +2 -2
  154. package/dist/lib/session/types.js +2 -2
  155. package/dist/lib/sessions-client.d.ts +60 -1
  156. package/dist/lib/sessions-client.js +227 -4
  157. package/dist/lib/setup-tool-status.js +5 -10
  158. package/dist/lib/signin-badge.d.ts +3 -2
  159. package/dist/lib/signin-badge.js +6 -3
  160. package/dist/lib/smart-launch.js +4 -1
  161. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  162. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  163. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  164. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  165. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  166. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  167. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  168. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  169. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  170. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  171. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  172. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  173. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  174. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  175. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  176. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  177. package/dist/lib/staleness/writers/sources.js +5 -0
  178. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  179. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  180. package/dist/lib/state.d.ts +6 -4
  181. package/dist/lib/state.js +7 -17
  182. package/dist/lib/teams/parsers.d.ts +1 -1
  183. package/dist/lib/teams/parsers.js +1 -136
  184. package/dist/lib/teams/placement-probe.js +50 -15
  185. package/dist/lib/teams/scheduler.d.ts +7 -0
  186. package/dist/lib/types.d.ts +16 -1
  187. package/dist/lib/types.js +1 -1
  188. package/dist/lib/view-types.d.ts +32 -0
  189. package/dist/lib/watchdog/read.js +0 -1
  190. package/dist/lib/watchdog/rotate.js +1 -2
  191. package/dist/lib/watchdog/runner.js +2 -2
  192. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  193. package/dist/lib/watchdog/watchdogTail.js +0 -31
  194. package/package.json +1 -1
  195. package/scripts/postinstall.js +8 -8
  196. package/dist/browser.d.ts +0 -2
  197. package/dist/browser.js +0 -17
  198. package/dist/commands/browser-picker.d.ts +0 -18
  199. package/dist/commands/browser-picker.js +0 -97
  200. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  201. package/dist/lib/browser/arc-discovery.js +0 -197
  202. package/dist/lib/browser/arc-dom.d.ts +0 -14
  203. package/dist/lib/browser/arc-dom.js +0 -121
  204. package/dist/lib/browser/caller-identity.d.ts +0 -40
  205. package/dist/lib/browser/caller-identity.js +0 -175
  206. package/dist/lib/browser/cdp.d.ts +0 -51
  207. package/dist/lib/browser/cdp.js +0 -254
  208. package/dist/lib/browser/chrome.d.ts +0 -141
  209. package/dist/lib/browser/chrome.js +0 -704
  210. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  211. package/dist/lib/browser/chromium-discovery.js +0 -96
  212. package/dist/lib/browser/devices.d.ts +0 -34
  213. package/dist/lib/browser/devices.js +0 -61
  214. package/dist/lib/browser/domain-skills.d.ts +0 -71
  215. package/dist/lib/browser/domain-skills.js +0 -195
  216. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  217. package/dist/lib/browser/drivers/arc.js +0 -276
  218. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  219. package/dist/lib/browser/drivers/firefox.js +0 -377
  220. package/dist/lib/browser/drivers/local.d.ts +0 -55
  221. package/dist/lib/browser/drivers/local.js +0 -266
  222. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  223. package/dist/lib/browser/drivers/ssh.js +0 -467
  224. package/dist/lib/browser/editor.d.ts +0 -3
  225. package/dist/lib/browser/editor.js +0 -50
  226. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  227. package/dist/lib/browser/ffmpeg.js +0 -184
  228. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  229. package/dist/lib/browser/firefox-discovery.js +0 -162
  230. package/dist/lib/browser/har.d.ts +0 -85
  231. package/dist/lib/browser/har.js +0 -77
  232. package/dist/lib/browser/hygiene.d.ts +0 -97
  233. package/dist/lib/browser/hygiene.js +0 -153
  234. package/dist/lib/browser/index.d.ts +0 -5
  235. package/dist/lib/browser/index.js +0 -5
  236. package/dist/lib/browser/input.d.ts +0 -7
  237. package/dist/lib/browser/input.js +0 -92
  238. package/dist/lib/browser/ipc.d.ts +0 -204
  239. package/dist/lib/browser/ipc.js +0 -1453
  240. package/dist/lib/browser/login-detection.d.ts +0 -87
  241. package/dist/lib/browser/login-detection.js +0 -274
  242. package/dist/lib/browser/profiles.d.ts +0 -422
  243. package/dist/lib/browser/profiles.js +0 -1157
  244. package/dist/lib/browser/refs.d.ts +0 -89
  245. package/dist/lib/browser/refs.js +0 -191
  246. package/dist/lib/browser/registry.d.ts +0 -70
  247. package/dist/lib/browser/registry.js +0 -252
  248. package/dist/lib/browser/remote-control.d.ts +0 -59
  249. package/dist/lib/browser/remote-control.js +0 -85
  250. package/dist/lib/browser/resolve-target.d.ts +0 -66
  251. package/dist/lib/browser/resolve-target.js +0 -264
  252. package/dist/lib/browser/runtime-state.d.ts +0 -291
  253. package/dist/lib/browser/runtime-state.js +0 -584
  254. package/dist/lib/browser/secret-ref.d.ts +0 -10
  255. package/dist/lib/browser/secret-ref.js +0 -14
  256. package/dist/lib/browser/service.d.ts +0 -812
  257. package/dist/lib/browser/service.js +0 -4693
  258. package/dist/lib/browser/stream.d.ts +0 -17
  259. package/dist/lib/browser/stream.js +0 -72
  260. package/dist/lib/browser/task-index.d.ts +0 -78
  261. package/dist/lib/browser/task-index.js +0 -199
  262. package/dist/lib/browser/types.d.ts +0 -647
  263. package/dist/lib/browser/types.js +0 -61
  264. package/dist/lib/browser/upload.d.ts +0 -29
  265. package/dist/lib/browser/upload.js +0 -298
  266. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  267. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  268. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  269. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  270. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  271. 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 {};
@@ -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 {};