@phnx-labs/agents-cli 1.22.39 → 1.22.40

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 (554) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/README.md +111 -70
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +45 -26
  5. package/dist/browser.js +2 -2
  6. package/dist/cli/command-registry.d.ts +135 -0
  7. package/dist/cli/command-registry.js +260 -0
  8. package/dist/commands/accounts.d.ts +20 -0
  9. package/dist/commands/accounts.js +186 -25
  10. package/dist/commands/apply.d.ts +3 -3
  11. package/dist/commands/apply.js +20 -63
  12. package/dist/commands/browser.js +206 -94
  13. package/dist/commands/budget.d.ts +7 -6
  14. package/dist/commands/budget.js +8 -7
  15. package/dist/commands/cloud.js +17 -20
  16. package/dist/commands/commands.js +1 -1
  17. package/dist/commands/computer-actions.d.ts +1 -35
  18. package/dist/commands/computer-actions.js +13 -181
  19. package/dist/commands/computer.d.ts +28 -3
  20. package/dist/commands/computer.js +93 -65
  21. package/dist/commands/config.js +16 -3
  22. package/dist/commands/cost.d.ts +2 -1
  23. package/dist/commands/cost.js +7 -6
  24. package/dist/commands/daemon.js +151 -4
  25. package/dist/commands/detach.js +1 -1
  26. package/dist/commands/doctor.js +19 -11
  27. package/dist/commands/events.js +1 -1
  28. package/dist/commands/exec.d.ts +2 -50
  29. package/dist/commands/exec.js +160 -215
  30. package/dist/commands/factory.js +1 -1
  31. package/dist/commands/feed.d.ts +2 -2
  32. package/dist/commands/feed.js +9 -12
  33. package/dist/commands/fleet-capture.js +25 -27
  34. package/dist/commands/focus.d.ts +2 -3
  35. package/dist/commands/focus.js +34 -9
  36. package/dist/commands/go.js +15 -5
  37. package/dist/commands/harness.d.ts +3 -3
  38. package/dist/commands/harness.js +53 -5
  39. package/dist/commands/hooks.js +1 -1
  40. package/dist/commands/import.js +2 -2
  41. package/dist/commands/insights.d.ts +5 -5
  42. package/dist/commands/insights.js +15 -9
  43. package/dist/commands/inspect.d.ts +2 -3
  44. package/dist/commands/inspect.js +4 -16
  45. package/dist/commands/logs.d.ts +2 -2
  46. package/dist/commands/logs.js +9 -9
  47. package/dist/commands/mcp.js +2 -2
  48. package/dist/commands/memory.js +1 -1
  49. package/dist/commands/message.d.ts +1 -1
  50. package/dist/commands/message.js +7 -7
  51. package/dist/commands/mine.d.ts +11 -8
  52. package/dist/commands/mine.js +11 -10
  53. package/dist/commands/models.js +1 -1
  54. package/dist/commands/modes.js +1 -1
  55. package/dist/commands/monitors.js +23 -6
  56. package/dist/commands/output.d.ts +7 -6
  57. package/dist/commands/output.js +11 -10
  58. package/dist/commands/packages.js +4 -4
  59. package/dist/commands/perf.js +1 -1
  60. package/dist/commands/permissions.js +49 -32
  61. package/dist/commands/plugins.d.ts +1 -1
  62. package/dist/commands/plugins.js +3 -3
  63. package/dist/commands/profiles.d.ts +6 -9
  64. package/dist/commands/profiles.js +8 -321
  65. package/dist/commands/projects.d.ts +9 -0
  66. package/dist/commands/projects.js +140 -8
  67. package/dist/commands/prune.js +5 -13
  68. package/dist/commands/refresh-rules.js +1 -1
  69. package/dist/commands/repo.d.ts +5 -7
  70. package/dist/commands/repo.js +34 -18
  71. package/dist/commands/resource-view.js +4 -0
  72. package/dist/commands/resume.d.ts +11 -7
  73. package/dist/commands/resume.js +82 -98
  74. package/dist/commands/route.d.ts +12 -0
  75. package/dist/commands/route.js +261 -0
  76. package/dist/commands/routines.d.ts +2 -3
  77. package/dist/commands/routines.js +120 -145
  78. package/dist/commands/rules.js +1 -1
  79. package/dist/commands/run-account-picker.d.ts +1 -1
  80. package/dist/commands/run-account-picker.js +2 -2
  81. package/dist/commands/run-broadcast.d.ts +21 -0
  82. package/dist/commands/run-broadcast.js +89 -0
  83. package/dist/commands/run-cloud.js +1 -1
  84. package/dist/commands/secrets-vault.d.ts +10 -0
  85. package/dist/commands/{login.js → secrets-vault.js} +34 -14
  86. package/dist/commands/secrets.d.ts +4 -4
  87. package/dist/commands/secrets.js +72 -63
  88. package/dist/commands/sessions-backfill.js +1 -1
  89. package/dist/commands/sessions-browser.d.ts +1 -1
  90. package/dist/commands/sessions-browser.js +9 -5
  91. package/dist/commands/sessions-export.js +2 -2
  92. package/dist/commands/sessions-inject.js +4 -5
  93. package/dist/commands/sessions-migrate.js +16 -27
  94. package/dist/commands/sessions-picker.d.ts +13 -0
  95. package/dist/commands/sessions-picker.js +182 -8
  96. package/dist/commands/sessions-resume.d.ts +7 -3
  97. package/dist/commands/sessions-resume.js +81 -33
  98. package/dist/commands/sessions-stats.js +2 -2
  99. package/dist/commands/sessions.d.ts +75 -68
  100. package/dist/commands/sessions.js +142 -180
  101. package/dist/commands/setup-computer.js +1 -1
  102. package/dist/commands/setup-fleet.js +3 -0
  103. package/dist/commands/setup-mine.d.ts +7 -3
  104. package/dist/commands/setup-mine.js +13 -8
  105. package/dist/commands/setup-secrets.js +2 -2
  106. package/dist/commands/setup-watchdog.js +2 -2
  107. package/dist/commands/setup.js +7 -2
  108. package/dist/commands/share.d.ts +65 -0
  109. package/dist/commands/share.js +253 -20
  110. package/dist/commands/skills.js +3 -3
  111. package/dist/commands/snapshot.d.ts +2 -2
  112. package/dist/commands/snapshot.js +9 -9
  113. package/dist/commands/ssh.d.ts +1 -1
  114. package/dist/commands/ssh.js +186 -64
  115. package/dist/commands/subagents.js +1 -1
  116. package/dist/commands/sync.js +24 -9
  117. package/dist/commands/teams.d.ts +2 -2
  118. package/dist/commands/teams.js +9 -18
  119. package/dist/commands/tmux.js +5 -1
  120. package/dist/commands/trash.js +1 -1
  121. package/dist/commands/usage.d.ts +1 -1
  122. package/dist/commands/usage.js +2 -2
  123. package/dist/commands/utils.d.ts +4 -14
  124. package/dist/commands/utils.js +6 -27
  125. package/dist/commands/versions.js +2 -2
  126. package/dist/commands/view.d.ts +9 -77
  127. package/dist/commands/view.js +23 -8
  128. package/dist/commands/watchdog.js +1 -1
  129. package/dist/commands/webhook.js +11 -25
  130. package/dist/commands/workflows.js +1 -1
  131. package/dist/index.js +1 -1
  132. package/dist/lib/account-capabilities.d.ts +39 -0
  133. package/dist/lib/account-capabilities.js +59 -0
  134. package/dist/lib/account-catalog.js +1 -1
  135. package/dist/lib/account-registry.d.ts +66 -0
  136. package/dist/lib/account-registry.js +151 -5
  137. package/dist/lib/{rotate.d.ts → accounting/rotate.d.ts} +4 -4
  138. package/dist/lib/{rotate.js → accounting/rotate.js} +8 -8
  139. package/dist/lib/{usage.d.ts → accounting/usage.d.ts} +4 -4
  140. package/dist/lib/{usage.js → accounting/usage.js} +11 -11
  141. package/dist/lib/agent-spec/provider.js +1 -1
  142. package/dist/lib/agents.d.ts +72 -4
  143. package/dist/lib/agents.js +231 -199
  144. package/dist/lib/answer-router.d.ts +2 -2
  145. package/dist/lib/ask-classifier.d.ts +1 -1
  146. package/dist/lib/ask-classifier.js +1 -1
  147. package/dist/lib/audit/log.js +1 -1
  148. package/dist/lib/auth-health.d.ts +1 -1
  149. package/dist/lib/auth-health.js +2 -2
  150. package/dist/lib/brand.js +1 -1
  151. package/dist/lib/browser/caller-identity.d.ts +28 -0
  152. package/dist/lib/browser/caller-identity.js +156 -0
  153. package/dist/lib/browser/cdp.js +1 -1
  154. package/dist/lib/browser/drivers/ssh.d.ts +1 -1
  155. package/dist/lib/browser/drivers/ssh.js +2 -2
  156. package/dist/lib/browser/hygiene.d.ts +10 -2
  157. package/dist/lib/browser/hygiene.js +7 -4
  158. package/dist/lib/browser/ipc.d.ts +22 -2
  159. package/dist/lib/browser/ipc.js +185 -21
  160. package/dist/lib/browser/profiles.d.ts +67 -3
  161. package/dist/lib/browser/profiles.js +129 -7
  162. package/dist/lib/browser/remote-control.d.ts +2 -2
  163. package/dist/lib/browser/remote-control.js +3 -3
  164. package/dist/lib/browser/runtime-state.d.ts +79 -0
  165. package/dist/lib/browser/runtime-state.js +119 -1
  166. package/dist/lib/browser/service.d.ts +113 -1
  167. package/dist/lib/browser/service.js +454 -40
  168. package/dist/lib/browser/sessions-list.d.ts +2 -1
  169. package/dist/lib/browser/sessions-list.js +4 -13
  170. package/dist/lib/browser/types.d.ts +18 -0
  171. package/dist/lib/budget/config.js +1 -1
  172. package/dist/lib/budget/ledger.js +1 -1
  173. package/dist/lib/byok-usage.js +1 -1
  174. package/dist/lib/capabilities.js +3 -15
  175. package/dist/lib/catchup.d.ts +1 -1
  176. package/dist/lib/catchup.js +1 -1
  177. package/dist/lib/cloud/dispatch.js +1 -1
  178. package/dist/lib/cloud/host.d.ts +3 -3
  179. package/dist/lib/cloud/host.js +5 -5
  180. package/dist/lib/cloud/rush.d.ts +9 -21
  181. package/dist/lib/cloud/rush.js +33 -120
  182. package/dist/lib/cloud/types.d.ts +2 -2
  183. package/dist/lib/commands.js +3 -13
  184. package/dist/lib/computer/actions.d.ts +36 -0
  185. package/dist/lib/computer/actions.js +162 -0
  186. package/dist/lib/computer/dispatch.d.ts +2 -2
  187. package/dist/lib/computer/dispatch.js +2 -2
  188. package/dist/lib/computer/sessions-list.d.ts +3 -3
  189. package/dist/lib/computer/sessions-list.js +2 -2
  190. package/dist/lib/config-keys.d.ts +2 -1
  191. package/dist/lib/config-keys.js +8 -4
  192. package/dist/lib/config-machine-keys.d.ts +4 -2
  193. package/dist/lib/config-machine-keys.js +5 -2
  194. package/dist/lib/crabbox/lease.js +9 -1
  195. package/dist/lib/crabbox/runtimes.d.ts +32 -7
  196. package/dist/lib/crabbox/runtimes.js +72 -30
  197. package/dist/lib/{daemon.d.ts → daemon/daemon.d.ts} +59 -1
  198. package/dist/lib/{daemon.js → daemon/daemon.js} +237 -49
  199. package/dist/lib/daemon-services.d.ts +1 -1
  200. package/dist/lib/daemon-services.js +5 -0
  201. package/dist/lib/daemon-ticks.d.ts +25 -0
  202. package/dist/lib/daemon-ticks.js +31 -1
  203. package/dist/lib/daemon-webhooks.d.ts +102 -0
  204. package/dist/lib/daemon-webhooks.js +210 -0
  205. package/dist/lib/device-config.d.ts +85 -75
  206. package/dist/lib/device-config.js +279 -141
  207. package/dist/lib/devices/config-migration.d.ts +29 -34
  208. package/dist/lib/devices/config-migration.js +209 -130
  209. package/dist/lib/devices/connect.d.ts +2 -2
  210. package/dist/lib/devices/connect.js +2 -2
  211. package/dist/lib/devices/discovery-policy.d.ts +30 -0
  212. package/dist/lib/devices/discovery-policy.js +101 -0
  213. package/dist/lib/devices/fleet-inventory.js +8 -3
  214. package/dist/lib/devices/harness-inventory.d.ts +1 -1
  215. package/dist/lib/devices/harness-inventory.js +12 -3
  216. package/dist/lib/devices/known-hosts.js +1 -1
  217. package/dist/lib/devices/pool.d.ts +10 -5
  218. package/dist/lib/devices/pool.js +5 -7
  219. package/dist/lib/devices/registry.d.ts +1 -1
  220. package/dist/lib/devices/registry.js +1 -1
  221. package/dist/lib/devices/resolve-profile.d.ts +2 -2
  222. package/dist/lib/devices/resolve-profile.js +2 -2
  223. package/dist/lib/devices/resolve-target.d.ts +1 -1
  224. package/dist/lib/devices/resolve-target.js +4 -4
  225. package/dist/lib/devices/self-host.d.ts +1 -1
  226. package/dist/lib/devices/self-host.js +3 -3
  227. package/dist/lib/devices/ssh-config.d.ts +1 -1
  228. package/dist/lib/devices/stats-cache.js +1 -1
  229. package/dist/lib/devices/sync.d.ts +3 -1
  230. package/dist/lib/devices/sync.js +3 -3
  231. package/dist/lib/doctor-diff.js +4 -4
  232. package/dist/lib/drift-sync.js +2 -2
  233. package/dist/lib/drift.js +2 -2
  234. package/dist/lib/event-families.d.ts +1 -1
  235. package/dist/lib/event-stream.d.ts +1 -1
  236. package/dist/lib/event-stream.js +2 -2
  237. package/dist/lib/events-ingest.js +2 -2
  238. package/dist/lib/exec.d.ts +4 -4
  239. package/dist/lib/exec.js +11 -11
  240. package/dist/lib/factory/snapshot.d.ts +1 -1
  241. package/dist/lib/factory/snapshot.js +1 -1
  242. package/dist/lib/{activity.d.ts → feed/activity.d.ts} +1 -1
  243. package/dist/lib/{activity.js → feed/activity.js} +7 -7
  244. package/dist/lib/{events.d.ts → feed/events.d.ts} +2 -2
  245. package/dist/lib/{events.js → feed/events.js} +5 -5
  246. package/dist/lib/{feed.js → feed/feed.js} +4 -4
  247. package/dist/lib/feed-outcome.d.ts +1 -1
  248. package/dist/lib/feed-policy.d.ts +1 -1
  249. package/dist/lib/feed-policy.js +1 -1
  250. package/dist/lib/feed-post.d.ts +1 -1
  251. package/dist/lib/feed-post.js +1 -1
  252. package/dist/lib/feed-ranking.d.ts +1 -1
  253. package/dist/lib/fleet/apply.d.ts +0 -6
  254. package/dist/lib/fleet/apply.js +18 -45
  255. package/dist/lib/fleet/auth-sync.d.ts +16 -35
  256. package/dist/lib/fleet/auth-sync.js +16 -75
  257. package/dist/lib/fleet/capture.js +8 -6
  258. package/dist/lib/fleet/manifest.js +23 -11
  259. package/dist/lib/fleet/types.d.ts +19 -7
  260. package/dist/lib/fleet-cache.d.ts +1 -1
  261. package/dist/lib/fleet-cache.js +1 -1
  262. package/dist/lib/format.d.ts +19 -0
  263. package/dist/lib/format.js +44 -1
  264. package/dist/lib/friction-heuristics.d.ts +1 -1
  265. package/dist/lib/git.d.ts +24 -1
  266. package/dist/lib/git.js +82 -38
  267. package/dist/lib/heal.js +3 -3
  268. package/dist/lib/hooks.d.ts +18 -0
  269. package/dist/lib/hooks.js +113 -1
  270. package/dist/lib/hosts/credentials.d.ts +30 -10
  271. package/dist/lib/hosts/credentials.js +38 -26
  272. package/dist/lib/hosts/dispatch.d.ts +3 -3
  273. package/dist/lib/hosts/dispatch.js +3 -3
  274. package/dist/lib/hosts/option.d.ts +3 -3
  275. package/dist/lib/hosts/option.js +7 -8
  276. package/dist/lib/hosts/passthrough.d.ts +17 -17
  277. package/dist/lib/hosts/passthrough.js +67 -50
  278. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  279. package/dist/lib/hosts/providers/devices.js +1 -1
  280. package/dist/lib/hosts/reconcile.d.ts +1 -1
  281. package/dist/lib/hosts/reconcile.js +1 -1
  282. package/dist/lib/hosts/reconnect.d.ts +1 -1
  283. package/dist/lib/hosts/reconnect.js +2 -2
  284. package/dist/lib/hosts/registry.d.ts +5 -5
  285. package/dist/lib/hosts/registry.js +5 -5
  286. package/dist/lib/hosts/remote-cmd.d.ts +10 -10
  287. package/dist/lib/hosts/remote-cmd.js +25 -18
  288. package/dist/lib/hosts/remote-os.d.ts +4 -4
  289. package/dist/lib/hosts/remote-os.js +4 -4
  290. package/dist/lib/hosts/remote-session-id.d.ts +2 -2
  291. package/dist/lib/hosts/remote-session-id.js +2 -2
  292. package/dist/lib/hosts/routing-flag.d.ts +3 -3
  293. package/dist/lib/hosts/routing-flag.js +4 -5
  294. package/dist/lib/hosts/run-target.d.ts +4 -4
  295. package/dist/lib/hosts/run-target.js +4 -4
  296. package/dist/lib/hosts/session-index.d.ts +2 -2
  297. package/dist/lib/hosts/session-index.js +1 -1
  298. package/dist/lib/hosts/session-marker.d.ts +1 -1
  299. package/dist/lib/hosts/session-marker.js +1 -1
  300. package/dist/lib/hosts/types.d.ts +2 -2
  301. package/dist/lib/hosts/types.js +2 -2
  302. package/dist/lib/import.js +1 -1
  303. package/dist/lib/{migrate.d.ts → installations/migrate.d.ts} +13 -1
  304. package/dist/lib/{migrate.js → installations/migrate.js} +160 -40
  305. package/dist/lib/installations/resolve.js +1 -1
  306. package/dist/lib/installations/strategies.js +1 -1
  307. package/dist/lib/installations/update.js +2 -2
  308. package/dist/lib/{versions.d.ts → installations/versions.d.ts} +12 -3
  309. package/dist/lib/{versions.js → installations/versions.js} +51 -31
  310. package/dist/lib/isolation-boundary-report.js +1 -1
  311. package/dist/lib/linear-projects.d.ts +28 -0
  312. package/dist/lib/linear-projects.js +30 -0
  313. package/dist/lib/loop.js +1 -1
  314. package/dist/lib/mailbox-gc.js +1 -1
  315. package/dist/lib/mailbox.js +1 -1
  316. package/dist/lib/mcp-registry.d.ts +62 -0
  317. package/dist/lib/mcp-registry.js +177 -0
  318. package/dist/lib/mcp.d.ts +5 -0
  319. package/dist/lib/mcp.js +124 -307
  320. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  321. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  322. package/dist/lib/menubar/install-menubar.d.ts +7 -0
  323. package/dist/lib/menubar/install-menubar.js +24 -6
  324. package/dist/lib/menubar/snapshot.d.ts +7 -0
  325. package/dist/lib/menubar/snapshot.js +8 -6
  326. package/dist/lib/models.js +1 -1
  327. package/dist/lib/monitors/config.d.ts +12 -0
  328. package/dist/lib/monitors/config.js +3 -0
  329. package/dist/lib/monitors/dispatch.js +26 -5
  330. package/dist/lib/monitors/engine.js +10 -0
  331. package/dist/lib/monitors/sources/webhook.d.ts +1 -1
  332. package/dist/lib/monitors/state.d.ts +42 -0
  333. package/dist/lib/monitors/state.js +30 -0
  334. package/dist/lib/notify.d.ts +1 -1
  335. package/dist/lib/observe-aliases.d.ts +2 -2
  336. package/dist/lib/observe-aliases.js +2 -11
  337. package/dist/lib/output/git-output.js +2 -2
  338. package/dist/lib/overdue.d.ts +1 -1
  339. package/dist/lib/overdue.js +1 -1
  340. package/dist/lib/permissions-registry.d.ts +65 -0
  341. package/dist/lib/permissions-registry.js +599 -0
  342. package/dist/lib/permissions.d.ts +24 -13
  343. package/dist/lib/permissions.js +52 -271
  344. package/dist/lib/picker.d.ts +16 -0
  345. package/dist/lib/picker.js +33 -3
  346. package/dist/lib/placement.d.ts +1 -1
  347. package/dist/lib/placement.js +4 -4
  348. package/dist/lib/platform/paths.d.ts +8 -0
  349. package/dist/lib/platform/paths.js +11 -0
  350. package/dist/lib/platform/posixpath.js +3 -3
  351. package/dist/lib/{plugin-marketplace.d.ts → plugins/plugin-marketplace.d.ts} +1 -1
  352. package/dist/lib/{plugin-marketplace.js → plugins/plugin-marketplace.js} +2 -2
  353. package/dist/lib/{plugins.d.ts → plugins/plugins.d.ts} +1 -1
  354. package/dist/lib/{plugins.js → plugins/plugins.js} +7 -7
  355. package/dist/lib/{skills.d.ts → plugins/skills.d.ts} +1 -1
  356. package/dist/lib/{skills.js → plugins/skills.js} +7 -7
  357. package/dist/lib/pricing/cost.d.ts +1 -1
  358. package/dist/lib/pricing/cost.js +1 -1
  359. package/dist/lib/profiles-presets.d.ts +1 -1
  360. package/dist/lib/profiles-presets.js +1 -1
  361. package/dist/lib/profiles.js +2 -2
  362. package/dist/lib/project-import.js +4 -1
  363. package/dist/lib/project-launch.d.ts +1 -1
  364. package/dist/lib/project-launch.js +3 -3
  365. package/dist/lib/project-pull.d.ts +128 -0
  366. package/dist/lib/project-pull.js +384 -0
  367. package/dist/lib/project-root.d.ts +2 -2
  368. package/dist/lib/project-root.js +2 -2
  369. package/dist/lib/project-status.js +1 -1
  370. package/dist/lib/projects.d.ts +23 -0
  371. package/dist/lib/projects.js +29 -6
  372. package/dist/lib/refresh.d.ts +12 -1
  373. package/dist/lib/refresh.js +16 -9
  374. package/dist/lib/registry.d.ts +9 -2
  375. package/dist/lib/registry.js +11 -4
  376. package/dist/lib/resources.d.ts +2 -2
  377. package/dist/lib/resources.js +2 -2
  378. package/dist/lib/routers.d.ts +81 -0
  379. package/dist/lib/routers.js +153 -0
  380. package/dist/lib/routine-context.d.ts +1 -0
  381. package/dist/lib/routine-context.js +7 -2
  382. package/dist/lib/routine-notify-owner.d.ts +1 -1
  383. package/dist/lib/routine-notify.d.ts +1 -1
  384. package/dist/lib/routine-notify.js +1 -1
  385. package/dist/lib/routine-process-cleanup.d.ts +1 -1
  386. package/dist/lib/routine-readiness.d.ts +1 -1
  387. package/dist/lib/routine-readiness.js +11 -4
  388. package/dist/lib/routines-placement.d.ts +12 -2
  389. package/dist/lib/routines-placement.js +20 -3
  390. package/dist/lib/routines-project.d.ts +1 -1
  391. package/dist/lib/routines-project.js +1 -1
  392. package/dist/lib/rules/compile.d.ts +1 -0
  393. package/dist/lib/rules/compile.js +10 -2
  394. package/dist/lib/rules/rules.js +1 -1
  395. package/dist/lib/run-notify.js +1 -1
  396. package/dist/lib/runner.d.ts +80 -2
  397. package/dist/lib/runner.js +241 -62
  398. package/dist/lib/sandbox.d.ts +1 -1
  399. package/dist/lib/scheduler.d.ts +1 -1
  400. package/dist/lib/scheduler.js +1 -1
  401. package/dist/lib/{routines.d.ts → scheduling/routines.d.ts} +54 -9
  402. package/dist/lib/{routines.js → scheduling/routines.js} +173 -14
  403. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  404. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  405. package/dist/lib/secrets/agent.js +2 -2
  406. package/dist/lib/secrets/audit.js +1 -1
  407. package/dist/lib/secrets/bundles.d.ts +3 -3
  408. package/dist/lib/secrets/bundles.js +7 -7
  409. package/dist/lib/secrets/index.js +1 -1
  410. package/dist/lib/secrets/list-filter.d.ts +1 -1
  411. package/dist/lib/secrets/list-filter.js +2 -2
  412. package/dist/lib/secrets/push.d.ts +1 -1
  413. package/dist/lib/secrets/push.js +10 -12
  414. package/dist/lib/secrets/remote.d.ts +12 -10
  415. package/dist/lib/secrets/remote.js +21 -14
  416. package/dist/lib/secrets/vault.js +2 -2
  417. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  418. package/dist/lib/self-heal/checks/shims.js +1 -1
  419. package/dist/lib/self-update.d.ts +39 -4
  420. package/dist/lib/self-update.js +78 -25
  421. package/dist/lib/serve/control.d.ts +1 -1
  422. package/dist/lib/serve/control.js +4 -4
  423. package/dist/lib/serve/data.d.ts +1 -1
  424. package/dist/lib/serve/data.js +1 -1
  425. package/dist/lib/serve/stream.js +1 -1
  426. package/dist/lib/service-manifest.d.ts +29 -0
  427. package/dist/lib/service-manifest.js +80 -0
  428. package/dist/lib/session/active.d.ts +59 -7
  429. package/dist/lib/session/active.js +158 -17
  430. package/dist/lib/session/bash-command.js +1 -1
  431. package/dist/lib/session/claude-accounts.d.ts +1 -1
  432. package/dist/lib/session/claude-accounts.js +1 -1
  433. package/dist/lib/session/db.d.ts +10 -2
  434. package/dist/lib/session/db.js +25 -6
  435. package/dist/lib/session/discover.d.ts +77 -0
  436. package/dist/lib/session/discover.js +130 -25
  437. package/dist/lib/session/fork.js +1 -1
  438. package/dist/lib/session/inject.d.ts +1 -1
  439. package/dist/lib/session/insights.d.ts +1 -1
  440. package/dist/lib/session/insights.js +1 -1
  441. package/dist/lib/session/live-metadata.d.ts +15 -0
  442. package/dist/lib/session/live-metadata.js +76 -0
  443. package/dist/lib/session/migrate-targets.d.ts +1 -1
  444. package/dist/lib/session/recovery.d.ts +1 -1
  445. package/dist/lib/session/recovery.js +2 -2
  446. package/dist/lib/session/remote-active.d.ts +10 -1
  447. package/dist/lib/session/remote-active.js +8 -3
  448. package/dist/lib/session/remote-bundle.d.ts +1 -1
  449. package/dist/lib/session/remote-bundle.js +2 -2
  450. package/dist/lib/session/remote-list.d.ts +15 -4
  451. package/dist/lib/session/remote-list.js +40 -3
  452. package/dist/lib/session/remote.d.ts +5 -5
  453. package/dist/lib/session/remote.js +13 -17
  454. package/dist/lib/session/resume-command.d.ts +3 -2
  455. package/dist/lib/session/resume-command.js +4 -3
  456. package/dist/lib/session/resume-owner.d.ts +3 -3
  457. package/dist/lib/session/resume-owner.js +3 -3
  458. package/dist/lib/session/session-cache.d.ts +12 -0
  459. package/dist/lib/session/session-cache.js +2 -0
  460. package/dist/lib/session/shell-programs.js +1 -1
  461. package/dist/lib/session/state.js +5 -5
  462. package/dist/lib/session/team-filter.d.ts +1 -1
  463. package/dist/lib/session/team-filter.js +1 -1
  464. package/dist/lib/session/types.d.ts +3 -3
  465. package/dist/lib/session/width.d.ts +2 -2
  466. package/dist/lib/session/width.js +2 -2
  467. package/dist/lib/share/delete.d.ts +21 -0
  468. package/dist/lib/share/delete.js +42 -0
  469. package/dist/lib/share/publish.d.ts +82 -0
  470. package/dist/lib/share/publish.js +170 -18
  471. package/dist/lib/share/worker-template.js +172 -9
  472. package/dist/lib/shims.js +35 -7
  473. package/dist/lib/smart-launch.d.ts +4 -4
  474. package/dist/lib/smart-launch.js +6 -4
  475. package/dist/lib/snapshot.d.ts +4 -4
  476. package/dist/lib/snapshot.js +2 -2
  477. package/dist/lib/ssh-exec.d.ts +1 -1
  478. package/dist/lib/ssh-exec.js +2 -2
  479. package/dist/lib/ssh-tunnel.d.ts +10 -10
  480. package/dist/lib/ssh-tunnel.js +12 -12
  481. package/dist/lib/staleness/checkers/hooks.d.ts +1 -1
  482. package/dist/lib/staleness/checkers/hooks.js +1 -1
  483. package/dist/lib/staleness/detectors/permissions.js +0 -23
  484. package/dist/lib/staleness/detectors/plugins.js +1 -1
  485. package/dist/lib/staleness/fingerprint.js +1 -1
  486. package/dist/lib/staleness/layers.d.ts +1 -1
  487. package/dist/lib/staleness/layers.js +1 -1
  488. package/dist/lib/staleness/writers/mcp.js +3 -1
  489. package/dist/lib/staleness/writers/plugins.js +1 -1
  490. package/dist/lib/staleness/writers/types.d.ts +8 -0
  491. package/dist/lib/startup/command-registry.d.ts +5 -147
  492. package/dist/lib/startup/command-registry.js +38 -303
  493. package/dist/lib/startup/dev-build.d.ts +0 -21
  494. package/dist/lib/startup/dev-build.js +29 -8
  495. package/dist/lib/state.d.ts +19 -1
  496. package/dist/lib/state.js +120 -51
  497. package/dist/lib/subagents.js +1 -1
  498. package/dist/lib/sync-status.js +1 -1
  499. package/dist/lib/sync-umbrella.d.ts +6 -1
  500. package/dist/lib/sync-umbrella.js +9 -4
  501. package/dist/lib/teams/agents.js +2 -2
  502. package/dist/lib/teams/registry.js +1 -1
  503. package/dist/lib/terminal/backends/vscodium-agent.js +1 -1
  504. package/dist/lib/terminal/engine.js +1 -1
  505. package/dist/lib/terminal/index.d.ts +4 -0
  506. package/dist/lib/terminal/index.js +3 -0
  507. package/dist/lib/terminal/inject.d.ts +2 -2
  508. package/dist/lib/terminal/inject.js +2 -2
  509. package/dist/lib/terminal/transport.js +1 -1
  510. package/dist/lib/tmux/index.d.ts +1 -1
  511. package/dist/lib/tmux/index.js +1 -1
  512. package/dist/lib/tmux/session.d.ts +21 -5
  513. package/dist/lib/tmux/session.js +76 -22
  514. package/dist/lib/triggers/handlers.d.ts +1 -1
  515. package/dist/lib/triggers/handlers.js +2 -2
  516. package/dist/lib/triggers/webhook.d.ts +36 -2
  517. package/dist/lib/triggers/webhook.js +97 -42
  518. package/dist/lib/types.d.ts +18 -6
  519. package/dist/lib/usage-fleet.d.ts +1 -1
  520. package/dist/lib/usage-fleet.js +1 -1
  521. package/dist/lib/usage-refresh.d.ts +1 -1
  522. package/dist/lib/usage-refresh.js +3 -3
  523. package/dist/lib/version-duplicates.js +2 -2
  524. package/dist/lib/view-types.d.ts +56 -0
  525. package/dist/lib/view-types.js +1 -0
  526. package/dist/lib/watchdog/rotate.d.ts +2 -2
  527. package/dist/lib/watchdog/rotate.js +3 -3
  528. package/dist/lib/watchdog/runner.d.ts +2 -3
  529. package/dist/lib/watchdog/runner.js +3 -4
  530. package/dist/lib/workflows.js +1 -1
  531. package/package.json +2 -1
  532. package/scripts/postinstall.js +2 -2
  533. package/dist/commands/bench.d.ts +0 -2
  534. package/dist/commands/bench.js +0 -101
  535. package/dist/commands/cp.d.ts +0 -84
  536. package/dist/commands/cp.js +0 -231
  537. package/dist/commands/login.d.ts +0 -2
  538. package/dist/lib/resources/commands.d.ts +0 -46
  539. package/dist/lib/resources/commands.js +0 -211
  540. package/dist/lib/resources/hooks.d.ts +0 -16
  541. package/dist/lib/resources/hooks.js +0 -153
  542. package/dist/lib/resources/mcp.d.ts +0 -34
  543. package/dist/lib/resources/mcp.js +0 -250
  544. package/dist/lib/resources/memory.d.ts +0 -15
  545. package/dist/lib/resources/memory.js +0 -46
  546. package/dist/lib/resources/permissions.d.ts +0 -13
  547. package/dist/lib/resources/permissions.js +0 -202
  548. package/dist/lib/resources/rules.d.ts +0 -46
  549. package/dist/lib/resources/rules.js +0 -179
  550. package/dist/lib/resources/skills.d.ts +0 -37
  551. package/dist/lib/resources/skills.js +0 -247
  552. package/dist/lib/resources/subagents.d.ts +0 -46
  553. package/dist/lib/resources/subagents.js +0 -202
  554. /package/dist/lib/{feed.d.ts → feed/feed.d.ts} +0 -0
@@ -10,29 +10,31 @@ import { spawn, execFileSync } from 'child_process';
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import * as os from 'os';
13
- import { getDaemonDir } from './state.js';
14
- import { isAlive, killTree, backgroundSpawnOptions, waitForExit } from './platform/index.js';
15
- import { listJobs as listAllJobs } from './routines.js';
16
- import { syncAllProjectRoutines } from './routines-project.js';
17
- import { JobScheduler } from './scheduler.js';
18
- import { MonitorEngine } from './monitors/engine.js';
19
- import { executeJobDetached, monitorRunningJobs, listLiveRoutineChildren } from './runner.js';
20
- import { detectOverdueJobs, notifyOverdue } from './overdue.js';
21
- import { runCatchup } from './catchup.js';
22
- import { notifyRoutineStart, notifyRoutineFinish, notifyRoutineStartFailed } from './routine-notify.js';
23
- import { notifyOwnerRoutineFinish, notifyOwnerRoutineStartFailed } from './routine-notify-owner.js';
24
- import { BrowserService } from './browser/service.js';
25
- import { BrowserIPCServer, getSocketPath as getBrowserIpcSocketPath } from './browser/ipc.js';
26
- import { secretsBrokerSocketPath, brokerPidAlive } from './secrets/agent.js';
27
- import { redactSecrets } from './redact.js';
28
- import { getAgentsBinPath, getCliLaunch, BUN_VIRTUAL_ROOT } from './cli-entry.js';
29
- import { isSchedulerEnabled, assertSchedulerEnabled, isDaemonEnabled, getConfigValue } from './device-config.js';
30
- import { reapTerminalRoutineProcesses } from './routine-process-cleanup.js';
31
- import { recordSubsystemOk, recordSubsystemError, recordSubsystemErrorReason, readSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START } from './daemon-health.js';
32
- import { startAccountStateService } from './account-state-service.js';
33
- import { runActiveSessionsWarmTick, runFleetCacheWarmTick, runUsageRefreshTick } from './daemon-ticks.js';
34
- import { emit, emitRoutineEnd } from './events.js';
35
- import { readDaemonServicesConfig } from './daemon-services.js';
13
+ import { getDaemonDir } from '../state.js';
14
+ import { isolatedHomeSuffix, namespacedServiceLabel, serviceManifestHomeEnv } from '../service-manifest.js';
15
+ import { isAlive, killTree, backgroundSpawnOptions, waitForExit } from '../platform/index.js';
16
+ import { listJobs as listAllJobs } from '../scheduling/routines.js';
17
+ import { syncAllProjectRoutines } from '../routines-project.js';
18
+ import { JobScheduler } from '../scheduler.js';
19
+ import { MonitorEngine } from '../monitors/engine.js';
20
+ import { executeJobDetached, monitorRunningJobs, listLiveRoutineChildren } from '../runner.js';
21
+ import { detectOverdueJobs, notifyOverdue } from '../overdue.js';
22
+ import { runCatchup } from '../catchup.js';
23
+ import { notifyRoutineStart, notifyRoutineFinish, notifyRoutineStartFailed } from '../routine-notify.js';
24
+ import { notifyOwnerRoutineFinish, notifyOwnerRoutineStartFailed } from '../routine-notify-owner.js';
25
+ import { BrowserService } from '../browser/service.js';
26
+ import { BrowserIPCServer, getSocketPath as getBrowserIpcSocketPath } from '../browser/ipc.js';
27
+ import { startHostedWebhookReceivers } from '../daemon-webhooks.js';
28
+ import { secretsBrokerSocketPath, brokerPidAlive } from '../secrets/agent.js';
29
+ import { redactSecrets } from '../redact.js';
30
+ import { getAgentsBinPath, getCliLaunch, BUN_VIRTUAL_ROOT } from '../cli-entry.js';
31
+ import { isSchedulerEnabled, assertSchedulerEnabled, isDaemonEnabled, getConfigValue, resolveBrowserTaskIdleMs } from '../device-config.js';
32
+ import { reapTerminalRoutineProcesses } from '../routine-process-cleanup.js';
33
+ import { recordSubsystemOk, recordSubsystemError, recordSubsystemErrorReason, readSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START } from '../daemon-health.js';
34
+ import { startAccountStateService } from '../account-state-service.js';
35
+ import { runActiveSessionsWarmTick, runFleetCacheWarmTick, runSessionIndexWarmTick, runUsageRefreshTick } from '../daemon-ticks.js';
36
+ import { emit, emitRoutineEnd } from '../feed/events.js';
37
+ import { readDaemonServicesConfig } from '../daemon-services.js';
36
38
  const PID_FILE = 'daemon.pid';
37
39
  const LIFETIME_FILE = 'daemon.lifetime';
38
40
  const LOCK_FILE = 'daemon.lock';
@@ -42,6 +44,46 @@ const LOG_MAX_SIZE = 5 * 1024 * 1024; // 5 MB
42
44
  const LOG_ROTATE_COUNT = 3;
43
45
  const PLIST_NAME = 'com.phnx-labs.agents-daemon';
44
46
  const SYSTEMD_UNIT = 'agents-daemon.service';
47
+ /**
48
+ * RUSH-2639 (residual): launchd/systemd route `unload`/`load`/`list` by the
49
+ * service identifier ALONE, never by the plist/unit file's path. Baking the
50
+ * caller's HOME into the plist content (the earlier RUSH-2639 fix, above)
51
+ * keeps the STARTED daemon inside its sandbox, but every hermetic-test
52
+ * instance and every real interactive install still share the one literal
53
+ * `PLIST_NAME`/`SYSTEMD_UNIT` string. `startDaemonLocked`'s own `unload`
54
+ * before `load` is written to be a no-op ("not loaded, expected") for a
55
+ * plist that has never been loaded — but confirmed on darwin: when a
56
+ * DIFFERENT plist is already loaded under that same label, `unload
57
+ * <this-instance's-own-never-loaded-path>` still tears down the OTHER job
58
+ * (verified directly against real launchctl with two throwaway plists
59
+ * sharing one label — the second job's own `unload` silently kills the
60
+ * first, still alive under a different path). On a machine running several
61
+ * hermetic test forks at once (CI) — or a developer's own suite next to
62
+ * their real always-on daemon — that "other job" is a live daemon with
63
+ * DIFFERENT baked-in state.
64
+ *
65
+ * Namespace the identifier itself whenever HOME has been redirected away
66
+ * from the account's real home. `os.userInfo().homedir` reads the OS/passwd
67
+ * record directly and ignores `$HOME` (unlike `os.homedir()`, which honors
68
+ * it), so comparing the two detects exactly this redirection — true for
69
+ * every hermetic test process, false for every real interactive/production
70
+ * invocation, so a real user's daemon keeps registering under the unchanged
71
+ * production identifier.
72
+ *
73
+ * The rule itself now lives in `service-manifest.ts` — the daemon was the first
74
+ * manifest to need it, not the only one — and is re-exported here because it is
75
+ * part of this module's published surface.
76
+ */
77
+ export { isolatedHomeSuffix };
78
+ /** launchd Label for this process's daemon — namespaced under a redirected HOME. */
79
+ export function daemonServiceLabel() {
80
+ return namespacedServiceLabel(PLIST_NAME);
81
+ }
82
+ /** systemd --user unit name for this process's daemon — namespaced under a redirected HOME. */
83
+ export function daemonSystemdUnitName() {
84
+ const suffix = isolatedHomeSuffix();
85
+ return suffix ? `agents-daemon-sandbox-${suffix}.service` : SYSTEMD_UNIT;
86
+ }
45
87
  const MONITOR_TICK_MS = 60_000;
46
88
  /**
47
89
  * How often to re-scan for missed fires. Deliberately slower than the monitor
@@ -73,6 +115,8 @@ const BROKER_SELF_HEAL_TICK_MS = 60_000;
73
115
  const KEYCHAIN_REAP_TICK_MS = 5 * 60_000;
74
116
  // RUSH-2501: reap tmux sessions whose panes are all dead every 5 minutes.
75
117
  const DEAD_PANE_REAP_TICK_MS = 5 * 60_000;
118
+ // RUSH-2622: close abandoned browser-task tabs every 5 minutes.
119
+ const BROWSER_TASK_REAP_TICK_MS = 5 * 60_000;
76
120
  const STATE_DIR_CHECK_TICK_MS = 60_000;
77
121
  // Watchdog nudges this host's own stalled sessions; device-probe refreshes
78
122
  // registered devices' reachability and surfaces newly appeared tailnet nodes.
@@ -84,6 +128,10 @@ const DEVICE_PROBE_TICK_MS = 3 * 60_000;
84
128
  // Matches DEFAULT_ACTIVE_CACHE_MAX_AGE_MS (15s) so long-lived watchers never
85
129
  // outlive the publisher (RUSH-2484 — CLI-owned continuous publish).
86
130
  const ACTIVE_SESSIONS_WARM_TICK_MS = 15_000;
131
+ // Incrementally scan this host's transcript dirs into the local index so a
132
+ // locally-started session is indexed within seconds, not on the next unrelated
133
+ // `agents sessions*` call (RUSH-2682 — indexing was lazy, nothing scheduled it).
134
+ const SESSION_INDEX_WARM_TICK_MS = 20_000;
87
135
  const WEDGE_THRESHOLD_TICKS = 3;
88
136
  /**
89
137
  * Crash-loop prevention (RUSH-2418). Three layers, because none of them alone
@@ -221,10 +269,10 @@ export function getDaemonLogPath() {
221
269
  return path.join(ensureDaemonDir(), LOG_FILE);
222
270
  }
223
271
  function getLaunchdPlistPath() {
224
- return path.join(os.homedir(), 'Library', 'LaunchAgents', `${PLIST_NAME}.plist`);
272
+ return path.join(os.homedir(), 'Library', 'LaunchAgents', `${daemonServiceLabel()}.plist`);
225
273
  }
226
274
  function getSystemdUnitPath() {
227
- return path.join(os.homedir(), '.config', 'systemd', 'user', `${SYSTEMD_UNIT}`);
275
+ return path.join(os.homedir(), '.config', 'systemd', 'user', daemonSystemdUnitName());
228
276
  }
229
277
  /** Read the stored daemon PID from disk. Returns null if not present or invalid. */
230
278
  export function readDaemonPid() {
@@ -668,6 +716,7 @@ export function warnEphemeralDaemonRoot(resolveBin = getAgentsBinPath) {
668
716
  let healing = false;
669
717
  let reapingKeychain = false;
670
718
  let reapingDeadPanes = false;
719
+ let reapingBrowserTasks = false;
671
720
  /**
672
721
  * Resource self-heal: fill missing resources, repair invalid manifests, and
673
722
  * fast-forward pristine stale plugins. Conservative 'safe' mode: never
@@ -684,7 +733,7 @@ async function runHealCheck() {
684
733
  return;
685
734
  healing = true;
686
735
  try {
687
- const { runSelfHeal, selfHealChangedAnything, selfHealNeedsAttention, summarizeSelfHeal } = await import('./self-heal/registry.js');
736
+ const { runSelfHeal, selfHealChangedAnything, selfHealNeedsAttention, summarizeSelfHeal } = await import('../self-heal/registry.js');
688
737
  if (!fs.existsSync(getDaemonDir()))
689
738
  return;
690
739
  const report = await runSelfHeal({ mode: 'safe' });
@@ -709,7 +758,7 @@ async function runKeychainReap() {
709
758
  return;
710
759
  reapingKeychain = true;
711
760
  try {
712
- const { reapOrphanedKeychainProcesses } = await import('./secrets/reaper.js');
761
+ const { reapOrphanedKeychainProcesses } = await import('../secrets/reaper.js');
713
762
  const result = reapOrphanedKeychainProcesses();
714
763
  if (result.reaped > 0) {
715
764
  log('WARN', `Reaped ${result.reaped} keychain orphan/stuck process(es)`);
@@ -738,8 +787,8 @@ async function runDeadPaneReap() {
738
787
  return;
739
788
  reapingDeadPanes = true;
740
789
  try {
741
- const { reapDeadTmuxPanes } = await import('./tmux/session.js');
742
- const { getDefaultSocketPath } = await import('./tmux/paths.js');
790
+ const { reapDeadTmuxPanes } = await import('../tmux/session.js');
791
+ const { getDefaultSocketPath } = await import('../tmux/paths.js');
743
792
  const result = await reapDeadTmuxPanes(getDefaultSocketPath());
744
793
  for (const w of result.warnings)
745
794
  log('WARN', `Dead-pane reaper: ${w}`);
@@ -761,6 +810,35 @@ async function runDeadPaneReap() {
761
810
  reapingDeadPanes = false;
762
811
  }
763
812
  }
813
+ /**
814
+ * RUSH-2622: close abandoned browser-task tabs — a task whose owning agent
815
+ * session has exited, or one idle past `browser.task-idle-minutes` (default 30,
816
+ * 0 disables idle reaping only). Same policy `agents browser gc` triggers on
817
+ * demand; this is the periodic side, and the daemon is the single executor so
818
+ * no UI surface can race it. `service` is the daemon's own long-lived
819
+ * BrowserService — the browser IPC server it started earlier in `runDaemon()`.
820
+ *
821
+ * Runs every BROWSER_TASK_REAP_TICK_MS (5 min).
822
+ */
823
+ async function runBrowserTaskReap(service) {
824
+ if (reapingBrowserTasks)
825
+ return;
826
+ reapingBrowserTasks = true;
827
+ try {
828
+ const result = await service.reapAbandoned({ idleMs: resolveBrowserTaskIdleMs() });
829
+ if (result.closed.length > 0) {
830
+ log('INFO', `Browser-task reaper: closed ${result.closed.length} task(s)`);
831
+ for (const c of result.closed)
832
+ log('INFO', ` ${c.task} (${c.reason}, profile ${c.profile})`);
833
+ }
834
+ }
835
+ catch (err) {
836
+ log('ERROR', `Browser-task reaper failed: ${err.message}`);
837
+ }
838
+ finally {
839
+ reapingBrowserTasks = false;
840
+ }
841
+ }
764
842
  /**
765
843
  * One monitor tick: write heartbeat + drain the running-job monitor + reap
766
844
  * stale terminal routine process groups. Runs on MONITOR_TICK_MS.
@@ -793,18 +871,37 @@ export async function runDaemon() {
793
871
  log('INFO', `Daemon started (PID: ${process.pid})`);
794
872
  anchorDaemonCwd();
795
873
  warnEphemeralDaemonRoot();
796
- // Fold legacy per-device config stores into the central
797
- // fleet.devices.<name>.config block (idempotent, cheap no-op once folded).
798
- // The daemon boots via `agents __daemon-run`, which bypasses bootstrap's
799
- // migration sentinel so the daemon runs this itself so its scheduler/
800
- // watchdog gates read the converged store.
874
+ // Converge the device-config/pins stores (legacy central block /
875
+ // auto-launch.json / tracked-doc pins → per-device docs + pins file).
876
+ // Idempotent, cheap no-op once folded. The daemon boots via
877
+ // `agents __daemon-run`, which bypasses bootstrap's migration sentinel so
878
+ // the daemon runs this itself so its scheduler/watchdog gates read the
879
+ // converged store.
801
880
  try {
802
- const { migrateDeviceConfigToCentral } = await import('./devices/config-migration.js');
803
- migrateDeviceConfigToCentral();
881
+ const { migrateDeviceConfigStores } = await import('../devices/config-migration.js');
882
+ migrateDeviceConfigStores();
804
883
  }
805
884
  catch (err) {
806
885
  log('WARN', `device config migration failed: ${err.message}`);
807
886
  }
887
+ // Version-skew one-shot (RUSH-2435): retrofit the current pane-died hook onto
888
+ // any managed tmux session a pre-fix binary left with a stale one. The 5-min
889
+ // `tmux-reconcile` routine that used to run this on a poll was deleted
890
+ // (RUSH-2495) — startup + `ensureSessionHookRepaired` at attach time
891
+ // (tmux/session.ts) now cover what that poll used to. Idempotent and
892
+ // non-destructive: a session already at the current schema is a no-op.
893
+ try {
894
+ const { reconcileSessionHooks } = await import('../tmux/session.js');
895
+ const { isTmuxInstalled } = await import('../tmux/binary.js');
896
+ if (isTmuxInstalled()) {
897
+ const r = await reconcileSessionHooks();
898
+ if (r.reconciled > 0)
899
+ log('INFO', `tmux: retrofitted pane-died hook on ${r.reconciled}/${r.scanned} session(s)`);
900
+ }
901
+ }
902
+ catch (err) {
903
+ log('WARN', `tmux hook reconcile failed: ${err.message}`);
904
+ }
808
905
  // Per-service toggles live outside device-config so they can be checked by
809
906
  // service clients (e.g. secrets) without loading the whole config stack.
810
907
  let servicesConfig = readDaemonServicesConfig();
@@ -842,7 +939,7 @@ export async function runDaemon() {
842
939
  let hostedBroker = null;
843
940
  if (isEnabled('secrets-broker')) {
844
941
  try {
845
- const { agentPing, startHostedBroker } = await import('./secrets/agent.js');
942
+ const { agentPing, startHostedBroker } = await import('../secrets/agent.js');
846
943
  if ((await agentPing()).reachable) {
847
944
  log('INFO', 'Secrets broker already running (standalone); daemon not hosting it');
848
945
  }
@@ -1028,6 +1125,35 @@ export async function runDaemon() {
1028
1125
  };
1029
1126
  void runActiveSessionsWarm();
1030
1127
  const activeSessionsWarmInterval = setInterval(() => { void runActiveSessionsWarm(); }, ACTIVE_SESSIONS_WARM_TICK_MS);
1128
+ // Session-index warm (RUSH-2682): keep THIS host's transcript index current so
1129
+ // a locally-started session is discoverable within seconds. Incremental +
1130
+ // single-flight (the DB scan claim), overlap-guarded so a slow scan never
1131
+ // stacks on the next tick.
1132
+ let sessionIndexWarmInFlight = false;
1133
+ const runSessionIndexWarm = async () => {
1134
+ if (sessionIndexWarmInFlight)
1135
+ return;
1136
+ sessionIndexWarmInFlight = true;
1137
+ try {
1138
+ const { indexed, claimed } = await runSessionIndexWarmTick();
1139
+ // Log only when the tick did something. A silent tick is what let it
1140
+ // report 0 forever unnoticed (RUSH-2691); a line on every idle 20s tick
1141
+ // would drown the log, so the steady state (claimed, nothing changed)
1142
+ // stays quiet and both interesting outcomes are visible.
1143
+ if (!claimed)
1144
+ log('INFO', 'session-index warm: skipped, another process holds the scan claim');
1145
+ else if (indexed > 0)
1146
+ log('INFO', `session-index warm: indexed ${indexed} transcript(s)`);
1147
+ }
1148
+ catch (err) {
1149
+ log('WARN', `session-index warm failed: ${err.message}`);
1150
+ }
1151
+ finally {
1152
+ sessionIndexWarmInFlight = false;
1153
+ }
1154
+ };
1155
+ void runSessionIndexWarm();
1156
+ const sessionIndexWarmInterval = setInterval(() => { void runSessionIndexWarm(); }, SESSION_INDEX_WARM_TICK_MS);
1031
1157
  // Watchdog: nudge this host's own stalled agent sessions. Gated on the
1032
1158
  // `watchdog.enabled` device-config flag (`agents watchdog enable`), so the
1033
1159
  // timer always fires but only does work when the user opted in. Overlap-safe
@@ -1042,7 +1168,7 @@ export async function runDaemon() {
1042
1168
  try {
1043
1169
  if (getConfigValue('watchdog.enabled').value !== true)
1044
1170
  return;
1045
- const { runWatchdogPass } = await import('./watchdog/service.js');
1171
+ const { runWatchdogPass } = await import('../watchdog/service.js');
1046
1172
  const result = await runWatchdogPass({ nudge: true });
1047
1173
  log('INFO', `watchdog: ${result.counts.total} live, ${result.counts.stalled} stalled, ${result.counts.nudged} nudged`);
1048
1174
  emit('watchdog.action', {
@@ -1081,8 +1207,8 @@ export async function runDaemon() {
1081
1207
  return;
1082
1208
  deviceProbeInFlight = true;
1083
1209
  try {
1084
- const { runDeviceSync } = await import('./devices/sync.js');
1085
- const { reconcilePendingSentinels, pruneDismissedPendingSentinels, } = await import('./devices/pending.js');
1210
+ const { runDeviceSync } = await import('../devices/sync.js');
1211
+ const { reconcilePendingSentinels, pruneDismissedPendingSentinels, } = await import('../devices/pending.js');
1086
1212
  const dev = await runDeviceSync({ soft: true, mode: 'refresh' });
1087
1213
  if (!dev.ok) {
1088
1214
  await pruneDismissedPendingSentinels();
@@ -1204,7 +1330,7 @@ export async function runDaemon() {
1204
1330
  // (cdp:// profile silently driven via stale ssh tunnel) or fail to
1205
1331
  // bind because the ports are still claimed.
1206
1332
  try {
1207
- const { reapOrphanedProcesses } = await import('./browser/runtime-state.js');
1333
+ const { reapOrphanedProcesses } = await import('../browser/runtime-state.js');
1208
1334
  const result = reapOrphanedProcesses();
1209
1335
  if (result.reaped > 0) {
1210
1336
  log('INFO', `Reaped ${result.reaped} orphan process(es) from prior daemon(s)`);
@@ -1232,6 +1358,25 @@ export async function runDaemon() {
1232
1358
  else {
1233
1359
  log('INFO', 'Browser IPC service disabled');
1234
1360
  }
1361
+ // 5th hosted service: signed webhook receiver(s) + their funnel (RUSH-2548).
1362
+ // Started after the broker (above) so it resolves each receiver's signing
1363
+ // secret headlessly — no AGENTS_SECRETS_PASSPHRASE, no nohup. Binds nothing
1364
+ // unless daemon/webhooks.yaml declares a receiver, so an unconfigured box no-ops.
1365
+ let webhookReceivers = null;
1366
+ if (isEnabled('webhook-receiver')) {
1367
+ try {
1368
+ webhookReceivers = await startHostedWebhookReceivers({ log });
1369
+ log('INFO', webhookReceivers.count > 0
1370
+ ? `Webhook receiver hosting ${webhookReceivers.count} receiver(s)`
1371
+ : 'Webhook receiver service enabled; no receivers declared in daemon/webhooks.yaml');
1372
+ }
1373
+ catch (err) {
1374
+ log('WARN', `Webhook receiver host skipped: ${err.message}`);
1375
+ }
1376
+ }
1377
+ else {
1378
+ log('INFO', 'Webhook receiver service disabled');
1379
+ }
1235
1380
  runMonitorTick();
1236
1381
  const monitorInterval = setInterval(runMonitorTick, MONITOR_TICK_MS);
1237
1382
  // Resource safety check: see runHealCheck above. Runs ~every 6h plus once
@@ -1264,7 +1409,7 @@ export async function runDaemon() {
1264
1409
  return;
1265
1410
  selfHealingBroker = true;
1266
1411
  try {
1267
- const { agentPing, startHostedBroker } = await import('./secrets/agent.js');
1412
+ const { agentPing, startHostedBroker } = await import('../secrets/agent.js');
1268
1413
  const reachable = (await agentPing()).reachable;
1269
1414
  if (!shouldTakeOverBroker(hostedBroker != null, reachable))
1270
1415
  return;
@@ -1296,6 +1441,15 @@ export async function runDaemon() {
1296
1441
  // 5-min cadence as the keychain reaper. Daemon-only (single executor).
1297
1442
  void runDeadPaneReap(); // kick-off on startup so the backlog clears immediately
1298
1443
  const deadPaneReapInterval = setInterval(() => { void runDeadPaneReap(); }, DEAD_PANE_REAP_TICK_MS);
1444
+ // RUSH-2622: close abandoned browser-task tabs on the same 5-min cadence,
1445
+ // reusing the daemon's own long-lived BrowserService instead of a second
1446
+ // one. Only when browser IPC is actually up — there is no service to reap
1447
+ // through otherwise.
1448
+ let browserTaskReapInterval;
1449
+ if (browserService) {
1450
+ void runBrowserTaskReap(browserService); // kick-off on startup
1451
+ browserTaskReapInterval = setInterval(() => { void runBrowserTaskReap(browserService); }, BROWSER_TASK_REAP_TICK_MS);
1452
+ }
1299
1453
  // RUSH-2367: self-terminate if this daemon's own state dir has been removed
1300
1454
  // out from under it — the shape of a leaked test-fixture daemon whose /tmp
1301
1455
  // HOME was deleted by its test's own cleanup while the process itself
@@ -1432,6 +1586,7 @@ export async function runDaemon() {
1432
1586
  log('INFO', 'Daemon shutting down');
1433
1587
  accountStateService?.stop();
1434
1588
  clearInterval(activeSessionsWarmInterval);
1589
+ clearInterval(sessionIndexWarmInterval);
1435
1590
  if (watchdogInterval)
1436
1591
  clearInterval(watchdogInterval);
1437
1592
  if (deviceProbeInterval)
@@ -1439,6 +1594,7 @@ export async function runDaemon() {
1439
1594
  stopScheduler();
1440
1595
  monitorEngine?.stop();
1441
1596
  await browserIPC?.stop();
1597
+ await webhookReceivers?.close();
1442
1598
  clearInterval(monitorInterval);
1443
1599
  if (healInterval)
1444
1600
  clearInterval(healInterval);
@@ -1449,6 +1605,8 @@ export async function runDaemon() {
1449
1605
  if (keychainReapInterval)
1450
1606
  clearInterval(keychainReapInterval);
1451
1607
  clearInterval(deadPaneReapInterval);
1608
+ if (browserTaskReapInterval)
1609
+ clearInterval(browserTaskReapInterval);
1452
1610
  if (stateDirCheckInterval)
1453
1611
  clearInterval(stateDirCheckInterval);
1454
1612
  try {
@@ -1500,16 +1658,32 @@ export function writeOwnerOnlyServiceManifest(filePath, content) {
1500
1658
  * credential at all. Routine runs authenticate through the per-account
1501
1659
  * CLAUDE_CONFIG_DIR login on this device, exactly like an interactive
1502
1660
  * `agents run`, so no credential ever touches the service manifest.
1661
+ *
1662
+ * RUSH-2639: launchd does NOT inherit `launchctl load`'s caller's process
1663
+ * environment — a spawned daemon only ever sees the login session's default
1664
+ * env plus whatever this dict adds/overrides. Before this fix the dict carried
1665
+ * only PATH, so HOME resolved to the launchd session's own value regardless of
1666
+ * what HOME the process that generated (and loaded) the plist was running
1667
+ * under. In production that's a no-op (the login session's HOME already is the
1668
+ * real HOME), but under a hermetic test harness that redirects HOME to a
1669
+ * fork-private sandbox, a launchd-started daemon silently escaped the sandbox
1670
+ * and bootstrapped `~/.agents` (.cache/.history/.system/routines) in the
1671
+ * developer's/runner's REAL home. Baking HOME (and the AGENTS_REAL_HOME seam
1672
+ * every version-home consumer honors, see tests/setup.ts) into the plist at
1673
+ * generation time makes the launchd child inherit the SAME home the caller
1674
+ * resolved, exactly like the plain detached-spawn path already does via
1675
+ * `env: {...process.env}`.
1503
1676
  */
1504
1677
  export function generateLaunchdPlist(agentsBin = getAgentsBinPath()) {
1505
1678
  const launch = getDaemonLaunch(agentsBin);
1506
1679
  const logPath = getDaemonLogPath();
1680
+ const { HOME: home, AGENTS_REAL_HOME: realHome } = serviceManifestHomeEnv();
1507
1681
  return `<?xml version="1.0" encoding="UTF-8"?>
1508
1682
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1509
1683
  <plist version="1.0">
1510
1684
  <dict>
1511
1685
  <key>Label</key>
1512
- <string>${PLIST_NAME}</string>
1686
+ <string>${daemonServiceLabel()}</string>
1513
1687
  <key>ProgramArguments</key>
1514
1688
  <array>
1515
1689
  ${[launch.command, ...launch.args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join('\n')}
@@ -1528,6 +1702,10 @@ ${[launch.command, ...launch.args].map((arg) => ` <string>${xmlEscape(arg)}</
1528
1702
  <dict>
1529
1703
  <key>PATH</key>
1530
1704
  <string>${daemonPathValue(agentsBin, ['/usr/local/bin', '/usr/bin', '/bin', '/opt/homebrew/bin', `${os.homedir()}/.bun/bin`])}</string>
1705
+ <key>HOME</key>
1706
+ <string>${xmlEscape(home)}</string>
1707
+ <key>AGENTS_REAL_HOME</key>
1708
+ <string>${xmlEscape(realHome)}</string>
1531
1709
  </dict>
1532
1710
  </dict>
1533
1711
  </plist>`;
@@ -1543,10 +1721,18 @@ function systemdExecArg(value) {
1543
1721
  * credential at all. Routine runs authenticate through the per-account
1544
1722
  * CLAUDE_CONFIG_DIR login on this device, exactly like an interactive
1545
1723
  * `agents run`, so no credential ever touches the unit file.
1724
+ *
1725
+ * RUSH-2639: same seam as `generateLaunchdPlist` — a systemd --user unit is
1726
+ * started by the user's systemd instance, not the process that generated the
1727
+ * unit, so HOME is whatever that session provides unless this file pins it.
1728
+ * Baking HOME (and AGENTS_REAL_HOME) in at generation time keeps a
1729
+ * hermetic-test-started unit inside its sandbox instead of resolving against
1730
+ * the real account home.
1546
1731
  */
1547
1732
  export function generateSystemdUnit(agentsBin = getAgentsBinPath()) {
1548
1733
  const launch = getDaemonLaunch(agentsBin);
1549
1734
  const execStart = [launch.command, ...launch.args].map(systemdExecArg).join(' ');
1735
+ const { HOME: home, AGENTS_REAL_HOME: realHome } = serviceManifestHomeEnv();
1550
1736
  return `[Unit]
1551
1737
  Description=Agents Daemon - Scheduled Job Runner
1552
1738
  After=network.target
@@ -1559,6 +1745,8 @@ ExecStart=${execStart}
1559
1745
  Restart=always
1560
1746
  RestartSec=${DAEMON_THROTTLE_SECONDS}
1561
1747
  Environment=PATH=${daemonPathValue(agentsBin, ['/usr/local/bin', '/usr/bin', '/bin'])}
1748
+ Environment=HOME=${home}
1749
+ Environment=AGENTS_REAL_HOME=${realHome}
1562
1750
 
1563
1751
  [Install]
1564
1752
  WantedBy=default.target`;
@@ -1577,12 +1765,12 @@ export { getAgentsBinPath };
1577
1765
  function readServiceManagerPid(platform = os.platform()) {
1578
1766
  try {
1579
1767
  if (platform === 'linux') {
1580
- const out = execFileSync('systemctl', ['--user', 'show', '-p', 'MainPID', '--value', SYSTEMD_UNIT], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
1768
+ const out = execFileSync('systemctl', ['--user', 'show', '-p', 'MainPID', '--value', daemonSystemdUnitName()], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
1581
1769
  const pid = parseInt(out, 10);
1582
1770
  return !isNaN(pid) && pid > 0 ? pid : null;
1583
1771
  }
1584
1772
  if (platform === 'darwin') {
1585
- const out = execFileSync('launchctl', ['list', PLIST_NAME], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
1773
+ const out = execFileSync('launchctl', ['list', daemonServiceLabel()], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
1586
1774
  const m = out.match(/"PID"\s*=\s*(\d+)/);
1587
1775
  if (m) {
1588
1776
  const pid = parseInt(m[1], 10);
@@ -1767,8 +1955,8 @@ function startDaemonLocked(agentsBin, releaseLock) {
1767
1955
  // itself at startup); owner-only to keep the PATH/log surface private.
1768
1956
  writeOwnerOnlyServiceManifest(unitPath, generateSystemdUnit(agentsBin));
1769
1957
  execFileSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf-8' });
1770
- execFileSync('systemctl', ['--user', 'enable', SYSTEMD_UNIT], { encoding: 'utf-8' });
1771
- execFileSync('systemctl', ['--user', 'start', SYSTEMD_UNIT], { encoding: 'utf-8' });
1958
+ execFileSync('systemctl', ['--user', 'enable', daemonSystemdUnitName()], { encoding: 'utf-8' });
1959
+ execFileSync('systemctl', ['--user', 'start', daemonSystemdUnitName()], { encoding: 'utf-8' });
1772
1960
  // Launch issued — the child needs this lock to claim (RUSH-2417).
1773
1961
  releaseLock();
1774
1962
  const pid = waitForPid(3000) ?? readServiceManagerPid();
@@ -2089,8 +2277,8 @@ export function stopDaemon() {
2089
2277
  }
2090
2278
  if (platform === 'linux') {
2091
2279
  try {
2092
- execFileSync('systemctl', ['--user', 'stop', SYSTEMD_UNIT], { encoding: 'utf-8' });
2093
- execFileSync('systemctl', ['--user', 'disable', SYSTEMD_UNIT], { encoding: 'utf-8' });
2280
+ execFileSync('systemctl', ['--user', 'stop', daemonSystemdUnitName()], { encoding: 'utf-8' });
2281
+ execFileSync('systemctl', ['--user', 'disable', daemonSystemdUnitName()], { encoding: 'utf-8' });
2094
2282
  }
2095
2283
  catch (err) {
2096
2284
  if (process.env.AGENTS_DEBUG) {
@@ -7,7 +7,7 @@
7
7
  * enabled without pulling in the whole daemon lifecycle.
8
8
  */
9
9
  /** Every service the daemon can host. IDs are kebab-case and stable. */
10
- export type DaemonServiceId = 'secrets-broker' | 'scheduler' | 'monitors' | 'browser-ipc' | 'self-heal' | 'keychain-reap' | 'account-state' | 'watchdog' | 'device-probe' | 'state-dir-check';
10
+ export type DaemonServiceId = 'secrets-broker' | 'scheduler' | 'monitors' | 'browser-ipc' | 'webhook-receiver' | 'self-heal' | 'keychain-reap' | 'account-state' | 'watchdog' | 'device-probe' | 'state-dir-check';
11
11
  /** Human-readable metadata for each service. */
12
12
  export interface DaemonServiceDef {
13
13
  id: DaemonServiceId;
@@ -32,6 +32,11 @@ export const DAEMON_SERVICES = [
32
32
  title: 'Browser IPC',
33
33
  description: 'Keeps a supervised browser automation IPC socket available for sessions.',
34
34
  },
35
+ {
36
+ id: 'webhook-receiver',
37
+ title: 'Webhook receiver',
38
+ description: 'Hosts signed GitHub/Linear webhook ingress declared in daemon/webhooks.yaml, drawing signing secrets from the broker; binds nothing when no receivers are declared.',
39
+ },
35
40
  {
36
41
  id: 'self-heal',
37
42
  title: 'Self-heal registry',
@@ -48,3 +48,28 @@ export declare function runActiveSessionsWarmTick(opts?: {
48
48
  }): Promise<{
49
49
  sessions: number;
50
50
  }>;
51
+ /**
52
+ * Session-index warm (RUSH-2682): incrementally scan THIS host's transcript dirs
53
+ * into the local SQLite index on a timer, so a session started HERE reaches this
54
+ * box's index within seconds instead of on the next unrelated `agents sessions*`
55
+ * call. Indexing was otherwise lazy — only `discoverSessions` writes the index,
56
+ * and nothing scheduled it — which inverted freshness: a peer's session arrived
57
+ * via sync in ~0s while a locally-started one sat unindexed for minutes.
58
+ *
59
+ * The scan is incremental (only files whose mtime/size changed) and single-flight
60
+ * across processes via the DB scan claim, so a foreground `agents sessions*` and
61
+ * this tick never double-scan. The daemon is the single scheduled executor,
62
+ * consistent with the one-scheduler rule.
63
+ *
64
+ * Calls `scanSessionsIncremental`, NOT `discoverSessions` (RUSH-2691). A bare
65
+ * `discoverSessions()` ends in a listing query that defaults its cwd filter to
66
+ * `process.cwd()` — the daemon's, i.e. `$HOME` — and caps at 50, so its row count
67
+ * described "sessions whose cwd is exactly $HOME", which is 0 on a normal box no
68
+ * matter how much the scan indexed. The tick reported that 0 every 20s while
69
+ * still paying for the query's existence check and Linear fetch. Scanning
70
+ * directly reports what was actually parsed and skips the query entirely.
71
+ */
72
+ export declare function runSessionIndexWarmTick(): Promise<{
73
+ indexed: number;
74
+ claimed: boolean;
75
+ }>;
@@ -77,7 +77,7 @@ export async function runUsageRefreshTick() {
77
77
  return;
78
78
  }
79
79
  const { runUsageRefresh, buildLocalUsageAccounts } = await import('./usage-refresh.js');
80
- const { writeClaudeUsageCache } = await import('./usage.js');
80
+ const { writeClaudeUsageCache } = await import('./accounting/usage.js');
81
81
  const { usageRateLimitedUntil } = await import('./usage-backoff.js');
82
82
  const r = await runUsageRefresh({
83
83
  listAccounts: buildLocalUsageAccounts,
@@ -106,3 +106,33 @@ export async function runActiveSessionsWarmTick(opts = {}) {
106
106
  console.log(`active-sessions warm: ${r.sessions.length} session(s) published`);
107
107
  return { sessions: r.sessions.length };
108
108
  }
109
+ /**
110
+ * Session-index warm (RUSH-2682): incrementally scan THIS host's transcript dirs
111
+ * into the local SQLite index on a timer, so a session started HERE reaches this
112
+ * box's index within seconds instead of on the next unrelated `agents sessions*`
113
+ * call. Indexing was otherwise lazy — only `discoverSessions` writes the index,
114
+ * and nothing scheduled it — which inverted freshness: a peer's session arrived
115
+ * via sync in ~0s while a locally-started one sat unindexed for minutes.
116
+ *
117
+ * The scan is incremental (only files whose mtime/size changed) and single-flight
118
+ * across processes via the DB scan claim, so a foreground `agents sessions*` and
119
+ * this tick never double-scan. The daemon is the single scheduled executor,
120
+ * consistent with the one-scheduler rule.
121
+ *
122
+ * Calls `scanSessionsIncremental`, NOT `discoverSessions` (RUSH-2691). A bare
123
+ * `discoverSessions()` ends in a listing query that defaults its cwd filter to
124
+ * `process.cwd()` — the daemon's, i.e. `$HOME` — and caps at 50, so its row count
125
+ * described "sessions whose cwd is exactly $HOME", which is 0 on a normal box no
126
+ * matter how much the scan indexed. The tick reported that 0 every 20s while
127
+ * still paying for the query's existence check and Linear fetch. Scanning
128
+ * directly reports what was actually parsed and skips the query entirely.
129
+ */
130
+ export async function runSessionIndexWarmTick() {
131
+ const { scanSessionsIncremental } = await import('./session/discover.js');
132
+ const { claimed, scanned } = await scanSessionsIncremental();
133
+ // A skipped claim is not a failure — a foreground `agents sessions*` is
134
+ // scanning right now and this tick would be a duplicate.
135
+ if (!claimed)
136
+ return { indexed: 0, claimed: false };
137
+ return { indexed: scanned, claimed: true };
138
+ }