@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.
Files changed (254) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/dist/bootstrap.js +4 -11
  3. package/dist/commands/browser.d.ts +52 -70
  4. package/dist/commands/browser.js +192 -3562
  5. package/dist/commands/daemon.d.ts +39 -2
  6. package/dist/commands/daemon.js +72 -73
  7. package/dist/commands/events.js +1 -1
  8. package/dist/commands/exec.d.ts +18 -0
  9. package/dist/commands/exec.js +104 -12
  10. package/dist/commands/repo.js +13 -49
  11. package/dist/commands/route.js +5 -5
  12. package/dist/commands/routines.js +39 -12
  13. package/dist/commands/sessions-render.d.ts +1 -1
  14. package/dist/commands/sessions-render.js +0 -1
  15. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  16. package/dist/commands/sessions.test-fixture.js +0 -37
  17. package/dist/commands/setup-browser.d.ts +17 -12
  18. package/dist/commands/setup-browser.js +52 -87
  19. package/dist/commands/setup-preferences.d.ts +0 -17
  20. package/dist/commands/setup-preferences.js +18 -49
  21. package/dist/commands/setup-term.d.ts +4 -3
  22. package/dist/commands/setup-term.js +5 -4
  23. package/dist/commands/setup.js +11 -11
  24. package/dist/commands/ssh.js +10 -1
  25. package/dist/commands/sync.js +6 -44
  26. package/dist/commands/teams.js +0 -1
  27. package/dist/commands/update.d.ts +19 -0
  28. package/dist/commands/update.js +34 -6
  29. package/dist/commands/view.d.ts +12 -1
  30. package/dist/commands/view.js +47 -2
  31. package/dist/index.js +107 -26
  32. package/dist/lib/account-capabilities.js +0 -2
  33. package/dist/lib/account-catalog.d.ts +38 -0
  34. package/dist/lib/account-catalog.js +110 -26
  35. package/dist/lib/account-provider-registry.js +1 -1
  36. package/dist/lib/account-registry.d.ts +1 -1
  37. package/dist/lib/account-registry.js +36 -2
  38. package/dist/lib/accounting/rotate.js +22 -8
  39. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  40. package/dist/lib/accounting/usage-ingest.js +115 -45
  41. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  42. package/dist/lib/accounting/usage-sync.js +227 -66
  43. package/dist/lib/accounting/usage.d.ts +16 -3
  44. package/dist/lib/accounting/usage.js +26 -8
  45. package/dist/lib/accounts/slots.d.ts +8 -0
  46. package/dist/lib/accounts/slots.js +17 -0
  47. package/dist/lib/add-dir.js +0 -1
  48. package/dist/lib/agent-cli-commands.js +0 -1
  49. package/dist/lib/agent-spec/agents.d.ts +6 -7
  50. package/dist/lib/agent-spec/agents.js +6 -60
  51. package/dist/lib/auth-health.d.ts +69 -1
  52. package/dist/lib/auth-health.js +133 -4
  53. package/dist/lib/browser/context.d.ts +82 -0
  54. package/dist/lib/browser/context.js +88 -0
  55. package/dist/lib/browser/paths.d.ts +25 -0
  56. package/dist/lib/browser/paths.js +51 -0
  57. package/dist/lib/browser/record.d.ts +38 -0
  58. package/dist/lib/browser/record.js +57 -0
  59. package/dist/lib/browser/sessions-list.js +1 -2
  60. package/dist/lib/browser-client.d.ts +158 -0
  61. package/dist/lib/browser-client.js +234 -0
  62. package/dist/lib/channels/owner-sink.d.ts +1 -18
  63. package/dist/lib/channels/owner-sink.js +29 -82
  64. package/dist/lib/channels/providers/rush.d.ts +7 -7
  65. package/dist/lib/channels/providers/rush.js +127 -57
  66. package/dist/lib/claude-statusline.d.ts +1 -1
  67. package/dist/lib/claude-statusline.js +15 -3
  68. package/dist/lib/commands.js +1 -1
  69. package/dist/lib/crabbox/lease.js +1 -1
  70. package/dist/lib/crabbox/runtimes.js +1 -3
  71. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  72. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  73. package/dist/lib/daemon/auth-sync-service.js +37 -43
  74. package/dist/lib/daemon/daemon.d.ts +25 -14
  75. package/dist/lib/daemon/daemon.js +92 -154
  76. package/dist/lib/daemon/runner.d.ts +2 -2
  77. package/dist/lib/daemon/runner.js +32 -9
  78. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  79. package/dist/lib/daemon/self-update-service.js +40 -36
  80. package/dist/lib/daemon/service.d.ts +12 -8
  81. package/dist/lib/daemon/supervisor.d.ts +65 -54
  82. package/dist/lib/daemon/supervisor.js +136 -171
  83. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  84. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  85. package/dist/lib/daemon/usage-sync-service.js +46 -78
  86. package/dist/lib/daemon-health.d.ts +22 -5
  87. package/dist/lib/daemon-health.js +47 -3
  88. package/dist/lib/daemon-services.d.ts +1 -1
  89. package/dist/lib/daemon-services.js +0 -10
  90. package/dist/lib/daemon-ticks.js +4 -2
  91. package/dist/lib/devices/doctor-findings.js +8 -11
  92. package/dist/lib/devices/harness-inventory.js +14 -5
  93. package/dist/lib/exec-account-home.js +5 -0
  94. package/dist/lib/exec.js +0 -11
  95. package/dist/lib/feed/events.js +0 -1
  96. package/dist/lib/feed/tool-activity.js +5 -2
  97. package/dist/lib/fleet-shared-state.d.ts +45 -0
  98. package/dist/lib/fleet-shared-state.js +78 -9
  99. package/dist/lib/fs-atomic.js +25 -0
  100. package/dist/lib/harness-auth-capabilities.js +0 -1
  101. package/dist/lib/helper-versions.js +1 -1
  102. package/dist/lib/hooks/cache.d.ts +7 -0
  103. package/dist/lib/hooks/cache.js +27 -2
  104. package/dist/lib/hooks/install.js +7 -1
  105. package/dist/lib/hosts/ready.d.ts +38 -8
  106. package/dist/lib/hosts/ready.js +62 -14
  107. package/dist/lib/hosts/remote-cmd.js +1 -0
  108. package/dist/lib/installations/active-check.d.ts +27 -0
  109. package/dist/lib/installations/active-check.js +61 -0
  110. package/dist/lib/installations/migrate.js +6 -3
  111. package/dist/lib/installations/shims.js +9 -7
  112. package/dist/lib/installations/update.js +21 -15
  113. package/dist/lib/installations/versions.js +12 -1
  114. package/dist/lib/manifest.js +1 -1
  115. package/dist/lib/models.js +0 -2
  116. package/dist/lib/open-url.js +28 -54
  117. package/dist/lib/placement.d.ts +2 -0
  118. package/dist/lib/placement.js +5 -0
  119. package/dist/lib/profiles.js +0 -2
  120. package/dist/lib/refresh.js +1 -1
  121. package/dist/lib/routine-readiness.d.ts +35 -0
  122. package/dist/lib/routine-readiness.js +74 -16
  123. package/dist/lib/rules/compile.js +6 -0
  124. package/dist/lib/sandbox.js +0 -2
  125. package/dist/lib/secrets-policy.d.ts +89 -11
  126. package/dist/lib/secrets-policy.js +175 -28
  127. package/dist/lib/session/db.d.ts +1 -1
  128. package/dist/lib/session/db.js +65 -18
  129. package/dist/lib/session/discover.js +1 -283
  130. package/dist/lib/session/mirror.d.ts +4 -3
  131. package/dist/lib/session/mirror.js +4 -3
  132. package/dist/lib/session/parse.d.ts +0 -2
  133. package/dist/lib/session/parse.js +1 -138
  134. package/dist/lib/session/throughput.d.ts +1 -3
  135. package/dist/lib/session/throughput.js +0 -20
  136. package/dist/lib/session/types.d.ts +2 -2
  137. package/dist/lib/session/types.js +2 -2
  138. package/dist/lib/sessions-client.d.ts +60 -1
  139. package/dist/lib/sessions-client.js +227 -4
  140. package/dist/lib/setup-tool-status.js +8 -12
  141. package/dist/lib/signin-badge.d.ts +3 -2
  142. package/dist/lib/signin-badge.js +6 -3
  143. package/dist/lib/smart-launch.js +4 -1
  144. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  145. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  146. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  147. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  148. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  149. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  150. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  151. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  152. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  153. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  154. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  155. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  156. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  157. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  158. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  159. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  160. package/dist/lib/staleness/writers/sources.js +5 -0
  161. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  162. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  163. package/dist/lib/state.d.ts +6 -4
  164. package/dist/lib/state.js +7 -17
  165. package/dist/lib/teams/parsers.d.ts +1 -1
  166. package/dist/lib/teams/parsers.js +1 -136
  167. package/dist/lib/teams/placement-probe.js +50 -15
  168. package/dist/lib/teams/scheduler.d.ts +7 -0
  169. package/dist/lib/types.d.ts +10 -1
  170. package/dist/lib/types.js +1 -1
  171. package/dist/lib/view-types.d.ts +32 -0
  172. package/dist/lib/watchdog/read.js +0 -1
  173. package/dist/lib/watchdog/rotate.js +1 -2
  174. package/dist/lib/watchdog/runner.js +2 -2
  175. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  176. package/dist/lib/watchdog/watchdogTail.js +0 -31
  177. package/package.json +1 -1
  178. package/scripts/postinstall.js +8 -8
  179. package/dist/browser.d.ts +0 -2
  180. package/dist/browser.js +0 -17
  181. package/dist/commands/browser-picker.d.ts +0 -18
  182. package/dist/commands/browser-picker.js +0 -97
  183. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  184. package/dist/lib/browser/arc-discovery.js +0 -197
  185. package/dist/lib/browser/arc-dom.d.ts +0 -14
  186. package/dist/lib/browser/arc-dom.js +0 -121
  187. package/dist/lib/browser/caller-identity.d.ts +0 -40
  188. package/dist/lib/browser/caller-identity.js +0 -175
  189. package/dist/lib/browser/cdp.d.ts +0 -51
  190. package/dist/lib/browser/cdp.js +0 -254
  191. package/dist/lib/browser/chrome.d.ts +0 -141
  192. package/dist/lib/browser/chrome.js +0 -704
  193. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  194. package/dist/lib/browser/chromium-discovery.js +0 -96
  195. package/dist/lib/browser/devices.d.ts +0 -34
  196. package/dist/lib/browser/devices.js +0 -61
  197. package/dist/lib/browser/domain-skills.d.ts +0 -71
  198. package/dist/lib/browser/domain-skills.js +0 -195
  199. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  200. package/dist/lib/browser/drivers/arc.js +0 -276
  201. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  202. package/dist/lib/browser/drivers/firefox.js +0 -377
  203. package/dist/lib/browser/drivers/local.d.ts +0 -55
  204. package/dist/lib/browser/drivers/local.js +0 -266
  205. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  206. package/dist/lib/browser/drivers/ssh.js +0 -467
  207. package/dist/lib/browser/editor.d.ts +0 -3
  208. package/dist/lib/browser/editor.js +0 -50
  209. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  210. package/dist/lib/browser/ffmpeg.js +0 -184
  211. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  212. package/dist/lib/browser/firefox-discovery.js +0 -162
  213. package/dist/lib/browser/har.d.ts +0 -85
  214. package/dist/lib/browser/har.js +0 -77
  215. package/dist/lib/browser/hygiene.d.ts +0 -97
  216. package/dist/lib/browser/hygiene.js +0 -153
  217. package/dist/lib/browser/index.d.ts +0 -5
  218. package/dist/lib/browser/index.js +0 -5
  219. package/dist/lib/browser/input.d.ts +0 -7
  220. package/dist/lib/browser/input.js +0 -92
  221. package/dist/lib/browser/ipc.d.ts +0 -204
  222. package/dist/lib/browser/ipc.js +0 -1453
  223. package/dist/lib/browser/login-detection.d.ts +0 -87
  224. package/dist/lib/browser/login-detection.js +0 -274
  225. package/dist/lib/browser/profiles.d.ts +0 -422
  226. package/dist/lib/browser/profiles.js +0 -1157
  227. package/dist/lib/browser/refs.d.ts +0 -89
  228. package/dist/lib/browser/refs.js +0 -191
  229. package/dist/lib/browser/registry.d.ts +0 -70
  230. package/dist/lib/browser/registry.js +0 -252
  231. package/dist/lib/browser/remote-control.d.ts +0 -59
  232. package/dist/lib/browser/remote-control.js +0 -85
  233. package/dist/lib/browser/resolve-target.d.ts +0 -66
  234. package/dist/lib/browser/resolve-target.js +0 -264
  235. package/dist/lib/browser/runtime-state.d.ts +0 -291
  236. package/dist/lib/browser/runtime-state.js +0 -584
  237. package/dist/lib/browser/secret-ref.d.ts +0 -10
  238. package/dist/lib/browser/secret-ref.js +0 -14
  239. package/dist/lib/browser/service.d.ts +0 -812
  240. package/dist/lib/browser/service.js +0 -4693
  241. package/dist/lib/browser/stream.d.ts +0 -17
  242. package/dist/lib/browser/stream.js +0 -72
  243. package/dist/lib/browser/task-index.d.ts +0 -78
  244. package/dist/lib/browser/task-index.js +0 -199
  245. package/dist/lib/browser/types.d.ts +0 -647
  246. package/dist/lib/browser/types.js +0 -61
  247. package/dist/lib/browser/upload.d.ts +0 -29
  248. package/dist/lib/browser/upload.js +0 -298
  249. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  250. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  251. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  252. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  253. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  254. package/dist/lib/fleet-shared-repo-sync.js +0 -490
@@ -1,153 +0,0 @@
1
- /**
2
- * Abandoned browser-task reaper (RUSH-2622).
3
- *
4
- * `agents browser done` / `stop` already close a task's tabs, but agents
5
- * routinely never call them — the run ends, the process exits, and the task's
6
- * tabs stay open in the profile window forever. Over a day of fleet activity
7
- * that is dozens of leftover tabs in Comet/Chrome.
8
- *
9
- * This closes the loop from the other end: a periodic pass that finds tasks
10
- * nobody is driving any more and stops them.
11
- *
12
- * Two independent reasons, both conservative:
13
- *
14
- * - `session-dead` — the task recorded WHICH agent session (`sessionId`) or
15
- * WHICH run (`launchId`) started it, and neither is alive on this host.
16
- * That is the honest end of the task: the thing that would have called
17
- * `done` no longer exists.
18
- * - `idle` — nothing has touched the task for `idleMs` (default 30 minutes).
19
- * The catch-all for a task with no recorded identity (a human running
20
- * `agents browser start` by hand) or an agent that stalled without exiting.
21
- *
22
- * Closing always goes through `BrowserService.stop`, never a direct
23
- * `Target.closeTarget`, so history, the session cache, the target cache, and
24
- * forked-profile teardown all stay on the one code path that already handles
25
- * them. Two things this deliberately never does: touch a tab that is not in
26
- * `task.tabs` (a tab the user opened themselves is not ours to close), and kill
27
- * the profile window or the browser process because one task went idle.
28
- */
29
- import { isPidAlive, isSessionIdLiveOnProcessTable } from '../session/active.js';
30
- import { listPidSessionEntries, sessionIdFromLivePid } from '../session/pid-registry.js';
31
- import { parseConnectionKey } from './types.js';
32
- /** Default idle window before an untouched task is reaped. */
33
- const DEFAULT_IDLE_MS = 30 * 60_000;
34
- /**
35
- * Session and launch ids belonging to a process that is alive right now.
36
- *
37
- * Built from the per-pid launch registry, filtered to live pids —
38
- * `isPidAlive(pid, startedAtMs)` rather than a bare existence check, so a pid
39
- * the OS recycled onto an unrelated process does not read as a live agent.
40
- * An entry with no recorded `sessionId` still contributes one when the live
41
- * process carries `--session-id` on its argv, which is the RUSH-2384 recovery
42
- * path the registry itself documents (pid-registry.ts:102-107).
43
- */
44
- export function resolveLiveIdentities(deps = {}) {
45
- const listEntries = deps.listEntries ?? listPidSessionEntries;
46
- const alive = deps.pidAlive ?? isPidAlive;
47
- const sessionIdOf = deps.sessionIdOfPid ?? sessionIdFromLivePid;
48
- const sessions = new Set();
49
- const launches = new Set();
50
- for (const entry of listEntries()) {
51
- if (!alive(entry.pid, entry.startedAtMs))
52
- continue;
53
- const sessionId = entry.sessionId ?? sessionIdOf(entry.pid);
54
- if (sessionId)
55
- sessions.add(sessionId);
56
- if (entry.launchId)
57
- launches.add(entry.launchId);
58
- }
59
- return { sessions, launches };
60
- }
61
- /**
62
- * True when the task's owner is PROVABLY gone. The bar is proof, not absence of
63
- * evidence, because being wrong here closes a working agent's tabs.
64
- *
65
- * Only a `sessionId` can carry that proof. It has two independent sources — the
66
- * per-pid launch registry, and a live process carrying `--session-id <id>` in
67
- * its argv — so a session the registry missed is still caught by the process
68
- * table. The registry misses constantly: a wrapper pid exits, a prune sweeps
69
- * the entry, or the agent was never launched via `agents run`
70
- * (pid-registry.ts:102-107, RUSH-2384).
71
- *
72
- * A `launchId` has NO second source — the registry is its only witness, and
73
- * that witness is the unreliable one. So a task carrying only a `launchId` is
74
- * never session-reaped, exactly like a task carrying no identity at all; both
75
- * fall through to the idle rule. This is not a corner case: `launchId` is
76
- * minted for every run (`exec.ts` `resolveLaunchId`) while `AGENT_SESSION_ID`
77
- * is Claude-only and skipped on resume, so treating a missing registry entry as
78
- * proof of death would close the tabs of every live codex/droid/grok run whose
79
- * launch pid had already exited.
80
- *
81
- * A live `launchId` still RESCUES a task whose `sessionId` looks dead — proof of
82
- * life needs only one witness, unlike proof of death.
83
- */
84
- export async function taskOwnerIsGone(task, live, deps = {}) {
85
- if (!task.sessionId)
86
- return false;
87
- if (task.launchId && live.launches.has(task.launchId))
88
- return false;
89
- if (live.sessions.has(task.sessionId))
90
- return false;
91
- const onProcessTable = deps.sessionLiveOnProcessTable ?? ((id) => isSessionIdLiveOnProcessTable(id));
92
- return !(await onProcessTable(task.sessionId));
93
- }
94
- /**
95
- * Stop every task whose owner is gone or that has sat untouched past `idleMs`.
96
- *
97
- * Returns what it closed and how many it left alone. A task that is mid-
98
- * recording is always left alone: reaping it would truncate a capture the user
99
- * asked for, and an in-flight recording is itself proof the task is in use.
100
- */
101
- export async function reapAbandonedTasks(service, opts = {}) {
102
- const idleDisabled = opts.idleMs === null;
103
- const idleMs = idleDisabled ? undefined : (opts.idleMs ?? DEFAULT_IDLE_MS);
104
- // Fail loud rather than reap everything. A caller-supplied `0` survives `??`
105
- // and would close every task including one created a millisecond ago; a
106
- // non-numeric value makes every `>=` comparison false and silently disables
107
- // idle reaping. Both are worse than an error. `null` (idleDisabled) skips
108
- // this check entirely — that is the one deliberate way to turn idle
109
- // reaping off, distinct from an accidental `0`.
110
- if (!idleDisabled && (!Number.isFinite(idleMs) || idleMs <= 0)) {
111
- throw new Error(`idleMs must be a positive number of milliseconds, got ${String(idleMs)}`);
112
- }
113
- const now = opts.now ?? Date.now();
114
- const deps = opts.deps ?? {};
115
- const live = resolveLiveIdentities(deps);
116
- const closed = [];
117
- let skipped = 0;
118
- for (const { profile, task } of service.listTasks()) {
119
- if ((await service.recordStatus(task.name)).recording) {
120
- skipped++;
121
- continue;
122
- }
123
- let reason;
124
- if (await taskOwnerIsGone(task, live, deps)) {
125
- reason = 'session-dead';
126
- }
127
- else if (!idleDisabled && now - (task.lastActionAt ?? task.createdAt) >= idleMs) {
128
- reason = 'idle';
129
- }
130
- if (!reason) {
131
- skipped++;
132
- continue;
133
- }
134
- if (opts.dryRun) {
135
- // Report the BARE profile, exactly as the real run does below — `profile`
136
- // here is the runtime key from `listTasks()`, and printing the two shapes
137
- // in one column made `gc --dry-run` and `gc` disagree (RUSH-2709).
138
- closed.push({ task: task.name, profile: parseConnectionKey(profile).profile, reason });
139
- continue;
140
- }
141
- const result = await service.stop(task.name);
142
- if (result.ok) {
143
- closed.push({ task: task.name, profile: result.profile ?? profile, reason });
144
- }
145
- else {
146
- // `stop` reports not-found only when the task already left the map — a
147
- // concurrent `done`, or a profile torn down mid-pass. Nothing was closed,
148
- // so it is not reported as closed.
149
- skipped++;
150
- }
151
- }
152
- return { closed, skipped };
153
- }
@@ -1,5 +0,0 @@
1
- export * from './types.js';
2
- export * from './profiles.js';
3
- export * from './cdp.js';
4
- export * from './chrome.js';
5
- export * from './service.js';
@@ -1,5 +0,0 @@
1
- export * from './types.js';
2
- export * from './profiles.js';
3
- export * from './cdp.js';
4
- export * from './chrome.js';
5
- export * from './service.js';
@@ -1,7 +0,0 @@
1
- import type { CDPClient } from './cdp.js';
2
- export declare function clickAtCoords(cdp: CDPClient, sessionId: string, x: number, y: number): Promise<void>;
3
- export declare function hoverAtCoords(cdp: CDPClient, sessionId: string, x: number, y: number): Promise<void>;
4
- export declare function scrollAtCoords(cdp: CDPClient, sessionId: string, x: number, y: number, deltaX: number, deltaY: number): Promise<void>;
5
- export declare function typeText(cdp: CDPClient, sessionId: string, text: string): Promise<void>;
6
- export declare function pressKey(cdp: CDPClient, sessionId: string, keyName: string): Promise<void>;
7
- export declare function focusNode(cdp: CDPClient, sessionId: string, backendNodeId: number): Promise<void>;
@@ -1,92 +0,0 @@
1
- export async function clickAtCoords(cdp, sessionId, x, y) {
2
- await cdp.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 }, sessionId);
3
- await cdp.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 }, sessionId);
4
- }
5
- export async function hoverAtCoords(cdp, sessionId, x, y) {
6
- await cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }, sessionId);
7
- }
8
- export async function scrollAtCoords(cdp, sessionId, x, y, deltaX, deltaY) {
9
- await cdp.send('Input.dispatchMouseEvent', { type: 'mouseWheel', x, y, deltaX, deltaY }, sessionId);
10
- }
11
- // `Input.insertText` is the CDP native text-insertion method. It dispatches a
12
- // real `beforeinput`/`input`/`textInput` sequence on the focused element, which
13
- // is what framework-controlled inputs (React, Vue, Solid, contenteditable
14
- // editors) actually listen for. Per-character `dispatchKeyEvent` only fires
15
- // `keydown`/`keyup` with no input event, so controlled inputs ignore it.
16
- export async function typeText(cdp, sessionId, text) {
17
- await cdp.send('Input.insertText', { text }, sessionId);
18
- }
19
- const KEY_CODES = {
20
- Enter: { key: 'Enter', code: 'Enter', keyCode: 13 },
21
- Tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
22
- Escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
23
- Backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
24
- Delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
25
- ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
26
- ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
27
- ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
28
- ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
29
- Home: { key: 'Home', code: 'Home', keyCode: 36 },
30
- End: { key: 'End', code: 'End', keyCode: 35 },
31
- PageUp: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
32
- PageDown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
33
- Space: { key: ' ', code: 'Space', keyCode: 32 },
34
- };
35
- export async function pressKey(cdp, sessionId, keyName) {
36
- const keyInfo = KEY_CODES[keyName];
37
- if (!keyInfo) {
38
- throw new Error(`Unknown key: ${keyName}. Valid: ${Object.keys(KEY_CODES).join(', ')}`);
39
- }
40
- await cdp.send('Input.dispatchKeyEvent', {
41
- type: 'keyDown',
42
- key: keyInfo.key,
43
- code: keyInfo.code,
44
- windowsVirtualKeyCode: keyInfo.keyCode,
45
- nativeVirtualKeyCode: keyInfo.keyCode,
46
- }, sessionId);
47
- await cdp.send('Input.dispatchKeyEvent', {
48
- type: 'keyUp',
49
- key: keyInfo.key,
50
- code: keyInfo.code,
51
- windowsVirtualKeyCode: keyInfo.keyCode,
52
- nativeVirtualKeyCode: keyInfo.keyCode,
53
- }, sessionId);
54
- }
55
- const FOCUS_DESCENDANT_FN = `(function() {
56
- const selector = 'input:not([disabled]):not([type=hidden]),textarea:not([disabled]),select:not([disabled]),[contenteditable=""],[contenteditable=true],[tabindex]:not([tabindex="-1"])';
57
- const candidates = this.querySelectorAll(selector);
58
- for (const el of candidates) {
59
- el.focus();
60
- if (document.activeElement === el) return true;
61
- }
62
- return false;
63
- })`;
64
- // `DOM.focus` only works on natively focusable elements. UIs that wrap real
65
- // inputs in styled containers (Slack composer, Linear comments, Notion blocks,
66
- // Canva pickers, MUI/Chakra/Mantine TextField) often expose the wrapper as the
67
- // accessible "ref" — focusing it throws "Element is not focusable". When that
68
- // happens, walk the subtree for the first focusable descendant.
69
- export async function focusNode(cdp, sessionId, backendNodeId) {
70
- try {
71
- await cdp.send('DOM.focus', { backendNodeId }, sessionId);
72
- return;
73
- }
74
- catch (err) {
75
- const focused = await focusFirstFocusableDescendant(cdp, sessionId, backendNodeId);
76
- if (!focused)
77
- throw err;
78
- }
79
- }
80
- async function focusFirstFocusableDescendant(cdp, sessionId, backendNodeId) {
81
- const { object } = await cdp.send('DOM.resolveNode', { backendNodeId }, sessionId);
82
- if (!object.objectId)
83
- return false;
84
- const objectId = object.objectId;
85
- try {
86
- const { result } = await cdp.send('Runtime.callFunctionOn', { objectId, functionDeclaration: FOCUS_DESCENDANT_FN, returnByValue: true }, sessionId);
87
- return result.value === true;
88
- }
89
- finally {
90
- await cdp.send('Runtime.releaseObject', { objectId }, sessionId);
91
- }
92
- }
@@ -1,204 +0,0 @@
1
- import * as net from 'net';
2
- import { BrowserService } from './service.js';
3
- import type { IPCRequest, IPCResponse } from './types.js';
4
- interface IPCRequestOptions {
5
- autoStartDaemon?: boolean;
6
- /**
7
- * Opt-in client-side deadline in ms — most IPC actions (browser automation,
8
- * long-running recordings) have no fixed budget and must NOT get one by
9
- * default. Set explicitly for a request that genuinely has one, e.g.
10
- * `request-self-update`, whose handler answers immediately (it kicks the
11
- * install off in the background — see `triggerSelfUpdateInBackground`), so its
12
- * client deadline (`DAEMON_SELF_UPDATE_TRIGGER_TIMEOUT_MS`) is a short
13
- * "did the daemon accept the trigger?" bound, not a wait on the install.
14
- */
15
- timeoutMs?: number;
16
- }
17
- export declare class BrowserServiceNotRunningError extends Error {
18
- constructor();
19
- }
20
- export declare function formatBrowserServiceNotRunningError(daemonRunning?: boolean): string;
21
- export declare function getSocketPath(): string;
22
- /** Is the daemon reachable? A real connect probe on every platform — a socket
23
- * file existing on disk is not proof a daemon is listening on it. */
24
- export declare function isBrowserServiceReachable(): Promise<boolean>;
25
- /**
26
- * Is a *reachable* daemon actually responsive, or is its event loop wedged
27
- * (PHNX-3411)? Retries {@link probeBrowserServiceResponsive} up to
28
- * {@link RESPONSIVENESS_PROBE_ATTEMPTS} times so a single transient miss (a GC
29
- * pause) never condemns a healthy daemon; returns true as soon as any attempt
30
- * gets a reply, and false only when every attempt fails.
31
- */
32
- export declare function isBrowserServiceResponsive(endpoint?: string, attempts?: number): Promise<boolean>;
33
- /**
34
- * Wait until the browser service is genuinely reachable, or throw.
35
- *
36
- * Re-probes across the whole window rather than latching on the first accept, so
37
- * it survives an IPC-server restart that happens mid-wait (PHNX-3289): a restart
38
- * just resets the consecutive-accept counter, and the loop keeps going until the
39
- * daemon is *stably* up or the deadline passes. Bounded and fail-loud — a daemon
40
- * that never comes up throws a message naming the endpoint and the budget, never
41
- * a silent hang.
42
- */
43
- export declare function waitForBrowserService(_socketPath: string, timeoutMs?: number): Promise<void>;
44
- /** Outcome of {@link stopBrowserService} — what the service-scoped stop did. */
45
- interface BrowserServiceStopResult {
46
- /** The browser IPC service was reachable before the stop was requested. */
47
- wasRunning: boolean;
48
- /** The shared daemon stayed running while only browser IPC was stopped. */
49
- daemonRunning: boolean;
50
- /** A leftover `browser.sock` file was unlinked (POSIX only). */
51
- socketCleared: boolean;
52
- }
53
- /**
54
- * Stop only the daemon-hosted browser IPC service so a subsequent `start` comes
55
- * up clean (PHNX-3605). The short-lived browser client persists the
56
- * `browser-ipc` service off and asks the daemon to apply that transition over
57
- * its existing SIGHUP control path. It never stops or restarts the shared
58
- * supervisor, so routines, usage sync, secrets, and every sibling service keep
59
- * their process and in-flight work.
60
- *
61
- * Once the endpoint is quiet, a stale socket left by a hard-crashed browser
62
- * service is removed. If the daemon event loop itself is wedged and cannot
63
- * consume SIGHUP, the endpoint remains reachable and this fails loud with the
64
- * deliberate operator-owned whole-daemon restart command; the client never
65
- * escalates into that process-wide action itself.
66
- */
67
- export declare function stopBrowserService(): Promise<BrowserServiceStopResult>;
68
- /**
69
- * Remove a leftover browser socket FILE, but only when nothing is listening on
70
- * it — re-probing liveness IMMEDIATELY before the unlink to close the TOCTOU
71
- * window (PHNX-3289 review). Between {@link stopBrowserService}'s quiesce loop
72
- * deciding the endpoint was unreachable and this unlink, a concurrent
73
- * `browser start` could bind a NEW listener on the same path; an unconditional
74
- * unlink would then delete a LIVE daemon's socket — the exact two-servers orphan
75
- * this function exists to prevent. A listener seen here means the wedge is already
76
- * resolved (a fresh daemon owns the path), so its socket is left intact. Returns
77
- * true only when a genuinely dead file was removed.
78
- */
79
- export declare function clearDeadSocketFile(endpoint: string, socketPath: string): Promise<boolean>;
80
- /**
81
- * One long-lived connection to the existing browser service. Requests are
82
- * serialized so the daemon's newline-delimited responses always map to the
83
- * caller that produced them, while the process and socket stay warm between
84
- * actions.
85
- */
86
- declare class BrowserIPCConnection {
87
- private readonly socket;
88
- private buffer;
89
- private pending;
90
- private tail;
91
- private closed;
92
- constructor(socket: net.Socket);
93
- request(request: IPCRequest): Promise<IPCResponse>;
94
- close(): Promise<void>;
95
- private requestOnce;
96
- private handleData;
97
- private fail;
98
- }
99
- export declare class BrowserIPCServer {
100
- private server;
101
- /** Filesystem identity of the POSIX binding this server created. */
102
- private socketIdentity;
103
- private service;
104
- /** Live client connections, so {@link stop} can end them rather than wait. */
105
- private connections;
106
- /** In-flight stop, so a second SIGTERM awaits the same close, never skips it. */
107
- private stopping;
108
- /**
109
- * Optional explicit socket path for TEST ISOLATION only (POSIX). When set, the
110
- * server binds (and unlinks a stale copy of) THIS path instead of the shared
111
- * production `browser.sock`, so a test never unlinks the user's live daemon
112
- * socket. Default (undefined) is unchanged. Not for production use.
113
- */
114
- private readonly socketPathOverride?;
115
- constructor(service: BrowserService, socketPathOverride?: string);
116
- start(): Promise<void>;
117
- /**
118
- * Stop accepting, release the binding, and only then resolve.
119
- *
120
- * RUSH-2421: this used to call `server.close()` and move on. `close()` is
121
- * asynchronous — it stops accepting immediately but the binding is not
122
- * actually released until the `'close'` event fires — so `stop()` resolved
123
- * while the socket (or named pipe) was still held. The daemon's
124
- * `handleShutdown` awaits this before it exits, and a successor's
125
- * `claimDaemonInstance` waits for that exit as its proof the predecessor's
126
- * resources are free (SING-11). Resolving early made that proof false: the
127
- * newcomer could bind before the incumbent's binding was gone, which is the
128
- * two-servers-on-one-socket orphan the eviction protocol exists to prevent.
129
- *
130
- * The first version of this waited out a timeout instead, and that made every
131
- * graceful daemon stop escalate to `killTree`: `close()` does not complete
132
- * while a connection is open, browser clients hold one open on purpose, and
133
- * the timeout was set equal to the daemon's own SIGTERM grace window
134
- * (`STOP_GRACE_MS`) — so `handleShutdown` was still inside `stop()` when
135
- * `stopDaemon` gave up waiting and killed it. So we END the connections
136
- * rather than wait for them: `close()` stops accepting, destroying the
137
- * tracked sockets lets it complete, and the timeout is only a backstop for a
138
- * socket that will not die, deliberately far below `STOP_GRACE_MS`.
139
- */
140
- stop(): Promise<void>;
141
- private doStop;
142
- /** Release the listening binding, resolving only once it is genuinely gone. */
143
- private closeServer;
144
- /**
145
- * Resolve `request.task` from caller identity when omitted. Page verbs may
146
- * create; done/stop never create. Returns an error response to short-circuit
147
- * the action, or undefined when the request is ready (task filled in).
148
- */
149
- private bindTask;
150
- private handleRequest;
151
- }
152
- /**
153
- * Decide whether a running daemon is stale enough to name an operator refresh.
154
- *
155
- * FORWARD ONLY: recommend a refresh when the daemon is *older* than this CLI.
156
- * An older CLI rides a newer daemon without noise. A client never performs the
157
- * refresh itself — two installs sharing one daemon dir (keyed off $HOME) must
158
- * not flap the daemon or interrupt one another's hosted services.
159
- *
160
- * `undefined`/`'unknown'` means the daemon is too old to answer the `version`
161
- * action reliably — don't recommend an action on that ambiguous signal.
162
- *
163
- * When numeric compare cannot order two distinct strings (e.g. two
164
- * `0.0.0-dev.*` builds), surface the mismatch without deciding either client
165
- * owns the shared process.
166
- */
167
- export declare function shouldRecommendDaemonRefresh(daemonVersion: string | undefined, clientVersion: string): boolean;
168
- /**
169
- * Reconcile the running daemon's version with ours without evicting it. If the
170
- * daemon is serving stale code, ask the daemon to run its OWN self-update path
171
- * (`request-self-update`, same fail-closed check/install/verify/exit
172
- * SelfUpdateService's periodic tick runs, self-update-service.ts) rather than
173
- * telling a human to run `agents daemon restart` by hand. A browser client
174
- * still owns neither the shared supervisor nor its sibling services, so
175
- * version skew can never be permission for THIS client to stop or restart
176
- * that process directly (PHNX-3605 holds) — it only asks the daemon to run a
177
- * path the daemon already runs on its own schedule, which only exits once the
178
- * daemon itself has verified the new version is good. If self-update declines
179
- * (dev build, shadowed install, already current, or a real failure) this
180
- * degrades to the same "nothing changed" outcome PHNX-3605 shipped.
181
- */
182
- export declare function reconcileDaemonVersion(): Promise<void>;
183
- /** Test-only: `reconcileDaemonVersion` runs at most once per real process — this clears that latch so a test file can drive more than one branch. */
184
- export declare function resetVersionReconciliationForTest(): void;
185
- export declare function sendIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
186
- /**
187
- * Fill actor / launchId / sessionId from the calling process when the request
188
- * left them blank. Explicit values on the request always win.
189
- */
190
- export declare function stampCallerIdentity(request: IPCRequest, env?: NodeJS.ProcessEnv): IPCRequest;
191
- /**
192
- * Open one connection that can serve many browser operations. This uses the
193
- * same daemon readiness and version-reconciliation path as sendIPCRequest;
194
- * only the client process and IPC socket lifetime differ.
195
- */
196
- export declare function connectBrowserIPC(opts?: IPCRequestOptions): Promise<BrowserIPCConnection>;
197
- /**
198
- * Exported (rather than kept module-private) so `ipc.test.ts` can drive the
199
- * `opts.timeoutMs` client-side deadline directly against a raw `net.Server`
200
- * test double that never responds — the hermetic way to prove the timeout
201
- * actually fires without needing a slow real IPC action to provoke it.
202
- */
203
- export declare function sendRawIPCRequest(request: IPCRequest, opts?: IPCRequestOptions): Promise<IPCResponse>;
204
- export {};