@phnx-labs/agents-cli 1.22.35 → 1.22.38

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 (500) hide show
  1. package/CHANGELOG.md +708 -2
  2. package/README.md +83 -43
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.d.ts +15 -0
  5. package/dist/bootstrap.js +1212 -0
  6. package/dist/commands/accounts.d.ts +4 -11
  7. package/dist/commands/accounts.js +159 -27
  8. package/dist/commands/artifacts-setup.d.ts +53 -0
  9. package/dist/commands/artifacts-setup.js +161 -0
  10. package/dist/commands/artifacts.d.ts +18 -0
  11. package/dist/commands/artifacts.js +58 -0
  12. package/dist/commands/attach.js +17 -4
  13. package/dist/commands/audit.d.ts +8 -8
  14. package/dist/commands/audit.js +57 -47
  15. package/dist/commands/browser.js +17 -11
  16. package/dist/commands/cloud.js +1 -1
  17. package/dist/commands/commands.js +0 -11
  18. package/dist/commands/computer-actions.js +33 -0
  19. package/dist/commands/computer-sessions-picker.d.ts +21 -0
  20. package/dist/commands/computer-sessions-picker.js +141 -0
  21. package/dist/commands/computer.d.ts +16 -0
  22. package/dist/commands/computer.js +37 -0
  23. package/dist/commands/config.d.ts +17 -0
  24. package/dist/commands/config.js +401 -0
  25. package/dist/commands/cp.d.ts +84 -0
  26. package/dist/commands/cp.js +231 -0
  27. package/dist/commands/daemon.js +243 -14
  28. package/dist/commands/detach-core.d.ts +1 -1
  29. package/dist/commands/detach-core.js +12 -2
  30. package/dist/commands/detach.js +1 -1
  31. package/dist/commands/doctor.js +64 -5
  32. package/dist/commands/events.d.ts +24 -12
  33. package/dist/commands/events.js +144 -28
  34. package/dist/commands/exec.js +86 -42
  35. package/dist/commands/feed.d.ts +1 -1
  36. package/dist/commands/feed.js +16 -8
  37. package/dist/commands/focus.d.ts +33 -1
  38. package/dist/commands/focus.js +177 -17
  39. package/dist/commands/funnel.d.ts +1 -1
  40. package/dist/commands/go.js +9 -8
  41. package/dist/commands/harness-wizard.d.ts +1 -0
  42. package/dist/commands/harness-wizard.js +18 -20
  43. package/dist/commands/harness.d.ts +2 -0
  44. package/dist/commands/harness.js +59 -26
  45. package/dist/commands/hooks.js +0 -11
  46. package/dist/commands/inspect.d.ts +94 -1
  47. package/dist/commands/inspect.js +866 -40
  48. package/dist/commands/lease.d.ts +4 -4
  49. package/dist/commands/lease.js +6 -6
  50. package/dist/commands/login.js +0 -13
  51. package/dist/commands/logs.d.ts +2 -2
  52. package/dist/commands/logs.js +2 -2
  53. package/dist/commands/mcp.js +7 -2
  54. package/dist/commands/memory.js +7 -2
  55. package/dist/commands/message.d.ts +1 -1
  56. package/dist/commands/message.js +37 -6
  57. package/dist/commands/models.js +71 -0
  58. package/dist/commands/monitors.d.ts +0 -1
  59. package/dist/commands/monitors.js +199 -16
  60. package/dist/commands/open.d.ts +18 -0
  61. package/dist/commands/open.js +57 -0
  62. package/dist/commands/packages.js +2 -107
  63. package/dist/commands/plugins.js +6 -3
  64. package/dist/commands/profiles.d.ts +1 -0
  65. package/dist/commands/profiles.js +16 -160
  66. package/dist/commands/projects.d.ts +28 -0
  67. package/dist/commands/projects.js +125 -5
  68. package/dist/commands/reconnect.js +9 -3
  69. package/dist/commands/repo.js +42 -1
  70. package/dist/commands/resource-view.d.ts +17 -0
  71. package/dist/commands/resource-view.js +36 -14
  72. package/dist/commands/resume.js +9 -2
  73. package/dist/commands/routines.d.ts +0 -2
  74. package/dist/commands/routines.js +473 -289
  75. package/dist/commands/secrets.js +52 -9
  76. package/dist/commands/sessions-export.d.ts +47 -0
  77. package/dist/commands/sessions-export.js +116 -6
  78. package/dist/commands/sessions-import.d.ts +17 -0
  79. package/dist/commands/sessions-import.js +114 -6
  80. package/dist/commands/sessions-migrate.d.ts +27 -0
  81. package/dist/commands/sessions-migrate.js +50 -9
  82. package/dist/commands/sessions-picker.js +23 -4
  83. package/dist/commands/sessions-resume.d.ts +5 -3
  84. package/dist/commands/sessions-resume.js +30 -8
  85. package/dist/commands/sessions-watch.d.ts +2 -0
  86. package/dist/commands/sessions-watch.js +53 -0
  87. package/dist/commands/sessions.d.ts +66 -3
  88. package/dist/commands/sessions.js +298 -47
  89. package/dist/commands/setup-browser.js +2 -2
  90. package/dist/commands/setup-mine.js +0 -1
  91. package/dist/commands/setup-preferences.js +1 -1
  92. package/dist/commands/setup.d.ts +5 -0
  93. package/dist/commands/setup.js +31 -5
  94. package/dist/commands/share.d.ts +87 -7
  95. package/dist/commands/share.js +320 -58
  96. package/dist/commands/skills.js +0 -11
  97. package/dist/commands/snapshot.js +1 -1
  98. package/dist/commands/ssh.d.ts +18 -0
  99. package/dist/commands/ssh.js +549 -210
  100. package/dist/commands/sync.d.ts +5 -0
  101. package/dist/commands/sync.js +212 -32
  102. package/dist/commands/teams.d.ts +13 -0
  103. package/dist/commands/teams.js +187 -30
  104. package/dist/commands/tickets.d.ts +2 -0
  105. package/dist/commands/tickets.js +43 -0
  106. package/dist/commands/update.js +2 -4
  107. package/dist/commands/usage.js +5 -3
  108. package/dist/commands/versions.js +11 -5
  109. package/dist/commands/view.js +15 -7
  110. package/dist/commands/webhook.d.ts +2 -2
  111. package/dist/commands/webhook.js +7 -7
  112. package/dist/index.d.ts +18 -2
  113. package/dist/index.js +53 -1264
  114. package/dist/lib/account-catalog.d.ts +18 -0
  115. package/dist/lib/account-catalog.js +38 -0
  116. package/dist/lib/account-provider-registry.d.ts +18 -0
  117. package/dist/lib/account-provider-registry.js +59 -0
  118. package/dist/lib/account-registry.d.ts +40 -0
  119. package/dist/lib/account-registry.js +239 -0
  120. package/dist/lib/account-schema.d.ts +44 -0
  121. package/dist/lib/account-schema.js +91 -0
  122. package/dist/lib/account-state-service.d.ts +21 -0
  123. package/dist/lib/account-state-service.js +60 -0
  124. package/dist/lib/activity.d.ts +1 -1
  125. package/dist/lib/activity.js +8 -1
  126. package/dist/lib/add-dir.d.ts +80 -0
  127. package/dist/lib/add-dir.js +241 -0
  128. package/dist/lib/agent-cli-commands.d.ts +11 -0
  129. package/dist/lib/agent-cli-commands.js +30 -0
  130. package/dist/lib/agents.js +44 -36
  131. package/dist/lib/answer-router.js +1 -1
  132. package/dist/lib/audit/log.d.ts +10 -27
  133. package/dist/lib/audit/log.js +20 -33
  134. package/dist/lib/auth-health.d.ts +9 -2
  135. package/dist/lib/auth-health.js +42 -9
  136. package/dist/lib/auto-pull-worker.js +7 -3
  137. package/dist/lib/auto-pull.d.ts +44 -3
  138. package/dist/lib/auto-pull.js +88 -5
  139. package/dist/lib/binary-shadow.d.ts +28 -0
  140. package/dist/lib/binary-shadow.js +121 -0
  141. package/dist/lib/brand.js +9 -3
  142. package/dist/lib/browser/hygiene.d.ts +90 -0
  143. package/dist/lib/browser/hygiene.js +146 -0
  144. package/dist/lib/browser/ipc.d.ts +30 -0
  145. package/dist/lib/browser/ipc.js +93 -4
  146. package/dist/lib/browser/profiles.d.ts +3 -2
  147. package/dist/lib/browser/profiles.js +65 -22
  148. package/dist/lib/browser/service.d.ts +79 -1
  149. package/dist/lib/browser/service.js +231 -14
  150. package/dist/lib/browser/sessions-list.d.ts +22 -2
  151. package/dist/lib/browser/sessions-list.js +54 -6
  152. package/dist/lib/browser/stream.d.ts +2 -0
  153. package/dist/lib/browser/stream.js +1 -0
  154. package/dist/lib/browser/types.d.ts +63 -1
  155. package/dist/lib/byok-usage.d.ts +9 -2
  156. package/dist/lib/byok-usage.js +133 -31
  157. package/dist/lib/cli-entry.d.ts +11 -0
  158. package/dist/lib/cli-entry.js +46 -0
  159. package/dist/lib/cloud/host.d.ts +1 -1
  160. package/dist/lib/cloud/host.js +2 -2
  161. package/dist/lib/cloud/registry.js +1 -1
  162. package/dist/lib/cloud/types.d.ts +1 -1
  163. package/dist/lib/commands.d.ts +2 -0
  164. package/dist/lib/commands.js +2 -0
  165. package/dist/lib/computer/sessions-list.d.ts +150 -0
  166. package/dist/lib/computer/sessions-list.js +363 -0
  167. package/dist/lib/config-keys.d.ts +86 -0
  168. package/dist/lib/config-keys.js +193 -0
  169. package/dist/lib/config-machine-keys.d.ts +12 -0
  170. package/dist/lib/config-machine-keys.js +17 -0
  171. package/dist/lib/config-transfer.js +3 -4
  172. package/dist/lib/crabbox/cli.d.ts +2 -2
  173. package/dist/lib/crabbox/cli.js +3 -3
  174. package/dist/lib/daemon-health.d.ts +22 -0
  175. package/dist/lib/daemon-health.js +33 -3
  176. package/dist/lib/daemon-services.d.ts +50 -0
  177. package/dist/lib/daemon-services.js +147 -0
  178. package/dist/lib/daemon-ticks.d.ts +35 -66
  179. package/dist/lib/daemon-ticks.js +77 -159
  180. package/dist/lib/daemon.d.ts +72 -4
  181. package/dist/lib/daemon.js +792 -202
  182. package/dist/lib/deeplink/register.d.ts +49 -0
  183. package/dist/lib/deeplink/register.js +253 -0
  184. package/dist/lib/deeplink/url.d.ts +34 -0
  185. package/dist/lib/deeplink/url.js +78 -0
  186. package/dist/lib/device-config.d.ts +124 -29
  187. package/dist/lib/device-config.js +346 -141
  188. package/dist/lib/devices/config-migration.d.ts +42 -0
  189. package/dist/lib/devices/config-migration.js +203 -0
  190. package/dist/lib/devices/connect.js +14 -7
  191. package/dist/lib/devices/doctor-findings.d.ts +4 -1
  192. package/dist/lib/devices/doctor-findings.js +20 -1
  193. package/dist/lib/devices/harness-inventory.d.ts +11 -2
  194. package/dist/lib/devices/harness-inventory.js +0 -0
  195. package/dist/lib/devices/health.d.ts +1 -1
  196. package/dist/lib/devices/health.js +1 -1
  197. package/dist/lib/devices/pending.d.ts +22 -1
  198. package/dist/lib/devices/pending.js +67 -2
  199. package/dist/lib/devices/registry.d.ts +0 -25
  200. package/dist/lib/devices/registry.js +2 -83
  201. package/dist/lib/devices/resolve-profile.d.ts +20 -0
  202. package/dist/lib/devices/resolve-profile.js +44 -0
  203. package/dist/lib/devices/rollout-verify.d.ts +101 -0
  204. package/dist/lib/devices/rollout-verify.js +193 -0
  205. package/dist/lib/devices/ssh-config.js +10 -6
  206. package/dist/lib/event-families.d.ts +28 -0
  207. package/dist/lib/event-families.js +159 -0
  208. package/dist/lib/event-stream.d.ts +9 -1
  209. package/dist/lib/event-stream.js +26 -9
  210. package/dist/lib/events.d.ts +9 -1
  211. package/dist/lib/events.js +21 -6
  212. package/dist/lib/exec.d.ts +30 -0
  213. package/dist/lib/exec.js +219 -121
  214. package/dist/lib/feed-broadcast.d.ts +25 -0
  215. package/dist/lib/feed-broadcast.js +33 -0
  216. package/dist/lib/feed.d.ts +1 -1
  217. package/dist/lib/feed.js +12 -2
  218. package/dist/lib/fleet/capture.js +15 -0
  219. package/dist/lib/fleet/manifest.js +9 -0
  220. package/dist/lib/fleet/types.d.ts +9 -0
  221. package/dist/lib/fleet-status.js +10 -8
  222. package/dist/lib/git.d.ts +21 -7
  223. package/dist/lib/git.js +122 -18
  224. package/dist/lib/hosts/dispatch.d.ts +8 -3
  225. package/dist/lib/hosts/dispatch.js +29 -3
  226. package/dist/lib/hosts/logs.d.ts +2 -2
  227. package/dist/lib/hosts/logs.js +2 -2
  228. package/dist/lib/hosts/option.js +1 -1
  229. package/dist/lib/hosts/passthrough.d.ts +2 -3
  230. package/dist/lib/hosts/passthrough.js +10 -24
  231. package/dist/lib/hosts/progress.js +2 -2
  232. package/dist/lib/hosts/providers/devices.d.ts +3 -3
  233. package/dist/lib/hosts/providers/devices.js +18 -12
  234. package/dist/lib/hosts/ready.d.ts +5 -1
  235. package/dist/lib/hosts/ready.js +9 -3
  236. package/dist/lib/hosts/reconcile.d.ts +2 -2
  237. package/dist/lib/hosts/reconcile.js +2 -2
  238. package/dist/lib/hosts/registry.js +14 -9
  239. package/dist/lib/hosts/remote-cmd.js +1 -1
  240. package/dist/lib/hosts/remote-os.d.ts +9 -6
  241. package/dist/lib/hosts/remote-os.js +13 -6
  242. package/dist/lib/hosts/routing-flag.d.ts +23 -0
  243. package/dist/lib/hosts/routing-flag.js +41 -0
  244. package/dist/lib/hosts/run-target.js +1 -1
  245. package/dist/lib/hosts/session-index.js +1 -1
  246. package/dist/lib/hosts/tasks.d.ts +6 -6
  247. package/dist/lib/hosts/tasks.js +4 -4
  248. package/dist/lib/hosts/types.js +1 -2
  249. package/dist/lib/installations/resolve.js +2 -8
  250. package/dist/lib/isolation-boundary-report.js +0 -1
  251. package/dist/lib/linear-cache.d.ts +7 -0
  252. package/dist/lib/linear-cache.js +31 -4
  253. package/dist/lib/linear-project-counts.d.ts +1 -1
  254. package/dist/lib/linear-project-counts.js +2 -3
  255. package/dist/lib/linear-projects.js +1 -1
  256. package/dist/lib/loop.d.ts +2 -2
  257. package/dist/lib/loop.js +1 -1
  258. package/dist/lib/mailbox-target.d.ts +1 -1
  259. package/dist/lib/mailbox-target.js +1 -1
  260. package/dist/lib/manifest.js +7 -4
  261. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  262. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  263. package/dist/lib/menubar/snapshot.d.ts +3 -2
  264. package/dist/lib/menubar/snapshot.js +6 -2
  265. package/dist/lib/migrate-fold.d.ts +33 -0
  266. package/dist/lib/migrate-fold.js +130 -0
  267. package/dist/lib/migrate.d.ts +34 -34
  268. package/dist/lib/migrate.js +106 -128
  269. package/dist/lib/model-tier-overrides.d.ts +1 -1
  270. package/dist/lib/model-tier-overrides.js +15 -2
  271. package/dist/lib/monitors/config.d.ts +17 -3
  272. package/dist/lib/monitors/config.js +52 -18
  273. package/dist/lib/monitors/engine.d.ts +32 -1
  274. package/dist/lib/monitors/engine.js +94 -11
  275. package/dist/lib/monitors/fingerprint.d.ts +46 -0
  276. package/dist/lib/monitors/fingerprint.js +100 -0
  277. package/dist/lib/monitors/remote.d.ts +47 -0
  278. package/dist/lib/monitors/remote.js +81 -0
  279. package/dist/lib/monitors/state.d.ts +39 -0
  280. package/dist/lib/monitors/state.js +56 -0
  281. package/dist/lib/onepassword.d.ts +17 -0
  282. package/dist/lib/onepassword.js +52 -22
  283. package/dist/lib/open-url.js +1 -1
  284. package/dist/lib/picker.d.ts +14 -2
  285. package/dist/lib/picker.js +35 -13
  286. package/dist/lib/placement.d.ts +1 -1
  287. package/dist/lib/placement.js +1 -1
  288. package/dist/lib/pr-land-detach.d.ts +32 -0
  289. package/dist/lib/pr-land-detach.js +79 -0
  290. package/dist/lib/profiles-presets.js +13 -0
  291. package/dist/lib/profiles.d.ts +2 -0
  292. package/dist/lib/profiles.js +44 -1
  293. package/dist/lib/project-focus.d.ts +1 -1
  294. package/dist/lib/project-focus.js +1 -1
  295. package/dist/lib/project-probe.d.ts +4 -1
  296. package/dist/lib/project-probe.js +5 -4
  297. package/dist/lib/project-resources.js +2 -18
  298. package/dist/lib/project-root.d.ts +16 -0
  299. package/dist/lib/project-root.js +20 -2
  300. package/dist/lib/projects.d.ts +20 -1
  301. package/dist/lib/projects.js +78 -1
  302. package/dist/lib/refresh-coordinator.d.ts +24 -0
  303. package/dist/lib/refresh-coordinator.js +52 -0
  304. package/dist/lib/registry.d.ts +2 -2
  305. package/dist/lib/registry.js +1 -1
  306. package/dist/lib/resource-aliases.d.ts +13 -0
  307. package/dist/lib/resource-aliases.js +26 -0
  308. package/dist/lib/resources/types.d.ts +1 -1
  309. package/dist/lib/resources.d.ts +16 -0
  310. package/dist/lib/resources.js +66 -10
  311. package/dist/lib/rotate.d.ts +3 -4
  312. package/dist/lib/rotate.js +4 -5
  313. package/dist/lib/routine-activation.d.ts +22 -2
  314. package/dist/lib/routine-activation.js +63 -16
  315. package/dist/lib/routine-context.d.ts +9 -2
  316. package/dist/lib/routine-context.js +21 -6
  317. package/dist/lib/routines-project.d.ts +58 -48
  318. package/dist/lib/routines-project.js +190 -159
  319. package/dist/lib/routines.d.ts +44 -7
  320. package/dist/lib/routines.js +92 -34
  321. package/dist/lib/run-defaults.d.ts +5 -0
  322. package/dist/lib/run-defaults.js +11 -1
  323. package/dist/lib/runner.d.ts +10 -8
  324. package/dist/lib/runner.js +122 -39
  325. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  326. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  327. package/dist/lib/secrets/agent.d.ts +10 -3
  328. package/dist/lib/secrets/agent.js +43 -8
  329. package/dist/lib/secrets/bundles.d.ts +1 -0
  330. package/dist/lib/secrets/bundles.js +92 -80
  331. package/dist/lib/secrets/index.js +32 -9
  332. package/dist/lib/secrets/install-helper.d.ts +4 -4
  333. package/dist/lib/secrets/install-helper.js +4 -4
  334. package/dist/lib/secrets/push.d.ts +40 -6
  335. package/dist/lib/secrets/push.js +58 -11
  336. package/dist/lib/secrets/read-backoff.d.ts +1 -1
  337. package/dist/lib/secrets/read-backoff.js +1 -1
  338. package/dist/lib/secrets/remote.d.ts +24 -0
  339. package/dist/lib/secrets/remote.js +54 -4
  340. package/dist/lib/self-update.d.ts +108 -0
  341. package/dist/lib/self-update.js +276 -1
  342. package/dist/lib/session/active.d.ts +89 -5
  343. package/dist/lib/session/active.js +165 -10
  344. package/dist/lib/session/actor-sidecar.d.ts +1 -1
  345. package/dist/lib/session/actor-sidecar.js +2 -1
  346. package/dist/lib/session/db.d.ts +176 -2
  347. package/dist/lib/session/db.js +484 -17
  348. package/dist/lib/session/discover.js +27 -4
  349. package/dist/lib/session/hook-sessions.js +2 -2
  350. package/dist/lib/session/host-link.d.ts +2 -2
  351. package/dist/lib/session/host-link.js +2 -2
  352. package/dist/lib/session/pid-registry.d.ts +21 -0
  353. package/dist/lib/session/pid-registry.js +57 -0
  354. package/dist/lib/session/remote-active.js +15 -1
  355. package/dist/lib/session/remote.js +2 -2
  356. package/dist/lib/session/session-cache.d.ts +14 -7
  357. package/dist/lib/session/session-cache.js +51 -9
  358. package/dist/lib/session/state.d.ts +2 -2
  359. package/dist/lib/session/state.js +1 -1
  360. package/dist/lib/session/sync/config.d.ts +10 -10
  361. package/dist/lib/session/sync/config.js +10 -10
  362. package/dist/lib/session/sync/r2.d.ts +34 -0
  363. package/dist/lib/session/sync/r2.js +123 -0
  364. package/dist/lib/session/throughput.d.ts +1 -1
  365. package/dist/lib/session/throughput.js +1 -1
  366. package/dist/lib/session/types.d.ts +23 -1
  367. package/dist/lib/session/types.js +15 -0
  368. package/dist/lib/session/viewing-in.d.ts +1 -1
  369. package/dist/lib/session/viewing-in.js +1 -1
  370. package/dist/lib/session/watch.d.ts +101 -0
  371. package/dist/lib/session/watch.js +242 -0
  372. package/dist/lib/share/analytics.js +1 -1
  373. package/dist/lib/share/capture.d.ts +1 -1
  374. package/dist/lib/share/capture.js +3 -3
  375. package/dist/lib/share/config.d.ts +6 -2
  376. package/dist/lib/share/config.js +6 -5
  377. package/dist/lib/share/delete.js +3 -3
  378. package/dist/lib/share/provision.d.ts +38 -0
  379. package/dist/lib/share/provision.js +51 -1
  380. package/dist/lib/share/publish.d.ts +40 -1
  381. package/dist/lib/share/publish.js +119 -6
  382. package/dist/lib/share/worker-template.d.ts +12 -1
  383. package/dist/lib/share/worker-template.js +83 -7
  384. package/dist/lib/shims.d.ts +17 -3
  385. package/dist/lib/shims.js +98 -26
  386. package/dist/lib/signin-badge.js +3 -2
  387. package/dist/lib/skills.js +2 -0
  388. package/dist/lib/smart-launch.d.ts +8 -8
  389. package/dist/lib/smart-launch.js +80 -59
  390. package/dist/lib/ssh-exec.d.ts +1 -1
  391. package/dist/lib/ssh-exec.js +1 -1
  392. package/dist/lib/staleness/prune.d.ts +88 -0
  393. package/dist/lib/staleness/prune.js +69 -0
  394. package/dist/lib/staleness/registry.d.ts +1 -1
  395. package/dist/lib/staleness/writers/commands.js +34 -2
  396. package/dist/lib/staleness/writers/hooks.js +16 -0
  397. package/dist/lib/staleness/writers/kinds.d.ts +6 -0
  398. package/dist/lib/staleness/writers/skills.js +19 -0
  399. package/dist/lib/staleness/writers/subagents.d.ts +2 -3
  400. package/dist/lib/staleness/writers/subagents.js +0 -4
  401. package/dist/lib/staleness/writers/types.d.ts +32 -0
  402. package/dist/lib/startup/command-registry.d.ts +16 -14
  403. package/dist/lib/startup/command-registry.js +26 -37
  404. package/dist/lib/startup/dev-build.d.ts +10 -0
  405. package/dist/lib/startup/dev-build.js +13 -1
  406. package/dist/lib/startup/spellcheck.d.ts +18 -0
  407. package/dist/lib/startup/spellcheck.js +45 -0
  408. package/dist/lib/state.d.ts +36 -9
  409. package/dist/lib/state.js +96 -46
  410. package/dist/lib/subagents-registry.d.ts +0 -7
  411. package/dist/lib/subagents-registry.js +38 -65
  412. package/dist/lib/subagents.d.ts +0 -33
  413. package/dist/lib/subagents.js +0 -75
  414. package/dist/lib/sync-umbrella.js +1 -1
  415. package/dist/lib/teams/agents.d.ts +41 -1
  416. package/dist/lib/teams/agents.js +187 -9
  417. package/dist/lib/teams/api.d.ts +11 -1
  418. package/dist/lib/teams/api.js +11 -2
  419. package/dist/lib/teams/delivery.d.ts +41 -0
  420. package/dist/lib/teams/delivery.js +60 -0
  421. package/dist/lib/teams/registry.d.ts +14 -0
  422. package/dist/lib/teams/registry.js +51 -1
  423. package/dist/lib/teams/remoteWorktree.d.ts +19 -0
  424. package/dist/lib/teams/remoteWorktree.js +29 -0
  425. package/dist/lib/teams/scheduler.d.ts +1 -1
  426. package/dist/lib/teams/scheduler.js +1 -1
  427. package/dist/lib/teams/worktree.d.ts +24 -0
  428. package/dist/lib/teams/worktree.js +42 -0
  429. package/dist/lib/terminal/backends/vscodium-agent.d.ts +10 -1
  430. package/dist/lib/terminal/backends/vscodium-agent.js +16 -5
  431. package/dist/lib/terminal/engine.d.ts +4 -0
  432. package/dist/lib/terminal/engine.js +8 -2
  433. package/dist/lib/terminal/types.d.ts +21 -2
  434. package/dist/lib/testdata/refresh-coordinator-worker.d.ts +1 -0
  435. package/dist/lib/testdata/refresh-coordinator-worker.js +23 -0
  436. package/dist/lib/tickets/list.d.ts +53 -0
  437. package/dist/lib/tickets/list.js +153 -0
  438. package/dist/lib/tmux/orphan-reap.d.ts +316 -0
  439. package/dist/lib/tmux/orphan-reap.js +638 -0
  440. package/dist/lib/tmux/session.d.ts +68 -3
  441. package/dist/lib/tmux/session.js +96 -5
  442. package/dist/lib/triggers/handlers.d.ts +20 -0
  443. package/dist/lib/triggers/handlers.js +16 -1
  444. package/dist/lib/triggers/webhook.js +10 -0
  445. package/dist/lib/types.d.ts +47 -35
  446. package/dist/lib/usage-fleet.d.ts +32 -0
  447. package/dist/lib/usage-fleet.js +125 -0
  448. package/dist/lib/usage-refresh.d.ts +3 -3
  449. package/dist/lib/usage-refresh.js +19 -16
  450. package/dist/lib/usage.d.ts +51 -47
  451. package/dist/lib/usage.js +145 -153
  452. package/dist/lib/version-duplicates.js +2 -2
  453. package/dist/lib/versions.d.ts +56 -13
  454. package/dist/lib/versions.js +195 -45
  455. package/dist/lib/watchdog/log.d.ts +1 -1
  456. package/dist/lib/watchdog/log.js +3 -3
  457. package/dist/lib/watchdog/rotate.d.ts +6 -6
  458. package/dist/lib/watchdog/rotate.js +6 -6
  459. package/dist/lib/workflows.d.ts +1 -1
  460. package/dist/lib/yaml-io.d.ts +47 -0
  461. package/dist/lib/yaml-io.js +55 -0
  462. package/package.json +2 -1
  463. package/scripts/install-helper.js +2 -2
  464. package/scripts/postinstall.js +23 -20
  465. package/dist/commands/defaults.d.ts +0 -7
  466. package/dist/commands/defaults.js +0 -107
  467. package/dist/commands/export.d.ts +0 -11
  468. package/dist/commands/export.js +0 -215
  469. package/dist/commands/helper.d.ts +0 -12
  470. package/dist/commands/helper.js +0 -87
  471. package/dist/commands/hosts.d.ts +0 -11
  472. package/dist/commands/hosts.js +0 -330
  473. package/dist/commands/lock.d.ts +0 -12
  474. package/dist/commands/lock.js +0 -70
  475. package/dist/commands/pull.d.ts +0 -17
  476. package/dist/commands/pull.js +0 -39
  477. package/dist/commands/push.d.ts +0 -14
  478. package/dist/commands/push.js +0 -30
  479. package/dist/commands/set.d.ts +0 -15
  480. package/dist/commands/set.js +0 -79
  481. package/dist/commands/setup-share.d.ts +0 -17
  482. package/dist/commands/setup-share.js +0 -85
  483. package/dist/commands/wallet.d.ts +0 -20
  484. package/dist/commands/wallet.js +0 -216
  485. package/dist/commands/worktree.d.ts +0 -19
  486. package/dist/commands/worktree.js +0 -272
  487. package/dist/lib/account-labels.d.ts +0 -24
  488. package/dist/lib/account-labels.js +0 -72
  489. package/dist/lib/auto-dispatch-linear.d.ts +0 -18
  490. package/dist/lib/auto-dispatch-linear.js +0 -107
  491. package/dist/lib/auto-dispatch-provider.d.ts +0 -10
  492. package/dist/lib/auto-dispatch-provider.js +0 -30
  493. package/dist/lib/auto-dispatch.d.ts +0 -93
  494. package/dist/lib/auto-dispatch.js +0 -128
  495. package/dist/lib/export.d.ts +0 -72
  496. package/dist/lib/export.js +0 -269
  497. package/dist/lib/lock.d.ts +0 -93
  498. package/dist/lib/lock.js +0 -207
  499. package/dist/lib/wallet/index.d.ts +0 -78
  500. package/dist/lib/wallet/index.js +0 -253
@@ -1,5 +1,6 @@
1
1
  import { type ResolvedDomainSkill } from './domain-skills.js';
2
- import { type TabInfo, type ProfileStatus, type HistoricalTask } from './types.js';
2
+ import { type Task, type TabInfo, type ProfileStatus, type HistoricalTask, type ReapResult } from './types.js';
3
+ import { type ReapOptions } from './hygiene.js';
3
4
  import { type RefOpts, type RefNode } from './refs.js';
4
5
  import type { TargetFilter } from './types.js';
5
6
  export type UploadMode = 'auto' | 'input' | 'drop' | 'chooser';
@@ -62,9 +63,11 @@ export interface HealInfo {
62
63
  export declare function resolveTaskIdentity(forwarded: {
63
64
  actor?: string;
64
65
  launchId?: string;
66
+ sessionId?: string;
65
67
  }, resolveLocalActor: () => string): {
66
68
  owner: string;
67
69
  launchId?: string;
70
+ sessionId?: string;
68
71
  };
69
72
  export declare class BrowserService {
70
73
  private static readonly SOURCE_PREFIX;
@@ -75,14 +78,22 @@ export declare class BrowserService {
75
78
  private networkRequests;
76
79
  private pendingDownloads;
77
80
  private enabledSessions;
81
+ /** Profile -> when a `touchTask` last persisted tasks.json for it. */
82
+ private lastTouchPersist;
83
+ /** Coalescing window for the `touchTask` write. See {@link touchTask}. */
84
+ private static readonly TOUCH_PERSIST_INTERVAL_MS;
78
85
  start(profileName: string, opts?: {
79
86
  taskName?: string;
80
87
  url?: string;
81
88
  endpointName?: string;
82
89
  skipDomainSkill?: boolean;
90
+ /** Always open a new tab, skipping the abandoned-task reclaim. */
91
+ fresh?: boolean;
83
92
  /** Caller identity, forwarded from the CLI (see IPCRequest.actor/launchId). */
84
93
  actor?: string;
85
94
  launchId?: string;
95
+ /** Calling agent session, forwarded from the CLI (see IPCRequest.sessionId). */
96
+ sessionId?: string;
86
97
  }): Promise<{
87
98
  task: string;
88
99
  name: string;
@@ -91,6 +102,54 @@ export declare class BrowserService {
91
102
  profile: string;
92
103
  skill?: ResolvedDomainSkill;
93
104
  }>;
105
+ /**
106
+ * Reclaim a live page already showing `url` from an ABANDONED task, instead
107
+ * of opening a second copy of the same page (RUSH-2622). Returns its CDP
108
+ * targetId, or undefined when there is nothing safe to reclaim.
109
+ *
110
+ * "Safe" is narrow on purpose, because both of the wider readings close a tab
111
+ * somebody is using:
112
+ *
113
+ * - An UNOWNED matching page is NOT taken. No task claims it, which most
114
+ * often means the user opened it themselves — and adopting it would make
115
+ * this task's `done` close the user's tab.
116
+ * - A page owned by a LIVE task is NOT taken. Stealing it would leave that
117
+ * agent's `screenshot`/`click` throwing "No tabs open for this task", its
118
+ * `navigate` silently opening the duplicate this feature exists to
119
+ * prevent, and any in-flight recording truncated when the new owner calls
120
+ * `done`.
121
+ *
122
+ * What is left is exactly the pile-up case: a page held by a task whose owner
123
+ * is provably gone. Liveness uses the reaper's own predicate
124
+ * (`taskOwnerIsGone`), so "dead" has one definition in this codebase rather
125
+ * than two that can drift. A task the reaper cannot prove dead keeps its tab
126
+ * and is closed later by the idle rule instead.
127
+ *
128
+ * Reclaiming transfers rather than shares: two tasks holding one targetId
129
+ * means the first `done` closes the other's tab. `start --fresh` skips this
130
+ * path entirely.
131
+ *
132
+ * URLs are compared canonically (`new URL(...).href`), so a requested
133
+ * `https://example.com` matches the `https://example.com/` Chrome reports. A
134
+ * page that has since redirected elsewhere simply does not match, and the
135
+ * caller opens a tab as before.
136
+ */
137
+ private adoptTabShowing;
138
+ /**
139
+ * Every live task across every connected profile — the reaper's input
140
+ * (`hygiene.ts`). A read-only view: mutating a returned `Task` mutates the
141
+ * daemon's live state, so callers only read it and act through `stop`.
142
+ */
143
+ listTasks(): Array<{
144
+ profile: string;
145
+ task: Task;
146
+ }>;
147
+ /**
148
+ * Close tasks whose owning agent session is gone, or that have sat untouched
149
+ * past the idle window. The entry point the daemon's periodic tick and
150
+ * `agents browser gc` both call; the policy lives in `hygiene.ts`.
151
+ */
152
+ reapAbandoned(opts?: ReapOptions): Promise<ReapResult>;
94
153
  stop(taskName: string): Promise<{
95
154
  ok: boolean;
96
155
  profile?: string;
@@ -277,6 +336,25 @@ export declare class BrowserService {
277
336
  private getOrCreateWindow;
278
337
  private hasTaskNamed;
279
338
  private generateUniqueTaskName;
339
+ /**
340
+ * Mark a task as active, so the reaper's idle window measures time since the
341
+ * last real use rather than time since `start` (RUSH-2622).
342
+ *
343
+ * Called from `findTask` — the single funnel every task-scoped operation
344
+ * resolves through — rather than from each of the ~26 call sites, so a new
345
+ * action added later cannot forget to stamp it.
346
+ *
347
+ * The in-memory stamp is what the reaper reads: it runs inside the same
348
+ * daemon that owns these `Task` objects. The write to tasks.json exists only
349
+ * so a daemon RESTART does not inherit a stale stamp and reap a task an agent
350
+ * has been clicking through for the last half hour — `click`, `type`,
351
+ * `evaluate`, and `screenshot` never call `saveTaskState` on their own, so
352
+ * without this the on-disk stamp would sit at the last navigation. It is
353
+ * coalesced to at most one write per profile per minute so a screenshot loop
354
+ * does not become a write loop; the resulting on-disk stamp trails by at most
355
+ * a minute, well inside the 30-minute idle window.
356
+ */
357
+ private touchTask;
280
358
  private findTask;
281
359
  private getTabsForTask;
282
360
  private getProfileStatus;
@@ -9,13 +9,29 @@ import { connectSSH, shellQuote } from './drivers/ssh.js';
9
9
  import { clearProfileRuntime, listProfileCacheDirs, readProfileRuntimeMeta, isProcessAlive } from './runtime-state.js';
10
10
  import { resolveDomainSkill } from './domain-skills.js';
11
11
  import { generateTaskId, generateShortId, generateTaskName, } from './types.js';
12
+ import { reapAbandonedTasks, resolveLiveIdentities, taskOwnerIsGone, } from './hygiene.js';
12
13
  import { getRefs, resolveRefToCoords, describeRefs, healRef } from './refs.js';
13
14
  import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focusNode } from './input.js';
14
15
  import { typeEditorText } from './editor.js';
15
16
  import { detectUploadPattern, stageUploadFile, uploadToDropTarget, uploadToFileInput, uploadViaFileChooser, } from './upload.js';
16
17
  import { emit } from '../events.js';
17
18
  import { resolveActor } from '../actor.js';
19
+ import { recordBrowserSession } from '../session/db.js';
18
20
  import { sshExecAsync } from '../ssh-exec.js';
21
+ /**
22
+ * Canonical form for comparing a requested URL against what CDP reports for a
23
+ * live target. `new URL('https://example.com').href` is `https://example.com/`,
24
+ * which is exactly what Chrome reports — comparing the raw strings would miss
25
+ * every bare-origin match. Unparseable input compares as itself.
26
+ */
27
+ function canonicalTabUrl(raw) {
28
+ try {
29
+ return new URL(raw).href;
30
+ }
31
+ catch {
32
+ return raw;
33
+ }
34
+ }
19
35
  function isPathInside(candidate, dir) {
20
36
  const rel = path.relative(dir, candidate);
21
37
  return rel === '' || (!!rel && !rel.startsWith('..') && !path.isAbsolute(rel));
@@ -235,6 +251,7 @@ export function resolveTaskIdentity(forwarded, resolveLocalActor) {
235
251
  return {
236
252
  owner: forwarded.actor ?? resolveLocalActor(),
237
253
  launchId: forwarded.launchId,
254
+ sessionId: forwarded.sessionId,
238
255
  };
239
256
  }
240
257
  export class BrowserService {
@@ -250,6 +267,10 @@ export class BrowserService {
250
267
  networkRequests = new Map();
251
268
  pendingDownloads = new Map();
252
269
  enabledSessions = new Map(); // sessionId -> enabled domains
270
+ /** Profile -> when a `touchTask` last persisted tasks.json for it. */
271
+ lastTouchPersist = new Map();
272
+ /** Coalescing window for the `touchTask` write. See {@link touchTask}. */
273
+ static TOUCH_PERSIST_INTERVAL_MS = 60_000;
253
274
  async start(profileName, opts = {}) {
254
275
  const profile = await getProfile(profileName);
255
276
  if (!profile) {
@@ -329,29 +350,49 @@ export class BrowserService {
329
350
  // Browsers launch with --no-startup-window (session-cookie persistence,
330
351
  // see launchBrowser), so a bare `start` with no --url would otherwise
331
352
  // leave the user staring at a process with zero windows. Recreate the
332
- // old startup-window affordance: if no page target exists, open a blank
333
- // one. Deliberately NOT registered on the task — the startup window
334
- // never was either, and tasks track only tabs they created.
353
+ // old startup-window affordance: if no page target exists, open a blank one.
354
+ //
355
+ // This tab IS registered on the task below. It used to be deliberately
356
+ // unregistered ("tasks track only tabs they created") — but this daemon did
357
+ // create it, and an unregistered tab is one `done`/`stop` can never close:
358
+ // `stop` closes exactly the entries in `task.tabs`. So every bare `start`
359
+ // left a blank globe tab behind forever, which is a large share of the
360
+ // pile-up in RUSH-2622. Tabs the daemon did NOT open are still left alone —
361
+ // the branch only fires when the profile has no page target at all.
362
+ let startupBlankTargetId;
335
363
  if (!opts.url && !conn.electron) {
336
364
  const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
337
365
  if (!targetInfos.some((t) => t.type === 'page')) {
338
- await conn.cdp.send('Target.createTarget', { url: 'about:blank' });
366
+ const created = (await conn.cdp.send('Target.createTarget', {
367
+ url: 'about:blank',
368
+ }));
369
+ startupBlankTargetId = created.targetId;
339
370
  this.invalidateTargetCache(conn);
340
371
  }
341
372
  }
373
+ const now = Date.now();
342
374
  const task = {
343
375
  id: taskId,
344
376
  name: taskName,
345
377
  profile: effectiveProfileName,
346
378
  tabs: {},
347
379
  currentTabId: undefined,
348
- createdAt: Date.now(),
380
+ createdAt: now,
381
+ // A brand-new task has done exactly one thing — start — so its last
382
+ // action is its creation. The reaper's idle window runs from here.
383
+ lastActionAt: now,
349
384
  pid: conn.pid,
350
385
  // Identity is forwarded from the caller (see resolveTaskIdentity): WHO
351
- // (owner) and WHICH run (launchId). Resolving daemon-side would attribute
352
- // every task to the shared daemon's actor (the RUSH-2020 bug).
353
- ...resolveTaskIdentity({ actor: opts.actor, launchId: opts.launchId }, () => resolveActor().id),
386
+ // (owner), WHICH run (launchId), and WHICH agent session (sessionId).
387
+ // Resolving daemon-side would attribute every task to the shared daemon's
388
+ // actor (the RUSH-2020 bug).
389
+ ...resolveTaskIdentity({ actor: opts.actor, launchId: opts.launchId, sessionId: opts.sessionId }, () => resolveActor().id),
354
390
  };
391
+ if (startupBlankTargetId) {
392
+ const shortId = generateShortId();
393
+ task.tabs[shortId] = startupBlankTargetId;
394
+ task.currentTabId = shortId;
395
+ }
355
396
  // For Electron, get the existing window as the tab
356
397
  if (conn.electron) {
357
398
  const windowId = await this.getOrCreateWindow(conn);
@@ -363,6 +404,30 @@ export class BrowserService {
363
404
  }
364
405
  conn.tasks.set(taskName, task);
365
406
  await this.saveTaskState(effectiveProfileName, conn.tasks);
407
+ // Durable identity, written ONCE at start and never deleted (RUSH-2549).
408
+ // tasks.json above stays live state: `stop` drops the task from that map, so
409
+ // anything recorded only there is gone the moment the task ends -- which is
410
+ // why every finished task used to list as "unlinked". This row outlives the
411
+ // task, the daemon, and a reboot. Metadata only: the capture bytes stay on
412
+ // disk under capture_dir.
413
+ // Guarded like `emit` below ("logging should never break the CLI",
414
+ // events.ts): the browser task is already started and registered. An
415
+ // unwritable session DB must not turn a working `agents browser start` into
416
+ // a failure -- the cost of a miss is one unlinked row, not a dead browser.
417
+ try {
418
+ recordBrowserSession({
419
+ task: taskName,
420
+ profile: effectiveProfileName,
421
+ sessionId: task.sessionId,
422
+ launchId: task.launchId,
423
+ actor: task.owner,
424
+ startedAt: task.createdAt,
425
+ captureDir: getProfileSessionsDir(effectiveProfileName, taskName),
426
+ });
427
+ }
428
+ catch {
429
+ // Recording is best-effort; the task itself is live either way.
430
+ }
366
431
  emit('browser.launch', { profile: effectiveProfileName, task: taskName, pid: conn.pid });
367
432
  void import('../analytics/usage-db.js').then(({ recordUsage }) => {
368
433
  recordUsage({
@@ -373,14 +438,17 @@ export class BrowserService {
373
438
  meta: { task: taskName },
374
439
  });
375
440
  }).catch(() => { });
376
- // If URL provided, create tab directly (no about:blank)
441
+ // If URL provided, reclaim a tab an abandoned task is holding on it, else
442
+ // create one directly (no about:blank).
377
443
  let tabId;
378
444
  if (opts.url && !conn.electron) {
379
- const result = (await conn.cdp.send('Target.createTarget', {
380
- url: opts.url,
381
- }));
445
+ const adopted = opts.fresh ? undefined : await this.adoptTabShowing(conn, opts.url);
446
+ const targetId = adopted ??
447
+ (await conn.cdp.send('Target.createTarget', {
448
+ url: opts.url,
449
+ })).targetId;
382
450
  const shortId = generateShortId();
383
- task.tabs[shortId] = result.targetId;
451
+ task.tabs[shortId] = targetId;
384
452
  task.currentTabId = shortId;
385
453
  this.invalidateTargetCache(conn);
386
454
  await this.saveTaskState(effectiveProfileName, conn.tasks);
@@ -401,6 +469,110 @@ export class BrowserService {
401
469
  }
402
470
  return { task: taskId, name: taskName, tabId, profile: effectiveProfileName, skill };
403
471
  }
472
+ /**
473
+ * Reclaim a live page already showing `url` from an ABANDONED task, instead
474
+ * of opening a second copy of the same page (RUSH-2622). Returns its CDP
475
+ * targetId, or undefined when there is nothing safe to reclaim.
476
+ *
477
+ * "Safe" is narrow on purpose, because both of the wider readings close a tab
478
+ * somebody is using:
479
+ *
480
+ * - An UNOWNED matching page is NOT taken. No task claims it, which most
481
+ * often means the user opened it themselves — and adopting it would make
482
+ * this task's `done` close the user's tab.
483
+ * - A page owned by a LIVE task is NOT taken. Stealing it would leave that
484
+ * agent's `screenshot`/`click` throwing "No tabs open for this task", its
485
+ * `navigate` silently opening the duplicate this feature exists to
486
+ * prevent, and any in-flight recording truncated when the new owner calls
487
+ * `done`.
488
+ *
489
+ * What is left is exactly the pile-up case: a page held by a task whose owner
490
+ * is provably gone. Liveness uses the reaper's own predicate
491
+ * (`taskOwnerIsGone`), so "dead" has one definition in this codebase rather
492
+ * than two that can drift. A task the reaper cannot prove dead keeps its tab
493
+ * and is closed later by the idle rule instead.
494
+ *
495
+ * Reclaiming transfers rather than shares: two tasks holding one targetId
496
+ * means the first `done` closes the other's tab. `start --fresh` skips this
497
+ * path entirely.
498
+ *
499
+ * URLs are compared canonically (`new URL(...).href`), so a requested
500
+ * `https://example.com` matches the `https://example.com/` Chrome reports. A
501
+ * page that has since redirected elsewhere simply does not match, and the
502
+ * caller opens a tab as before.
503
+ */
504
+ async adoptTabShowing(conn, url) {
505
+ const { targetInfos } = (await conn.cdp.send('Target.getTargets'));
506
+ const wanted = canonicalTabUrl(url);
507
+ const matching = new Set(targetInfos
508
+ .filter((t) => t.type === 'page' && canonicalTabUrl(t.url) === wanted)
509
+ .map((t) => t.targetId));
510
+ if (matching.size === 0)
511
+ return undefined;
512
+ const candidates = [];
513
+ for (const task of conn.tasks.values()) {
514
+ for (const [shortId, cdpId] of Object.entries(task.tabs)) {
515
+ if (matching.has(cdpId))
516
+ candidates.push({ task, shortId, targetId: cdpId });
517
+ }
518
+ }
519
+ if (candidates.length === 0)
520
+ return undefined;
521
+ const live = resolveLiveIdentities();
522
+ for (const { task, shortId, targetId } of candidates) {
523
+ // An in-flight capture means the task is in use whatever its owner looks
524
+ // like — same guard the reaper applies before stopping anything.
525
+ if (this.recordings.has(task.name))
526
+ continue;
527
+ if (!(await taskOwnerIsGone(task, live)))
528
+ continue;
529
+ // Re-check the claim after the await. Nothing serializes IPC requests —
530
+ // `BrowserIPCServer` registers a per-connection `socket.on('data', async …)`
531
+ // that Node never awaits — so two concurrent `start`s can both pass the
532
+ // liveness check on one candidate and both return the same targetId,
533
+ // putting two live tasks on one tab and re-opening the double-owner bug
534
+ // this reclaim is careful to avoid. Whoever deletes it first owns it.
535
+ if (task.tabs[shortId] !== targetId)
536
+ continue;
537
+ delete task.tabs[shortId];
538
+ delete task.refDescriptors?.[shortId];
539
+ if (task.currentTabId === shortId) {
540
+ const remaining = Object.keys(task.tabs);
541
+ task.currentTabId = remaining.length > 0 ? remaining[remaining.length - 1] : undefined;
542
+ }
543
+ try {
544
+ await conn.cdp.send('Target.activateTarget', { targetId });
545
+ }
546
+ catch {
547
+ // Bringing the tab to the front is cosmetic and not every endpoint
548
+ // implements activateTarget; the tab is reclaimed either way.
549
+ }
550
+ return targetId;
551
+ }
552
+ return undefined;
553
+ }
554
+ /**
555
+ * Every live task across every connected profile — the reaper's input
556
+ * (`hygiene.ts`). A read-only view: mutating a returned `Task` mutates the
557
+ * daemon's live state, so callers only read it and act through `stop`.
558
+ */
559
+ listTasks() {
560
+ const out = [];
561
+ for (const [key, conn] of this.connections) {
562
+ for (const task of conn.tasks.values()) {
563
+ out.push({ profile: conn.profileName ?? key, task });
564
+ }
565
+ }
566
+ return out;
567
+ }
568
+ /**
569
+ * Close tasks whose owning agent session is gone, or that have sat untouched
570
+ * past the idle window. The entry point the daemon's periodic tick and
571
+ * `agents browser gc` both call; the policy lives in `hygiene.ts`.
572
+ */
573
+ async reapAbandoned(opts = {}) {
574
+ return reapAbandonedTasks(this, opts);
575
+ }
404
576
  async stop(taskName) {
405
577
  for (const [profileName, conn] of this.connections) {
406
578
  const task = conn.tasks.get(taskName);
@@ -1850,6 +2022,41 @@ export class BrowserService {
1850
2022
  }
1851
2023
  throw new Error('Could not generate unique task name after 8 attempts');
1852
2024
  }
2025
+ /**
2026
+ * Mark a task as active, so the reaper's idle window measures time since the
2027
+ * last real use rather than time since `start` (RUSH-2622).
2028
+ *
2029
+ * Called from `findTask` — the single funnel every task-scoped operation
2030
+ * resolves through — rather than from each of the ~26 call sites, so a new
2031
+ * action added later cannot forget to stamp it.
2032
+ *
2033
+ * The in-memory stamp is what the reaper reads: it runs inside the same
2034
+ * daemon that owns these `Task` objects. The write to tasks.json exists only
2035
+ * so a daemon RESTART does not inherit a stale stamp and reap a task an agent
2036
+ * has been clicking through for the last half hour — `click`, `type`,
2037
+ * `evaluate`, and `screenshot` never call `saveTaskState` on their own, so
2038
+ * without this the on-disk stamp would sit at the last navigation. It is
2039
+ * coalesced to at most one write per profile per minute so a screenshot loop
2040
+ * does not become a write loop; the resulting on-disk stamp trails by at most
2041
+ * a minute, well inside the 30-minute idle window.
2042
+ */
2043
+ async touchTask(conn, task) {
2044
+ const now = Date.now();
2045
+ task.lastActionAt = now;
2046
+ const last = this.lastTouchPersist.get(task.profile) ?? 0;
2047
+ if (now - last < BrowserService.TOUCH_PERSIST_INTERVAL_MS)
2048
+ return;
2049
+ this.lastTouchPersist.set(task.profile, now);
2050
+ try {
2051
+ await this.saveTaskState(task.profile, conn.tasks);
2052
+ }
2053
+ catch {
2054
+ // Durability here is best-effort — the in-memory stamp above is
2055
+ // authoritative for the running daemon — and an unwritable runtime dir
2056
+ // must not turn a working browser action into a failure. Same guard the
2057
+ // `recordBrowserSession` call in `start` uses for the same reason.
2058
+ }
2059
+ }
1853
2060
  async findTask(taskId, profileName) {
1854
2061
  if (profileName) {
1855
2062
  const conn = this.connections.get(profileName);
@@ -1860,11 +2067,13 @@ export class BrowserService {
1860
2067
  if (!task) {
1861
2068
  throw new Error(`Task "${taskId}" not found on profile "${profileName}"`);
1862
2069
  }
2070
+ await this.touchTask(conn, task);
1863
2071
  return { conn, task, profileName };
1864
2072
  }
1865
2073
  for (const [key, conn] of this.connections) {
1866
2074
  const task = conn.tasks.get(taskId);
1867
2075
  if (task) {
2076
+ await this.touchTask(conn, task);
1868
2077
  return { conn, task, profileName: conn.profileName ?? key };
1869
2078
  }
1870
2079
  }
@@ -2028,11 +2237,19 @@ export class BrowserService {
2028
2237
  tabs,
2029
2238
  currentTabId: tabIds.length > 0 ? tabIds[tabIds.length - 1] : undefined,
2030
2239
  createdAt: task.createdAt,
2240
+ lastActionAt: task.lastActionAt ?? task.createdAt,
2031
2241
  pid: task.pid,
2032
2242
  });
2033
2243
  }
2034
2244
  else {
2035
- tasks.set(key, task);
2245
+ const loaded = task;
2246
+ // Tasks persisted before RUSH-2622 carry no lastActionAt. Normalize on
2247
+ // read so every in-memory task has one: its own createdAt is the last
2248
+ // moment we can prove the task did anything.
2249
+ if (typeof loaded.lastActionAt !== 'number') {
2250
+ loaded.lastActionAt = loaded.createdAt;
2251
+ }
2252
+ tasks.set(key, loaded);
2036
2253
  }
2037
2254
  }
2038
2255
  // Save migrated data back to disk
@@ -33,10 +33,17 @@ export declare function renderBrowserSessions(groups: ProfileArtifacts[]): strin
33
33
  export declare function resolveArtifact(groups: ProfileArtifacts[], selector: string): string | null;
34
34
  /** Open a file in the OS default app. Returns true on success. */
35
35
  export declare function openArtifact(filePath: string): boolean;
36
- /** The identity fields this module cares about from a profile's `tasks.json`. */
36
+ /** The identity fields this module cares about for one task. */
37
37
  export interface TaskIdentity {
38
38
  owner?: string;
39
39
  launchId?: string;
40
+ /**
41
+ * The agent session that drove the task. Primary identity: it is carried by
42
+ * every agent process, whereas a launch id is present on a minority of them.
43
+ * Sourced from the durable `browser_sessions` row, which survives task stop —
44
+ * `tasks.json` never records it beyond the task's own lifetime (RUSH-2549).
45
+ */
46
+ sessionId?: string;
40
47
  }
41
48
  /**
42
49
  * Read a profile's `tasks.json` for the identity of its CURRENTLY LIVE tasks,
@@ -48,6 +55,17 @@ export interface TaskIdentity {
48
55
  * browser daemon is running.
49
56
  */
50
57
  export declare function loadTaskIdentities(profile: string): Map<string, TaskIdentity>;
58
+ /**
59
+ * Identity for one profile's tasks, durable copy first.
60
+ *
61
+ * `browser_sessions` is the source of truth: it is written at task start and
62
+ * never deleted, so it answers for tasks that have already stopped — the case
63
+ * `tasks.json` structurally cannot, since `saveTaskState` rewrites that file
64
+ * from the LIVE task map. The live file is still merged on top for a task that
65
+ * is running right now, so a task started by an older CLI (no DB row yet) keeps
66
+ * whatever identity it does have rather than regressing to nothing.
67
+ */
68
+ export declare function loadDurableTaskIdentities(profile: string): Map<string, TaskIdentity>;
51
69
  /** launchId -> indexed session id, built once from every source that records
52
70
  * this join, so a task-heavy profile resolves without re-scanning per task. */
53
71
  export interface LaunchSessionIndex {
@@ -85,6 +103,8 @@ export interface BrowserSessionRow {
85
103
  task?: string;
86
104
  owner?: string;
87
105
  launchId?: string;
106
+ /** The agent session that drove the task, when one was recorded. */
107
+ sessionId?: string;
88
108
  linkStatus: BrowserSessionLinkStatus;
89
109
  linkedSession?: SessionMeta;
90
110
  /** Newest first. */
@@ -99,7 +119,7 @@ export interface BrowserSessionRow {
99
119
  * or session-index dependency and is unit-testable with synthetic data (see
100
120
  * {@link buildBrowserSessionRows} for the impure disk/index-reading caller).
101
121
  */
102
- export declare function groupIntoRows(groups: ProfileArtifacts[], taskIdentities: Map<string, Map<string, TaskIdentity>>, resolveLaunch?: (launchId: string) => SessionMeta | null): BrowserSessionRow[];
122
+ export declare function groupIntoRows(groups: ProfileArtifacts[], taskIdentities: Map<string, Map<string, TaskIdentity>>, resolveLaunch?: (launchId: string) => SessionMeta | null, resolveSession?: (sessionId: string) => SessionMeta | null): BrowserSessionRow[];
103
123
  /** Build task-first rows for one profile (or every profile with captures),
104
124
  * reading `tasks.json` and resolving launchIds against the live indexes.
105
125
  * The interactive picker's data source. */
@@ -19,7 +19,7 @@ import * as path from 'path';
19
19
  import { spawnSync } from 'child_process';
20
20
  import { getBrowserRuntimeDir, getProfileRuntimeDir } from './profiles.js';
21
21
  import { formatRelativeTime } from '../session/relative-time.js';
22
- import { getSessionById } from '../session/db.js';
22
+ import { getSessionById, listBrowserSessionRecords, pruneToolSessions } from '../session/db.js';
23
23
  import { listPidSessionEntries } from '../session/pid-registry.js';
24
24
  import { loadHookSessionIndex } from '../session/hook-sessions.js';
25
25
  import { readRecentActivity } from '../activity.js';
@@ -214,10 +214,40 @@ export function loadTaskIdentities(profile) {
214
214
  out.set(name, {
215
215
  owner: typeof t.owner === 'string' ? t.owner : undefined,
216
216
  launchId: typeof t.launchId === 'string' ? t.launchId : undefined,
217
+ sessionId: typeof t.sessionId === 'string' ? t.sessionId : undefined,
217
218
  });
218
219
  }
219
220
  return out;
220
221
  }
222
+ /**
223
+ * Identity for one profile's tasks, durable copy first.
224
+ *
225
+ * `browser_sessions` is the source of truth: it is written at task start and
226
+ * never deleted, so it answers for tasks that have already stopped — the case
227
+ * `tasks.json` structurally cannot, since `saveTaskState` rewrites that file
228
+ * from the LIVE task map. The live file is still merged on top for a task that
229
+ * is running right now, so a task started by an older CLI (no DB row yet) keeps
230
+ * whatever identity it does have rather than regressing to nothing.
231
+ */
232
+ export function loadDurableTaskIdentities(profile) {
233
+ const merged = new Map();
234
+ for (const record of listBrowserSessionRecords(profile)) {
235
+ merged.set(record.task, {
236
+ owner: record.actor,
237
+ launchId: record.launchId,
238
+ sessionId: record.sessionId,
239
+ });
240
+ }
241
+ for (const [task, live] of loadTaskIdentities(profile)) {
242
+ const durable = merged.get(task);
243
+ merged.set(task, {
244
+ owner: live.owner ?? durable?.owner,
245
+ launchId: live.launchId ?? durable?.launchId,
246
+ sessionId: live.sessionId ?? durable?.sessionId,
247
+ });
248
+ }
249
+ return merged;
250
+ }
221
251
  /**
222
252
  * Build the launchId -> sessionId index from the same three sources
223
253
  * `feed-post.ts` `resolvePostIdentity` already uses for this exact problem —
@@ -260,7 +290,7 @@ export function resolveLaunchSession(index, launchId) {
260
290
  * or session-index dependency and is unit-testable with synthetic data (see
261
291
  * {@link buildBrowserSessionRows} for the impure disk/index-reading caller).
262
292
  */
263
- export function groupIntoRows(groups, taskIdentities, resolveLaunch) {
293
+ export function groupIntoRows(groups, taskIdentities, resolveLaunch, resolveSession) {
264
294
  const rows = [];
265
295
  for (const g of groups) {
266
296
  const identities = taskIdentities.get(g.profile) ?? new Map();
@@ -278,14 +308,24 @@ export function groupIntoRows(groups, taskIdentities, resolveLaunch) {
278
308
  for (const [task, artifacts] of byTask) {
279
309
  artifacts.sort((a, b) => b.mtimeMs - a.mtimeMs);
280
310
  const identity = identities.get(task);
281
- const linkedSession = identity?.launchId ? resolveLaunch?.(identity.launchId) ?? null : null;
311
+ // Session id first: it is the direct answer and the one agents actually
312
+ // carry. The launchId join stays as the fallback for a task that recorded
313
+ // only that (an older CLI, or a run whose session id was unset).
314
+ let linkedSession = null;
315
+ if (identity?.sessionId && resolveSession)
316
+ linkedSession = resolveSession(identity.sessionId);
317
+ if (!linkedSession && identity?.launchId && resolveLaunch) {
318
+ linkedSession = resolveLaunch(identity.launchId);
319
+ }
320
+ const hasIdentityKey = !!(identity?.sessionId || identity?.launchId);
282
321
  rows.push({
283
322
  kind: 'task',
284
323
  profile: g.profile,
285
324
  task,
286
325
  owner: identity?.owner,
287
326
  launchId: identity?.launchId,
288
- linkStatus: linkedSession ? 'linked' : identity?.launchId ? 'unresolved' : 'unlinked',
327
+ sessionId: identity?.sessionId,
328
+ linkStatus: linkedSession ? 'linked' : hasIdentityKey ? 'unresolved' : 'unlinked',
289
329
  linkedSession: linkedSession ?? undefined,
290
330
  artifacts,
291
331
  counts: countByKind(artifacts),
@@ -311,10 +351,18 @@ export function groupIntoRows(groups, taskIdentities, resolveLaunch) {
311
351
  * reading `tasks.json` and resolving launchIds against the live indexes.
312
352
  * The interactive picker's data source. */
313
353
  export function buildBrowserSessionRows(profile) {
354
+ // Retention runs from BOTH listing paths, not just the computer one: a box
355
+ // that uses `agents browser` and never runs `agents sessions --computer`
356
+ // would otherwise never sweep either table. Best-effort — a read-only DB
357
+ // must not break a listing.
358
+ try {
359
+ pruneToolSessions();
360
+ }
361
+ catch { /* listing must not fail on retention */ }
314
362
  const groups = listBrowserSessions(profile);
315
- const taskIdentities = new Map(groups.map((g) => [g.profile, loadTaskIdentities(g.profile)]));
363
+ const taskIdentities = new Map(groups.map((g) => [g.profile, loadDurableTaskIdentities(g.profile)]));
316
364
  const index = buildLaunchSessionIndex();
317
- return groupIntoRows(groups, taskIdentities, (launchId) => resolveLaunchSession(index, launchId));
365
+ return groupIntoRows(groups, taskIdentities, (launchId) => resolveLaunchSession(index, launchId), (sessionId) => getSessionById(sessionId));
318
366
  }
319
367
  /**
320
368
  * Search predicate for the interactive picker: task name, profile, the linked
@@ -5,6 +5,8 @@ export interface BrowserIPCStreamOptions {
5
5
  task?: string;
6
6
  actor: string;
7
7
  launchId?: string;
8
+ /** Calling agent session, forwarded onto `start` requests (RUSH-2549). */
9
+ sessionId?: string;
8
10
  autoStartDaemon?: boolean;
9
11
  }
10
12
  /**
@@ -53,6 +53,7 @@ export async function runBrowserIPCStream(options) {
53
53
  taskName: request.taskName ?? defaultTask,
54
54
  actor: request.actor ?? options.actor,
55
55
  launchId: request.launchId ?? options.launchId,
56
+ sessionId: request.sessionId ?? options.sessionId,
56
57
  };
57
58
  }
58
59
  else if (!request.task && defaultTask) {