@phnx-labs/agents-cli 1.22.34 → 1.22.37

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 (493) hide show
  1. package/CHANGELOG.md +672 -2
  2. package/README.md +82 -35
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.d.ts +15 -0
  5. package/dist/bootstrap.js +1211 -0
  6. package/dist/commands/accounts.d.ts +4 -11
  7. package/dist/commands/accounts.js +159 -27
  8. package/dist/commands/attach.js +17 -4
  9. package/dist/commands/audit.d.ts +8 -8
  10. package/dist/commands/audit.js +57 -47
  11. package/dist/commands/browser.js +15 -11
  12. package/dist/commands/cloud.js +1 -1
  13. package/dist/commands/commands.js +0 -11
  14. package/dist/commands/computer-actions.js +33 -0
  15. package/dist/commands/computer-sessions-picker.d.ts +21 -0
  16. package/dist/commands/computer-sessions-picker.js +141 -0
  17. package/dist/commands/computer.d.ts +16 -0
  18. package/dist/commands/computer.js +37 -0
  19. package/dist/commands/config.d.ts +17 -0
  20. package/dist/commands/config.js +401 -0
  21. package/dist/commands/cp.d.ts +84 -0
  22. package/dist/commands/cp.js +231 -0
  23. package/dist/commands/daemon.js +243 -14
  24. package/dist/commands/detach-core.d.ts +1 -1
  25. package/dist/commands/detach-core.js +12 -2
  26. package/dist/commands/detach.js +1 -1
  27. package/dist/commands/doctor.js +64 -5
  28. package/dist/commands/events.d.ts +24 -12
  29. package/dist/commands/events.js +144 -28
  30. package/dist/commands/exec.js +85 -41
  31. package/dist/commands/feed.d.ts +1 -1
  32. package/dist/commands/feed.js +16 -8
  33. package/dist/commands/focus.d.ts +33 -1
  34. package/dist/commands/focus.js +177 -17
  35. package/dist/commands/funnel.d.ts +1 -1
  36. package/dist/commands/go.js +9 -8
  37. package/dist/commands/harness-wizard.d.ts +1 -0
  38. package/dist/commands/harness-wizard.js +18 -20
  39. package/dist/commands/harness.d.ts +2 -0
  40. package/dist/commands/harness.js +59 -26
  41. package/dist/commands/hooks.js +0 -11
  42. package/dist/commands/inspect.d.ts +94 -1
  43. package/dist/commands/inspect.js +866 -40
  44. package/dist/commands/lease.d.ts +4 -4
  45. package/dist/commands/lease.js +6 -6
  46. package/dist/commands/login.js +0 -13
  47. package/dist/commands/logs.d.ts +2 -2
  48. package/dist/commands/logs.js +2 -2
  49. package/dist/commands/mcp.js +7 -2
  50. package/dist/commands/memory.js +7 -2
  51. package/dist/commands/message.d.ts +1 -1
  52. package/dist/commands/message.js +37 -6
  53. package/dist/commands/models.js +4 -0
  54. package/dist/commands/monitors.d.ts +0 -1
  55. package/dist/commands/monitors.js +199 -16
  56. package/dist/commands/open.d.ts +18 -0
  57. package/dist/commands/open.js +57 -0
  58. package/dist/commands/packages.js +2 -107
  59. package/dist/commands/plugins.js +6 -3
  60. package/dist/commands/profiles.d.ts +1 -0
  61. package/dist/commands/profiles.js +16 -160
  62. package/dist/commands/projects.d.ts +28 -0
  63. package/dist/commands/projects.js +125 -5
  64. package/dist/commands/reconnect.js +9 -3
  65. package/dist/commands/repo.js +42 -1
  66. package/dist/commands/resource-view.d.ts +17 -0
  67. package/dist/commands/resource-view.js +36 -14
  68. package/dist/commands/resume.js +9 -2
  69. package/dist/commands/routines.d.ts +0 -2
  70. package/dist/commands/routines.js +473 -289
  71. package/dist/commands/secrets.js +52 -9
  72. package/dist/commands/sessions-export.d.ts +47 -0
  73. package/dist/commands/sessions-export.js +116 -6
  74. package/dist/commands/sessions-import.d.ts +17 -0
  75. package/dist/commands/sessions-import.js +114 -6
  76. package/dist/commands/sessions-migrate.d.ts +27 -0
  77. package/dist/commands/sessions-migrate.js +50 -9
  78. package/dist/commands/sessions-picker.js +23 -4
  79. package/dist/commands/sessions-resume.d.ts +5 -3
  80. package/dist/commands/sessions-resume.js +30 -8
  81. package/dist/commands/sessions-watch.d.ts +2 -0
  82. package/dist/commands/sessions-watch.js +53 -0
  83. package/dist/commands/sessions.d.ts +66 -3
  84. package/dist/commands/sessions.js +298 -47
  85. package/dist/commands/set.d.ts +1 -1
  86. package/dist/commands/set.js +2 -2
  87. package/dist/commands/setup-browser.js +2 -2
  88. package/dist/commands/setup-mine.js +0 -1
  89. package/dist/commands/setup-preferences.js +1 -1
  90. package/dist/commands/setup-share.js +34 -4
  91. package/dist/commands/setup.d.ts +5 -0
  92. package/dist/commands/setup.js +26 -0
  93. package/dist/commands/share.d.ts +85 -0
  94. package/dist/commands/share.js +379 -8
  95. package/dist/commands/skills.js +0 -11
  96. package/dist/commands/snapshot.js +1 -1
  97. package/dist/commands/ssh.d.ts +18 -0
  98. package/dist/commands/ssh.js +549 -210
  99. package/dist/commands/sync.d.ts +5 -0
  100. package/dist/commands/sync.js +212 -32
  101. package/dist/commands/teams.d.ts +13 -0
  102. package/dist/commands/teams.js +187 -30
  103. package/dist/commands/tickets.d.ts +2 -0
  104. package/dist/commands/tickets.js +43 -0
  105. package/dist/commands/update.js +2 -4
  106. package/dist/commands/usage.js +5 -3
  107. package/dist/commands/versions.js +11 -5
  108. package/dist/commands/view.js +15 -7
  109. package/dist/commands/webhook.d.ts +2 -2
  110. package/dist/commands/webhook.js +7 -7
  111. package/dist/index.d.ts +18 -2
  112. package/dist/index.js +53 -1264
  113. package/dist/lib/account-catalog.d.ts +18 -0
  114. package/dist/lib/account-catalog.js +38 -0
  115. package/dist/lib/account-provider-registry.d.ts +18 -0
  116. package/dist/lib/account-provider-registry.js +59 -0
  117. package/dist/lib/account-registry.d.ts +40 -0
  118. package/dist/lib/account-registry.js +239 -0
  119. package/dist/lib/account-schema.d.ts +44 -0
  120. package/dist/lib/account-schema.js +91 -0
  121. package/dist/lib/account-state-service.d.ts +21 -0
  122. package/dist/lib/account-state-service.js +60 -0
  123. package/dist/lib/activity.d.ts +1 -1
  124. package/dist/lib/activity.js +8 -1
  125. package/dist/lib/add-dir.d.ts +80 -0
  126. package/dist/lib/add-dir.js +241 -0
  127. package/dist/lib/agent-cli-commands.d.ts +11 -0
  128. package/dist/lib/agent-cli-commands.js +30 -0
  129. package/dist/lib/agents.js +44 -36
  130. package/dist/lib/answer-router.js +1 -1
  131. package/dist/lib/audit/log.d.ts +10 -27
  132. package/dist/lib/audit/log.js +20 -33
  133. package/dist/lib/auth-health.d.ts +9 -2
  134. package/dist/lib/auth-health.js +42 -9
  135. package/dist/lib/auto-pull-worker.js +7 -3
  136. package/dist/lib/auto-pull.d.ts +44 -3
  137. package/dist/lib/auto-pull.js +88 -5
  138. package/dist/lib/binary-shadow.d.ts +28 -0
  139. package/dist/lib/binary-shadow.js +121 -0
  140. package/dist/lib/brand.js +9 -3
  141. package/dist/lib/browser/ipc.d.ts +30 -0
  142. package/dist/lib/browser/ipc.js +81 -4
  143. package/dist/lib/browser/profiles.d.ts +3 -2
  144. package/dist/lib/browser/profiles.js +65 -22
  145. package/dist/lib/browser/service.d.ts +4 -0
  146. package/dist/lib/browser/service.js +30 -3
  147. package/dist/lib/browser/sessions-list.d.ts +22 -2
  148. package/dist/lib/browser/sessions-list.js +54 -6
  149. package/dist/lib/browser/stream.d.ts +2 -0
  150. package/dist/lib/browser/stream.js +1 -0
  151. package/dist/lib/browser/types.d.ts +19 -0
  152. package/dist/lib/byok-usage.d.ts +9 -2
  153. package/dist/lib/byok-usage.js +133 -31
  154. package/dist/lib/cli-entry.d.ts +11 -0
  155. package/dist/lib/cli-entry.js +46 -0
  156. package/dist/lib/cloud/host.d.ts +1 -1
  157. package/dist/lib/cloud/host.js +2 -2
  158. package/dist/lib/cloud/registry.js +1 -1
  159. package/dist/lib/cloud/types.d.ts +1 -1
  160. package/dist/lib/codex-policy.d.ts +9 -1
  161. package/dist/lib/codex-policy.js +17 -2
  162. package/dist/lib/commands.d.ts +2 -0
  163. package/dist/lib/commands.js +2 -0
  164. package/dist/lib/computer/sessions-list.d.ts +150 -0
  165. package/dist/lib/computer/sessions-list.js +363 -0
  166. package/dist/lib/config-keys.d.ts +86 -0
  167. package/dist/lib/config-keys.js +193 -0
  168. package/dist/lib/config-machine-keys.d.ts +12 -0
  169. package/dist/lib/config-machine-keys.js +17 -0
  170. package/dist/lib/config-transfer.js +3 -4
  171. package/dist/lib/crabbox/cli.d.ts +2 -2
  172. package/dist/lib/crabbox/cli.js +3 -3
  173. package/dist/lib/daemon-health.d.ts +22 -0
  174. package/dist/lib/daemon-health.js +33 -3
  175. package/dist/lib/daemon-services.d.ts +50 -0
  176. package/dist/lib/daemon-services.js +147 -0
  177. package/dist/lib/daemon-ticks.d.ts +35 -66
  178. package/dist/lib/daemon-ticks.js +77 -159
  179. package/dist/lib/daemon.d.ts +72 -4
  180. package/dist/lib/daemon.js +792 -202
  181. package/dist/lib/deeplink/register.d.ts +49 -0
  182. package/dist/lib/deeplink/register.js +253 -0
  183. package/dist/lib/deeplink/url.d.ts +34 -0
  184. package/dist/lib/deeplink/url.js +78 -0
  185. package/dist/lib/device-config.d.ts +124 -29
  186. package/dist/lib/device-config.js +346 -141
  187. package/dist/lib/devices/config-migration.d.ts +42 -0
  188. package/dist/lib/devices/config-migration.js +203 -0
  189. package/dist/lib/devices/connect.js +14 -7
  190. package/dist/lib/devices/doctor-findings.d.ts +4 -1
  191. package/dist/lib/devices/doctor-findings.js +20 -1
  192. package/dist/lib/devices/harness-inventory.d.ts +11 -2
  193. package/dist/lib/devices/harness-inventory.js +0 -0
  194. package/dist/lib/devices/health.d.ts +1 -1
  195. package/dist/lib/devices/health.js +1 -1
  196. package/dist/lib/devices/pending.d.ts +22 -1
  197. package/dist/lib/devices/pending.js +67 -2
  198. package/dist/lib/devices/registry.d.ts +0 -25
  199. package/dist/lib/devices/registry.js +2 -83
  200. package/dist/lib/devices/resolve-profile.d.ts +20 -0
  201. package/dist/lib/devices/resolve-profile.js +44 -0
  202. package/dist/lib/devices/rollout-verify.d.ts +101 -0
  203. package/dist/lib/devices/rollout-verify.js +193 -0
  204. package/dist/lib/devices/ssh-config.js +10 -6
  205. package/dist/lib/event-families.d.ts +28 -0
  206. package/dist/lib/event-families.js +159 -0
  207. package/dist/lib/event-stream.d.ts +9 -1
  208. package/dist/lib/event-stream.js +26 -9
  209. package/dist/lib/events.d.ts +9 -1
  210. package/dist/lib/events.js +21 -6
  211. package/dist/lib/exec.d.ts +30 -0
  212. package/dist/lib/exec.js +220 -122
  213. package/dist/lib/feed-broadcast.d.ts +25 -0
  214. package/dist/lib/feed-broadcast.js +33 -0
  215. package/dist/lib/feed.d.ts +1 -1
  216. package/dist/lib/feed.js +12 -2
  217. package/dist/lib/fleet/capture.js +15 -0
  218. package/dist/lib/fleet/manifest.js +9 -0
  219. package/dist/lib/fleet/types.d.ts +9 -0
  220. package/dist/lib/fleet-status.js +10 -8
  221. package/dist/lib/git.d.ts +20 -6
  222. package/dist/lib/git.js +121 -17
  223. package/dist/lib/hosts/dispatch.d.ts +8 -3
  224. package/dist/lib/hosts/dispatch.js +29 -3
  225. package/dist/lib/hosts/logs.d.ts +2 -2
  226. package/dist/lib/hosts/logs.js +2 -2
  227. package/dist/lib/hosts/option.js +1 -1
  228. package/dist/lib/hosts/passthrough.d.ts +2 -3
  229. package/dist/lib/hosts/passthrough.js +10 -24
  230. package/dist/lib/hosts/progress.js +2 -2
  231. package/dist/lib/hosts/providers/devices.d.ts +3 -3
  232. package/dist/lib/hosts/providers/devices.js +18 -12
  233. package/dist/lib/hosts/ready.d.ts +5 -1
  234. package/dist/lib/hosts/ready.js +9 -3
  235. package/dist/lib/hosts/reconcile.d.ts +2 -2
  236. package/dist/lib/hosts/reconcile.js +2 -2
  237. package/dist/lib/hosts/registry.js +14 -9
  238. package/dist/lib/hosts/remote-cmd.js +1 -1
  239. package/dist/lib/hosts/remote-os.d.ts +9 -6
  240. package/dist/lib/hosts/remote-os.js +13 -6
  241. package/dist/lib/hosts/routing-flag.d.ts +23 -0
  242. package/dist/lib/hosts/routing-flag.js +41 -0
  243. package/dist/lib/hosts/run-target.js +1 -1
  244. package/dist/lib/hosts/session-index.js +1 -1
  245. package/dist/lib/hosts/tasks.d.ts +6 -6
  246. package/dist/lib/hosts/tasks.js +4 -4
  247. package/dist/lib/hosts/types.js +1 -2
  248. package/dist/lib/installations/resolve.js +2 -8
  249. package/dist/lib/isolation-boundary-report.js +0 -1
  250. package/dist/lib/linear-cache.d.ts +7 -0
  251. package/dist/lib/linear-cache.js +31 -4
  252. package/dist/lib/linear-project-counts.d.ts +1 -1
  253. package/dist/lib/linear-project-counts.js +2 -3
  254. package/dist/lib/linear-projects.js +1 -1
  255. package/dist/lib/loop.d.ts +2 -2
  256. package/dist/lib/loop.js +1 -1
  257. package/dist/lib/mailbox-target.d.ts +1 -1
  258. package/dist/lib/mailbox-target.js +1 -1
  259. package/dist/lib/manifest.js +7 -4
  260. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  261. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  262. package/dist/lib/menubar/snapshot.d.ts +3 -2
  263. package/dist/lib/menubar/snapshot.js +6 -2
  264. package/dist/lib/migrate-fold.d.ts +33 -0
  265. package/dist/lib/migrate-fold.js +130 -0
  266. package/dist/lib/migrate.d.ts +34 -34
  267. package/dist/lib/migrate.js +106 -128
  268. package/dist/lib/model-tier-overrides.d.ts +1 -1
  269. package/dist/lib/model-tier-overrides.js +15 -2
  270. package/dist/lib/monitors/config.d.ts +17 -3
  271. package/dist/lib/monitors/config.js +52 -18
  272. package/dist/lib/monitors/engine.d.ts +32 -1
  273. package/dist/lib/monitors/engine.js +94 -11
  274. package/dist/lib/monitors/fingerprint.d.ts +46 -0
  275. package/dist/lib/monitors/fingerprint.js +100 -0
  276. package/dist/lib/monitors/remote.d.ts +47 -0
  277. package/dist/lib/monitors/remote.js +81 -0
  278. package/dist/lib/monitors/state.d.ts +39 -0
  279. package/dist/lib/monitors/state.js +56 -0
  280. package/dist/lib/onepassword.d.ts +17 -0
  281. package/dist/lib/onepassword.js +52 -22
  282. package/dist/lib/open-url.js +1 -1
  283. package/dist/lib/picker.d.ts +14 -2
  284. package/dist/lib/picker.js +35 -13
  285. package/dist/lib/placement.d.ts +1 -1
  286. package/dist/lib/placement.js +1 -1
  287. package/dist/lib/pr-land-detach.d.ts +32 -0
  288. package/dist/lib/pr-land-detach.js +79 -0
  289. package/dist/lib/profiles-presets.js +13 -0
  290. package/dist/lib/profiles.d.ts +2 -0
  291. package/dist/lib/profiles.js +44 -1
  292. package/dist/lib/project-focus.d.ts +1 -1
  293. package/dist/lib/project-focus.js +1 -1
  294. package/dist/lib/project-key.d.ts +17 -0
  295. package/dist/lib/project-key.js +26 -0
  296. package/dist/lib/project-probe.d.ts +4 -1
  297. package/dist/lib/project-probe.js +5 -4
  298. package/dist/lib/project-resources.js +2 -18
  299. package/dist/lib/project-root.d.ts +16 -0
  300. package/dist/lib/project-root.js +20 -2
  301. package/dist/lib/projects.d.ts +20 -1
  302. package/dist/lib/projects.js +78 -1
  303. package/dist/lib/refresh-coordinator.d.ts +24 -0
  304. package/dist/lib/refresh-coordinator.js +52 -0
  305. package/dist/lib/registry.d.ts +2 -2
  306. package/dist/lib/registry.js +1 -1
  307. package/dist/lib/resource-aliases.d.ts +13 -0
  308. package/dist/lib/resource-aliases.js +26 -0
  309. package/dist/lib/resources/types.d.ts +1 -1
  310. package/dist/lib/resources.d.ts +16 -0
  311. package/dist/lib/resources.js +66 -10
  312. package/dist/lib/rotate.d.ts +3 -4
  313. package/dist/lib/rotate.js +4 -5
  314. package/dist/lib/routine-activation.d.ts +22 -2
  315. package/dist/lib/routine-activation.js +63 -16
  316. package/dist/lib/routine-context.d.ts +9 -2
  317. package/dist/lib/routine-context.js +21 -6
  318. package/dist/lib/routines-project.d.ts +58 -48
  319. package/dist/lib/routines-project.js +190 -159
  320. package/dist/lib/routines.d.ts +41 -6
  321. package/dist/lib/routines.js +92 -34
  322. package/dist/lib/run-defaults.d.ts +5 -0
  323. package/dist/lib/run-defaults.js +11 -1
  324. package/dist/lib/runner.d.ts +10 -8
  325. package/dist/lib/runner.js +122 -39
  326. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  327. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  328. package/dist/lib/secrets/agent.d.ts +10 -3
  329. package/dist/lib/secrets/agent.js +43 -8
  330. package/dist/lib/secrets/bundles.d.ts +1 -0
  331. package/dist/lib/secrets/bundles.js +92 -80
  332. package/dist/lib/secrets/index.js +32 -9
  333. package/dist/lib/secrets/install-helper.d.ts +4 -4
  334. package/dist/lib/secrets/install-helper.js +4 -4
  335. package/dist/lib/secrets/push.d.ts +40 -6
  336. package/dist/lib/secrets/push.js +58 -11
  337. package/dist/lib/secrets/read-backoff.d.ts +1 -1
  338. package/dist/lib/secrets/read-backoff.js +1 -1
  339. package/dist/lib/secrets/remote.d.ts +24 -0
  340. package/dist/lib/secrets/remote.js +54 -4
  341. package/dist/lib/self-update.d.ts +108 -0
  342. package/dist/lib/self-update.js +276 -1
  343. package/dist/lib/session/active.d.ts +89 -5
  344. package/dist/lib/session/active.js +165 -10
  345. package/dist/lib/session/actor-sidecar.d.ts +1 -1
  346. package/dist/lib/session/actor-sidecar.js +2 -1
  347. package/dist/lib/session/db.d.ts +176 -2
  348. package/dist/lib/session/db.js +484 -17
  349. package/dist/lib/session/discover.js +27 -4
  350. package/dist/lib/session/hook-sessions.js +2 -2
  351. package/dist/lib/session/host-link.d.ts +2 -2
  352. package/dist/lib/session/host-link.js +2 -2
  353. package/dist/lib/session/pid-registry.d.ts +21 -0
  354. package/dist/lib/session/pid-registry.js +57 -0
  355. package/dist/lib/session/remote-active.js +15 -1
  356. package/dist/lib/session/remote.js +2 -2
  357. package/dist/lib/session/session-cache.d.ts +14 -7
  358. package/dist/lib/session/session-cache.js +51 -9
  359. package/dist/lib/session/state.d.ts +2 -2
  360. package/dist/lib/session/state.js +1 -1
  361. package/dist/lib/session/sync/config.d.ts +10 -10
  362. package/dist/lib/session/sync/config.js +10 -10
  363. package/dist/lib/session/sync/r2.d.ts +34 -0
  364. package/dist/lib/session/sync/r2.js +123 -0
  365. package/dist/lib/session/throughput.d.ts +1 -1
  366. package/dist/lib/session/throughput.js +1 -1
  367. package/dist/lib/session/types.d.ts +23 -1
  368. package/dist/lib/session/types.js +15 -0
  369. package/dist/lib/session/viewing-in.d.ts +1 -1
  370. package/dist/lib/session/viewing-in.js +1 -1
  371. package/dist/lib/session/watch.d.ts +101 -0
  372. package/dist/lib/session/watch.js +242 -0
  373. package/dist/lib/share/config.d.ts +4 -0
  374. package/dist/lib/share/config.js +1 -0
  375. package/dist/lib/share/delete.d.ts +93 -0
  376. package/dist/lib/share/delete.js +127 -0
  377. package/dist/lib/share/provision.d.ts +38 -0
  378. package/dist/lib/share/provision.js +50 -0
  379. package/dist/lib/share/publish.d.ts +40 -1
  380. package/dist/lib/share/publish.js +116 -3
  381. package/dist/lib/share/worker-template.js +71 -6
  382. package/dist/lib/shims.d.ts +17 -3
  383. package/dist/lib/shims.js +136 -27
  384. package/dist/lib/signin-badge.js +3 -2
  385. package/dist/lib/skills.js +2 -0
  386. package/dist/lib/smart-launch.d.ts +8 -8
  387. package/dist/lib/smart-launch.js +80 -59
  388. package/dist/lib/ssh-exec.d.ts +1 -1
  389. package/dist/lib/ssh-exec.js +1 -1
  390. package/dist/lib/staleness/prune.d.ts +88 -0
  391. package/dist/lib/staleness/prune.js +69 -0
  392. package/dist/lib/staleness/registry.d.ts +1 -1
  393. package/dist/lib/staleness/writers/commands.js +34 -2
  394. package/dist/lib/staleness/writers/hooks.js +16 -0
  395. package/dist/lib/staleness/writers/kinds.d.ts +6 -0
  396. package/dist/lib/staleness/writers/skills.js +19 -0
  397. package/dist/lib/staleness/writers/subagents.d.ts +2 -3
  398. package/dist/lib/staleness/writers/subagents.js +0 -4
  399. package/dist/lib/staleness/writers/types.d.ts +32 -0
  400. package/dist/lib/startup/command-registry.d.ts +6 -12
  401. package/dist/lib/startup/command-registry.js +14 -31
  402. package/dist/lib/startup/dev-build.d.ts +10 -0
  403. package/dist/lib/startup/dev-build.js +13 -1
  404. package/dist/lib/startup/spellcheck.d.ts +18 -0
  405. package/dist/lib/startup/spellcheck.js +45 -0
  406. package/dist/lib/state.d.ts +36 -9
  407. package/dist/lib/state.js +96 -46
  408. package/dist/lib/subagents-registry.d.ts +0 -7
  409. package/dist/lib/subagents-registry.js +38 -65
  410. package/dist/lib/subagents.d.ts +0 -33
  411. package/dist/lib/subagents.js +0 -75
  412. package/dist/lib/sync-umbrella.js +1 -1
  413. package/dist/lib/teams/agents.d.ts +41 -1
  414. package/dist/lib/teams/agents.js +187 -9
  415. package/dist/lib/teams/api.d.ts +11 -1
  416. package/dist/lib/teams/api.js +11 -2
  417. package/dist/lib/teams/delivery.d.ts +41 -0
  418. package/dist/lib/teams/delivery.js +60 -0
  419. package/dist/lib/teams/registry.d.ts +14 -0
  420. package/dist/lib/teams/registry.js +51 -1
  421. package/dist/lib/teams/remoteWorktree.d.ts +19 -0
  422. package/dist/lib/teams/remoteWorktree.js +29 -0
  423. package/dist/lib/teams/scheduler.d.ts +1 -1
  424. package/dist/lib/teams/scheduler.js +1 -1
  425. package/dist/lib/teams/worktree.d.ts +24 -0
  426. package/dist/lib/teams/worktree.js +42 -0
  427. package/dist/lib/terminal/backends/vscodium-agent.d.ts +10 -1
  428. package/dist/lib/terminal/backends/vscodium-agent.js +16 -5
  429. package/dist/lib/terminal/engine.d.ts +4 -0
  430. package/dist/lib/terminal/engine.js +8 -2
  431. package/dist/lib/terminal/types.d.ts +21 -2
  432. package/dist/lib/testdata/refresh-coordinator-worker.d.ts +1 -0
  433. package/dist/lib/testdata/refresh-coordinator-worker.js +23 -0
  434. package/dist/lib/tickets/list.d.ts +53 -0
  435. package/dist/lib/tickets/list.js +153 -0
  436. package/dist/lib/tmux/orphan-reap.d.ts +320 -0
  437. package/dist/lib/tmux/orphan-reap.js +644 -0
  438. package/dist/lib/tmux/session.d.ts +68 -3
  439. package/dist/lib/tmux/session.js +95 -5
  440. package/dist/lib/triggers/handlers.d.ts +20 -0
  441. package/dist/lib/triggers/handlers.js +6 -1
  442. package/dist/lib/types.d.ts +46 -34
  443. package/dist/lib/usage-fleet.d.ts +32 -0
  444. package/dist/lib/usage-fleet.js +125 -0
  445. package/dist/lib/usage-refresh.d.ts +3 -3
  446. package/dist/lib/usage-refresh.js +19 -16
  447. package/dist/lib/usage.d.ts +51 -47
  448. package/dist/lib/usage.js +145 -153
  449. package/dist/lib/version-duplicates.js +2 -2
  450. package/dist/lib/versions.d.ts +56 -13
  451. package/dist/lib/versions.js +195 -45
  452. package/dist/lib/watchdog/log.d.ts +1 -1
  453. package/dist/lib/watchdog/log.js +3 -3
  454. package/dist/lib/watchdog/rotate.d.ts +6 -6
  455. package/dist/lib/watchdog/rotate.js +6 -6
  456. package/dist/lib/workflows.d.ts +1 -1
  457. package/dist/lib/yaml-io.d.ts +47 -0
  458. package/dist/lib/yaml-io.js +55 -0
  459. package/package.json +2 -1
  460. package/scripts/install-helper.js +2 -2
  461. package/scripts/postinstall.js +23 -20
  462. package/dist/commands/defaults.d.ts +0 -7
  463. package/dist/commands/defaults.js +0 -107
  464. package/dist/commands/export.d.ts +0 -11
  465. package/dist/commands/export.js +0 -215
  466. package/dist/commands/helper.d.ts +0 -12
  467. package/dist/commands/helper.js +0 -87
  468. package/dist/commands/hosts.d.ts +0 -11
  469. package/dist/commands/hosts.js +0 -330
  470. package/dist/commands/lock.d.ts +0 -12
  471. package/dist/commands/lock.js +0 -70
  472. package/dist/commands/pull.d.ts +0 -17
  473. package/dist/commands/pull.js +0 -39
  474. package/dist/commands/push.d.ts +0 -14
  475. package/dist/commands/push.js +0 -30
  476. package/dist/commands/wallet.d.ts +0 -20
  477. package/dist/commands/wallet.js +0 -216
  478. package/dist/commands/worktree.d.ts +0 -19
  479. package/dist/commands/worktree.js +0 -272
  480. package/dist/lib/account-labels.d.ts +0 -24
  481. package/dist/lib/account-labels.js +0 -72
  482. package/dist/lib/auto-dispatch-linear.d.ts +0 -18
  483. package/dist/lib/auto-dispatch-linear.js +0 -107
  484. package/dist/lib/auto-dispatch-provider.d.ts +0 -10
  485. package/dist/lib/auto-dispatch-provider.js +0 -30
  486. package/dist/lib/auto-dispatch.d.ts +0 -93
  487. package/dist/lib/auto-dispatch.js +0 -128
  488. package/dist/lib/export.d.ts +0 -72
  489. package/dist/lib/export.js +0 -269
  490. package/dist/lib/lock.d.ts +0 -93
  491. package/dist/lib/lock.js +0 -207
  492. package/dist/lib/wallet/index.d.ts +0 -78
  493. package/dist/lib/wallet/index.js +0 -253
@@ -0,0 +1,644 @@
1
+ /**
2
+ * Reap the helper processes an exited agent leaves behind (RUSH-2521).
3
+ *
4
+ * ## The leak
5
+ *
6
+ * An interactive agent runs as the leaf of a detached tmux pane
7
+ * (`runInTmux` → `createSession`, [`../exec.ts`](../exec.ts)). Anything the
8
+ * harness spawns — MCP servers, background daemons, `exec` helper trees — is a
9
+ * child of that pane. When the agent exits, the kernel SIGHUPs the pane's
10
+ * FOREGROUND process group only. A helper that put itself in its own process
11
+ * group, or that ignores SIGHUP, survives: it reparents to init and keeps its
12
+ * memory forever. Measured on the fleet: one pane holding 2.5 GB of Claude Code
13
+ * background daemons 22 days after its session ended, and 34 orphaned
14
+ * `cgraph-mcp --daemon` processes on a single worker.
15
+ *
16
+ * ## Why attribution is by ENVIRONMENT, not by pid ancestry / tty / pgid
17
+ *
18
+ * Every obvious OS handle is destroyed by exactly the event we are detecting:
19
+ *
20
+ * - **ppid ancestry** — an orphan is reparented to init the instant its parent
21
+ * dies, so the chain back to the pane is gone.
22
+ * - **controlling terminal** — POSIX disassociates the controlling terminal from
23
+ * every process in a session when the session leader exits, so the orphan's
24
+ * `tty` reads `?`. Verified on Linux: an orphan under a dead pane leader shows
25
+ * `tty=?` while its own pane still reports `pane_tty=/dev/pts/6`.
26
+ * - **process group** — the helpers that survive are precisely the ones that
27
+ * left the pane's process group (that is why they survived the SIGHUP).
28
+ * - **POSIX session id** — durable and correct, but not portable: `ps -o sid=`
29
+ * exists on Linux and does not exist on macOS (`ps: sid: keyword not found`),
30
+ * `ps -o sess=` reports 0 there, and `pgrep -s` is Linux-only.
31
+ *
32
+ * What DOES survive all of it is the process environment. `runInTmux` stamps
33
+ * `AGENT_TMUX_SESSION_NAME` into the pane env ([`../exec.ts`](../exec.ts)), and
34
+ * every descendant inherits it — including a reparented one. So a process
35
+ * carrying `AGENT_TMUX_SESSION_NAME=<name>` is attributable to that tmux session
36
+ * with no heuristics at all, and tmux itself is the liveness oracle for whether
37
+ * that session's agent is still running.
38
+ *
39
+ * ## The second tier: harness daemons that scrub the environment
40
+ *
41
+ * Claude Code's background pool (`claude … daemon run`, which owns the
42
+ * `bg-pty-host` / `bg-spare` processes) starts with a clean environment, so tier
43
+ * one cannot see it. It does, however, declare its owner in its own argv:
44
+ * `daemon run --origin transient --spawned-by {"label":"claude",…,"pid":3834601}`.
45
+ * A pool whose declared spawner pid is dead is unowned, so it and its descendants
46
+ * are reaped. That rule lives in {@link DETACHED_HELPER_RULES} rather than an
47
+ * inline `if (agent === 'claude')`, so another harness that detaches helpers the
48
+ * same way is one table entry.
49
+ *
50
+ * ## Safety
51
+ *
52
+ * Nothing is killed without positive proof its owner is gone:
53
+ *
54
+ * - a tmux session that still exists AND whose agent pane process is alive
55
+ * protects every process it owns;
56
+ * - a tmux session with a client attached protects every process it owns,
57
+ * whatever the pane state;
58
+ * - the reaping process itself, its ancestors, pid 1, and every long-lived
59
+ * agents-cli service ({@link isProtectedAgentsService}) are never candidates.
60
+ * - an UNRELIABLE read of tmux's session state (the query threw, timed out, or
61
+ * tmux itself never answered) disables tier 1 for that sweep entirely — an
62
+ * absent map entry proves a session is gone only when the map is known-good.
63
+ * Distinguishing "tmux answered: no such session" (a completed, if nonzero,
64
+ * exit — genuinely nothing there) from "tmux did not answer" (a thrown
65
+ * error/timeout — unknown) is exactly this: a completed process is a real
66
+ * answer, a rejected promise is not one at all.
67
+ * - tier 2 is anchored on the actual claude EXECUTABLE (argv[0]'s basename),
68
+ * never a substring match anywhere in the command line, and excludes any
69
+ * process still structurally part of a LIVE pane leaf's process tree right
70
+ * now (tmux's own `#{pane_pid}`, expanded to its current descendants —
71
+ * independent of whether any of those processes' environment markers were
72
+ * ever readable) or that carries a marker whose own session is
73
+ * live/attached — a live interactive agent, OR a subprocess it spawns
74
+ * (e.g. its own Bash tool invoking `claude --print "…"` as a sub-task),
75
+ * whose argv happens to quote this file's docblock (it contains the exact
76
+ * `daemon run --spawned-by {"pid":...}` shape) must never become a kill
77
+ * seed, and that has to hold even where tier 1's marker is dead (macOS, or
78
+ * any `claude` invocation started outside an agents-cli pane). Two review
79
+ * rounds found this: first that marker-only exclusion left the whole
80
+ * macOS/bare-invocation case open (round 2, closed by the pane-pid check),
81
+ * then that the pane-pid check alone only covered the pane LEAF itself, not
82
+ * its own live children (round 3, closed by expanding the check to the
83
+ * leaf's current descendant set via {@link descendantsOf}). A process that
84
+ * has genuinely reparented away — a real detached daemon, no longer chained
85
+ * to any live leaf by ppid — is deliberately NOT in that set and remains
86
+ * reapable, whatever unrelated live agent happens to also be running.
87
+ *
88
+ * ## Known platform gap — tier 1 is Linux-only today
89
+ *
90
+ * Tier 1's env-marker attribution requires reading another process's
91
+ * environment. `/proc/<pid>/environ` makes that a plain file read on Linux.
92
+ * macOS has no `/proc`; `ps -E` was the fallback, but on modern macOS (verified
93
+ * live on mac-mini 15.6, macOS Sequoia) `ps -E` no longer prints another
94
+ * process's environment at all — only the CALLING process's own env shows up.
95
+ * `readAgentProcesses()` therefore returns every macOS process with
96
+ * `tmuxSession: undefined`, and tier 1's `if (!p.tmuxSession) continue` skips
97
+ * all of them. This is SAFE (tier 1 silently no-ops rather than misattributing
98
+ * anything) but not effective: on macOS, only tier 2 — the detached-helper
99
+ * registry — can reap anything today. A macOS fix needs a different channel
100
+ * entirely (env is inherently unreadable cross-process there without a native
101
+ * helper); it is not a parsing bug {@link parseTmuxSessionMarker} can fix.
102
+ */
103
+ import { execFile } from 'child_process';
104
+ import * as fs from 'fs';
105
+ import { promisify } from 'util';
106
+ const execFileAsync = promisify(execFile);
107
+ /** Ceiling on the `ps` snapshot so a wedged `ps` can never stall the daemon tick. */
108
+ const PS_TIMEOUT_MS = 10_000;
109
+ /**
110
+ * Ceiling on a single tmux pane-owner query. Without this a wedged tmux server
111
+ * hangs `readPaneOwners` forever, which — before this bound existed — could
112
+ * only be told apart from "genuinely no sessions" by the caller giving up and
113
+ * treating it as empty (RUSH-2521 review: exactly the failure mode that let a
114
+ * flaky tick select every marked process on the box as orphaned).
115
+ */
116
+ const TMUX_QUERY_TIMEOUT_MS = 10_000;
117
+ /** Grace between SIGTERM and SIGKILL for a reaped orphan. */
118
+ export const REAP_GRACE_MS = 2_000;
119
+ /** Env var `runInTmux` stamps into the pane, inherited by every descendant. */
120
+ export const TMUX_SESSION_ENV = 'AGENT_TMUX_SESSION_NAME';
121
+ /**
122
+ * Basename of argv[0] — the actual executable a process runs, not whatever
123
+ * text later argv tokens happen to contain. `ps -o args=` gives one opaque
124
+ * command-line string (no reliable per-token quoting to split on), so this
125
+ * takes the first whitespace-delimited run and strips its directory prefix.
126
+ */
127
+ function argv0Basename(args) {
128
+ const m = /^\s*(\S+)/.exec(args);
129
+ const token = m ? m[1] : '';
130
+ const base = token.split(/[\\/]/).pop() ?? '';
131
+ return base.toLowerCase();
132
+ }
133
+ /**
134
+ * Claude Code's background daemon pool. Its argv carries a JSON `--spawned-by`
135
+ * blob naming the claude process that started it, e.g.
136
+ * `claude.exe daemon run --origin transient --spawned-by {"label":"claude","cwd":"/…","pid":3834601}`.
137
+ * The `bg-pty-host` / `bg-spare` workers are its children and are reaped as
138
+ * descendants rather than matched individually — they carry no owner of their own.
139
+ *
140
+ * Anchored on argv[0]'s basename, not a substring match anywhere in the full
141
+ * command line: `daemon\s+run` and `--spawned-by\s+.*"pid"\s*:\s*(\d+)` both
142
+ * appear verbatim in THIS FILE's own docblock above, so a `grep`, `cat`, or
143
+ * pager viewing `orphan-reap.ts` — or a coding agent whose tool-call argv
144
+ * quotes this review's own bug report — matched the old substring-only rule
145
+ * and became a kill seed for its whole process subtree (RUSH-2521 review,
146
+ * reproduced against the exact diff text). Only a process whose real
147
+ * executable is `claude`/`claude.exe` can seed this rule now.
148
+ */
149
+ const CLAUDE_BG_DAEMON = {
150
+ agent: 'claude',
151
+ spawnerPid(args) {
152
+ const exe = argv0Basename(args);
153
+ if (exe !== 'claude' && exe !== 'claude.exe')
154
+ return undefined;
155
+ if (!/\bdaemon\s+run\b/.test(args))
156
+ return undefined;
157
+ const m = /--spawned-by\s+.*?"pid"\s*:\s*(\d+)/.exec(args);
158
+ if (!m)
159
+ return undefined;
160
+ const pid = parseInt(m[1], 10);
161
+ return Number.isFinite(pid) && pid > 1 ? pid : undefined;
162
+ },
163
+ };
164
+ export const DETACHED_HELPER_RULES = [CLAUDE_BG_DAEMON];
165
+ /**
166
+ * Long-lived agents-cli services that must never be reaped, even when they
167
+ * inherited a pane's environment because the CLI invocation that started them
168
+ * happened to run inside an agent pane.
169
+ *
170
+ * This is a safety invariant, not a fallback: the routines daemon runs this very
171
+ * reaper, so killing it would make the reaper delete its own executor, and the
172
+ * secrets broker holds the keychain session every live agent depends on.
173
+ */
174
+ export function isProtectedAgentsService(args) {
175
+ return /\b__daemon-run\b/.test(args)
176
+ || /\bsecrets\s+_agent-run\b/.test(args)
177
+ || /\bMenubarHelper\b/.test(args)
178
+ || /\bAgents CLI\.app\b/.test(args);
179
+ }
180
+ /** Is this pid currently running? Signal-0 probe, matching `platform/process.ts`. */
181
+ function livePid(pid) {
182
+ if (!Number.isFinite(pid) || pid <= 0)
183
+ return false;
184
+ try {
185
+ process.kill(pid, 0);
186
+ return true;
187
+ }
188
+ catch (err) {
189
+ // EPERM means the process exists but belongs to another user — alive.
190
+ return err.code === 'EPERM';
191
+ }
192
+ }
193
+ /**
194
+ * Pure. Select the processes whose owning agent is provably gone.
195
+ *
196
+ * `owners` maps a tmux session name to its liveness; a name ABSENT from the map
197
+ * is a session tmux no longer has, which is the strongest orphan signal there is
198
+ * — but ONLY when `owners` is itself known-complete. `opts.ownersReliable`
199
+ * (default `true`, so existing direct callers/tests keep their prior meaning)
200
+ * says whether the caller actually got a real answer from tmux for every
201
+ * socket it queried. When it is `false` — a query threw, timed out, or tmux
202
+ * itself never answered — tier 1 is skipped entirely for this call: an absent
203
+ * map entry proves nothing when the map itself might be missing entries for
204
+ * sessions tmux never got asked about (RUSH-2521 review — a flaky tick must
205
+ * never fall back to "treat every marked process as orphaned").
206
+ */
207
+ export function selectOrphanProcesses(procs, owners, opts) {
208
+ const isAlive = opts.isAlive ?? livePid;
209
+ const ownersReliable = opts.ownersReliable ?? true;
210
+ const eligible = (p) => p.pid > 1 && !opts.protectedPids.has(p.pid) && !isProtectedAgentsService(p.args);
211
+ /**
212
+ * Every pid STILL reachable, right now, from a live pane leaf through ppid
213
+ * links — not just the leaf itself. A live agent's own child (e.g. a Bash
214
+ * tool spawning `claude --print "…daemon run --spawned-by…"` as a
215
+ * sub-invocation) inherits nothing from `livePanePids` (that set only ever
216
+ * contains the leaf's own pid) unless we walk the tree, and on macOS that
217
+ * child ALSO carries no readable env marker (tier 1 is dead there) — so
218
+ * without this expansion, the "must never become a kill seed, whatever its
219
+ * own argv says" guarantee only held for the leaf itself, not the live
220
+ * agent's actual subprocess tree the same docblock's threat model names
221
+ * (RUSH-2521 review, round 3). A process that has genuinely reparented away
222
+ * (a real detached daemon, ppid now 1 or otherwise no longer chained to the
223
+ * leaf) is NOT in this set and remains reapable — this only protects what
224
+ * is still structurally part of the live agent's own tree right now.
225
+ */
226
+ const livePaneSubtree = opts.livePanePids && opts.livePanePids.size > 0
227
+ ? new Set(descendantsOf(procs, [...opts.livePanePids]))
228
+ : undefined;
229
+ /**
230
+ * A process still owned by a live/attached pane can never seed a kill,
231
+ * whatever its own argv says. Two independent signals, either sufficient:
232
+ * the process is still IN a live pane leaf's process tree right now (tmux's
233
+ * own data, no marker needed), or it carries a marker whose session is
234
+ * live/attached.
235
+ */
236
+ const ownedByLivePane = (p) => {
237
+ if (livePaneSubtree?.has(p.pid))
238
+ return true;
239
+ if (!p.tmuxSession)
240
+ return false;
241
+ const owner = owners.get(p.tmuxSession);
242
+ return !!owner && (owner.agentAlive || owner.attached);
243
+ };
244
+ const out = [];
245
+ const seen = new Set();
246
+ const push = (p, reason) => {
247
+ if (seen.has(p.pid))
248
+ return;
249
+ seen.add(p.pid);
250
+ out.push({ pid: p.pid, args: p.args, reason, tmuxSession: p.tmuxSession });
251
+ };
252
+ // Tier 1 — inherited pane marker with a dead owner. Skipped whole when the
253
+ // owners read is unreliable (see the docstring above).
254
+ if (ownersReliable) {
255
+ for (const p of procs) {
256
+ if (!p.tmuxSession || !eligible(p))
257
+ continue;
258
+ const owner = owners.get(p.tmuxSession);
259
+ if (!owner) {
260
+ push(p, 'tmux-session-gone');
261
+ continue;
262
+ }
263
+ // An attached client or a live agent pane process is a hard exclusion.
264
+ if (owner.attached || owner.agentAlive)
265
+ continue;
266
+ push(p, 'tmux-agent-exited');
267
+ }
268
+ }
269
+ // Tier 2 — a detached helper daemon whose declared spawner is dead, plus the
270
+ // workers it owns. Its own descendants are pulled in because they carry no
271
+ // owner declaration of their own. A candidate seed that still carries a
272
+ // LIVE/attached pane marker is excluded even if its argv matches a rule: it
273
+ // is provably not a detached daemon, whatever text its command line quotes.
274
+ const seeds = [];
275
+ for (const p of procs) {
276
+ if (!eligible(p) || ownedByLivePane(p))
277
+ continue;
278
+ for (const rule of DETACHED_HELPER_RULES) {
279
+ const spawner = rule.spawnerPid(p.args);
280
+ if (spawner === undefined || isAlive(spawner))
281
+ continue;
282
+ seeds.push(p);
283
+ break;
284
+ }
285
+ }
286
+ if (seeds.length > 0) {
287
+ const byPid = new Map(procs.map(p => [p.pid, p]));
288
+ for (const seed of descendantsOf(procs, seeds.map(s => s.pid))) {
289
+ const p = byPid.get(seed);
290
+ if (p && eligible(p))
291
+ push(p, 'detached-helper');
292
+ }
293
+ }
294
+ return out;
295
+ }
296
+ /**
297
+ * Pure. `seeds` plus every process reachable from them through ppid links.
298
+ * Depth is bounded by the table size, so a ppid cycle (which the kernel cannot
299
+ * produce, but a malformed snapshot could) terminates.
300
+ */
301
+ export function descendantsOf(procs, seeds) {
302
+ const children = new Map();
303
+ for (const p of procs) {
304
+ const list = children.get(p.ppid);
305
+ if (list)
306
+ list.push(p.pid);
307
+ else
308
+ children.set(p.ppid, [p.pid]);
309
+ }
310
+ const out = [];
311
+ const seen = new Set();
312
+ const stack = [...seeds];
313
+ while (stack.length > 0) {
314
+ const pid = stack.pop();
315
+ if (seen.has(pid))
316
+ continue;
317
+ seen.add(pid);
318
+ out.push(pid);
319
+ for (const child of children.get(pid) ?? [])
320
+ stack.push(child);
321
+ }
322
+ return out;
323
+ }
324
+ /** Pure. Parse `ps -A -o pid=,ppid=,args=` output. */
325
+ export function parseProcessRows(stdout) {
326
+ const rows = [];
327
+ for (const line of stdout.split('\n')) {
328
+ const m = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
329
+ if (!m)
330
+ continue;
331
+ const pid = parseInt(m[1], 10);
332
+ const ppid = parseInt(m[2], 10);
333
+ if (!Number.isFinite(pid) || !Number.isFinite(ppid))
334
+ continue;
335
+ rows.push({ pid, ppid, args: m[3].trim() });
336
+ }
337
+ return rows;
338
+ }
339
+ /**
340
+ * Pure. Extract the pane marker from a blob that contains the process
341
+ * environment — a NUL-separated `/proc/<pid>/environ` on Linux, or the
342
+ * space-separated `KEY=VALUE` tail macOS `ps -E` appends after the command.
343
+ * Session names are `[A-Za-z0-9_-]` ({@link ../tmux/session.ts} `VALID_NAME`),
344
+ * so the value ends at the first character outside that set.
345
+ */
346
+ export function parseTmuxSessionMarker(blob) {
347
+ const m = new RegExp(`(?:^|[\\s\\0])${TMUX_SESSION_ENV}=([A-Za-z0-9_-]{1,64})`).exec(blob);
348
+ return m ? m[1] : undefined;
349
+ }
350
+ /** Pure. Parse `tmux list-panes -a -F '#{session_name}\t#{pane_pid}\t#{session_attached}'`. */
351
+ export function parsePaneOwners(stdout, isAlive = livePid) {
352
+ const owners = new Map();
353
+ for (const line of stdout.split('\n')) {
354
+ const parts = line.trim().split('\t');
355
+ if (parts.length < 3)
356
+ continue;
357
+ const name = parts[0];
358
+ if (!name)
359
+ continue;
360
+ const panePid = parseInt(parts[1], 10);
361
+ const attached = parts[2].trim() !== '0';
362
+ const prev = owners.get(name);
363
+ const agentAlive = Number.isFinite(panePid) && isAlive(panePid);
364
+ owners.set(name, {
365
+ agentAlive: (prev?.agentAlive ?? false) || agentAlive,
366
+ attached: (prev?.attached ?? false) || attached,
367
+ });
368
+ }
369
+ return owners;
370
+ }
371
+ /**
372
+ * Pure. Every `#{pane_pid}` tmux reports in the same `list-panes -a` output
373
+ * {@link parsePaneOwners} reads — a pid tmux itself says is a pane's leaf
374
+ * process, independent of whether that process's OWN environment is readable.
375
+ *
376
+ * This is the marker-independent half of pane ownership: {@link parsePaneOwners}
377
+ * answers "is SESSION X owned" by aggregating over every pane a session has;
378
+ * this answers "is PID N itself a pane leaf tmux currently tracks" for exactly
379
+ * one candidate pid, with no dependency on {@link TMUX_SESSION_ENV} ever having
380
+ * been readable for it.
381
+ */
382
+ export function parsePanePids(stdout) {
383
+ const pids = new Set();
384
+ for (const line of stdout.split('\n')) {
385
+ const parts = line.trim().split('\t');
386
+ if (parts.length < 2)
387
+ continue;
388
+ const pid = parseInt(parts[1], 10);
389
+ if (Number.isFinite(pid))
390
+ pids.add(pid);
391
+ }
392
+ return pids;
393
+ }
394
+ /**
395
+ * Snapshot the process table with each process's pane marker.
396
+ *
397
+ * Two readers because no single command works on both platforms: Linux exposes
398
+ * `/proc/<pid>/environ` (one cheap read per pid), while macOS has no `/proc` and
399
+ * instead lets `ps -E` print a process's own-uid environment after its command
400
+ * — though on modern macOS `ps -E` no longer includes it at all (see the
401
+ * "Known platform gap" section in this file's docblock); tier 1 safely no-ops
402
+ * there rather than misattributing anything. Windows has no tmux integration
403
+ * at all, so it returns an empty table rather than pretending to reap.
404
+ *
405
+ * `opts.pids` restricts the `ps` snapshot to an explicit pid allowlist instead
406
+ * of the whole machine (`-A`) — a test-only escape hatch so a reap exercised
407
+ * against real spawned processes can never select or signal a real, unrelated
408
+ * process on a shared box (RUSH-2521 review: the integration tests previously
409
+ * called the machine-wide reap directly, which is safe only by construction of
410
+ * the fixture, not by anything the reaper itself guarantees).
411
+ */
412
+ export async function readAgentProcesses(opts = {}) {
413
+ if (process.platform === 'win32')
414
+ return [];
415
+ const scope = opts.pids && opts.pids.length > 0 ? ['-p', opts.pids.join(',')] : ['-A'];
416
+ const hasProc = fs.existsSync('/proc/self/environ');
417
+ const args = hasProc ? [...scope, '-o', 'pid=,ppid=,args='] : [...scope, '-E', '-o', 'pid=,ppid=,args='];
418
+ let stdout;
419
+ try {
420
+ ({ stdout } = await execFileAsync('ps', args, { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, timeout: PS_TIMEOUT_MS }));
421
+ }
422
+ catch {
423
+ return [];
424
+ }
425
+ const rows = parseProcessRows(stdout);
426
+ if (!hasProc) {
427
+ // `ps -E` already appended the environment to the args column.
428
+ for (const row of rows)
429
+ row.tmuxSession = parseTmuxSessionMarker(row.args);
430
+ return rows;
431
+ }
432
+ for (const row of rows) {
433
+ let blob;
434
+ try {
435
+ blob = fs.readFileSync(`/proc/${row.pid}/environ`, 'utf8');
436
+ }
437
+ catch {
438
+ continue; // exited between ps and the read, or not ours to inspect
439
+ }
440
+ row.tmuxSession = parseTmuxSessionMarker(blob);
441
+ }
442
+ return rows;
443
+ }
444
+ /**
445
+ * Read one tmux socket's session-ownership state.
446
+ *
447
+ * Distinguishes "tmux answered: nothing here" from "tmux did not answer" —
448
+ * the two were previously conflated (`.catch(() => null)` then `!res` folded
449
+ * into the same empty-map return as a clean nonzero exit), which meant a
450
+ * missing/wrong-version tmux, a spawn failure, or a wedged server were
451
+ * indistinguishable from a genuinely empty box. A completed process — even a
452
+ * nonzero exit — is a real answer; a rejected promise is not an answer at all
453
+ * (RUSH-2521 review).
454
+ */
455
+ export async function readPaneOwners(socket) {
456
+ // No socket file at all means no server was ever started here — for the
457
+ // shared agents socket this is reliable (tmux unlinks its own socket on
458
+ // exit), so this is a confident, reliable EMPTY read, not an unknown one.
459
+ if (!fs.existsSync(socket))
460
+ return { ok: true, owners: new Map(), panePids: new Set() };
461
+ const { runTmux } = await import('./binary.js');
462
+ try {
463
+ const res = await runTmux({
464
+ socket,
465
+ args: ['list-panes', '-a', '-F', '#{session_name}\t#{pane_pid}\t#{session_attached}'],
466
+ throwOnError: false,
467
+ timeoutMs: TMUX_QUERY_TIMEOUT_MS,
468
+ });
469
+ // A completed run answered, whatever its exit code — tmux itself is
470
+ // telling us there's no server/no sessions on a nonzero exit here.
471
+ if (res.code !== 0)
472
+ return { ok: true, owners: new Map(), panePids: new Set() };
473
+ return { ok: true, owners: parsePaneOwners(res.stdout), panePids: parsePanePids(res.stdout) };
474
+ }
475
+ catch {
476
+ // Threw: tmux missing/unsupported version (assertTmuxAvailable), a spawn
477
+ // failure, or the query timed out. We genuinely do not know.
478
+ return { ok: false, owners: new Map(), panePids: new Set() };
479
+ }
480
+ }
481
+ /**
482
+ * Ownership across every socket that could own a marked process, merged so a
483
+ * session that is live on ANY of them protects its processes. `reliable` is
484
+ * `false` the moment ANY queried socket's read failed — the merged map could
485
+ * then be missing entries for sessions that live there, so a caller MUST NOT
486
+ * treat an absent key in it as proof of anything.
487
+ *
488
+ * The union is load bearing, not defensive: the process table is machine-wide,
489
+ * so a reap driven from a non-default socket (`agents sessions reap --socket`,
490
+ * or a test's temp socket) would otherwise see every real agent's marker with no
491
+ * matching session and classify a whole box's live helpers as orphans.
492
+ */
493
+ async function readAllPaneOwners(socket) {
494
+ const { getDefaultSocketPath } = await import('./paths.js');
495
+ const merged = new Map();
496
+ const panePids = new Set();
497
+ let reliable = true;
498
+ for (const sock of new Set([socket, getDefaultSocketPath()].filter(Boolean))) {
499
+ const read = await readPaneOwners(sock);
500
+ reliable = reliable && read.ok;
501
+ for (const pid of read.panePids)
502
+ panePids.add(pid);
503
+ for (const [name, owner] of read.owners) {
504
+ const prev = merged.get(name);
505
+ merged.set(name, {
506
+ agentAlive: (prev?.agentAlive ?? false) || owner.agentAlive,
507
+ attached: (prev?.attached ?? false) || owner.attached,
508
+ });
509
+ }
510
+ }
511
+ return { owners: merged, panePids, reliable };
512
+ }
513
+ /** Pids the reaper must never signal: itself and its whole ancestor chain. */
514
+ function selfProtectedPids(procs) {
515
+ const byPid = new Map(procs.map(p => [p.pid, p]));
516
+ const out = new Set([process.pid]);
517
+ let cursor = process.ppid;
518
+ // Bounded by the table size — an ancestor walk cannot exceed it.
519
+ for (let i = 0; i <= procs.length && cursor > 1; i += 1) {
520
+ out.add(cursor);
521
+ cursor = byPid.get(cursor)?.ppid ?? 0;
522
+ }
523
+ return out;
524
+ }
525
+ /** SIGTERM every candidate, then SIGKILL whatever ignored it. */
526
+ async function terminate(candidates, graceMs) {
527
+ let signalled = 0;
528
+ for (const c of candidates) {
529
+ try {
530
+ process.kill(c.pid, 'SIGTERM');
531
+ signalled += 1;
532
+ }
533
+ catch { /* already gone */ }
534
+ }
535
+ if (signalled === 0)
536
+ return 0;
537
+ await new Promise(resolve => setTimeout(resolve, graceMs));
538
+ for (const c of candidates) {
539
+ if (!livePid(c.pid))
540
+ continue;
541
+ try {
542
+ process.kill(c.pid, 'SIGKILL');
543
+ }
544
+ catch { /* already gone */ }
545
+ }
546
+ return signalled;
547
+ }
548
+ function describe(c) {
549
+ const where = c.tmuxSession ? ` [${c.tmuxSession}]` : '';
550
+ return `pid ${c.pid}${where} (${c.reason}): ${c.args.slice(0, 120)}`;
551
+ }
552
+ /**
553
+ * Reap every helper process whose owning agent has exited.
554
+ *
555
+ * Called from the daemon's periodic tick and from `agents sessions reap`. Panes
556
+ * are read BEFORE processes so a session that disappears mid-scan is treated as
557
+ * gone (reapable) rather than as a live owner.
558
+ *
559
+ * `opts.pids` is the test-only process-table scope described on
560
+ * {@link readAgentProcesses} — never set by production callers.
561
+ */
562
+ export async function reapOrphanAgentProcesses(opts = { socket: '' }) {
563
+ const result = { killed: 0, details: [], candidates: [], warnings: [] };
564
+ if (process.platform === 'win32')
565
+ return result;
566
+ const { owners, panePids, reliable } = await readAllPaneOwners(opts.socket);
567
+ if (!reliable) {
568
+ result.warnings.push('tier 1 (pane-marker) sweep skipped this tick: a tmux session query failed to answer');
569
+ }
570
+ const procs = await readAgentProcesses({ pids: opts.pids });
571
+ if (procs.length === 0)
572
+ return result;
573
+ const candidates = selectOrphanProcesses(procs, owners, {
574
+ protectedPids: selfProtectedPids(procs),
575
+ ownersReliable: reliable,
576
+ livePanePids: panePids,
577
+ });
578
+ result.candidates = candidates;
579
+ if (candidates.length === 0)
580
+ return result;
581
+ result.details = candidates.map(describe);
582
+ if (opts.dryRun)
583
+ return result;
584
+ result.killed = await terminate(candidates, opts.graceMs ?? REAP_GRACE_MS);
585
+ return result;
586
+ }
587
+ /**
588
+ * Reap the helper processes belonging to ONE tmux session, on the teardown path.
589
+ *
590
+ * `killSession` calls this so an agent's helpers die with the session that owned
591
+ * them instead of waiting for the next daemon tick. The tmux session is being
592
+ * destroyed by the caller, so ownership needs no liveness check — anything still
593
+ * carrying this session's marker is leftover by definition, PROVIDED the name
594
+ * is unique to the socket in play.
595
+ *
596
+ * `socket` narrows that: a session name is only unique WITHIN one tmux server,
597
+ * so the exact same marker value could legitimately belong to a live, unrelated
598
+ * session on a DIFFERENT socket (e.g. the shared agents socket vs. a test's own
599
+ * temp socket). When `socket` names a non-default socket, this checks the
600
+ * default agents socket for a live/attached session of the same name and backs
601
+ * off entirely if one exists — the caller is tearing down one session, not a
602
+ * same-named one it does not own.
603
+ *
604
+ * `opts.pids` is the test-only process-table scope described on
605
+ * {@link readAgentProcesses} — never set by production callers.
606
+ */
607
+ export async function reapProcessesForTmuxSession(name, socket, opts = {}) {
608
+ const result = { killed: 0, details: [], candidates: [], warnings: [] };
609
+ if (process.platform === 'win32')
610
+ return result;
611
+ const { getDefaultSocketPath } = await import('./paths.js');
612
+ const defaultSocket = getDefaultSocketPath();
613
+ if (socket && socket !== defaultSocket) {
614
+ const elsewhere = await readPaneOwners(defaultSocket);
615
+ const owner = elsewhere.owners.get(name);
616
+ if (owner && (owner.agentAlive || owner.attached)) {
617
+ result.warnings.push(`skipped: "${name}" is a live session on the agents socket, not the one being torn down`);
618
+ return result;
619
+ }
620
+ }
621
+ const procs = await readAgentProcesses({ pids: opts.pids });
622
+ if (procs.length === 0)
623
+ return result;
624
+ const protectedPids = selfProtectedPids(procs);
625
+ const byPid = new Map(procs.map(p => [p.pid, p]));
626
+ const owned = procs.filter(p => p.tmuxSession === name);
627
+ // Descendants too: a helper that scrubbed its own environment (so it carries
628
+ // no marker) is still attributable through the marked process that spawned it.
629
+ const candidates = [];
630
+ for (const pid of descendantsOf(procs, owned.map(p => p.pid))) {
631
+ const p = byPid.get(pid);
632
+ if (!p || p.pid <= 1 || protectedPids.has(p.pid) || isProtectedAgentsService(p.args))
633
+ continue;
634
+ candidates.push({ pid: p.pid, args: p.args, reason: 'tmux-agent-exited', tmuxSession: name });
635
+ }
636
+ result.candidates = candidates;
637
+ if (candidates.length === 0)
638
+ return result;
639
+ result.details = candidates.map(describe);
640
+ if (opts.dryRun)
641
+ return result;
642
+ result.killed = await terminate(candidates, opts.graceMs ?? REAP_GRACE_MS);
643
+ return result;
644
+ }