@phnx-labs/agents-cli 1.22.46 → 1.22.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/LICENSE +102 -182
  3. package/README.md +96 -49
  4. package/dist/bootstrap.js +4 -2
  5. package/dist/cli/command-registry.d.ts +2 -1
  6. package/dist/cli/command-registry.js +6 -2
  7. package/dist/commands/accounts.d.ts +58 -0
  8. package/dist/commands/accounts.js +243 -25
  9. package/dist/commands/artifacts.js +7 -6
  10. package/dist/commands/attach.js +1 -1
  11. package/dist/commands/auth.js +7 -5
  12. package/dist/commands/browser-sessions-picker.js +9 -7
  13. package/dist/commands/browser.js +711 -62
  14. package/dist/commands/config.d.ts +0 -1
  15. package/dist/commands/config.js +26 -24
  16. package/dist/commands/cost.d.ts +1 -1
  17. package/dist/commands/daemon.d.ts +21 -0
  18. package/dist/commands/daemon.js +147 -12
  19. package/dist/commands/detach.d.ts +13 -0
  20. package/dist/commands/detach.js +11 -8
  21. package/dist/commands/exec.js +107 -53
  22. package/dist/commands/feed-watch.d.ts +2 -0
  23. package/dist/commands/feed-watch.js +28 -0
  24. package/dist/commands/feed.js +27 -0
  25. package/dist/commands/feedback.js +3 -19
  26. package/dist/commands/focus.d.ts +14 -0
  27. package/dist/commands/focus.js +48 -5
  28. package/dist/commands/go.d.ts +3 -0
  29. package/dist/commands/go.js +16 -3
  30. package/dist/commands/insights.d.ts +1 -1
  31. package/dist/commands/insights.js +2 -2
  32. package/dist/commands/inspect.js +2 -2
  33. package/dist/commands/lease.d.ts +1 -1
  34. package/dist/commands/lease.js +7 -6
  35. package/dist/commands/mailboxes.js +4 -3
  36. package/dist/commands/menubar.js +11 -4
  37. package/dist/commands/projects.d.ts +16 -1
  38. package/dist/commands/projects.js +58 -64
  39. package/dist/commands/resume.js +1 -1
  40. package/dist/commands/route.js +4 -4
  41. package/dist/commands/routines.js +4 -3
  42. package/dist/commands/run-broadcast.d.ts +1 -1
  43. package/dist/commands/run-broadcast.js +1 -1
  44. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  45. package/dist/commands/sessions-picker-factory.js +1 -1
  46. package/dist/commands/sessions-picker.d.ts +22 -0
  47. package/dist/commands/sessions-picker.js +48 -9
  48. package/dist/commands/sessions-stop.d.ts +24 -0
  49. package/dist/commands/sessions-stop.js +66 -0
  50. package/dist/commands/sessions-trace.d.ts +82 -0
  51. package/dist/commands/sessions-trace.js +387 -0
  52. package/dist/commands/sessions.js +5 -0
  53. package/dist/commands/setup-secrets.js +23 -0
  54. package/dist/commands/share.d.ts +20 -5
  55. package/dist/commands/share.js +152 -52
  56. package/dist/commands/ssh.d.ts +15 -0
  57. package/dist/commands/ssh.js +243 -18
  58. package/dist/commands/teams.js +34 -2
  59. package/dist/commands/traces.d.ts +2 -0
  60. package/dist/commands/traces.js +184 -0
  61. package/dist/commands/view.d.ts +2 -0
  62. package/dist/commands/view.js +32 -62
  63. package/dist/commands/watchdog.js +9 -14
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +5 -0
  66. package/dist/lib/account-capabilities.d.ts +4 -0
  67. package/dist/lib/account-capabilities.js +13 -9
  68. package/dist/lib/account-registry.d.ts +2 -0
  69. package/dist/lib/account-registry.js +30 -2
  70. package/dist/lib/accounting/rotate.d.ts +9 -0
  71. package/dist/lib/accounting/rotate.js +12 -2
  72. package/dist/lib/accounting/usage.d.ts +84 -6
  73. package/dist/lib/accounting/usage.js +293 -75
  74. package/dist/lib/agent-modes.js +29 -1
  75. package/dist/lib/agent-spec/agents.d.ts +4 -3
  76. package/dist/lib/agent-spec/agents.js +25 -15
  77. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  78. package/dist/lib/analytics/mix-commands.js +2 -2
  79. package/dist/lib/auth-health.d.ts +13 -3
  80. package/dist/lib/auth-health.js +50 -8
  81. package/dist/lib/browser/devices.d.ts +19 -0
  82. package/dist/lib/browser/devices.js +23 -0
  83. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  84. package/dist/lib/browser/drivers/ssh.js +36 -9
  85. package/dist/lib/browser/ipc.d.ts +1 -1
  86. package/dist/lib/browser/ipc.js +40 -9
  87. package/dist/lib/browser/profiles.d.ts +104 -65
  88. package/dist/lib/browser/profiles.js +277 -165
  89. package/dist/lib/browser/registry.d.ts +39 -0
  90. package/dist/lib/browser/registry.js +149 -0
  91. package/dist/lib/browser/remote-control.d.ts +28 -2
  92. package/dist/lib/browser/remote-control.js +35 -3
  93. package/dist/lib/browser/resolve-target.d.ts +68 -0
  94. package/dist/lib/browser/resolve-target.js +250 -0
  95. package/dist/lib/browser/runtime-state.d.ts +44 -11
  96. package/dist/lib/browser/runtime-state.js +98 -15
  97. package/dist/lib/browser/service.d.ts +65 -9
  98. package/dist/lib/browser/service.js +196 -61
  99. package/dist/lib/browser/sessions-list.d.ts +1 -3
  100. package/dist/lib/browser/sessions-list.js +3 -16
  101. package/dist/lib/browser/task-index.d.ts +70 -0
  102. package/dist/lib/browser/task-index.js +180 -0
  103. package/dist/lib/browser/types.d.ts +12 -1
  104. package/dist/lib/byok-usage.js +2 -2
  105. package/dist/lib/claude-statusline.d.ts +29 -0
  106. package/dist/lib/claude-statusline.js +139 -0
  107. package/dist/lib/cloud/host.d.ts +1 -1
  108. package/dist/lib/cloud/host.js +1 -1
  109. package/dist/lib/cloud/rush.js +1 -1
  110. package/dist/lib/codex-policy.d.ts +11 -1
  111. package/dist/lib/codex-policy.js +20 -5
  112. package/dist/lib/computer/computer-rpc.js +2 -2
  113. package/dist/lib/computer/download.d.ts +15 -15
  114. package/dist/lib/computer/download.js +35 -98
  115. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  116. package/dist/lib/computer/ssh-tunnel.js +12 -22
  117. package/dist/lib/config-keys.d.ts +6 -11
  118. package/dist/lib/config-keys.js +18 -19
  119. package/dist/lib/crabbox/cli.d.ts +1 -1
  120. package/dist/lib/crabbox/cli.js +2 -2
  121. package/dist/lib/crabbox/lease.d.ts +9 -0
  122. package/dist/lib/crabbox/lease.js +70 -15
  123. package/dist/lib/crabbox/runtimes.js +1 -1
  124. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  125. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  126. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  127. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  128. package/dist/lib/daemon/daemon.d.ts +3 -0
  129. package/dist/lib/daemon/daemon.js +212 -444
  130. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  131. package/dist/lib/daemon/device-probe-service.js +47 -0
  132. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  133. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  134. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  135. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  136. package/dist/lib/daemon/runner.js +15 -2
  137. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  138. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  139. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  140. package/dist/lib/daemon/self-heal-service.js +48 -0
  141. package/dist/lib/daemon/service.d.ts +102 -0
  142. package/dist/lib/daemon/service.js +98 -0
  143. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  144. package/dist/lib/daemon/session-index-service.js +40 -0
  145. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  146. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  147. package/dist/lib/daemon/supervisor.d.ts +87 -0
  148. package/dist/lib/daemon/supervisor.js +298 -0
  149. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  150. package/dist/lib/daemon/watchdog-service.js +40 -0
  151. package/dist/lib/daemon-health.d.ts +15 -0
  152. package/dist/lib/daemon-health.js +29 -5
  153. package/dist/lib/daemon-services.d.ts +8 -1
  154. package/dist/lib/daemon-services.js +55 -0
  155. package/dist/lib/daemon-ticks.d.ts +13 -2
  156. package/dist/lib/daemon-ticks.js +22 -20
  157. package/dist/lib/device-config.d.ts +25 -10
  158. package/dist/lib/device-config.js +90 -39
  159. package/dist/lib/devices/config-migration.d.ts +7 -0
  160. package/dist/lib/devices/config-migration.js +67 -3
  161. package/dist/lib/devices/health.d.ts +27 -2
  162. package/dist/lib/devices/health.js +39 -9
  163. package/dist/lib/devices/interactive-host.d.ts +15 -0
  164. package/dist/lib/devices/interactive-host.js +53 -0
  165. package/dist/lib/devices/registry.d.ts +61 -4
  166. package/dist/lib/devices/registry.js +94 -38
  167. package/dist/lib/devices/stats-cache.d.ts +26 -0
  168. package/dist/lib/devices/stats-cache.js +59 -7
  169. package/dist/lib/devices/worker-pick.d.ts +48 -0
  170. package/dist/lib/devices/worker-pick.js +113 -0
  171. package/dist/lib/exec.d.ts +70 -14
  172. package/dist/lib/exec.js +110 -38
  173. package/dist/lib/feed/answer.d.ts +29 -0
  174. package/dist/lib/feed/answer.js +145 -0
  175. package/dist/lib/feed/attention.d.ts +83 -0
  176. package/dist/lib/feed/attention.js +0 -0
  177. package/dist/lib/feed/feed.d.ts +109 -0
  178. package/dist/lib/feed/feed.js +154 -3
  179. package/dist/lib/feed/pr-status.d.ts +14 -0
  180. package/dist/lib/feed/pr-status.js +47 -0
  181. package/dist/lib/feed/watch.d.ts +59 -0
  182. package/dist/lib/feed/watch.js +180 -0
  183. package/dist/lib/fleet/capture.js +6 -0
  184. package/dist/lib/fleet/remote-login.js +8 -3
  185. package/dist/lib/fleet/types.d.ts +17 -0
  186. package/dist/lib/harness/adapter.d.ts +11 -0
  187. package/dist/lib/harness/adapters/claude.js +38 -24
  188. package/dist/lib/harness/adapters/codex.js +2 -4
  189. package/dist/lib/harness/adapters/cursor.js +8 -11
  190. package/dist/lib/helper-download.d.ts +108 -0
  191. package/dist/lib/helper-download.js +194 -0
  192. package/dist/lib/helper-versions.d.ts +52 -0
  193. package/dist/lib/helper-versions.js +58 -0
  194. package/dist/lib/hooks/cache.js +19 -0
  195. package/dist/lib/hooks/install.js +2 -2
  196. package/dist/lib/hooks/match.d.ts +3 -0
  197. package/dist/lib/hooks/match.js +25 -0
  198. package/dist/lib/hosts/credentials.d.ts +1 -1
  199. package/dist/lib/hosts/credentials.js +1 -1
  200. package/dist/lib/hosts/dispatch.d.ts +7 -1
  201. package/dist/lib/hosts/dispatch.js +24 -5
  202. package/dist/lib/hosts/passthrough.d.ts +8 -15
  203. package/dist/lib/hosts/passthrough.js +35 -16
  204. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  205. package/dist/lib/hosts/providers/devices.js +1 -1
  206. package/dist/lib/hosts/reconnect.d.ts +179 -21
  207. package/dist/lib/hosts/reconnect.js +266 -45
  208. package/dist/lib/hosts/registry.js +7 -0
  209. package/dist/lib/installations/migrate.d.ts +4 -5
  210. package/dist/lib/installations/migrate.js +6 -7
  211. package/dist/lib/installations/shims.d.ts +1 -1
  212. package/dist/lib/installations/shims.js +23 -13
  213. package/dist/lib/installations/versions.js +25 -2
  214. package/dist/lib/linear-autoclose.d.ts +1 -1
  215. package/dist/lib/linear-autoclose.js +1 -1
  216. package/dist/lib/loop.d.ts +2 -2
  217. package/dist/lib/loop.js +1 -1
  218. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  219. package/dist/lib/menubar/download-menubar.js +60 -0
  220. package/dist/lib/menubar/install-menubar.d.ts +44 -7
  221. package/dist/lib/menubar/install-menubar.js +105 -23
  222. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  223. package/dist/lib/menubar/notify-desktop.js +6 -6
  224. package/dist/lib/model-tiers.d.ts +1 -1
  225. package/dist/lib/open-url.d.ts +60 -2
  226. package/dist/lib/open-url.js +167 -12
  227. package/dist/lib/project-probe.d.ts +1 -1
  228. package/dist/lib/project-probe.js +1 -1
  229. package/dist/lib/project-pull.d.ts +1 -1
  230. package/dist/lib/project-pull.js +2 -2
  231. package/dist/lib/projects.d.ts +1 -1
  232. package/dist/lib/projects.js +1 -1
  233. package/dist/lib/refresh-coordinator.d.ts +1 -1
  234. package/dist/lib/refresh-coordinator.js +1 -1
  235. package/dist/lib/sandbox.js +6 -7
  236. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  237. package/dist/lib/secrets/download-keychain.js +70 -0
  238. package/dist/lib/secrets/install-helper.d.ts +20 -1
  239. package/dist/lib/secrets/install-helper.js +44 -3
  240. package/dist/lib/session/active.d.ts +67 -0
  241. package/dist/lib/session/active.js +86 -2
  242. package/dist/lib/session/db.d.ts +11 -1
  243. package/dist/lib/session/db.js +142 -8
  244. package/dist/lib/session/discover.d.ts +23 -1
  245. package/dist/lib/session/discover.js +44 -10
  246. package/dist/lib/session/highlights.d.ts +49 -1
  247. package/dist/lib/session/highlights.js +95 -0
  248. package/dist/lib/session/host-link.d.ts +16 -2
  249. package/dist/lib/session/host-link.js +7 -1
  250. package/dist/lib/session/parse.js +33 -1
  251. package/dist/lib/session/prompt.d.ts +35 -0
  252. package/dist/lib/session/prompt.js +56 -0
  253. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  254. package/dist/lib/session/remote/remote-list.js +19 -2
  255. package/dist/lib/session/remote/watch.d.ts +1 -1
  256. package/dist/lib/session/remote/watch.js +17 -3
  257. package/dist/lib/session/render.js +7 -3
  258. package/dist/lib/session/session-cache.d.ts +53 -0
  259. package/dist/lib/session/session-cache.js +93 -1
  260. package/dist/lib/session/state.js +1 -1
  261. package/dist/lib/session/tool-calls.d.ts +2 -0
  262. package/dist/lib/session/tool-calls.js +1 -1
  263. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  264. package/dist/lib/session/trajectory-compare.js +116 -0
  265. package/dist/lib/session/trajectory-html.d.ts +19 -0
  266. package/dist/lib/session/trajectory-html.js +770 -0
  267. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  268. package/dist/lib/session/trajectory-lineage.js +195 -0
  269. package/dist/lib/session/trajectory-text.d.ts +37 -0
  270. package/dist/lib/session/trajectory-text.js +284 -0
  271. package/dist/lib/session/trajectory.d.ts +102 -0
  272. package/dist/lib/session/trajectory.js +384 -0
  273. package/dist/lib/session/types.d.ts +15 -0
  274. package/dist/lib/sha256-asset.d.ts +26 -0
  275. package/dist/lib/sha256-asset.js +41 -0
  276. package/dist/lib/share/backend.d.ts +95 -0
  277. package/dist/lib/share/backend.js +139 -0
  278. package/dist/lib/share/delete.d.ts +6 -2
  279. package/dist/lib/share/delete.js +8 -9
  280. package/dist/lib/share/html.d.ts +22 -0
  281. package/dist/lib/share/html.js +88 -0
  282. package/dist/lib/share/provision.d.ts +18 -4
  283. package/dist/lib/share/provision.js +38 -4
  284. package/dist/lib/share/publish.d.ts +39 -15
  285. package/dist/lib/share/publish.js +55 -26
  286. package/dist/lib/share/worker-template.js +171 -11
  287. package/dist/lib/sqlite.js +16 -3
  288. package/dist/lib/ssh-exec.d.ts +65 -0
  289. package/dist/lib/ssh-exec.js +120 -4
  290. package/dist/lib/startup/command-registry.d.ts +3 -1
  291. package/dist/lib/startup/command-registry.js +6 -3
  292. package/dist/lib/startup/dev-build.js +3 -3
  293. package/dist/lib/state.d.ts +2 -2
  294. package/dist/lib/state.js +11 -5
  295. package/dist/lib/subagents-registry.js +1 -1
  296. package/dist/lib/teams/agents.js +9 -0
  297. package/dist/lib/terminal/index.d.ts +1 -1
  298. package/dist/lib/terminal/inject.d.ts +19 -0
  299. package/dist/lib/terminal/inject.js +19 -9
  300. package/dist/lib/terminal/types.d.ts +1 -1
  301. package/dist/lib/terminal/types.js +1 -1
  302. package/dist/lib/tmux/orphan-reap.js +1 -1
  303. package/dist/lib/tmux/session.d.ts +46 -13
  304. package/dist/lib/tmux/session.js +204 -27
  305. package/dist/lib/traces/backend.d.ts +29 -0
  306. package/dist/lib/traces/backend.js +46 -0
  307. package/dist/lib/traces/classify.d.ts +52 -0
  308. package/dist/lib/traces/classify.js +72 -0
  309. package/dist/lib/traces/config.d.ts +10 -0
  310. package/dist/lib/traces/config.js +2 -0
  311. package/dist/lib/traces/provision.d.ts +19 -0
  312. package/dist/lib/traces/provision.js +30 -0
  313. package/dist/lib/traces/sync.d.ts +150 -0
  314. package/dist/lib/traces/sync.js +447 -0
  315. package/dist/lib/traces/worker-template.d.ts +4 -0
  316. package/dist/lib/traces/worker-template.js +159 -0
  317. package/dist/lib/types.d.ts +34 -19
  318. package/dist/lib/types.js +14 -0
  319. package/dist/lib/usage-backoff.d.ts +2 -1
  320. package/dist/lib/usage-backoff.js +60 -19
  321. package/dist/lib/usage-refresh.d.ts +18 -4
  322. package/dist/lib/usage-refresh.js +68 -19
  323. package/dist/lib/watchdog/log.d.ts +1 -1
  324. package/dist/lib/watchdog/log.js +1 -1
  325. package/dist/lib/watchdog/runner.d.ts +36 -45
  326. package/dist/lib/watchdog/runner.js +150 -119
  327. package/dist/lib/watchdog/service.d.ts +0 -1
  328. package/dist/lib/watchdog/service.js +0 -1
  329. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  330. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  331. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  332. package/dist/lib/watchdog/watchdog.js +37 -104
  333. package/dist/lib/workflows.d.ts +1 -1
  334. package/package.json +5 -7
  335. package/dist/commands/usage.d.ts +0 -27
  336. package/dist/commands/usage.js +0 -96
  337. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  338. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  339. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  340. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  341. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  342. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  343. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
  344. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  345. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
  346. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  347. package/dist/lib/usage-fleet.d.ts +0 -32
  348. package/dist/lib/usage-fleet.js +0 -125
@@ -4,11 +4,13 @@ import * as path from 'path';
4
4
  import { BrowserCdpConnectionError, CDPClient, discoverBrowserWsUrl, verifyBrowserIdentity, } from './cdp.js';
5
5
  import { getProfile, getProfileRuntimeDir, getProfileDownloadsDir, getProfileSessionsDir, getBrowserRuntimeDir, listProfiles, extractConfiguredPort, resolveEndpoint, parseEndpointUrl, } from './profiles.js';
6
6
  import { killChrome, getRunningChromeInfo, launchBrowser, allocatePort } from './chrome.js';
7
+ import { assertRemoteControlAllowedForRequest } from './remote-control.js';
7
8
  import { connectLocal } from './drivers/local.js';
8
9
  import { connectSSH, shellQuote } from './drivers/ssh.js';
10
+ import { adoptLegacyRuntimeIfLocal, isLegacyEndpointKey, resolveBrowserTarget, shouldForkProfile, } from './resolve-target.js';
9
11
  import { clearProfileRuntime, listProfileCacheDirs, readProfileRuntimeMeta, isProcessAlive } from './runtime-state.js';
10
12
  import { resolveDomainSkill } from './domain-skills.js';
11
- import { generateTaskId, generateShortId, connectionKey, asConnectionKey, parseConnectionKey, keyBelongsToProfile, } from './types.js';
13
+ import { generateTaskId, generateShortId, asConnectionKey, parseConnectionKey, keyBelongsToProfile, } from './types.js';
12
14
  import { reapAbandonedTasks, resolveLiveIdentities, taskOwnerIsGone, } from './hygiene.js';
13
15
  import { taskMatchesCaller } from './caller-identity.js';
14
16
  import { getRefs, resolveRefToCoords, describeRefs, healRef } from './refs.js';
@@ -297,7 +299,8 @@ export class BrowserService {
297
299
  * Live browsers, keyed by {@link ConnectionKey} — NOT by profile name. The
298
300
  * branded key type is what makes `connections.get(someProfileName)` (the
299
301
  * RUSH-2709 miss that made `status --profile <name>` return empty for a
300
- * running `<name>@endpoint-0`) fail to compile.
302
+ * running `<name>@<device>`) fail to compile. Keys are `<profile>@<device>`
303
+ * (legacy leftover `<profile>@endpoint-N` dirs are still recognized).
301
304
  */
302
305
  connections = new Map();
303
306
  forkingProfiles = new Set();
@@ -312,26 +315,21 @@ export class BrowserService {
312
315
  /** Coalescing window for the `touchTask` write. See {@link touchTask}. */
313
316
  static TOUCH_PERSIST_INTERVAL_MS = 60_000;
314
317
  async start(profileName, opts = {}) {
315
- const profile = await getProfile(profileName);
316
- if (!profile) {
317
- throw new Error(`Profile "${profileName}" not found`);
318
- }
319
- // Pick the endpoint preset. Throws with the candidate list if the user
320
- // passed an unknown name. The runtime key `<profile>@<endpoint>` is what
321
- // the connection map + per-profile runtime dirs are keyed on, so a single
322
- // YAML profile can run at multiple endpoints concurrently.
323
- //
324
- // `effectiveProfile.name` stays BARE. Overwriting it with the runtime key
325
- // here (pre-RUSH-2709) is what leaked `comet-local@endpoint-0` into every
326
- // listing and made `status --profile comet-local` miss its own browser.
327
- // The key travels as its own argument instead.
328
- const resolved = resolveEndpoint(profile, opts.endpointName);
329
- const composite = connectionKey(profileName, resolved.name);
330
- const effectiveProfile = {
331
- ...profile,
332
- binary: resolved.binary,
333
- targetFilter: resolved.targetFilter,
334
- };
318
+ // Consent gate, before anything is resolved or launched. This is the
319
+ // authoritative one: gating only the `browser start` COMMAND left the ~18
320
+ // page verbs that create a browser implicitly (navigate, click, screenshot,
321
+ // …) able to open a browser on a machine whose owner never opted in.
322
+ assertRemoteControlAllowedForRequest(opts.fleetRemote, { actor: opts.actor });
323
+ // Registry decides WHERE. Three outcomes, no fourth: local if this
324
+ // device declares the name; tunnel to a declaring device otherwise;
325
+ // fail loud if nobody does. Never auto-create a local browser under a
326
+ // name that means a logged-in browser somewhere else.
327
+ const routed = resolveBrowserTarget(profileName, {
328
+ endpointName: opts.endpointName,
329
+ probe: opts.probe,
330
+ });
331
+ const composite = routed.key;
332
+ const effectiveProfile = routed.profile;
335
333
  const taskId = generateTaskId();
336
334
  let taskName;
337
335
  if (opts.taskName) {
@@ -345,23 +343,12 @@ export class BrowserService {
345
343
  taskName = taskId;
346
344
  }
347
345
  const taskLabel = deriveTaskLabel({ title: opts.title, url: opts.url });
348
- let conn = this.connections.get(composite);
349
- let effectiveKey = composite;
350
- // If we have a cached connection, confirm it's still usable before any
351
- // caller relies on it. A browser killed externally (Cmd-Q, crash, or a
352
- // user clearing the profile by hand) leaves a closed WebSocket here.
353
- // Without this check the next `cdp.send` throws "CDP connection not
354
- // open" and the user has no way to recover short of killing the daemon.
355
- if (conn && !(await isConnHealthy(conn))) {
356
- try {
357
- conn.cdp.close();
358
- }
359
- catch { /* already closed */ }
360
- conn.cleanup?.();
361
- this.connections.delete(composite);
362
- conn = undefined;
363
- }
364
- if (conn && conn.electron && conn.tasks.size > 0) {
346
+ const reused = await this.reuseProfileConnection(profileName, composite, routed.local);
347
+ let conn = reused?.conn;
348
+ let effectiveKey = reused?.key ?? composite;
349
+ // Fork is a local Electron chrome-data mint. Never fork a tunnelled
350
+ // connection that would launch a logged-out browser on THIS box.
351
+ if (conn && routed.local && shouldForkProfile(routed.kind, conn)) {
365
352
  if (this.forkingProfiles.has(composite)) {
366
353
  while (this.forkingProfiles.has(composite)) {
367
354
  await new Promise((r) => setTimeout(r, 50));
@@ -388,13 +375,14 @@ export class BrowserService {
388
375
  }
389
376
  }
390
377
  else if (!conn) {
391
- conn = await this.connectProfile(effectiveProfile, resolved.target, composite);
392
- conn.key = composite;
393
- conn.profile = profileName;
394
- this.connections.set(composite, conn);
395
- await this.applyDefaultDownloadBehavior(conn, composite);
378
+ adoptLegacyRuntimeIfLocal(routed.local, profileName, composite, getBrowserRuntimeDir());
379
+ conn = await this.openConnection(effectiveProfile, routed.target, composite, profileName, { persistRemote: !routed.local });
380
+ effectiveKey = composite;
396
381
  }
397
- if (conn && !conn.profile)
382
+ if (!conn) {
383
+ throw new Error(`Could not connect to profile "${profileName}"`);
384
+ }
385
+ if (!conn.profile)
398
386
  conn.profile = profileName;
399
387
  // Browsers launch with --no-startup-window (session-cookie persistence,
400
388
  // see launchBrowser), so a bare `start` with no --url would otherwise
@@ -515,7 +503,16 @@ export class BrowserService {
515
503
  if (resolved)
516
504
  skill = resolved;
517
505
  }
518
- return { task: taskId, name: taskName, tabId, profile: profileName, key: effectiveKey, skill };
506
+ return {
507
+ task: taskId,
508
+ name: taskName,
509
+ tabId,
510
+ profile: profileName,
511
+ key: effectiveKey,
512
+ device: routed.device,
513
+ picked: routed.picked,
514
+ skill,
515
+ };
519
516
  }
520
517
  /**
521
518
  * Reclaim a live page already showing `url` from an ABANDONED task, instead
@@ -668,7 +665,7 @@ export class BrowserService {
668
665
  /**
669
666
  * Close tasks whose owning agent session is gone, or that have sat untouched
670
667
  * past the idle window. The entry point the daemon's periodic tick and
671
- * `agents browser gc` both call; the policy lives in `hygiene.ts`.
668
+ * `agents browser prune` both call; the policy lives in `hygiene.ts`.
672
669
  */
673
670
  async reapAbandoned(opts = {}) {
674
671
  return reapAbandonedTasks(this, opts);
@@ -748,7 +745,7 @@ export class BrowserService {
748
745
  return this.stop(taskName);
749
746
  }
750
747
  async stopProfile(profileRef) {
751
- // Connections are keyed by the runtime key `<profile>@<endpoint>` (see
748
+ // Connections are keyed by the runtime key `<profile>@<device>` (see
752
749
  // start()) while callers pass the bare profile name (or, occasionally, an
753
750
  // exact key). A plain `connections.get(profileRef)` therefore missed every
754
751
  // real remote connection, so `cleanup()` never ran — leaving the SSH tunnel
@@ -1529,8 +1526,9 @@ export class BrowserService {
1529
1526
  * `profileName` is a {@link ProfileName} — a BARE name, never a runtime key.
1530
1527
  * Every candidate is selected by the one rule ({@link keyBelongsToProfile}),
1531
1528
  * and a scoped query that finds no live connection falls through to disk, so
1532
- * `status --profile comet-local` reports the LIVE `comet-local@endpoint-0`
1533
- * instead of the empty list it used to return (RUSH-2709).
1529
+ * `status --profile comet-local` reports the LIVE `comet-local@<device>`
1530
+ * (and leftover `comet-local@endpoint-0` dirs) instead of the empty list
1531
+ * it used to return (RUSH-2709).
1534
1532
  */
1535
1533
  async status(profileRef) {
1536
1534
  // Reconnect live browsers after a daemon restart so status is not empty
@@ -2064,14 +2062,24 @@ export class BrowserService {
2064
2062
  * already resolved the endpoint and built the `effectiveProfile` with
2065
2063
  * the per-endpoint binary/targetFilter overrides applied; we just use it.
2066
2064
  *
2067
- * `key` is the runtime key (`<profile>@<endpoint>`) and is what every on-disk
2068
- * lookup uses, so per-endpoint pid/port files don't collide when the same app
2069
- * runs locally and remotely at the same time. It is a SEPARATE argument
2070
- * because `effectiveProfile.name` stays the bare, user-facing name (RUSH-2709).
2065
+ * `key` is the runtime key (`<profile>@<device>`) and is what every on-disk
2066
+ * lookup uses. It is a SEPARATE argument because `effectiveProfile.name`
2067
+ * stays the bare, user-facing name (RUSH-2709).
2071
2068
  */
2072
- async connectProfile(effectiveProfile, target, key) {
2069
+ async connectProfile(effectiveProfile, target, key, opts = {}) {
2073
2070
  const existingInfo = getRunningChromeInfo(key);
2074
- if (existingInfo) {
2071
+ const tunnelled = opts.persistRemote || target.startsWith('ssh:');
2072
+ if (tunnelled) {
2073
+ // A leftover local chrome-data / pid file under this key is the
2074
+ // pre-T2 logged-out browser on THIS box. Attaching localhost CDP
2075
+ // here is the original bug. Only a recorded SSH tunnel is ours to
2076
+ // reuse, and connectSSH already does that via isOwnTunnel.
2077
+ const meta = readProfileRuntimeMeta(key);
2078
+ if (meta && meta.kind !== 'tunnel') {
2079
+ clearProfileRuntime(key);
2080
+ }
2081
+ }
2082
+ else if (existingInfo) {
2075
2083
  try {
2076
2084
  const { wsUrl, browser } = await discoverBrowserWsUrl(existingInfo.port, 'localhost', effectiveProfile.name);
2077
2085
  verifyBrowserIdentity(browser, effectiveProfile.browser, existingInfo.port);
@@ -2100,13 +2108,13 @@ export class BrowserService {
2100
2108
  clearProfileRuntime(key);
2101
2109
  }
2102
2110
  }
2103
- const conn = await this.connectEndpoint(effectiveProfile, target, key);
2111
+ const conn = await this.connectEndpoint(effectiveProfile, target, key, opts);
2104
2112
  if (!conn) {
2105
2113
  throw new Error(`Could not connect to endpoint ${target} for profile "${effectiveProfile.name}"`);
2106
2114
  }
2107
2115
  return conn;
2108
2116
  }
2109
- async connectEndpoint(profile, endpoint, key) {
2117
+ async connectEndpoint(profile, endpoint, key, opts = {}) {
2110
2118
  const url = new URL(endpoint);
2111
2119
  if (url.protocol === 'cdp:') {
2112
2120
  const conn = await connectLocal(endpoint, profile, key);
@@ -2123,7 +2131,9 @@ export class BrowserService {
2123
2131
  };
2124
2132
  }
2125
2133
  if (url.protocol === 'ssh:') {
2126
- const conn = await connectSSH(endpoint, profile, key);
2134
+ const conn = await connectSSH(endpoint, profile, key, {
2135
+ persistRemote: opts.persistRemote,
2136
+ });
2127
2137
  await this.enableDomains(conn.cdp);
2128
2138
  return {
2129
2139
  cdp: conn.cdp,
@@ -2277,6 +2287,15 @@ export class BrowserService {
2277
2287
  * - no handle, zero, !createIfMissing → return null (caller reports "nothing to close")
2278
2288
  */
2279
2289
  async resolveOrCreateTask(opts) {
2290
+ // Consent gate — the one chokepoint every drive/mutate verb passes through.
2291
+ // ipc.ts routes each PAGE_RESOLVE_VERB and CLOSE_VERB here via bindTask, so
2292
+ // gating at the top refuses a fleet-remote ATTACH (an explicit --task, or a
2293
+ // lone identity match) exactly as it already refused an implicit CREATE.
2294
+ // Gating only the create branch below left tab-add/navigate/done on an
2295
+ // already-running browser ungated, so a remote caller could drive the
2296
+ // owner's authenticated profile with consent off (RUSH-3064). Read the
2297
+ // per-request marker, never the daemon's env — see remote-control.ts.
2298
+ assertRemoteControlAllowedForRequest(opts.fleetRemote, { actor: opts.actor });
2280
2299
  if (opts.task) {
2281
2300
  const found = await this.findTask(opts.task, opts.profile);
2282
2301
  return { ...found, created: false };
@@ -2310,6 +2329,10 @@ export class BrowserService {
2310
2329
  // Zero matches for this caller.
2311
2330
  if (!opts.createIfMissing)
2312
2331
  return null;
2332
+ // The top-of-function consent gate already refused a fleet-remote create
2333
+ // here — before ensureDefaultBrowserProfile() below can probe local browser
2334
+ // installs and persist an `auto-chrome` profile — so a refused request never
2335
+ // leaves state behind on the target machine.
2313
2336
  // Implicit start on the default / named profile.
2314
2337
  let profileName = opts.profile;
2315
2338
  if (!profileName) {
@@ -2318,13 +2341,19 @@ export class BrowserService {
2318
2341
  profileName = detected.name;
2319
2342
  }
2320
2343
  const started = await this.start(profileName, {
2344
+ fleetRemote: opts.fleetRemote,
2321
2345
  url: opts.url,
2322
2346
  actor: opts.actor,
2323
2347
  launchId: opts.launchId,
2324
2348
  sessionId: opts.sessionId,
2325
2349
  title: opts.title,
2326
2350
  });
2327
- return { ...(await this.findTask(started.name, started.profile)), created: true };
2351
+ return {
2352
+ ...(await this.findTask(started.name, started.profile)),
2353
+ created: true,
2354
+ picked: started.picked,
2355
+ device: started.device,
2356
+ };
2328
2357
  }
2329
2358
  listTasksForCaller(caller) {
2330
2359
  const out = [];
@@ -2465,7 +2494,15 @@ export class BrowserService {
2465
2494
  return null;
2466
2495
  let resolved;
2467
2496
  try {
2468
- resolved = resolveEndpoint(profile, endpointFromKey);
2497
+ // Legacy keys encode the preset (`endpoint-0`). New keys encode the
2498
+ // declaring device, which is not a preset name — resolve via registry.
2499
+ if (endpointFromKey && /^endpoint-\d+$/.test(endpointFromKey)) {
2500
+ resolved = resolveEndpoint(profile, endpointFromKey);
2501
+ }
2502
+ else {
2503
+ const routed = resolveBrowserTarget(bare);
2504
+ resolved = { name: routed.device, target: routed.target };
2505
+ }
2469
2506
  }
2470
2507
  catch {
2471
2508
  return null;
@@ -2835,6 +2872,104 @@ export class BrowserService {
2835
2872
  history = history.slice(0, 50);
2836
2873
  await fs.promises.writeFile(historyFile, JSON.stringify(history, null, 2));
2837
2874
  }
2875
+ /**
2876
+ * The cached connection for `key`, or undefined — evicting it first if the
2877
+ * browser died underneath us.
2878
+ *
2879
+ * A browser killed externally (Cmd-Q, crash, a user clearing the profile by
2880
+ * hand) leaves a closed WebSocket in the map. Without this the next
2881
+ * `cdp.send` throws "CDP connection not open" and there is no recovery short
2882
+ * of killing the daemon.
2883
+ */
2884
+ async reuseHealthyConnection(key) {
2885
+ const conn = this.connections.get(key);
2886
+ if (!conn)
2887
+ return undefined;
2888
+ if (await isConnHealthy(conn))
2889
+ return conn;
2890
+ try {
2891
+ conn.cdp.close();
2892
+ }
2893
+ catch { /* already closed */ }
2894
+ conn.cleanup?.();
2895
+ this.connections.delete(key);
2896
+ return undefined;
2897
+ }
2898
+ /**
2899
+ * Reuse a live connection for this profile: the new `<name>@<device>` key
2900
+ * first, then any leftover `<name>@endpoint-N` key still in the map so a
2901
+ * browser started before the key collapse is not abandoned.
2902
+ */
2903
+ async reuseProfileConnection(profileName, preferred, local) {
2904
+ const preferredConn = await this.reuseHealthyConnection(preferred);
2905
+ if (preferredConn)
2906
+ return { conn: preferredConn, key: preferred };
2907
+ // Leftover `@endpoint-N` connections are THIS machine's pre-T2 local
2908
+ // browser. Reusing them on a tunnelled resolve is the original bug.
2909
+ if (!local)
2910
+ return undefined;
2911
+ for (const key of [...this.connections.keys()]) {
2912
+ if (key === preferred)
2913
+ continue;
2914
+ if (!isLegacyEndpointKey(key, profileName))
2915
+ continue;
2916
+ if (parseConnectionKey(key).fork !== undefined)
2917
+ continue;
2918
+ const conn = await this.reuseHealthyConnection(key);
2919
+ if (conn)
2920
+ return { conn, key };
2921
+ }
2922
+ return undefined;
2923
+ }
2924
+ /** Connect a profile at `target`, register it under `key`, and arm downloads. */
2925
+ async openConnection(profile, target, key, profileName, opts = {}) {
2926
+ const conn = await this.connectProfile(profile, target, key, opts);
2927
+ conn.key = key;
2928
+ conn.profile = profileName;
2929
+ this.connections.set(key, conn);
2930
+ await this.applyDefaultDownloadBehavior(conn, key);
2931
+ return conn;
2932
+ }
2933
+ /**
2934
+ * Open a tab for a HUMAN to read, bound to no task.
2935
+ *
2936
+ * That is the entire difference from {@link start}, and it is the point. The
2937
+ * abandoned-task reaper closes a task's tabs when the calling session dies or
2938
+ * after the idle window, but it "deliberately never touches a tab that is not
2939
+ * in `task.tabs`" (hygiene.ts) — so a viewer tab must not be in one, or the
2940
+ * artifact the user is reading vanishes when the agent that rendered it exits.
2941
+ *
2942
+ * Consequently `stop`/`done` do not close it either, which is correct: it is
2943
+ * the user's tab now.
2944
+ */
2945
+ async showUrl(profileName, url, opts = {}) {
2946
+ // Same consent gate as start(): this opens a browser, so a fleet-remote
2947
+ // caller needs the target machine's opt-in.
2948
+ assertRemoteControlAllowedForRequest(opts.fleetRemote, { actor: opts.actor });
2949
+ const routed = resolveBrowserTarget(profileName, {
2950
+ endpointName: opts.endpointName,
2951
+ probe: opts.probe,
2952
+ });
2953
+ const key = routed.key;
2954
+ const effectiveProfile = routed.profile;
2955
+ const reused = await this.reuseProfileConnection(profileName, key, routed.local);
2956
+ let conn = reused?.conn;
2957
+ const effectiveKey = reused?.key ?? key;
2958
+ if (!conn) {
2959
+ adoptLegacyRuntimeIfLocal(routed.local, profileName, key, getBrowserRuntimeDir());
2960
+ conn = await this.openConnection(effectiveProfile, routed.target, key, profileName, { persistRemote: !routed.local });
2961
+ }
2962
+ // createPageTarget refuses Arc with an actionable error rather than crashing it.
2963
+ const created = await this.createPageTarget(conn, { url });
2964
+ this.invalidateTargetCache(conn);
2965
+ return {
2966
+ profile: profileName,
2967
+ key: effectiveKey,
2968
+ tabId: created.targetId,
2969
+ device: routed.device,
2970
+ picked: routed.picked,
2971
+ };
2972
+ }
2838
2973
  async getHistory(limit = 10) {
2839
2974
  const historyFile = path.join(getBrowserRuntimeDir(), 'history.json');
2840
2975
  if (!fs.existsSync(historyFile))
@@ -32,8 +32,6 @@ export declare function renderBrowserSessions(groups: ProfileArtifacts[]): strin
32
32
  * substring match. Returns the absolute path, or null if nothing matched.
33
33
  */
34
34
  export declare function resolveArtifact(groups: ProfileArtifacts[], selector: string): string | null;
35
- /** Open a file in the OS default app. Returns true on success. */
36
- export declare function openArtifact(filePath: string): boolean;
37
35
  /** The identity fields this module cares about for one task. */
38
36
  export interface TaskIdentity {
39
37
  owner?: string;
@@ -139,4 +137,4 @@ export declare function runBrowserSessions(opts: {
139
137
  profile?: string;
140
138
  open?: string | boolean;
141
139
  json?: boolean;
142
- }): void;
140
+ }): Promise<void>;
@@ -14,12 +14,12 @@
14
14
  * problem (pid registry, the SessionStart hook index, the activity log; see
15
15
  * `feed-post.ts` `resolvePostIdentity`), never a second parser.
16
16
  */
17
+ import { showFile } from '../open-url.js';
17
18
  import * as fs from 'fs';
18
19
  import { formatBytes } from '../format.js';
19
20
  // Re-exported for the picker, which lists artifacts from this module's rows.
20
21
  export { formatBytes };
21
22
  import * as path from 'path';
22
- import { spawnSync } from 'child_process';
23
23
  import { getBrowserRuntimeDir, getProfileRuntimeDir } from './profiles.js';
24
24
  import { formatRelativeTime } from '../session/relative-time.js';
25
25
  import { getSessionById, listBrowserSessionRecords, pruneToolSessions } from '../session/db.js';
@@ -158,19 +158,6 @@ export function resolveArtifact(groups, selector) {
158
158
  const hit = all.find((a) => a.name === selector) ?? all.find((a) => a.name.includes(selector));
159
159
  return hit ? hit.path : null;
160
160
  }
161
- /** Open a file in the OS default app. Returns true on success. */
162
- export function openArtifact(filePath) {
163
- const openers = process.platform === 'darwin'
164
- ? [['open', [filePath]]]
165
- : process.platform === 'win32'
166
- ? [['cmd', ['/c', 'start', '""', filePath]]]
167
- : [['xdg-open', [filePath]], ['gnome-open', [filePath]]];
168
- for (const [cmd, args] of openers) {
169
- if (spawnSync(cmd, args, { stdio: 'ignore' }).status === 0)
170
- return true;
171
- }
172
- return false;
173
- }
174
161
  /**
175
162
  * Read a profile's `tasks.json` for the identity of its CURRENTLY LIVE tasks,
176
163
  * keyed by task name. A task's entry is deleted on `agents browser stop` (see
@@ -380,7 +367,7 @@ export function matchesBrowserSessionRow(row, query) {
380
367
  * `open` is the Commander value for `--open [selector]`: undefined when the flag
381
368
  * is absent, `true` when passed bare (defaults to 'latest'), or the selector string.
382
369
  */
383
- export function runBrowserSessions(opts) {
370
+ export async function runBrowserSessions(opts) {
384
371
  const groups = listBrowserSessions(opts.profile);
385
372
  if (opts.open !== undefined && opts.open !== false) {
386
373
  const selector = opts.open === true ? 'latest' : opts.open;
@@ -390,7 +377,7 @@ export function runBrowserSessions(opts) {
390
377
  process.exit(1);
391
378
  }
392
379
  console.log(target);
393
- if (!openArtifact(target)) {
380
+ if ((await showFile(target)).via === 'none') {
394
381
  console.error(`Could not open ${target}`);
395
382
  process.exit(1);
396
383
  }
@@ -0,0 +1,70 @@
1
+ export interface TaskBinding {
2
+ device: string;
3
+ profile?: string;
4
+ url?: string;
5
+ sessionId?: string;
6
+ launchId?: string;
7
+ createdAt: number;
8
+ }
9
+ export type TaskIndex = Record<string, TaskBinding>;
10
+ export declare const REJECT_DEVICE_MESSAGE: string;
11
+ export type TaskRoute = {
12
+ kind: 'proceed';
13
+ task?: string;
14
+ device: string;
15
+ } | {
16
+ kind: 'unknown';
17
+ task: string;
18
+ message: string;
19
+ } | {
20
+ kind: 'ambiguous';
21
+ message: string;
22
+ } | {
23
+ kind: 'reject-device';
24
+ message: string;
25
+ };
26
+ export declare function taskIndexPath(): string;
27
+ export declare function readTaskIndex(): TaskIndex;
28
+ export declare function getTaskBinding(name: string): TaskBinding | undefined;
29
+ export declare function listTaskBindings(): Array<{
30
+ name: string;
31
+ } & TaskBinding>;
32
+ export declare function bindTask(name: string, binding: TaskBinding): void;
33
+ export declare function updateTaskBinding(name: string, patch: Partial<TaskBinding>): void;
34
+ export declare function unbindTask(name: string): void;
35
+ /** Drop every binding recorded under this profile (after `stop --profile` / gc). */
36
+ export declare function unbindTasksForProfile(profile: string): void;
37
+ export declare function tasksForCaller(sessionId?: string, launchId?: string): Array<{
38
+ name: string;
39
+ } & TaskBinding>;
40
+ export declare function formatOpenTaskList(entries: Array<{
41
+ name: string;
42
+ } & TaskBinding>, now?: number): string;
43
+ export declare function unknownTaskMessage(name: string, entries?: ({
44
+ name: string;
45
+ } & TaskBinding)[]): string;
46
+ export declare function ambiguousTasksMessage(entries: Array<{
47
+ name: string;
48
+ } & TaskBinding>): string;
49
+ /**
50
+ * Decide where a page verb should run.
51
+ *
52
+ * `--device` on a later verb is always rejected (bind it at start).
53
+ * A named task that is not in the local index fails loud with the open-task
54
+ * list. Two or more tasks for this caller, with no `--task`, fail the same way.
55
+ * Zero matches with no `--task` proceeds locally — the daemon still resolves
56
+ * from caller identity for the first implicit create.
57
+ */
58
+ export declare function resolveTaskRoute(opts: {
59
+ task?: string;
60
+ device?: string;
61
+ sessionId?: string;
62
+ launchId?: string;
63
+ self?: string;
64
+ }): TaskRoute;
65
+ /**
66
+ * The daemon sandboxes screenshot writes to the browser runtime dir, so `-o`
67
+ * outside that dir is ignored there. The CLI process honors `-o` by copying
68
+ * the captured file to the requested path. Same path → no copy.
69
+ */
70
+ export declare function honorScreenshotOutput(requested: string | undefined, daemonPath: string): string;