@phnx-labs/agents-cli 1.22.120 → 1.22.121

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 (1603) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +19 -2
  3. package/dist/bootstrap.d.ts +0 -15
  4. package/dist/bootstrap.js +10 -409
  5. package/dist/cli/command-registry.d.ts +0 -58
  6. package/dist/cli/command-registry.js +2 -74
  7. package/dist/commands/accounts.d.ts +0 -17
  8. package/dist/commands/accounts.js +1 -52
  9. package/dist/commands/alias.d.ts +0 -12
  10. package/dist/commands/alias.js +0 -6
  11. package/dist/commands/apply.d.ts +0 -15
  12. package/dist/commands/apply.js +0 -63
  13. package/dist/commands/audit.d.ts +0 -12
  14. package/dist/commands/audit.js +0 -13
  15. package/dist/commands/auth.js +4 -12
  16. package/dist/commands/beta.d.ts +0 -6
  17. package/dist/commands/beta.js +2 -6
  18. package/dist/commands/browser-sessions-picker.d.ts +0 -8
  19. package/dist/commands/browser-sessions-picker.js +0 -26
  20. package/dist/commands/browser.d.ts +0 -57
  21. package/dist/commands/browser.js +0 -80
  22. package/dist/commands/budget.d.ts +0 -13
  23. package/dist/commands/budget.js +0 -2
  24. package/dist/commands/cli.d.ts +0 -12
  25. package/dist/commands/cli.js +0 -4
  26. package/dist/commands/cloud.d.ts +0 -9
  27. package/dist/commands/cloud.js +0 -43
  28. package/dist/commands/commands.d.ts +0 -9
  29. package/dist/commands/commands.js +0 -18
  30. package/dist/commands/computer-sessions-picker.d.ts +0 -11
  31. package/dist/commands/computer-sessions-picker.js +0 -29
  32. package/dist/commands/computer.d.ts +0 -62
  33. package/dist/commands/computer.js +3 -114
  34. package/dist/commands/config.d.ts +0 -14
  35. package/dist/commands/config.js +21 -60
  36. package/dist/commands/cost.d.ts +0 -11
  37. package/dist/commands/cost.js +1 -17
  38. package/dist/commands/daemon-test-harness.d.ts +0 -32
  39. package/dist/commands/daemon-test-harness.js +1 -36
  40. package/dist/commands/daemon.d.ts +0 -46
  41. package/dist/commands/daemon.js +8 -255
  42. package/dist/commands/detach-core.d.ts +0 -28
  43. package/dist/commands/detach-core.js +0 -30
  44. package/dist/commands/detach.d.ts +1 -12
  45. package/dist/commands/detach.js +16 -52
  46. package/dist/commands/device-config-test-harness.js +0 -12
  47. package/dist/commands/doctor.d.ts +0 -103
  48. package/dist/commands/doctor.js +3 -279
  49. package/dist/commands/events.d.ts +0 -29
  50. package/dist/commands/events.js +1 -55
  51. package/dist/commands/exec.d.ts +0 -163
  52. package/dist/commands/exec.js +15 -964
  53. package/dist/commands/factory.d.ts +0 -6
  54. package/dist/commands/factory.js +0 -2
  55. package/dist/commands/feed-watch.js +0 -20
  56. package/dist/commands/feed.d.ts +0 -38
  57. package/dist/commands/feed.js +1 -151
  58. package/dist/commands/feedback.d.ts +0 -5
  59. package/dist/commands/feedback.js +0 -5
  60. package/dist/commands/fleet-capture.d.ts +0 -12
  61. package/dist/commands/fleet-capture.js +1 -39
  62. package/dist/commands/focus.d.ts +6 -94
  63. package/dist/commands/focus.js +13 -183
  64. package/dist/commands/fork.d.ts +0 -21
  65. package/dist/commands/fork.js +0 -45
  66. package/dist/commands/funnel.d.ts +0 -3
  67. package/dist/commands/go.d.ts +2 -71
  68. package/dist/commands/go.js +5 -99
  69. package/dist/commands/harness-hooks.d.ts +0 -41
  70. package/dist/commands/harness-hooks.js +0 -48
  71. package/dist/commands/harness-wizard.d.ts +0 -137
  72. package/dist/commands/harness-wizard.js +0 -109
  73. package/dist/commands/harness.d.ts +0 -63
  74. package/dist/commands/harness.js +0 -103
  75. package/dist/commands/hooks.d.ts +0 -9
  76. package/dist/commands/hooks.js +0 -26
  77. package/dist/commands/humans.d.ts +0 -8
  78. package/dist/commands/humans.js +0 -8
  79. package/dist/commands/import.d.ts +0 -22
  80. package/dist/commands/import.js +0 -79
  81. package/dist/commands/insights.d.ts +0 -37
  82. package/dist/commands/insights.js +3 -115
  83. package/dist/commands/inspect.d.ts +0 -117
  84. package/dist/commands/inspect.js +2 -448
  85. package/dist/commands/lease.d.ts +0 -28
  86. package/dist/commands/lease.js +0 -43
  87. package/dist/commands/logs.d.ts +0 -23
  88. package/dist/commands/logs.js +0 -30
  89. package/dist/commands/mailboxes.d.ts +0 -18
  90. package/dist/commands/mailboxes.js +0 -29
  91. package/dist/commands/mcp.d.ts +0 -9
  92. package/dist/commands/mcp.js +0 -53
  93. package/dist/commands/memory.d.ts +0 -7
  94. package/dist/commands/memory.js +0 -7
  95. package/dist/commands/menubar.d.ts +0 -8
  96. package/dist/commands/menubar.js +0 -24
  97. package/dist/commands/message.d.ts +0 -23
  98. package/dist/commands/message.js +0 -50
  99. package/dist/commands/mine.d.ts +0 -22
  100. package/dist/commands/mine.js +0 -21
  101. package/dist/commands/models.d.ts +0 -8
  102. package/dist/commands/models.js +0 -33
  103. package/dist/commands/modes.d.ts +0 -10
  104. package/dist/commands/modes.js +0 -13
  105. package/dist/commands/monitors.d.ts +0 -9
  106. package/dist/commands/monitors.js +3 -160
  107. package/dist/commands/open.d.ts +0 -26
  108. package/dist/commands/open.js +0 -10
  109. package/dist/commands/output.d.ts +0 -18
  110. package/dist/commands/output.js +1 -34
  111. package/dist/commands/packages-materialize.d.ts +0 -11
  112. package/dist/commands/packages-materialize.js +0 -3
  113. package/dist/commands/packages.d.ts +0 -10
  114. package/dist/commands/packages.js +0 -50
  115. package/dist/commands/perf.d.ts +0 -41
  116. package/dist/commands/perf.js +0 -55
  117. package/dist/commands/permissions.d.ts +0 -9
  118. package/dist/commands/permissions.js +0 -43
  119. package/dist/commands/plugins.d.ts +0 -14
  120. package/dist/commands/plugins.js +2 -55
  121. package/dist/commands/profiles.d.ts +0 -25
  122. package/dist/commands/profiles.js +0 -27
  123. package/dist/commands/projects-todo.d.ts +2 -0
  124. package/dist/commands/projects-todo.js +132 -0
  125. package/dist/commands/projects.d.ts +0 -85
  126. package/dist/commands/projects.js +344 -291
  127. package/dist/commands/prune.js +0 -30
  128. package/dist/commands/ps-roster.d.ts +92 -0
  129. package/dist/commands/ps-roster.js +744 -0
  130. package/dist/commands/ps.d.ts +6 -0
  131. package/dist/commands/ps.js +98 -0
  132. package/dist/commands/refresh-rules.d.ts +0 -13
  133. package/dist/commands/refresh-rules.js +0 -14
  134. package/dist/commands/reminders.d.ts +0 -7
  135. package/dist/commands/repo.d.ts +0 -89
  136. package/dist/commands/repo.js +7 -205
  137. package/dist/commands/resource-view.d.ts +0 -33
  138. package/dist/commands/resource-view.js +0 -49
  139. package/dist/commands/resume.d.ts +1 -19
  140. package/dist/commands/resume.js +0 -50
  141. package/dist/commands/route.d.ts +0 -10
  142. package/dist/commands/route.js +0 -19
  143. package/dist/commands/routines.d.ts +0 -42
  144. package/dist/commands/routines.js +5 -290
  145. package/dist/commands/routines.test-fixture.d.ts +0 -34
  146. package/dist/commands/routines.test-fixture.js +4 -100
  147. package/dist/commands/rules.d.ts +0 -9
  148. package/dist/commands/rules.js +1 -6
  149. package/dist/commands/run-account-picker.d.ts +0 -59
  150. package/dist/commands/run-account-picker.js +0 -71
  151. package/dist/commands/run-broadcast.d.ts +0 -9
  152. package/dist/commands/run-broadcast.js +0 -7
  153. package/dist/commands/run-cloud.d.ts +0 -15
  154. package/dist/commands/run-cloud.js +0 -37
  155. package/dist/commands/run-device-picker.d.ts +0 -25
  156. package/dist/commands/run-device-picker.js +1 -53
  157. package/dist/commands/secrets-passthrough.d.ts +0 -31
  158. package/dist/commands/secrets-passthrough.js +0 -10
  159. package/dist/commands/send.d.ts +0 -20
  160. package/dist/commands/send.js +14 -18
  161. package/dist/commands/sessions-backfill.d.ts +0 -7
  162. package/dist/commands/sessions-backfill.js +1 -16
  163. package/dist/commands/sessions-backup-setup.js +0 -11
  164. package/dist/commands/sessions-bookmark.d.ts +0 -8
  165. package/dist/commands/sessions-bookmark.js +0 -28
  166. package/dist/commands/sessions-browser.d.ts +2 -117
  167. package/dist/commands/sessions-browser.js +5 -243
  168. package/dist/commands/sessions-export.d.ts +1 -26
  169. package/dist/commands/sessions-export.js +3 -81
  170. package/dist/commands/sessions-import.d.ts +0 -14
  171. package/dist/commands/sessions-import.js +0 -59
  172. package/dist/commands/sessions-inject.d.ts +0 -60
  173. package/dist/commands/sessions-inject.js +3 -96
  174. package/dist/commands/sessions-migrate.d.ts +2 -37
  175. package/dist/commands/sessions-migrate.js +7 -159
  176. package/dist/commands/sessions-optimize.d.ts +0 -12
  177. package/dist/commands/sessions-picker-factory.d.ts +0 -7
  178. package/dist/commands/sessions-picker-factory.js +0 -8
  179. package/dist/commands/sessions-picker.d.ts +4 -103
  180. package/dist/commands/sessions-picker.js +13 -352
  181. package/dist/commands/sessions-render.d.ts +1 -2
  182. package/dist/commands/sessions-render.js +3 -7
  183. package/dist/commands/sessions-resume.d.ts +1 -24
  184. package/dist/commands/sessions-resume.js +2 -65
  185. package/dist/commands/sessions-share.d.ts +1 -9
  186. package/dist/commands/sessions-share.js +1 -45
  187. package/dist/commands/sessions-stats.d.ts +0 -24
  188. package/dist/commands/sessions-stats.js +0 -49
  189. package/dist/commands/sessions-stop.d.ts +1 -20
  190. package/dist/commands/sessions-stop.js +4 -9
  191. package/dist/commands/sessions-tail.d.ts +1 -19
  192. package/dist/commands/sessions-tail.js +3 -36
  193. package/dist/commands/sessions-trace.d.ts +5 -36
  194. package/dist/commands/sessions-trace.js +7 -82
  195. package/dist/commands/sessions.d.ts +1 -666
  196. package/dist/commands/sessions.js +30 -2279
  197. package/dist/commands/sessions.test-fixture.d.ts +0 -9
  198. package/dist/commands/sessions.test-fixture.js +8 -23
  199. package/dist/commands/setup-accounts.d.ts +0 -6
  200. package/dist/commands/setup-browser.d.ts +0 -23
  201. package/dist/commands/setup-browser.js +0 -28
  202. package/dist/commands/setup-computer.d.ts +0 -16
  203. package/dist/commands/setup-computer.js +0 -28
  204. package/dist/commands/setup-fleet.d.ts +0 -8
  205. package/dist/commands/setup-fleet.js +0 -11
  206. package/dist/commands/setup-mine.d.ts +0 -14
  207. package/dist/commands/setup-mine.js +0 -25
  208. package/dist/commands/setup-preferences.d.ts +0 -47
  209. package/dist/commands/setup-preferences.js +0 -53
  210. package/dist/commands/setup-secrets.d.ts +2 -24
  211. package/dist/commands/setup-secrets.js +0 -24
  212. package/dist/commands/setup-term.d.ts +0 -21
  213. package/dist/commands/setup-term.js +0 -21
  214. package/dist/commands/setup-terminal.d.ts +0 -1
  215. package/dist/commands/setup-terminal.js +0 -1
  216. package/dist/commands/setup-tools.d.ts +2 -0
  217. package/dist/commands/setup-tools.js +73 -0
  218. package/dist/commands/setup.d.ts +0 -20
  219. package/dist/commands/setup.js +4 -57
  220. package/dist/commands/skills.d.ts +0 -9
  221. package/dist/commands/skills.js +1 -22
  222. package/dist/commands/snapshot.d.ts +0 -9
  223. package/dist/commands/snapshot.js +0 -10
  224. package/dist/commands/ssh.d.ts +0 -64
  225. package/dist/commands/ssh.js +9 -420
  226. package/dist/commands/status.d.ts +0 -12
  227. package/dist/commands/status.js +0 -26
  228. package/dist/commands/subagents.d.ts +0 -8
  229. package/dist/commands/subagents.js +0 -31
  230. package/dist/commands/sync.d.ts +0 -37
  231. package/dist/commands/sync.js +0 -256
  232. package/dist/commands/teams-picker.d.ts +0 -3
  233. package/dist/commands/teams-picker.js +0 -34
  234. package/dist/commands/teams.d.ts +1 -76
  235. package/dist/commands/teams.js +9 -477
  236. package/dist/commands/tmux.d.ts +0 -25
  237. package/dist/commands/tmux.js +0 -46
  238. package/dist/commands/traces.js +0 -19
  239. package/dist/commands/trash.d.ts +0 -17
  240. package/dist/commands/trash.js +1 -11
  241. package/dist/commands/uninstall.d.ts +0 -9
  242. package/dist/commands/uninstall.js +0 -6
  243. package/dist/commands/update.d.ts +0 -22
  244. package/dist/commands/update.js +0 -44
  245. package/dist/commands/upgrade.d.ts +0 -1
  246. package/dist/commands/upgrade.js +0 -1
  247. package/dist/commands/utils.d.ts +0 -85
  248. package/dist/commands/utils.js +1 -95
  249. package/dist/commands/versions.d.ts +0 -16
  250. package/dist/commands/versions.js +0 -134
  251. package/dist/commands/view.d.ts +0 -90
  252. package/dist/commands/view.js +4 -343
  253. package/dist/commands/watchdog.d.ts +0 -19
  254. package/dist/commands/watchdog.js +3 -43
  255. package/dist/commands/webhook.d.ts +0 -8
  256. package/dist/commands/webhook.js +0 -14
  257. package/dist/commands/workflows.d.ts +0 -8
  258. package/dist/commands/workflows.js +0 -6
  259. package/dist/index.d.ts +0 -25
  260. package/dist/index.js +5 -119
  261. package/dist/lib/account-capabilities.d.ts +0 -34
  262. package/dist/lib/account-capabilities.js +0 -32
  263. package/dist/lib/account-catalog.d.ts +0 -128
  264. package/dist/lib/account-catalog.js +8 -154
  265. package/dist/lib/account-provider-registry.d.ts +0 -14
  266. package/dist/lib/account-provider-registry.js +0 -8
  267. package/dist/lib/account-registry.d.ts +0 -74
  268. package/dist/lib/account-registry.js +14 -189
  269. package/dist/lib/account-schema.d.ts +0 -19
  270. package/dist/lib/account-schema.js +0 -43
  271. package/dist/lib/accounting/account-launch.d.ts +0 -15
  272. package/dist/lib/accounting/account-launch.js +1 -3
  273. package/dist/lib/accounting/account-pool-collect.d.ts +0 -26
  274. package/dist/lib/accounting/account-pool-collect.js +0 -42
  275. package/dist/lib/accounting/account-pool.d.ts +0 -41
  276. package/dist/lib/accounting/account-pool.js +0 -17
  277. package/dist/lib/accounting/capacity.d.ts +0 -32
  278. package/dist/lib/accounting/capacity.js +0 -32
  279. package/dist/lib/accounting/rotate.d.ts +0 -518
  280. package/dist/lib/accounting/rotate.js +4 -598
  281. package/dist/lib/accounting/usage-ingest.d.ts +0 -1
  282. package/dist/lib/accounting/usage-ingest.js +0 -44
  283. package/dist/lib/accounting/usage-sync.d.ts +0 -60
  284. package/dist/lib/accounting/usage-sync.js +4 -51
  285. package/dist/lib/accounting/usage.d.ts +0 -632
  286. package/dist/lib/accounting/usage.js +14 -1055
  287. package/dist/lib/accounts/add.d.ts +0 -60
  288. package/dist/lib/accounts/add.js +0 -127
  289. package/dist/lib/accounts/auth-operation-lock.d.ts +0 -1
  290. package/dist/lib/accounts/auth-operation-lock.js +0 -4
  291. package/dist/lib/accounts/migrate.d.ts +0 -9
  292. package/dist/lib/accounts/migrate.js +3 -42
  293. package/dist/lib/accounts/slots.d.ts +0 -25
  294. package/dist/lib/accounts/slots.js +1 -49
  295. package/dist/lib/acp/client.d.ts +0 -16
  296. package/dist/lib/acp/client.js +0 -29
  297. package/dist/lib/acp/harnesses.d.ts +0 -15
  298. package/dist/lib/acp/harnesses.js +0 -16
  299. package/dist/lib/acp/run.d.ts +0 -8
  300. package/dist/lib/acp/run.js +0 -8
  301. package/dist/lib/actor.d.ts +1 -71
  302. package/dist/lib/actor.js +1 -99
  303. package/dist/lib/add-dir.d.ts +0 -46
  304. package/dist/lib/add-dir.js +1 -70
  305. package/dist/lib/address.d.ts +0 -17
  306. package/dist/lib/address.js +0 -15
  307. package/dist/lib/agent-cli-commands.d.ts +0 -10
  308. package/dist/lib/agent-cli-commands.js +0 -10
  309. package/dist/lib/agent-modes.d.ts +0 -29
  310. package/dist/lib/agent-modes.js +0 -31
  311. package/dist/lib/agent-spec/agents.d.ts +0 -426
  312. package/dist/lib/agent-spec/agents.js +37 -924
  313. package/dist/lib/agent-spec/index.d.ts +0 -5
  314. package/dist/lib/agent-spec/index.js +0 -14
  315. package/dist/lib/agent-spec/materialize.d.ts +0 -10
  316. package/dist/lib/agent-spec/materialize.js +2 -139
  317. package/dist/lib/agent-spec/package-resolve.d.ts +0 -8
  318. package/dist/lib/agent-spec/package-resolve.js +1 -44
  319. package/dist/lib/agent-spec/package-schema.d.ts +0 -2
  320. package/dist/lib/agent-spec/package-schema.js +0 -11
  321. package/dist/lib/agent-spec/package-types.d.ts +0 -27
  322. package/dist/lib/agent-spec/package-types.js +0 -1
  323. package/dist/lib/agent-spec/primitives.d.ts +0 -18
  324. package/dist/lib/agent-spec/primitives.js +0 -27
  325. package/dist/lib/agent-spec/provider.js +0 -2
  326. package/dist/lib/agent-spec/resolve.d.ts +0 -23
  327. package/dist/lib/agent-spec/resolve.js +0 -41
  328. package/dist/lib/agent-spec/types.d.ts +0 -34
  329. package/dist/lib/agent-spec/types.js +0 -6
  330. package/dist/lib/agents.d.ts +0 -1
  331. package/dist/lib/agents.js +0 -1
  332. package/dist/lib/analytics/mix-commands.d.ts +0 -23
  333. package/dist/lib/analytics/mix-commands.js +0 -26
  334. package/dist/lib/analytics/recipes.d.ts +0 -1
  335. package/dist/lib/analytics/recipes.js +5 -10
  336. package/dist/lib/analytics/usage-db.js +4 -6
  337. package/dist/lib/answer-router.d.ts +0 -61
  338. package/dist/lib/answer-router.js +0 -57
  339. package/dist/lib/app-bundle-install.d.ts +0 -13
  340. package/dist/lib/app-bundle-install.js +0 -50
  341. package/dist/lib/artifact-actions.d.ts +0 -15
  342. package/dist/lib/artifact-actions.js +0 -18
  343. package/dist/lib/artifacts-client.d.ts +0 -10
  344. package/dist/lib/artifacts-client.js +0 -17
  345. package/dist/lib/ask-classifier.d.ts +0 -38
  346. package/dist/lib/ask-classifier.js +0 -27
  347. package/dist/lib/audit/log.d.ts +0 -47
  348. package/dist/lib/audit/log.js +1 -59
  349. package/dist/lib/auth-health.d.ts +0 -213
  350. package/dist/lib/auth-health.js +16 -266
  351. package/dist/lib/auth-mint.d.ts +0 -83
  352. package/dist/lib/auth-mint.js +8 -85
  353. package/dist/lib/auto-pull-worker.d.ts +0 -10
  354. package/dist/lib/auto-pull-worker.js +3 -48
  355. package/dist/lib/auto-pull.d.ts +0 -64
  356. package/dist/lib/auto-pull.js +0 -79
  357. package/dist/lib/beta.d.ts +0 -9
  358. package/dist/lib/beta.js +0 -9
  359. package/dist/lib/binary-shadow.d.ts +0 -22
  360. package/dist/lib/binary-shadow.js +4 -34
  361. package/dist/lib/boot-profile.d.ts +0 -10
  362. package/dist/lib/boot-profile.js +1 -31
  363. package/dist/lib/brand.d.ts +0 -20
  364. package/dist/lib/brand.js +0 -50
  365. package/dist/lib/browser/context.d.ts +0 -54
  366. package/dist/lib/browser/context.js +0 -49
  367. package/dist/lib/browser/paths.d.ts +0 -21
  368. package/dist/lib/browser/paths.js +0 -26
  369. package/dist/lib/browser/record.d.ts +0 -34
  370. package/dist/lib/browser/record.js +0 -38
  371. package/dist/lib/browser/sessions-list.d.ts +1 -79
  372. package/dist/lib/browser/sessions-list.js +2 -98
  373. package/dist/lib/browser-client.d.ts +0 -112
  374. package/dist/lib/browser-client.js +1 -90
  375. package/dist/lib/budget/config.d.ts +0 -6
  376. package/dist/lib/budget/config.js +0 -29
  377. package/dist/lib/budget/enforce.d.ts +0 -50
  378. package/dist/lib/budget/enforce.js +0 -27
  379. package/dist/lib/budget/ledger.d.ts +0 -23
  380. package/dist/lib/budget/ledger.js +0 -28
  381. package/dist/lib/budget/live-cloud.d.ts +0 -26
  382. package/dist/lib/budget/live-cloud.js +0 -12
  383. package/dist/lib/budget/live-team.d.ts +0 -17
  384. package/dist/lib/budget/live-team.js +1 -35
  385. package/dist/lib/budget/preflight.d.ts +0 -51
  386. package/dist/lib/budget/preflight.js +0 -36
  387. package/dist/lib/byok-usage.d.ts +0 -20
  388. package/dist/lib/byok-usage.js +1 -24
  389. package/dist/lib/capabilities.d.ts +0 -24
  390. package/dist/lib/capabilities.js +0 -29
  391. package/dist/lib/catchup.d.ts +0 -87
  392. package/dist/lib/catchup.js +1 -81
  393. package/dist/lib/channels/owner-forward.d.ts +0 -70
  394. package/dist/lib/channels/owner-forward.js +1 -43
  395. package/dist/lib/channels/owner-sink.js +0 -21
  396. package/dist/lib/channels/providers/desktop.d.ts +0 -37
  397. package/dist/lib/channels/providers/desktop.js +0 -72
  398. package/dist/lib/channels/providers/index.d.ts +0 -1
  399. package/dist/lib/channels/providers/index.js +2 -6
  400. package/dist/lib/channels/providers/mailbox.js +0 -7
  401. package/dist/lib/channels/providers/openclaw-telegram.js +0 -8
  402. package/dist/lib/channels/providers/rush.d.ts +0 -2
  403. package/dist/lib/channels/providers/rush.js +0 -26
  404. package/dist/lib/channels/providers/session.d.ts +2 -0
  405. package/dist/lib/channels/providers/session.js +26 -0
  406. package/dist/lib/channels/registry.d.ts +0 -27
  407. package/dist/lib/channels/registry.js +0 -10
  408. package/dist/lib/channels/resolve.d.ts +0 -19
  409. package/dist/lib/channels/resolve.js +0 -2
  410. package/dist/lib/channels/send.d.ts +0 -50
  411. package/dist/lib/channels/send.js +0 -25
  412. package/dist/lib/checkpoint.d.ts +0 -36
  413. package/dist/lib/checkpoint.js +0 -28
  414. package/dist/lib/claude-account-token.d.ts +0 -98
  415. package/dist/lib/claude-account-token.js +7 -169
  416. package/dist/lib/claude-statusline.d.ts +0 -41
  417. package/dist/lib/claude-statusline.js +0 -68
  418. package/dist/lib/cli-entry.d.ts +0 -26
  419. package/dist/lib/cli-entry.js +1 -61
  420. package/dist/lib/cli-resources.d.ts +31 -89
  421. package/dist/lib/cli-resources.js +116 -152
  422. package/dist/lib/cloud/antigravity.d.ts +0 -29
  423. package/dist/lib/cloud/antigravity.js +0 -36
  424. package/dist/lib/cloud/codex.d.ts +0 -8
  425. package/dist/lib/cloud/codex.js +0 -41
  426. package/dist/lib/cloud/cursor.d.ts +0 -5
  427. package/dist/lib/cloud/cursor.js +2 -7
  428. package/dist/lib/cloud/dispatch.d.ts +0 -13
  429. package/dist/lib/cloud/dispatch.js +1 -48
  430. package/dist/lib/cloud/factory.d.ts +0 -52
  431. package/dist/lib/cloud/factory.js +3 -67
  432. package/dist/lib/cloud/host.d.ts +0 -39
  433. package/dist/lib/cloud/host.js +5 -48
  434. package/dist/lib/cloud/registry.d.ts +0 -25
  435. package/dist/lib/cloud/registry.js +0 -29
  436. package/dist/lib/cloud/rush.d.ts +0 -47
  437. package/dist/lib/cloud/rush.js +1 -82
  438. package/dist/lib/cloud/session-index.d.ts +0 -27
  439. package/dist/lib/cloud/session-index.js +1 -32
  440. package/dist/lib/cloud/store.d.ts +0 -18
  441. package/dist/lib/cloud/store.js +0 -28
  442. package/dist/lib/cloud/stream.d.ts +0 -14
  443. package/dist/lib/cloud/stream.js +0 -25
  444. package/dist/lib/cloud/types.d.ts +0 -168
  445. package/dist/lib/cloud/types.js +0 -46
  446. package/dist/lib/cloudflare/creds.d.ts +0 -2
  447. package/dist/lib/cloudflare/creds.js +0 -14
  448. package/dist/lib/cloudflare/provision.d.ts +0 -8
  449. package/dist/lib/cloudflare/provision.js +1 -19
  450. package/dist/lib/codex-home.d.ts +0 -60
  451. package/dist/lib/codex-home.js +2 -72
  452. package/dist/lib/codex-policy.d.ts +0 -10
  453. package/dist/lib/codex-policy.js +0 -13
  454. package/dist/lib/command-skills.d.ts +0 -10
  455. package/dist/lib/command-skills.js +0 -10
  456. package/dist/lib/commands.d.ts +0 -74
  457. package/dist/lib/commands.js +0 -112
  458. package/dist/lib/comms-render.d.ts +0 -5
  459. package/dist/lib/comms-render.js +0 -5
  460. package/dist/lib/computer/context.d.ts +0 -23
  461. package/dist/lib/computer/context.js +0 -14
  462. package/dist/lib/computer/policy.d.ts +0 -20
  463. package/dist/lib/computer/policy.js +0 -53
  464. package/dist/lib/computer/record.d.ts +0 -33
  465. package/dist/lib/computer/record.js +1 -48
  466. package/dist/lib/computer/sessions-list.d.ts +1 -128
  467. package/dist/lib/computer/sessions-list.js +6 -165
  468. package/dist/lib/computer-client.d.ts +0 -106
  469. package/dist/lib/computer-client.js +5 -86
  470. package/dist/lib/concurrency.d.ts +0 -14
  471. package/dist/lib/concurrency.js +0 -3
  472. package/dist/lib/config-drift.d.ts +0 -31
  473. package/dist/lib/config-drift.js +0 -32
  474. package/dist/lib/config-keys.d.ts +1 -65
  475. package/dist/lib/config-keys.js +6 -52
  476. package/dist/lib/config-transfer.d.ts +0 -14
  477. package/dist/lib/config-transfer.js +0 -26
  478. package/dist/lib/convert.d.ts +0 -17
  479. package/dist/lib/convert.js +0 -18
  480. package/dist/lib/crabbox/cli.d.ts +0 -156
  481. package/dist/lib/crabbox/cli.js +1 -188
  482. package/dist/lib/crabbox/config.d.ts +0 -18
  483. package/dist/lib/crabbox/config.js +1 -19
  484. package/dist/lib/crabbox/lease.d.ts +0 -93
  485. package/dist/lib/crabbox/lease.js +4 -148
  486. package/dist/lib/crabbox/progress.d.ts +0 -56
  487. package/dist/lib/crabbox/progress.js +2 -49
  488. package/dist/lib/crabbox/runtimes.d.ts +0 -94
  489. package/dist/lib/crabbox/runtimes.js +1 -111
  490. package/dist/lib/crabbox/setup-copy.d.ts +0 -64
  491. package/dist/lib/crabbox/setup-copy.js +1 -53
  492. package/dist/lib/daemon/account-state-daemon-service.d.ts +0 -48
  493. package/dist/lib/daemon/account-state-daemon-service.js +2 -63
  494. package/dist/lib/daemon/attention-notify-service.d.ts +0 -12
  495. package/dist/lib/daemon/attention-notify-service.js +1 -24
  496. package/dist/lib/daemon/auth-sync-service.d.ts +0 -28
  497. package/dist/lib/daemon/auth-sync-service.js +0 -56
  498. package/dist/lib/daemon/catchup-service.d.ts +0 -34
  499. package/dist/lib/daemon/catchup-service.js +0 -30
  500. package/dist/lib/daemon/daemon.d.ts +0 -374
  501. package/dist/lib/daemon/daemon.js +44 -1049
  502. package/dist/lib/daemon/daemon.test-fixture.d.ts +0 -19
  503. package/dist/lib/daemon/daemon.test-fixture.js +1 -24
  504. package/dist/lib/daemon/device-probe-service.d.ts +0 -16
  505. package/dist/lib/daemon/device-probe-service.js +0 -20
  506. package/dist/lib/daemon/feed-stream-service.d.ts +0 -15
  507. package/dist/lib/daemon/feed-stream-service.js +0 -20
  508. package/dist/lib/daemon/harness-update-service.d.ts +2 -61
  509. package/dist/lib/daemon/harness-update-service.js +6 -86
  510. package/dist/lib/daemon/heartbeat-service.d.ts +0 -1
  511. package/dist/lib/daemon/heartbeat-service.js +0 -1
  512. package/dist/lib/daemon/leaked-daemons.d.ts +0 -46
  513. package/dist/lib/daemon/leaked-daemons.js +2 -78
  514. package/dist/lib/daemon/monitor-engine-service.d.ts +0 -13
  515. package/dist/lib/daemon/monitor-engine-service.js +3 -15
  516. package/dist/lib/daemon/runner.d.ts +0 -194
  517. package/dist/lib/daemon/runner.js +39 -760
  518. package/dist/lib/daemon/self-heal-service.d.ts +18 -17
  519. package/dist/lib/daemon/self-heal-service.js +83 -37
  520. package/dist/lib/daemon/self-update-service.d.ts +1 -131
  521. package/dist/lib/daemon/self-update-service.js +26 -189
  522. package/dist/lib/daemon/service.d.ts +0 -73
  523. package/dist/lib/daemon/service.js +0 -34
  524. package/dist/lib/daemon/session-index-service.d.ts +0 -12
  525. package/dist/lib/daemon/session-index-service.js +0 -23
  526. package/dist/lib/daemon/session-state-service.d.ts +0 -8
  527. package/dist/lib/daemon/session-state-service.js +0 -8
  528. package/dist/lib/daemon/session-summarizer-service.d.ts +0 -14
  529. package/dist/lib/daemon/session-summarizer-service.js +1 -19
  530. package/dist/lib/daemon/session-title-service.d.ts +0 -25
  531. package/dist/lib/daemon/session-title-service.js +0 -32
  532. package/dist/lib/daemon/state-dir-check-service.d.ts +0 -24
  533. package/dist/lib/daemon/state-dir-check-service.js +0 -29
  534. package/dist/lib/daemon/supervisor.d.ts +0 -112
  535. package/dist/lib/daemon/supervisor.js +4 -147
  536. package/dist/lib/daemon/testdata/process-view-daemon.js +0 -2
  537. package/dist/lib/daemon/testdata/process-view-start.js +0 -5
  538. package/dist/lib/daemon/tmux-reap-service.d.ts +0 -1
  539. package/dist/lib/daemon/tmux-reap-service.js +0 -1
  540. package/dist/lib/daemon/usage-sync-service.d.ts +0 -23
  541. package/dist/lib/daemon/usage-sync-service.js +0 -26
  542. package/dist/lib/daemon/watchdog-service.d.ts +0 -9
  543. package/dist/lib/daemon/watchdog-service.js +0 -13
  544. package/dist/lib/daemon/webhook-receiver-service.d.ts +0 -1
  545. package/dist/lib/daemon/webhook-receiver-service.js +0 -1
  546. package/dist/lib/daemon-health.d.ts +0 -55
  547. package/dist/lib/daemon-health.js +4 -77
  548. package/dist/lib/daemon-services.d.ts +0 -36
  549. package/dist/lib/daemon-services.js +0 -45
  550. package/dist/lib/daemon-ticks.d.ts +0 -126
  551. package/dist/lib/daemon-ticks.js +1 -156
  552. package/dist/lib/daemon-webhooks.d.ts +0 -57
  553. package/dist/lib/daemon-webhooks.js +2 -54
  554. package/dist/lib/deeplink/register.d.ts +0 -15
  555. package/dist/lib/deeplink/register.js +0 -60
  556. package/dist/lib/deeplink/url.d.ts +0 -24
  557. package/dist/lib/deeplink/url.js +1 -33
  558. package/dist/lib/device-config.d.ts +1 -192
  559. package/dist/lib/device-config.js +86 -240
  560. package/dist/lib/devices/config-migration.d.ts +0 -43
  561. package/dist/lib/devices/config-migration.js +7 -127
  562. package/dist/lib/devices/connect.d.ts +0 -168
  563. package/dist/lib/devices/connect.js +6 -254
  564. package/dist/lib/devices/device-docs.d.ts +0 -24
  565. package/dist/lib/devices/device-docs.js +0 -46
  566. package/dist/lib/devices/discovery-policy.d.ts +0 -30
  567. package/dist/lib/devices/discovery-policy.js +3 -34
  568. package/dist/lib/devices/doctor-findings.d.ts +0 -109
  569. package/dist/lib/devices/doctor-findings.js +30 -326
  570. package/dist/lib/devices/doctor-overview-cache.d.ts +0 -33
  571. package/dist/lib/devices/doctor-overview-cache.js +0 -81
  572. package/dist/lib/devices/fleet-divergence.d.ts +0 -89
  573. package/dist/lib/devices/fleet-divergence.js +1 -60
  574. package/dist/lib/devices/fleet-inventory.d.ts +0 -27
  575. package/dist/lib/devices/fleet-inventory.js +0 -38
  576. package/dist/lib/devices/fleet.d.ts +0 -95
  577. package/dist/lib/devices/fleet.js +0 -79
  578. package/dist/lib/devices/harness-inventory.d.ts +0 -75
  579. package/dist/lib/devices/harness-inventory.js +0 -101
  580. package/dist/lib/devices/health-report.d.ts +0 -37
  581. package/dist/lib/devices/health-report.js +0 -73
  582. package/dist/lib/devices/health.d.ts +0 -71
  583. package/dist/lib/devices/health.js +2 -82
  584. package/dist/lib/devices/interactive-host.d.ts +0 -10
  585. package/dist/lib/devices/interactive-host.js +0 -34
  586. package/dist/lib/devices/known-hosts.d.ts +0 -45
  587. package/dist/lib/devices/known-hosts.js +0 -77
  588. package/dist/lib/devices/pending.d.ts +0 -31
  589. package/dist/lib/devices/pending.js +7 -67
  590. package/dist/lib/devices/pool.d.ts +0 -36
  591. package/dist/lib/devices/pool.js +0 -30
  592. package/dist/lib/devices/reachability.d.ts +0 -25
  593. package/dist/lib/devices/reachability.js +0 -14
  594. package/dist/lib/devices/registry.d.ts +0 -141
  595. package/dist/lib/devices/registry.js +4 -151
  596. package/dist/lib/devices/resolve-profile.d.ts +0 -18
  597. package/dist/lib/devices/resolve-profile.js +0 -18
  598. package/dist/lib/devices/resolve-target.d.ts +0 -20
  599. package/dist/lib/devices/resolve-target.js +1 -51
  600. package/dist/lib/devices/rollout-verify.d.ts +1 -81
  601. package/dist/lib/devices/rollout-verify.js +0 -77
  602. package/dist/lib/devices/self-host.d.ts +0 -7
  603. package/dist/lib/devices/self-host.js +1 -29
  604. package/dist/lib/devices/ssh-config.d.ts +0 -18
  605. package/dist/lib/devices/ssh-config.js +0 -9
  606. package/dist/lib/devices/stats-cache.d.ts +0 -48
  607. package/dist/lib/devices/stats-cache.js +0 -80
  608. package/dist/lib/devices/sync.d.ts +0 -97
  609. package/dist/lib/devices/sync.js +0 -88
  610. package/dist/lib/devices/tailscale.d.ts +0 -18
  611. package/dist/lib/devices/tailscale.js +1 -38
  612. package/dist/lib/devices/terminfo.d.ts +0 -27
  613. package/dist/lib/devices/terminfo.js +2 -64
  614. package/dist/lib/devices/worker-pick.d.ts +0 -15
  615. package/dist/lib/devices/worker-pick.js +0 -48
  616. package/dist/lib/diff-text.d.ts +0 -18
  617. package/dist/lib/diff-text.js +0 -15
  618. package/dist/lib/doctor-diff.d.ts +0 -79
  619. package/dist/lib/doctor-diff.js +2 -181
  620. package/dist/lib/drift-sync.d.ts +0 -29
  621. package/dist/lib/drift-sync.js +0 -40
  622. package/dist/lib/drift.d.ts +0 -43
  623. package/dist/lib/drift.js +0 -31
  624. package/dist/lib/event-families.d.ts +0 -17
  625. package/dist/lib/event-families.js +0 -33
  626. package/dist/lib/event-provenance.d.ts +0 -4
  627. package/dist/lib/event-provenance.js +0 -5
  628. package/dist/lib/event-stream.d.ts +0 -29
  629. package/dist/lib/event-stream.js +0 -36
  630. package/dist/lib/events-ingest.d.ts +0 -27
  631. package/dist/lib/events-ingest.js +0 -54
  632. package/dist/lib/exec-account-home.d.ts +0 -24
  633. package/dist/lib/exec-account-home.js +0 -41
  634. package/dist/lib/exec-bounded.d.ts +1 -37
  635. package/dist/lib/exec-bounded.js +10 -46
  636. package/dist/lib/exec.d.ts +3 -583
  637. package/dist/lib/exec.js +18 -1025
  638. package/dist/lib/factory/snapshot.js +1 -15
  639. package/dist/lib/feed/activity-stream.d.ts +0 -27
  640. package/dist/lib/feed/activity-stream.js +4 -80
  641. package/dist/lib/feed/activity.d.ts +1 -243
  642. package/dist/lib/feed/activity.js +8 -273
  643. package/dist/lib/feed/answer.d.ts +0 -103
  644. package/dist/lib/feed/answer.js +0 -267
  645. package/dist/lib/feed/attention.d.ts +0 -56
  646. package/dist/lib/feed/attention.js +0 -161
  647. package/dist/lib/feed/envelope.d.ts +2 -17
  648. package/dist/lib/feed/envelope.js +0 -14
  649. package/dist/lib/feed/events.d.ts +0 -118
  650. package/dist/lib/feed/events.js +13 -213
  651. package/dist/lib/feed/feed.d.ts +1 -250
  652. package/dist/lib/feed/feed.js +24 -334
  653. package/dist/lib/feed/hub-server.d.ts +0 -100
  654. package/dist/lib/feed/hub-server.js +1 -191
  655. package/dist/lib/feed/hub.d.ts +0 -53
  656. package/dist/lib/feed/hub.js +0 -89
  657. package/dist/lib/feed/pr-status.d.ts +1 -19
  658. package/dist/lib/feed/pr-status.js +0 -18
  659. package/dist/lib/feed/tool-activity.d.ts +0 -59
  660. package/dist/lib/feed/tool-activity.js +1 -119
  661. package/dist/lib/feed/tools.d.ts +0 -100
  662. package/dist/lib/feed/tools.js +0 -89
  663. package/dist/lib/feed/watch.d.ts +0 -33
  664. package/dist/lib/feed/watch.js +2 -94
  665. package/dist/lib/feed-broadcast.d.ts +0 -148
  666. package/dist/lib/feed-broadcast.js +1 -283
  667. package/dist/lib/feed-outcome.d.ts +0 -50
  668. package/dist/lib/feed-outcome.js +1 -67
  669. package/dist/lib/feed-policy.d.ts +0 -6
  670. package/dist/lib/feed-policy.js +1 -27
  671. package/dist/lib/feed-post.d.ts +0 -61
  672. package/dist/lib/feed-post.js +4 -61
  673. package/dist/lib/feed-ranking.d.ts +1 -1
  674. package/dist/lib/fleet/apply.d.ts +0 -64
  675. package/dist/lib/fleet/apply.js +0 -125
  676. package/dist/lib/fleet/auth-sync.d.ts +0 -48
  677. package/dist/lib/fleet/auth-sync.js +4 -49
  678. package/dist/lib/fleet/capture.d.ts +0 -26
  679. package/dist/lib/fleet/capture.js +1 -36
  680. package/dist/lib/fleet/manifest.d.ts +0 -38
  681. package/dist/lib/fleet/manifest.js +0 -40
  682. package/dist/lib/fleet/types.d.ts +1 -138
  683. package/dist/lib/fleet/types.js +0 -12
  684. package/dist/lib/fleet-cache.d.ts +0 -12
  685. package/dist/lib/fleet-cache.js +0 -24
  686. package/dist/lib/fleet-shared-state.d.ts +6 -59
  687. package/dist/lib/fleet-shared-state.js +5 -51
  688. package/dist/lib/fleet-status.d.ts +0 -27
  689. package/dist/lib/fleet-status.js +0 -48
  690. package/dist/lib/format.d.ts +0 -74
  691. package/dist/lib/format.js +3 -77
  692. package/dist/lib/friction-heuristics.d.ts +0 -20
  693. package/dist/lib/friction-heuristics.js +0 -8
  694. package/dist/lib/fs-atomic.d.ts +0 -51
  695. package/dist/lib/fs-atomic.js +8 -66
  696. package/dist/lib/fs-walk.d.ts +0 -13
  697. package/dist/lib/fs-walk.js +0 -22
  698. package/dist/lib/fuzzy.d.ts +0 -29
  699. package/dist/lib/fuzzy.js +0 -31
  700. package/dist/lib/gemini-settings.js +0 -12
  701. package/dist/lib/git.d.ts +0 -366
  702. package/dist/lib/git.js +7 -574
  703. package/dist/lib/github/ci-failure.d.ts +30 -0
  704. package/dist/lib/github/ci-failure.js +95 -0
  705. package/dist/lib/github/gh-overload.d.ts +0 -38
  706. package/dist/lib/github/gh-overload.js +3 -55
  707. package/dist/lib/github/pr-mergeable.d.ts +3 -34
  708. package/dist/lib/github/pr-mergeable.js +2 -42
  709. package/dist/lib/github/pr-verdict.d.ts +0 -44
  710. package/dist/lib/github/pr-verdict.js +0 -36
  711. package/dist/lib/github/project-prs.d.ts +135 -105
  712. package/dist/lib/github/project-prs.js +530 -170
  713. package/dist/lib/github/release-drift.d.ts +45 -0
  714. package/dist/lib/github/release-drift.js +91 -0
  715. package/dist/lib/github/rest.d.ts +0 -50
  716. package/dist/lib/github/rest.js +0 -49
  717. package/dist/lib/github/viewer.d.ts +27 -0
  718. package/dist/lib/github/viewer.js +101 -0
  719. package/dist/lib/goose-commands.d.ts +0 -29
  720. package/dist/lib/goose-commands.js +3 -48
  721. package/dist/lib/harness/adapter.d.ts +0 -151
  722. package/dist/lib/harness/adapter.js +0 -41
  723. package/dist/lib/harness/adapters/claude.d.ts +0 -8
  724. package/dist/lib/harness/adapters/claude.js +0 -89
  725. package/dist/lib/harness/adapters/codex.js +0 -18
  726. package/dist/lib/harness/adapters/copilot.js +0 -3
  727. package/dist/lib/harness/adapters/cursor.js +0 -16
  728. package/dist/lib/harness/adapters/droid.js +0 -3
  729. package/dist/lib/harness/adapters/kimi.js +0 -19
  730. package/dist/lib/harness/adapters/muse.js +0 -9
  731. package/dist/lib/harness/adapters/opencode.js +0 -2
  732. package/dist/lib/harness/exec-config-version.d.ts +0 -2
  733. package/dist/lib/harness/exec-config-version.js +0 -13
  734. package/dist/lib/harness/index.js +0 -10
  735. package/dist/lib/harness-auth-capabilities.d.ts +0 -26
  736. package/dist/lib/harness-auth-capabilities.js +0 -18
  737. package/dist/lib/harness-connection-test.d.ts +0 -27
  738. package/dist/lib/harness-connection-test.js +2 -20
  739. package/dist/lib/heal.d.ts +0 -45
  740. package/dist/lib/heal.js +2 -82
  741. package/dist/lib/help.d.ts +0 -32
  742. package/dist/lib/help.js +0 -41
  743. package/dist/lib/helper-download.d.ts +0 -85
  744. package/dist/lib/helper-download.js +0 -95
  745. package/dist/lib/helper-versions.d.ts +0 -51
  746. package/dist/lib/helper-versions.js +1 -45
  747. package/dist/lib/hooks/cache.d.ts +0 -40
  748. package/dist/lib/hooks/cache.js +8 -122
  749. package/dist/lib/hooks/install.d.ts +0 -253
  750. package/dist/lib/hooks/install.js +49 -822
  751. package/dist/lib/hooks/match.d.ts +0 -24
  752. package/dist/lib/hooks/match.js +0 -31
  753. package/dist/lib/hooks/profile.d.ts +0 -21
  754. package/dist/lib/hooks/profile.js +0 -28
  755. package/dist/lib/hosts/credential-transport.d.ts +0 -6
  756. package/dist/lib/hosts/credential-transport.js +0 -17
  757. package/dist/lib/hosts/credentials.d.ts +0 -35
  758. package/dist/lib/hosts/credentials.js +0 -38
  759. package/dist/lib/hosts/dispatch.d.ts +0 -140
  760. package/dist/lib/hosts/dispatch.js +0 -176
  761. package/dist/lib/hosts/logs.d.ts +0 -25
  762. package/dist/lib/hosts/logs.js +1 -51
  763. package/dist/lib/hosts/option.d.ts +0 -12
  764. package/dist/lib/hosts/option.js +0 -12
  765. package/dist/lib/hosts/passthrough.d.ts +0 -129
  766. package/dist/lib/hosts/passthrough.js +12 -249
  767. package/dist/lib/hosts/progress.d.ts +0 -90
  768. package/dist/lib/hosts/progress.js +5 -115
  769. package/dist/lib/hosts/providers/devices.d.ts +0 -19
  770. package/dist/lib/hosts/providers/devices.js +1 -34
  771. package/dist/lib/hosts/providers/local.d.ts +0 -14
  772. package/dist/lib/hosts/providers/local.js +0 -22
  773. package/dist/lib/hosts/ready.d.ts +0 -99
  774. package/dist/lib/hosts/ready.js +0 -101
  775. package/dist/lib/hosts/reconcile.d.ts +0 -38
  776. package/dist/lib/hosts/reconcile.js +1 -50
  777. package/dist/lib/hosts/reconnect.d.ts +0 -75
  778. package/dist/lib/hosts/reconnect.js +0 -98
  779. package/dist/lib/hosts/registry.d.ts +0 -74
  780. package/dist/lib/hosts/registry.js +0 -110
  781. package/dist/lib/hosts/remote-cmd.d.ts +1 -228
  782. package/dist/lib/hosts/remote-cmd.js +19 -294
  783. package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +0 -1
  784. package/dist/lib/hosts/remote-cmd.test-fixture.js +0 -12
  785. package/dist/lib/hosts/remote-os.d.ts +0 -19
  786. package/dist/lib/hosts/remote-os.js +0 -22
  787. package/dist/lib/hosts/remote-session-id.d.ts +0 -22
  788. package/dist/lib/hosts/remote-session-id.js +0 -24
  789. package/dist/lib/hosts/routing-flag.d.ts +0 -21
  790. package/dist/lib/hosts/routing-flag.js +0 -21
  791. package/dist/lib/hosts/run-target.d.ts +0 -53
  792. package/dist/lib/hosts/run-target.js +0 -56
  793. package/dist/lib/hosts/session-index.d.ts +1 -53
  794. package/dist/lib/hosts/session-index.js +2 -59
  795. package/dist/lib/hosts/session-marker.d.ts +0 -31
  796. package/dist/lib/hosts/session-marker.js +0 -32
  797. package/dist/lib/hosts/ssh-config.d.ts +0 -25
  798. package/dist/lib/hosts/ssh-config.js +1 -32
  799. package/dist/lib/hosts/tasks.d.ts +0 -54
  800. package/dist/lib/hosts/tasks.js +0 -35
  801. package/dist/lib/hosts/types.d.ts +0 -39
  802. package/dist/lib/hosts/types.js +0 -22
  803. package/dist/lib/humans.d.ts +0 -21
  804. package/dist/lib/humans.js +0 -30
  805. package/dist/lib/identity/client.d.ts +1 -35
  806. package/dist/lib/identity/client.js +3 -29
  807. package/dist/lib/identity/index.d.ts +3 -27
  808. package/dist/lib/identity/index.js +8 -24
  809. package/dist/lib/import.d.ts +0 -104
  810. package/dist/lib/import.js +1 -137
  811. package/dist/lib/installations/active-check.d.ts +0 -53
  812. package/dist/lib/installations/active-check.js +0 -49
  813. package/dist/lib/installations/index.d.ts +0 -9
  814. package/dist/lib/installations/index.js +0 -9
  815. package/dist/lib/installations/installation-lock.js +0 -8
  816. package/dist/lib/installations/launch-gate.d.ts +0 -56
  817. package/dist/lib/installations/launch-gate.js +0 -83
  818. package/dist/lib/installations/migrate.d.ts +0 -100
  819. package/dist/lib/installations/migrate.js +105 -572
  820. package/dist/lib/installations/native-command.d.ts +0 -1
  821. package/dist/lib/installations/native-command.js +0 -6
  822. package/dist/lib/installations/resolve.d.ts +0 -24
  823. package/dist/lib/installations/resolve.js +0 -24
  824. package/dist/lib/installations/shims.d.ts +0 -395
  825. package/dist/lib/installations/shims.js +30 -957
  826. package/dist/lib/installations/store.d.ts +0 -199
  827. package/dist/lib/installations/store.js +1 -297
  828. package/dist/lib/installations/strategies.d.ts +0 -44
  829. package/dist/lib/installations/strategies.js +0 -87
  830. package/dist/lib/installations/types.d.ts +1 -67
  831. package/dist/lib/installations/types.js +0 -6
  832. package/dist/lib/installations/update-cancellation.d.ts +0 -74
  833. package/dist/lib/installations/update-cancellation.js +0 -74
  834. package/dist/lib/installations/update-policy.d.ts +0 -57
  835. package/dist/lib/installations/update-policy.js +0 -62
  836. package/dist/lib/installations/update-runtime.d.ts +0 -88
  837. package/dist/lib/installations/update-runtime.js +1 -127
  838. package/dist/lib/installations/update.d.ts +0 -53
  839. package/dist/lib/installations/update.js +0 -95
  840. package/dist/lib/installations/versions.d.ts +0 -310
  841. package/dist/lib/installations/versions.js +14 -837
  842. package/dist/lib/isolation-boundary-report.d.ts +0 -7
  843. package/dist/lib/isolation-boundary-report.js +0 -7
  844. package/dist/lib/launch-identity.d.ts +0 -8
  845. package/dist/lib/launch-identity.js +0 -8
  846. package/dist/lib/linear-autoclose.d.ts +0 -25
  847. package/dist/lib/linear-autoclose.js +0 -19
  848. package/dist/lib/linear-cache.d.ts +0 -57
  849. package/dist/lib/linear-cache.js +1 -81
  850. package/dist/lib/linear-project-counts.d.ts +0 -83
  851. package/dist/lib/linear-project-counts.js +2 -64
  852. package/dist/lib/linear-projects.d.ts +0 -62
  853. package/dist/lib/linear-projects.js +0 -85
  854. package/dist/lib/linear-rate-limit.d.ts +0 -21
  855. package/dist/lib/linear-rate-limit.js +1 -93
  856. package/dist/lib/linux-userns.d.ts +1 -42
  857. package/dist/lib/linux-userns.js +0 -47
  858. package/dist/lib/lock-compromise.d.ts +0 -7
  859. package/dist/lib/lock-compromise.js +0 -7
  860. package/dist/lib/log-follow.d.ts +0 -2
  861. package/dist/lib/log-follow.js +5 -15
  862. package/dist/lib/loop.d.ts +0 -106
  863. package/dist/lib/loop.js +0 -135
  864. package/dist/lib/machine-id.d.ts +0 -19
  865. package/dist/lib/machine-id.js +0 -19
  866. package/dist/lib/mailbox-gc.d.ts +0 -7
  867. package/dist/lib/mailbox-gc.js +0 -25
  868. package/dist/lib/mailbox-target.d.ts +0 -31
  869. package/dist/lib/mailbox-target.js +0 -27
  870. package/dist/lib/mailbox.d.ts +0 -77
  871. package/dist/lib/mailbox.js +5 -105
  872. package/dist/lib/manifest.d.ts +0 -4
  873. package/dist/lib/manifest.js +0 -36
  874. package/dist/lib/markdown.d.ts +0 -3
  875. package/dist/lib/markdown.js +0 -9
  876. package/dist/lib/mcp-registry.d.ts +1 -50
  877. package/dist/lib/mcp-registry.js +0 -64
  878. package/dist/lib/mcp.d.ts +0 -115
  879. package/dist/lib/mcp.js +3 -199
  880. package/dist/lib/memory-cache.d.ts +0 -11
  881. package/dist/lib/memory-cache.js +0 -9
  882. package/dist/lib/memory.d.ts +0 -52
  883. package/dist/lib/memory.js +12 -74
  884. package/dist/lib/menubar/download-menubar.d.ts +0 -35
  885. package/dist/lib/menubar/download-menubar.js +0 -38
  886. package/dist/lib/menubar/install-menubar.d.ts +0 -405
  887. package/dist/lib/menubar/install-menubar.js +25 -667
  888. package/dist/lib/menubar/migrate-prefs.d.ts +0 -47
  889. package/dist/lib/menubar/migrate-prefs.js +1 -70
  890. package/dist/lib/menubar/notify-desktop.d.ts +0 -82
  891. package/dist/lib/menubar/notify-desktop.js +0 -68
  892. package/dist/lib/menubar/resolve-version.d.ts +0 -41
  893. package/dist/lib/menubar/resolve-version.js +2 -44
  894. package/dist/lib/menubar/snapshot.d.ts +13 -66
  895. package/dist/lib/menubar/snapshot.js +45 -39
  896. package/dist/lib/merged-resources.d.ts +0 -10
  897. package/dist/lib/merged-resources.js +0 -10
  898. package/dist/lib/migrate-fold.d.ts +0 -31
  899. package/dist/lib/migrate-fold.js +6 -38
  900. package/dist/lib/model-tier-overrides.d.ts +0 -30
  901. package/dist/lib/model-tier-overrides.js +0 -14
  902. package/dist/lib/model-tiers.d.ts +0 -43
  903. package/dist/lib/model-tiers.js +5 -78
  904. package/dist/lib/models.d.ts +0 -145
  905. package/dist/lib/models.js +3 -333
  906. package/dist/lib/monitors/config.d.ts +0 -187
  907. package/dist/lib/monitors/config.js +4 -101
  908. package/dist/lib/monitors/dispatch.d.ts +0 -22
  909. package/dist/lib/monitors/dispatch.js +0 -35
  910. package/dist/lib/monitors/engine.d.ts +1 -53
  911. package/dist/lib/monitors/engine.js +17 -106
  912. package/dist/lib/monitors/fingerprint.d.ts +0 -43
  913. package/dist/lib/monitors/fingerprint.js +0 -57
  914. package/dist/lib/monitors/pid-watch.d.ts +0 -31
  915. package/dist/lib/monitors/pid-watch.js +0 -31
  916. package/dist/lib/monitors/remote.d.ts +0 -52
  917. package/dist/lib/monitors/remote.js +0 -42
  918. package/dist/lib/monitors/sources/command.d.ts +2 -9
  919. package/dist/lib/monitors/sources/command.js +13 -34
  920. package/dist/lib/monitors/sources/device.d.ts +0 -10
  921. package/dist/lib/monitors/sources/device.js +0 -14
  922. package/dist/lib/monitors/sources/failure.d.ts +0 -27
  923. package/dist/lib/monitors/sources/failure.js +0 -27
  924. package/dist/lib/monitors/sources/file.d.ts +0 -10
  925. package/dist/lib/monitors/sources/file.js +0 -11
  926. package/dist/lib/monitors/sources/http.d.ts +0 -7
  927. package/dist/lib/monitors/sources/http.js +0 -9
  928. package/dist/lib/monitors/sources/index.d.ts +0 -9
  929. package/dist/lib/monitors/sources/index.js +0 -9
  930. package/dist/lib/monitors/sources/poll.d.ts +0 -8
  931. package/dist/lib/monitors/sources/poll.js +0 -8
  932. package/dist/lib/monitors/sources/types.d.ts +0 -18
  933. package/dist/lib/monitors/sources/types.js +0 -8
  934. package/dist/lib/monitors/sources/webhook.d.ts +0 -7
  935. package/dist/lib/monitors/sources/webhook.js +0 -7
  936. package/dist/lib/monitors/sources/ws.d.ts +0 -10
  937. package/dist/lib/monitors/sources/ws.js +3 -13
  938. package/dist/lib/monitors/state.d.ts +0 -132
  939. package/dist/lib/monitors/state.js +2 -86
  940. package/dist/lib/native-accounts.d.ts +0 -32
  941. package/dist/lib/native-accounts.js +0 -17
  942. package/dist/lib/net-close.d.ts +0 -9
  943. package/dist/lib/net-close.js +0 -5
  944. package/dist/lib/notify.d.ts +0 -47
  945. package/dist/lib/notify.js +0 -30
  946. package/dist/lib/onepassword.d.ts +0 -14
  947. package/dist/lib/onepassword.js +0 -12
  948. package/dist/lib/open-url.d.ts +0 -38
  949. package/dist/lib/open-url.js +1 -69
  950. package/dist/lib/openclaw-keychain.js +2 -7
  951. package/dist/lib/operator.d.ts +0 -9
  952. package/dist/lib/operator.js +0 -34
  953. package/dist/lib/origin-machine.d.ts +0 -16
  954. package/dist/lib/origin-machine.js +0 -16
  955. package/dist/lib/output/git-output.d.ts +0 -31
  956. package/dist/lib/output/git-output.js +4 -43
  957. package/dist/lib/overdue.d.ts +0 -35
  958. package/dist/lib/overdue.js +0 -60
  959. package/dist/lib/owner-message.d.ts +0 -20
  960. package/dist/lib/owner-message.js +1 -40
  961. package/dist/lib/packages/output-home.d.ts +0 -28
  962. package/dist/lib/packages/output-home.js +0 -114
  963. package/dist/lib/paths.d.ts +0 -26
  964. package/dist/lib/paths.js +1 -27
  965. package/dist/lib/percentile.d.ts +0 -11
  966. package/dist/lib/percentile.js +0 -11
  967. package/dist/lib/perf/db.d.ts +0 -16
  968. package/dist/lib/perf/db.js +3 -32
  969. package/dist/lib/perf/spool.d.ts +0 -12
  970. package/dist/lib/perf/spool.js +0 -13
  971. package/dist/lib/perf/types.d.ts +0 -26
  972. package/dist/lib/perf/types.js +0 -1
  973. package/dist/lib/permissions-registry.d.ts +0 -48
  974. package/dist/lib/permissions-registry.js +0 -108
  975. package/dist/lib/permissions.d.ts +0 -215
  976. package/dist/lib/permissions.js +4 -407
  977. package/dist/lib/picker.d.ts +0 -124
  978. package/dist/lib/picker.js +0 -140
  979. package/dist/lib/placement.d.ts +0 -57
  980. package/dist/lib/placement.js +0 -47
  981. package/dist/lib/platform/exec.d.ts +0 -73
  982. package/dist/lib/platform/exec.js +0 -80
  983. package/dist/lib/platform/index.d.ts +0 -14
  984. package/dist/lib/platform/index.js +0 -14
  985. package/dist/lib/platform/ipc.d.ts +0 -10
  986. package/dist/lib/platform/ipc.js +0 -13
  987. package/dist/lib/platform/links.d.ts +0 -14
  988. package/dist/lib/platform/links.js +0 -25
  989. package/dist/lib/platform/paths.d.ts +0 -48
  990. package/dist/lib/platform/paths.js +0 -52
  991. package/dist/lib/platform/posixpath.d.ts +0 -18
  992. package/dist/lib/platform/posixpath.js +0 -62
  993. package/dist/lib/platform/process.d.ts +0 -67
  994. package/dist/lib/platform/process.js +5 -99
  995. package/dist/lib/platform/winpath.d.ts +0 -50
  996. package/dist/lib/platform/winpath.js +0 -81
  997. package/dist/lib/plugins/plugin-marketplace.d.ts +0 -165
  998. package/dist/lib/plugins/plugin-marketplace.js +10 -238
  999. package/dist/lib/plugins/plugins.d.ts +0 -238
  1000. package/dist/lib/plugins/plugins.js +20 -454
  1001. package/dist/lib/plugins/skills.d.ts +0 -86
  1002. package/dist/lib/plugins/skills.js +1 -139
  1003. package/dist/lib/pr-land-detach.d.ts +0 -17
  1004. package/dist/lib/pr-land-detach.js +0 -30
  1005. package/dist/lib/pricing/cost.d.ts +0 -29
  1006. package/dist/lib/pricing/cost.js +0 -38
  1007. package/dist/lib/pricing/index.d.ts +0 -6
  1008. package/dist/lib/pricing/index.js +0 -6
  1009. package/dist/lib/pricing/table.d.ts +0 -8
  1010. package/dist/lib/pricing/table.js +3 -56
  1011. package/dist/lib/probe.d.ts +0 -5
  1012. package/dist/lib/probe.js +0 -40
  1013. package/dist/lib/profiles-presets.d.ts +0 -20
  1014. package/dist/lib/profiles-presets.js +1 -36
  1015. package/dist/lib/profiles.d.ts +0 -185
  1016. package/dist/lib/profiles.js +1 -158
  1017. package/dist/lib/project-doctor.d.ts +0 -29
  1018. package/dist/lib/project-doctor.js +0 -26
  1019. package/dist/lib/project-focus.d.ts +0 -41
  1020. package/dist/lib/project-focus.js +0 -49
  1021. package/dist/lib/project-import.d.ts +0 -41
  1022. package/dist/lib/project-import.js +0 -36
  1023. package/dist/lib/project-key.d.ts +0 -65
  1024. package/dist/lib/project-key.js +0 -70
  1025. package/dist/lib/project-launch.d.ts +0 -49
  1026. package/dist/lib/project-launch.js +2 -91
  1027. package/dist/lib/project-probe.d.ts +0 -86
  1028. package/dist/lib/project-probe.js +1 -88
  1029. package/dist/lib/project-pull.d.ts +0 -96
  1030. package/dist/lib/project-pull.js +0 -122
  1031. package/dist/lib/project-resources.d.ts +0 -44
  1032. package/dist/lib/project-resources.js +7 -171
  1033. package/dist/lib/project-root.d.ts +0 -95
  1034. package/dist/lib/project-root.js +0 -101
  1035. package/dist/lib/project-schedule.d.ts +0 -43
  1036. package/dist/lib/project-schedule.js +0 -52
  1037. package/dist/lib/project-status.d.ts +0 -108
  1038. package/dist/lib/project-status.js +0 -117
  1039. package/dist/lib/projects.d.ts +0 -197
  1040. package/dist/lib/projects.js +1 -213
  1041. package/dist/lib/pwsh.d.ts +0 -41
  1042. package/dist/lib/pwsh.js +0 -51
  1043. package/dist/lib/quick-todo.d.ts +84 -0
  1044. package/dist/lib/quick-todo.js +243 -0
  1045. package/dist/lib/reconcile-and-repair.d.ts +0 -78
  1046. package/dist/lib/reconcile-and-repair.js +3 -66
  1047. package/dist/lib/redact.d.ts +0 -29
  1048. package/dist/lib/redact.js +0 -54
  1049. package/dist/lib/refresh-coordinator.d.ts +0 -15
  1050. package/dist/lib/refresh-coordinator.js +0 -10
  1051. package/dist/lib/refresh.d.ts +0 -39
  1052. package/dist/lib/refresh.js +0 -45
  1053. package/dist/lib/registry.d.ts +0 -65
  1054. package/dist/lib/registry.js +0 -127
  1055. package/dist/lib/reminders.d.ts +0 -21
  1056. package/dist/lib/reminders.js +0 -30
  1057. package/dist/lib/remote-agents-json.d.ts +0 -44
  1058. package/dist/lib/remote-agents-json.js +1 -44
  1059. package/dist/lib/reserved-stores.d.ts +0 -28
  1060. package/dist/lib/reserved-stores.js +2 -28
  1061. package/dist/lib/resource-aliases.d.ts +0 -12
  1062. package/dist/lib/resource-aliases.js +0 -12
  1063. package/dist/lib/resource-content-diff.d.ts +0 -28
  1064. package/dist/lib/resource-content-diff.js +7 -34
  1065. package/dist/lib/resource-inventory.d.ts +0 -51
  1066. package/dist/lib/resource-inventory.js +0 -45
  1067. package/dist/lib/resource-patterns.d.ts +0 -32
  1068. package/dist/lib/resource-patterns.js +0 -34
  1069. package/dist/lib/resource-profiles.d.ts +0 -7
  1070. package/dist/lib/resource-profiles.js +0 -11
  1071. package/dist/lib/resources/types.d.ts +0 -50
  1072. package/dist/lib/resources/types.js +0 -7
  1073. package/dist/lib/resources/workflows.d.ts +0 -7
  1074. package/dist/lib/resources/workflows.js +0 -13
  1075. package/dist/lib/resources.d.ts +0 -65
  1076. package/dist/lib/resources.js +0 -78
  1077. package/dist/lib/routers.d.ts +0 -67
  1078. package/dist/lib/routers.js +0 -60
  1079. package/dist/lib/routine-activation.d.ts +0 -23
  1080. package/dist/lib/routine-activation.js +0 -24
  1081. package/dist/lib/routine-context.d.ts +1 -91
  1082. package/dist/lib/routine-context.js +0 -107
  1083. package/dist/lib/routine-notify-owner.d.ts +0 -79
  1084. package/dist/lib/routine-notify-owner.js +1 -103
  1085. package/dist/lib/routine-notify.d.ts +0 -70
  1086. package/dist/lib/routine-notify.js +1 -75
  1087. package/dist/lib/routine-process-cleanup.d.ts +0 -1
  1088. package/dist/lib/routine-process-cleanup.js +1 -4
  1089. package/dist/lib/routine-readiness.d.ts +0 -63
  1090. package/dist/lib/routine-readiness.js +4 -86
  1091. package/dist/lib/routines-format.d.ts +0 -40
  1092. package/dist/lib/routines-format.js +0 -67
  1093. package/dist/lib/routines-placement.d.ts +0 -33
  1094. package/dist/lib/routines-placement.js +0 -40
  1095. package/dist/lib/routines-project.d.ts +0 -57
  1096. package/dist/lib/routines-project.js +6 -80
  1097. package/dist/lib/rules/compile.d.ts +0 -66
  1098. package/dist/lib/rules/compile.js +6 -118
  1099. package/dist/lib/rules/compose.d.ts +0 -56
  1100. package/dist/lib/rules/compose.js +2 -83
  1101. package/dist/lib/rules/rules.d.ts +0 -23
  1102. package/dist/lib/rules/rules.js +0 -35
  1103. package/dist/lib/rules/run-sync.d.ts +0 -16
  1104. package/dist/lib/rules/run-sync.js +1 -44
  1105. package/dist/lib/run-config.d.ts +0 -7
  1106. package/dist/lib/run-config.js +0 -8
  1107. package/dist/lib/run-defaults.d.ts +0 -14
  1108. package/dist/lib/run-defaults.js +0 -14
  1109. package/dist/lib/run-notify.d.ts +0 -18
  1110. package/dist/lib/run-notify.js +0 -36
  1111. package/dist/lib/run-trace-sync.d.ts +0 -21
  1112. package/dist/lib/run-trace-sync.js +2 -66
  1113. package/dist/lib/rush-session.d.ts +0 -26
  1114. package/dist/lib/rush-session.js +0 -28
  1115. package/dist/lib/sandbox.d.ts +0 -45
  1116. package/dist/lib/sandbox.js +5 -93
  1117. package/dist/lib/scheduler.d.ts +0 -28
  1118. package/dist/lib/scheduler.js +0 -45
  1119. package/dist/lib/scheduling/routines.d.ts +0 -652
  1120. package/dist/lib/scheduling/routines.js +8 -475
  1121. package/dist/lib/secrets-cli.d.ts +3 -8
  1122. package/dist/lib/secrets-cli.js +1 -19
  1123. package/dist/lib/secrets-client.d.ts +0 -117
  1124. package/dist/lib/secrets-client.js +4 -275
  1125. package/dist/lib/secrets-policy.d.ts +0 -142
  1126. package/dist/lib/secrets-policy.js +4 -237
  1127. package/dist/lib/secrets-types.d.ts +0 -87
  1128. package/dist/lib/secrets-types.js +0 -13
  1129. package/dist/lib/self-heal/checks/hook-manifest.js +0 -25
  1130. package/dist/lib/self-heal/checks/hook-runtime.js +0 -5
  1131. package/dist/lib/self-heal/checks/install-staging.d.ts +0 -1
  1132. package/dist/lib/self-heal/checks/install-staging.js +1 -28
  1133. package/dist/lib/self-heal/checks/menubar-helper.js +0 -5
  1134. package/dist/lib/self-heal/checks/path.js +0 -9
  1135. package/dist/lib/self-heal/checks/resources.js +0 -5
  1136. package/dist/lib/self-heal/checks/shadowing.js +2 -16
  1137. package/dist/lib/self-heal/checks/shims.js +0 -14
  1138. package/dist/lib/self-heal/child.d.ts +12 -0
  1139. package/dist/lib/self-heal/child.js +23 -0
  1140. package/dist/lib/self-heal/registry.d.ts +0 -9
  1141. package/dist/lib/self-heal/registry.js +0 -16
  1142. package/dist/lib/self-heal/types.d.ts +0 -12
  1143. package/dist/lib/self-heal/types.js +0 -15
  1144. package/dist/lib/self-update.d.ts +0 -312
  1145. package/dist/lib/self-update.js +9 -360
  1146. package/dist/lib/service-manifest.d.ts +0 -39
  1147. package/dist/lib/service-manifest.js +0 -72
  1148. package/dist/lib/session/active.d.ts +13 -869
  1149. package/dist/lib/session/active.js +17 -976
  1150. package/dist/lib/session/actor-sidecar.d.ts +1 -32
  1151. package/dist/lib/session/actor-sidecar.js +1 -41
  1152. package/dist/lib/session/bookmarks.d.ts +0 -33
  1153. package/dist/lib/session/bookmarks.js +0 -37
  1154. package/dist/lib/session/bundle.d.ts +0 -86
  1155. package/dist/lib/session/bundle.js +0 -54
  1156. package/dist/lib/session/claude-accounts.d.ts +0 -40
  1157. package/dist/lib/session/claude-accounts.js +0 -49
  1158. package/dist/lib/session/cloud.d.ts +1 -23
  1159. package/dist/lib/session/cloud.js +0 -26
  1160. package/dist/lib/session/db.d.ts +5 -658
  1161. package/dist/lib/session/db.js +22 -1443
  1162. package/dist/lib/session/detached.d.ts +0 -21
  1163. package/dist/lib/session/detached.js +0 -29
  1164. package/dist/lib/session/discover.d.ts +3 -420
  1165. package/dist/lib/session/discover.js +30 -1123
  1166. package/dist/lib/session/fork.d.ts +1 -41
  1167. package/dist/lib/session/fork.js +0 -19
  1168. package/dist/lib/session/ghostty-tabs.d.ts +0 -24
  1169. package/dist/lib/session/ghostty-tabs.js +0 -37
  1170. package/dist/lib/session/glance-files.d.ts +19 -0
  1171. package/dist/lib/session/glance-files.js +258 -0
  1172. package/dist/lib/session/hook-sessions.d.ts +0 -33
  1173. package/dist/lib/session/hook-sessions.js +4 -99
  1174. package/dist/lib/session/host-link.d.ts +1 -94
  1175. package/dist/lib/session/host-link.js +1 -81
  1176. package/dist/lib/session/inject-target.d.ts +8 -0
  1177. package/dist/lib/session/inject-target.js +32 -0
  1178. package/dist/lib/session/inject.d.ts +0 -15
  1179. package/dist/lib/session/inject.js +0 -15
  1180. package/dist/lib/session/live-metadata.d.ts +1 -34
  1181. package/dist/lib/session/live-metadata.js +1 -73
  1182. package/dist/lib/session/local-tmux-attach.d.ts +0 -37
  1183. package/dist/lib/session/local-tmux-attach.js +1 -76
  1184. package/dist/lib/session/migrate-targets.d.ts +0 -46
  1185. package/dist/lib/session/migrate-targets.js +0 -24
  1186. package/dist/lib/session/migrations.d.ts +0 -12
  1187. package/dist/lib/session/migrations.js +1 -20
  1188. package/dist/lib/session/mirror.d.ts +0 -32
  1189. package/dist/lib/session/mirror.js +0 -77
  1190. package/dist/lib/session/origin-machine.js +0 -3
  1191. package/dist/lib/session/pid-registry.d.ts +0 -80
  1192. package/dist/lib/session/pid-registry.js +2 -81
  1193. package/dist/lib/session/presence.d.ts +0 -39
  1194. package/dist/lib/session/presence.js +2 -52
  1195. package/dist/lib/session/process-view.d.ts +0 -11
  1196. package/dist/lib/session/process-view.js +0 -25
  1197. package/dist/lib/session/projection.d.ts +0 -3
  1198. package/dist/lib/session/projection.js +0 -10
  1199. package/dist/lib/session/provenance.d.ts +1 -37
  1200. package/dist/lib/session/provenance.js +0 -69
  1201. package/dist/lib/session/recovery.d.ts +1 -40
  1202. package/dist/lib/session/recovery.js +2 -80
  1203. package/dist/lib/session/relative-time.js +0 -3
  1204. package/dist/lib/session/remote/peer-stream.d.ts +0 -42
  1205. package/dist/lib/session/remote/peer-stream.js +0 -64
  1206. package/dist/lib/session/remote/remote-bundle.d.ts +0 -6
  1207. package/dist/lib/session/remote/remote-bundle.js +0 -21
  1208. package/dist/lib/session/remote/remote-list.d.ts +1 -110
  1209. package/dist/lib/session/remote/remote-list.js +5 -139
  1210. package/dist/lib/session/remote/remote.d.ts +0 -98
  1211. package/dist/lib/session/remote/remote.js +3 -119
  1212. package/dist/lib/session/remote/watch.d.ts +1 -34
  1213. package/dist/lib/session/remote/watch.js +12 -72
  1214. package/dist/lib/session/remote-active.d.ts +0 -35
  1215. package/dist/lib/session/remote-active.js +0 -48
  1216. package/dist/lib/session/remote-bundle.js +0 -2
  1217. package/dist/lib/session/remote-list.js +0 -2
  1218. package/dist/lib/session/remote-preview-cache.d.ts +0 -7
  1219. package/dist/lib/session/remote-preview-cache.js +1 -76
  1220. package/dist/lib/session/remote.js +0 -2
  1221. package/dist/lib/session/resume-command.d.ts +0 -6
  1222. package/dist/lib/session/resume-command.js +0 -6
  1223. package/dist/lib/session/resume-owner.d.ts +1 -52
  1224. package/dist/lib/session/resume-owner.js +0 -56
  1225. package/dist/lib/session/run-names.d.ts +0 -24
  1226. package/dist/lib/session/run-names.js +0 -26
  1227. package/dist/lib/session/session-cache.d.ts +2 -146
  1228. package/dist/lib/session/session-cache.js +26 -203
  1229. package/dist/lib/session/short-id.js +0 -3
  1230. package/dist/lib/session/sync/agents.d.ts +0 -68
  1231. package/dist/lib/session/sync/agents.js +0 -0
  1232. package/dist/lib/session/sync/backend.d.ts +0 -39
  1233. package/dist/lib/session/sync/backend.js +0 -45
  1234. package/dist/lib/session/sync/config.d.ts +0 -27
  1235. package/dist/lib/session/sync/config.js +0 -56
  1236. package/dist/lib/session/sync/managed-config.d.ts +0 -17
  1237. package/dist/lib/session/sync/managed-config.js +0 -17
  1238. package/dist/lib/session/sync/managed-key.d.ts +0 -38
  1239. package/dist/lib/session/sync/managed-key.js +1 -42
  1240. package/dist/lib/session/sync/net-client.d.ts +0 -36
  1241. package/dist/lib/session/sync/net-client.js +0 -30
  1242. package/dist/lib/session/sync/provision.d.ts +0 -2
  1243. package/dist/lib/session/sync/provision.js +0 -13
  1244. package/dist/lib/session/sync/r2.d.ts +0 -20
  1245. package/dist/lib/session/sync/r2.js +0 -20
  1246. package/dist/lib/session/sync/transcript-crypto.d.ts +0 -37
  1247. package/dist/lib/session/sync/transcript-crypto.js +5 -37
  1248. package/dist/lib/session/sync/worker-template.d.ts +0 -5
  1249. package/dist/lib/session/sync/worker-template.js +0 -40
  1250. package/dist/lib/session/testdata/pid-registry-namespace.js +0 -2
  1251. package/dist/lib/session/testdata/process-view-private.js +0 -1
  1252. package/dist/lib/session/throughput.d.ts +0 -24
  1253. package/dist/lib/session/throughput.js +1 -25
  1254. package/dist/lib/session/timeline-pass.d.ts +1 -63
  1255. package/dist/lib/session/timeline-pass.js +161 -148
  1256. package/dist/lib/session/title.d.ts +0 -182
  1257. package/dist/lib/session/title.js +0 -139
  1258. package/dist/lib/session/tool-index.d.ts +2 -21
  1259. package/dist/lib/session/tool-index.js +3 -75
  1260. package/dist/lib/session/tool-store.d.ts +2 -48
  1261. package/dist/lib/session/tool-store.js +1 -33
  1262. package/dist/lib/session/viewing-in.d.ts +0 -67
  1263. package/dist/lib/session/viewing-in.js +1 -71
  1264. package/dist/lib/session/watch.js +0 -2
  1265. package/dist/lib/session/width.js +0 -3
  1266. package/dist/lib/sessions-client.d.ts +0 -46
  1267. package/dist/lib/sessions-client.js +36 -113
  1268. package/dist/lib/settings-manifest.d.ts +1 -17
  1269. package/dist/lib/settings-manifest.js +4 -57
  1270. package/dist/lib/setup-tool-install.d.ts +0 -1
  1271. package/dist/lib/setup-tool-install.js +2 -7
  1272. package/dist/lib/setup-tool-status.d.ts +0 -3
  1273. package/dist/lib/setup-tool-status.js +4 -14
  1274. package/dist/lib/sha256-asset.d.ts +0 -20
  1275. package/dist/lib/sha256-asset.js +0 -20
  1276. package/dist/lib/share-runtime.d.ts +0 -10
  1277. package/dist/lib/share-runtime.js +1 -27
  1278. package/dist/lib/shim-heal.d.ts +0 -15
  1279. package/dist/lib/shim-heal.js +3 -29
  1280. package/dist/lib/signin-badge.d.ts +0 -72
  1281. package/dist/lib/signin-badge.js +0 -86
  1282. package/dist/lib/sink-format.d.ts +0 -32
  1283. package/dist/lib/sink-format.js +0 -14
  1284. package/dist/lib/smart-launch.d.ts +0 -75
  1285. package/dist/lib/smart-launch.js +1 -83
  1286. package/dist/lib/snapshot.d.ts +0 -46
  1287. package/dist/lib/snapshot.js +5 -31
  1288. package/dist/lib/spinner.d.ts +0 -37
  1289. package/dist/lib/spinner.js +0 -37
  1290. package/dist/lib/sqlite.d.ts +0 -13
  1291. package/dist/lib/sqlite.js +3 -71
  1292. package/dist/lib/ssh-exec.d.ts +0 -209
  1293. package/dist/lib/ssh-exec.js +11 -248
  1294. package/dist/lib/ssh-tunnel.d.ts +0 -43
  1295. package/dist/lib/ssh-tunnel.js +2 -48
  1296. package/dist/lib/staleness/checkers/commands.d.ts +0 -4
  1297. package/dist/lib/staleness/checkers/commands.js +0 -4
  1298. package/dist/lib/staleness/checkers/hooks.d.ts +0 -10
  1299. package/dist/lib/staleness/checkers/hooks.js +0 -15
  1300. package/dist/lib/staleness/checkers/mcp.d.ts +0 -9
  1301. package/dist/lib/staleness/checkers/mcp.js +0 -9
  1302. package/dist/lib/staleness/checkers/permissions.d.ts +0 -12
  1303. package/dist/lib/staleness/checkers/permissions.js +0 -13
  1304. package/dist/lib/staleness/checkers/plugins.d.ts +0 -8
  1305. package/dist/lib/staleness/checkers/plugins.js +0 -8
  1306. package/dist/lib/staleness/checkers/rules.d.ts +0 -15
  1307. package/dist/lib/staleness/checkers/rules.js +0 -26
  1308. package/dist/lib/staleness/checkers/skills.d.ts +0 -4
  1309. package/dist/lib/staleness/checkers/skills.js +0 -4
  1310. package/dist/lib/staleness/checkers/subagents.d.ts +0 -9
  1311. package/dist/lib/staleness/checkers/subagents.js +0 -9
  1312. package/dist/lib/staleness/checkers/types.d.ts +0 -34
  1313. package/dist/lib/staleness/checkers/types.js +0 -19
  1314. package/dist/lib/staleness/checkers/workflows.d.ts +0 -7
  1315. package/dist/lib/staleness/checkers/workflows.js +0 -7
  1316. package/dist/lib/staleness/detectors/commands.js +0 -17
  1317. package/dist/lib/staleness/detectors/hooks.js +1 -6
  1318. package/dist/lib/staleness/detectors/mcp.js +0 -4
  1319. package/dist/lib/staleness/detectors/permissions.js +10 -23
  1320. package/dist/lib/staleness/detectors/rules.js +0 -6
  1321. package/dist/lib/staleness/detectors/skills.d.ts +0 -1
  1322. package/dist/lib/staleness/detectors/skills.js +1 -12
  1323. package/dist/lib/staleness/detectors/types.d.ts +0 -10
  1324. package/dist/lib/staleness/fingerprint.d.ts +0 -19
  1325. package/dist/lib/staleness/fingerprint.js +0 -35
  1326. package/dist/lib/staleness/index.d.ts +0 -28
  1327. package/dist/lib/staleness/index.js +1 -45
  1328. package/dist/lib/staleness/layers.d.ts +0 -23
  1329. package/dist/lib/staleness/layers.js +0 -34
  1330. package/dist/lib/staleness/prune.d.ts +0 -71
  1331. package/dist/lib/staleness/prune.js +2 -27
  1332. package/dist/lib/staleness/registry.d.ts +0 -10
  1333. package/dist/lib/staleness/registry.js +3 -33
  1334. package/dist/lib/staleness/types.d.ts +0 -36
  1335. package/dist/lib/staleness/types.js +0 -5
  1336. package/dist/lib/staleness/writers/commands.js +1 -48
  1337. package/dist/lib/staleness/writers/hooks.js +1 -18
  1338. package/dist/lib/staleness/writers/kinds.d.ts +0 -12
  1339. package/dist/lib/staleness/writers/kinds.js +0 -1
  1340. package/dist/lib/staleness/writers/lazy-map.d.ts +0 -11
  1341. package/dist/lib/staleness/writers/mcp.js +0 -13
  1342. package/dist/lib/staleness/writers/plugins.js +0 -4
  1343. package/dist/lib/staleness/writers/rules.d.ts +0 -1
  1344. package/dist/lib/staleness/writers/rules.js +1 -19
  1345. package/dist/lib/staleness/writers/skills.js +3 -14
  1346. package/dist/lib/staleness/writers/sources.d.ts +0 -11
  1347. package/dist/lib/staleness/writers/sources.js +0 -32
  1348. package/dist/lib/staleness/writers/subagents.js +0 -16
  1349. package/dist/lib/staleness/writers/types.d.ts +0 -58
  1350. package/dist/lib/staleness/writers/workflows.js +1 -1
  1351. package/dist/lib/standalone-tools.d.ts +24 -0
  1352. package/dist/lib/standalone-tools.js +58 -0
  1353. package/dist/lib/star-nudge.d.ts +0 -32
  1354. package/dist/lib/star-nudge.js +2 -36
  1355. package/dist/lib/startup/command-registry.d.ts +0 -45
  1356. package/dist/lib/startup/command-registry.js +1 -46
  1357. package/dist/lib/startup/dev-build.d.ts +0 -9
  1358. package/dist/lib/startup/dev-build.js +1 -42
  1359. package/dist/lib/startup/root-command.d.ts +0 -19
  1360. package/dist/lib/startup/root-command.js +0 -19
  1361. package/dist/lib/startup/spellcheck.d.ts +0 -13
  1362. package/dist/lib/startup/spellcheck.js +0 -13
  1363. package/dist/lib/state.d.ts +0 -376
  1364. package/dist/lib/state.js +19 -737
  1365. package/dist/lib/storage/selection.d.ts +0 -40
  1366. package/dist/lib/storage/selection.js +0 -29
  1367. package/dist/lib/subagents-registry.d.ts +0 -56
  1368. package/dist/lib/subagents-registry.js +0 -99
  1369. package/dist/lib/subagents.d.ts +0 -128
  1370. package/dist/lib/subagents.js +1 -158
  1371. package/dist/lib/summarizer/config.d.ts +0 -37
  1372. package/dist/lib/summarizer/config.js +0 -37
  1373. package/dist/lib/summarizer/pass.d.ts +0 -20
  1374. package/dist/lib/summarizer/pass.js +1 -34
  1375. package/dist/lib/summarizer/summarize.d.ts +1 -49
  1376. package/dist/lib/summarizer/summarize.js +0 -35
  1377. package/dist/lib/sync-passphrase.d.ts +0 -16
  1378. package/dist/lib/sync-passphrase.js +1 -18
  1379. package/dist/lib/sync-status.d.ts +0 -72
  1380. package/dist/lib/sync-status.js +0 -55
  1381. package/dist/lib/sync-umbrella.d.ts +0 -54
  1382. package/dist/lib/sync-umbrella.js +0 -48
  1383. package/dist/lib/system-run-sync.d.ts +0 -5
  1384. package/dist/lib/system-run-sync.js +1 -31
  1385. package/dist/lib/teams/agents.d.ts +1 -444
  1386. package/dist/lib/teams/agents.js +11 -871
  1387. package/dist/lib/teams/api.d.ts +2 -52
  1388. package/dist/lib/teams/api.js +4 -63
  1389. package/dist/lib/teams/debug.d.ts +0 -6
  1390. package/dist/lib/teams/debug.js +0 -6
  1391. package/dist/lib/teams/delivery.d.ts +0 -41
  1392. package/dist/lib/teams/delivery.js +0 -32
  1393. package/dist/lib/teams/file_ops.d.ts +0 -11
  1394. package/dist/lib/teams/file_ops.js +0 -11
  1395. package/dist/lib/teams/forEach.d.ts +0 -76
  1396. package/dist/lib/teams/forEach.js +1 -77
  1397. package/dist/lib/teams/index.d.ts +1 -7
  1398. package/dist/lib/teams/index.js +1 -7
  1399. package/dist/lib/teams/parsers.d.ts +0 -4
  1400. package/dist/lib/teams/parsers.js +0 -81
  1401. package/dist/lib/teams/persistence.d.ts +0 -2
  1402. package/dist/lib/teams/persistence.js +0 -12
  1403. package/dist/lib/teams/placement-probe.d.ts +0 -12
  1404. package/dist/lib/teams/placement-probe.js +1 -51
  1405. package/dist/lib/teams/pr-watch.d.ts +0 -107
  1406. package/dist/lib/teams/pr-watch.js +1 -107
  1407. package/dist/lib/teams/registry.d.ts +0 -36
  1408. package/dist/lib/teams/registry.js +0 -47
  1409. package/dist/lib/teams/remoteWorktree.d.ts +0 -50
  1410. package/dist/lib/teams/remoteWorktree.js +1 -96
  1411. package/dist/lib/teams/scheduler.d.ts +0 -111
  1412. package/dist/lib/teams/scheduler.js +2 -125
  1413. package/dist/lib/teams/summarizer.d.ts +0 -22
  1414. package/dist/lib/teams/summarizer.js +6 -50
  1415. package/dist/lib/teams/supervisor.d.ts +0 -38
  1416. package/dist/lib/teams/supervisor.js +0 -21
  1417. package/dist/lib/teams/worktree.d.ts +0 -95
  1418. package/dist/lib/teams/worktree.js +3 -110
  1419. package/dist/lib/template.d.ts +0 -21
  1420. package/dist/lib/template.js +0 -23
  1421. package/dist/lib/term-client.d.ts +0 -26
  1422. package/dist/lib/term-client.js +0 -48
  1423. package/dist/lib/term-driver.d.ts +0 -5
  1424. package/dist/lib/term-driver.js +0 -8
  1425. package/dist/lib/terminal/backends/ghostty.d.ts +0 -2
  1426. package/dist/lib/terminal/backends/ghostty.js +0 -12
  1427. package/dist/lib/terminal/backends/index.d.ts +0 -17
  1428. package/dist/lib/terminal/backends/index.js +0 -15
  1429. package/dist/lib/terminal/backends/iterm.d.ts +0 -2
  1430. package/dist/lib/terminal/backends/iterm.js +0 -10
  1431. package/dist/lib/terminal/backends/terminal-app.d.ts +0 -10
  1432. package/dist/lib/terminal/backends/terminal-app.js +0 -30
  1433. package/dist/lib/terminal/backends/tmux.d.ts +0 -10
  1434. package/dist/lib/terminal/backends/tmux.js +0 -2
  1435. package/dist/lib/terminal/backends/vscodium-agent.d.ts +0 -25
  1436. package/dist/lib/terminal/backends/vscodium-agent.js +0 -36
  1437. package/dist/lib/terminal/engine.d.ts +0 -17
  1438. package/dist/lib/terminal/engine.js +0 -10
  1439. package/dist/lib/terminal/index.d.ts +0 -8
  1440. package/dist/lib/terminal/inject.d.ts +1 -173
  1441. package/dist/lib/terminal/inject.js +0 -146
  1442. package/dist/lib/terminal/policy.d.ts +0 -8
  1443. package/dist/lib/terminal/policy.js +0 -1
  1444. package/dist/lib/terminal/preferred.d.ts +0 -66
  1445. package/dist/lib/terminal/preferred.js +0 -30
  1446. package/dist/lib/terminal/quote.d.ts +0 -8
  1447. package/dist/lib/terminal/quote.js +0 -8
  1448. package/dist/lib/terminal/resolve.d.ts +0 -46
  1449. package/dist/lib/terminal/resolve.js +0 -30
  1450. package/dist/lib/terminal/run-surface.d.ts +0 -56
  1451. package/dist/lib/terminal/run-surface.js +0 -46
  1452. package/dist/lib/terminal/shell.d.ts +0 -3
  1453. package/dist/lib/terminal/shell.js +0 -13
  1454. package/dist/lib/terminal/transport.d.ts +0 -14
  1455. package/dist/lib/terminal/transport.js +1 -30
  1456. package/dist/lib/terminal/types.d.ts +0 -40
  1457. package/dist/lib/terminal/types.js +0 -10
  1458. package/dist/lib/text/relative-time.d.ts +0 -36
  1459. package/dist/lib/text/relative-time.js +0 -43
  1460. package/dist/lib/text/short-id.d.ts +0 -16
  1461. package/dist/lib/text/short-id.js +0 -16
  1462. package/dist/lib/text/width.d.ts +0 -24
  1463. package/dist/lib/text/width.js +10 -52
  1464. package/dist/lib/tmux/binary.d.ts +0 -36
  1465. package/dist/lib/tmux/binary.js +0 -28
  1466. package/dist/lib/tmux/index.d.ts +0 -5
  1467. package/dist/lib/tmux/index.js +0 -5
  1468. package/dist/lib/tmux/orphan-reap.d.ts +0 -253
  1469. package/dist/lib/tmux/orphan-reap.js +3 -299
  1470. package/dist/lib/tmux/paths.d.ts +0 -14
  1471. package/dist/lib/tmux/paths.js +0 -14
  1472. package/dist/lib/tmux/session.d.ts +0 -263
  1473. package/dist/lib/tmux/session.js +6 -344
  1474. package/dist/lib/traces/backend.d.ts +0 -20
  1475. package/dist/lib/traces/backend.js +0 -23
  1476. package/dist/lib/traces/classify.d.ts +0 -20
  1477. package/dist/lib/traces/classify.js +0 -7
  1478. package/dist/lib/traces/insights.d.ts +1 -84
  1479. package/dist/lib/traces/insights.js +0 -126
  1480. package/dist/lib/traces/phenotype.d.ts +0 -69
  1481. package/dist/lib/traces/phenotype.js +0 -150
  1482. package/dist/lib/traces/provision.d.ts +0 -1
  1483. package/dist/lib/traces/provision.js +0 -5
  1484. package/dist/lib/traces/schema2-build.d.ts +3 -60
  1485. package/dist/lib/traces/schema2-build.js +8 -149
  1486. package/dist/lib/traces/schema2-danger.d.ts +0 -30
  1487. package/dist/lib/traces/schema2-danger.js +1 -63
  1488. package/dist/lib/traces/schema2.d.ts +0 -11
  1489. package/dist/lib/traces/segments.d.ts +0 -44
  1490. package/dist/lib/traces/segments.js +0 -65
  1491. package/dist/lib/traces/sync.d.ts +2 -239
  1492. package/dist/lib/traces/sync.js +15 -237
  1493. package/dist/lib/traces/worker-template.d.ts +0 -3
  1494. package/dist/lib/traces/worker-template.js +0 -29
  1495. package/dist/lib/triggers/handlers.d.ts +0 -82
  1496. package/dist/lib/triggers/handlers.js +0 -75
  1497. package/dist/lib/triggers/webhook.d.ts +0 -158
  1498. package/dist/lib/triggers/webhook.js +0 -143
  1499. package/dist/lib/types.d.ts +0 -736
  1500. package/dist/lib/types.js +0 -50
  1501. package/dist/lib/uninstall.d.ts +0 -17
  1502. package/dist/lib/uninstall.js +0 -75
  1503. package/dist/lib/usage-backoff.d.ts +0 -20
  1504. package/dist/lib/usage-backoff.js +3 -60
  1505. package/dist/lib/usage-refresh.d.ts +0 -154
  1506. package/dist/lib/usage-refresh.js +8 -212
  1507. package/dist/lib/verbs.d.ts +0 -12
  1508. package/dist/lib/verbs.js +0 -12
  1509. package/dist/lib/version-duplicates.d.ts +0 -6
  1510. package/dist/lib/version-duplicates.js +0 -6
  1511. package/dist/lib/version.d.ts +0 -55
  1512. package/dist/lib/version.js +1 -58
  1513. package/dist/lib/view-types.d.ts +0 -48
  1514. package/dist/lib/warn-unpushed.d.ts +0 -27
  1515. package/dist/lib/warn-unpushed.js +1 -45
  1516. package/dist/lib/watchdog/history.d.ts +0 -1
  1517. package/dist/lib/watchdog/history.js +0 -3
  1518. package/dist/lib/watchdog/index.js +0 -2
  1519. package/dist/lib/watchdog/log.d.ts +0 -19
  1520. package/dist/lib/watchdog/log.js +0 -34
  1521. package/dist/lib/watchdog/read.d.ts +0 -27
  1522. package/dist/lib/watchdog/read.js +3 -58
  1523. package/dist/lib/watchdog/rotate.d.ts +0 -139
  1524. package/dist/lib/watchdog/rotate.js +2 -151
  1525. package/dist/lib/watchdog/runner.d.ts +0 -125
  1526. package/dist/lib/watchdog/runner.js +3 -223
  1527. package/dist/lib/watchdog/service.d.ts +0 -1
  1528. package/dist/lib/watchdog/service.js +0 -2
  1529. package/dist/lib/watchdog/watchdog-agent.d.ts +0 -31
  1530. package/dist/lib/watchdog/watchdog-agent.js +0 -27
  1531. package/dist/lib/watchdog/watchdog.d.ts +0 -7
  1532. package/dist/lib/watchdog/watchdog.js +0 -10
  1533. package/dist/lib/watchdog/watchdogTail.js +0 -7
  1534. package/dist/lib/whats-new.d.ts +0 -10
  1535. package/dist/lib/whats-new.js +0 -21
  1536. package/dist/lib/workflows-registry.d.ts +0 -45
  1537. package/dist/lib/workflows-registry.js +0 -60
  1538. package/dist/lib/workflows.d.ts +0 -282
  1539. package/dist/lib/workflows.js +3 -213
  1540. package/dist/lib/worktree/held.d.ts +0 -89
  1541. package/dist/lib/worktree/held.js +1 -103
  1542. package/dist/lib/wrap.d.ts +0 -32
  1543. package/dist/lib/wrap.js +0 -32
  1544. package/dist/lib/yaml-io.d.ts +0 -45
  1545. package/dist/lib/yaml-io.js +0 -47
  1546. package/dist/session-tracker/dist/adapters/claude.js +0 -2
  1547. package/dist/session-tracker/dist/hook.sh +4 -37
  1548. package/dist/session-tracker/dist/index.d.ts +0 -7
  1549. package/dist/session-tracker/dist/index.js +0 -5
  1550. package/dist/session-tracker/dist/install-hook.d.ts +0 -5
  1551. package/dist/session-tracker/dist/install-hook.js +1 -15
  1552. package/dist/session-tracker/dist/prune-state.js +0 -4
  1553. package/dist/session-tracker/dist/reader.js +0 -4
  1554. package/dist/session-tracker/dist/state-file.d.ts +0 -3
  1555. package/dist/session-tracker/dist/state-file.js +3 -8
  1556. package/package.json +3 -4
  1557. package/scripts/postinstall.js +8 -116
  1558. package/dist/lib/session/artifacts.d.ts +0 -14
  1559. package/dist/lib/session/artifacts.js +0 -101
  1560. package/dist/lib/session/bash-command.d.ts +0 -63
  1561. package/dist/lib/session/bash-command.js +0 -490
  1562. package/dist/lib/session/digest.d.ts +0 -66
  1563. package/dist/lib/session/digest.js +0 -328
  1564. package/dist/lib/session/highlights.d.ts +0 -151
  1565. package/dist/lib/session/highlights.js +0 -391
  1566. package/dist/lib/session/insights.d.ts +0 -165
  1567. package/dist/lib/session/insights.js +0 -563
  1568. package/dist/lib/session/linear.d.ts +0 -19
  1569. package/dist/lib/session/linear.js +0 -99
  1570. package/dist/lib/session/parse.d.ts +0 -224
  1571. package/dist/lib/session/parse.js +0 -2408
  1572. package/dist/lib/session/prompt.d.ts +0 -213
  1573. package/dist/lib/session/prompt.js +0 -601
  1574. package/dist/lib/session/render.d.ts +0 -133
  1575. package/dist/lib/session/render.js +0 -1047
  1576. package/dist/lib/session/share-html.d.ts +0 -35
  1577. package/dist/lib/session/share-html.js +0 -319
  1578. package/dist/lib/session/shell-programs.d.ts +0 -33
  1579. package/dist/lib/session/shell-programs.js +0 -380
  1580. package/dist/lib/session/state.d.ts +0 -269
  1581. package/dist/lib/session/state.js +0 -777
  1582. package/dist/lib/session/stream-render.d.ts +0 -4
  1583. package/dist/lib/session/stream-render.js +0 -131
  1584. package/dist/lib/session/tail.d.ts +0 -88
  1585. package/dist/lib/session/tail.js +0 -294
  1586. package/dist/lib/session/team-filter.d.ts +0 -129
  1587. package/dist/lib/session/team-filter.js +0 -269
  1588. package/dist/lib/session/timeline.d.ts +0 -172
  1589. package/dist/lib/session/timeline.js +0 -636
  1590. package/dist/lib/session/tool-calls.d.ts +0 -132
  1591. package/dist/lib/session/tool-calls.js +0 -674
  1592. package/dist/lib/session/trajectory-compare.d.ts +0 -48
  1593. package/dist/lib/session/trajectory-compare.js +0 -116
  1594. package/dist/lib/session/trajectory-html.d.ts +0 -19
  1595. package/dist/lib/session/trajectory-html.js +0 -771
  1596. package/dist/lib/session/trajectory-lineage.d.ts +0 -93
  1597. package/dist/lib/session/trajectory-lineage.js +0 -195
  1598. package/dist/lib/session/trajectory-text.d.ts +0 -38
  1599. package/dist/lib/session/trajectory-text.js +0 -284
  1600. package/dist/lib/session/trajectory.d.ts +0 -145
  1601. package/dist/lib/session/trajectory.js +0 -394
  1602. package/dist/lib/session/types.d.ts +0 -627
  1603. package/dist/lib/session/types.js +0 -43
@@ -1,12 +1,3 @@
1
- /**
2
- * Session discovery, search, and rendering commands.
3
- *
4
- * Implements `agents sessions` -- the unified interface for finding, browsing,
5
- * and reading agent conversation transcripts across Claude, Codex, Gemini,
6
- * and OpenCode. Supports interactive picker mode, text/path search, markdown
7
- * and JSON rendering, role/turn filtering, artifact inspection, and session
8
- * resume via agent-native CLI flags.
9
- */
10
1
  import * as fs from 'fs';
11
2
  import * as os from 'os';
12
3
  import * as path from 'path';
@@ -19,33 +10,31 @@ import { resolveProjectKey } from '../lib/project-key.js';
19
10
  import { listProjectDefs, resolveProjectNameForCwd } from '../lib/projects.js';
20
11
  import ora from 'ora';
21
12
  import { interruptibleSpinner } from '../lib/spinner.js';
22
- import { SESSION_AGENTS, isAgentTmuxAlias, sessionDisplayAgent } from '../lib/session/types.js';
23
- import { discoverArtifacts, readArtifact, resolveArtifact } from '../lib/session/artifacts.js';
13
+ import { SESSION_AGENTS, isAgentTmuxAlias, sessionDisplayAgent } from '@phnx-labs/sessions-cli/reader';
14
+ import { discoverArtifacts, readArtifact, resolveArtifact } from '@phnx-labs/sessions-cli/reader';
24
15
  import { looksLikePath, toComparablePath, homeDir, needsWindowsShell, composeWin32CommandLine } from '../lib/platform/index.js';
25
- import { getActiveSessions, describeActiveDiscoveryHealth, sessionProcessIsLocal, backfillActiveRowsFromIndex, isRunningLiveSession, serializeActiveSessionsForJson, serializeSessionsJson, shortIdFromName } from '../lib/session/active.js';
16
+ import { getActiveSessions, serializeSessionsJson, shortIdFromName } from '../lib/session/active.js';
26
17
  export { activeSessionProjectKey, backfillActiveRowsFromIndex, backfillActiveRowsFromMeta, isRunningLiveSession, serializeActiveSessionsForJson, serializeSessionsJson } from '../lib/session/active.js';
27
- import { enumerateGhosttyTabs, assignGhosttyTabs } from '../lib/session/ghostty-tabs.js';
28
- import { mapPanesToTargets, listClients } from '../lib/tmux/session.js';
29
- import { resolveViewingIn, viewingInLabel } from '../lib/session/viewing-in.js';
30
- import { machineId, normalizeHost } from '../lib/session/sync/config.js';
31
- import { gatherRemoteActive, NO_FANOUT_ENV } from '../lib/session/remote-active.js';
32
- import { loadFleetActiveSessions, loadLocalActiveSessions, readActiveSessionsCache, } from '../lib/session/session-cache.js';
18
+ import { viewingInLabel } from '../lib/session/viewing-in.js';
19
+ import { machineId } from '../lib/session/sync/config.js';
20
+ import { NO_FANOUT_ENV } from '../lib/session/remote-active.js';
21
+ import { loadLocalActiveSessions, readActiveSessionsCache, } from '../lib/session/session-cache.js';
33
22
  import { gatherRemoteList, gatherRemoteToolProgramCounts, gatherRemoteToolSearch, runOnPeer } from '../lib/session/remote-list.js';
34
23
  import { gatherRemoteAgentsJson } from '../lib/remote-agents-json.js';
35
24
  import { stringWidth, truncateToWidth, padToWidth, terminalWidth } from '../lib/session/width.js';
36
- import { inferSessionState } from '../lib/session/state.js';
25
+ import { inferSessionState } from '@phnx-labs/sessions-cli/reader';
37
26
  import { discoverSessions, queryIndexedSessions, countSessionsInScope, resolveSessionById, isCompleteSessionId, looksLikeSessionId, searchContentIndex, getSessionRoots, scopeToManaged } from '../lib/session/discover.js';
38
27
  import { findSessionsById, querySessions, readSessionContent, readArchivedSessionPreview, readSessionTimelineAny } from '../lib/session/db.js';
39
- import { foldTimeline, emptyTimelineState, projectTimeline, projectSessionFiles } from '../lib/session/timeline.js';
40
- import { readSessionTail } from '../lib/session/tail.js';
28
+ import { foldTimeline, emptyTimelineState, projectTimeline, projectSessionFiles } from '@phnx-labs/sessions-cli/reader';
29
+ import { readSessionTail } from '@phnx-labs/sessions-cli/reader';
41
30
  import { liveSessionMetas, fleetExecutionMachineById, reconcileLiveMetaMachine } from '../lib/session/live-metadata.js';
42
31
  import { sessionHeadline } from '../lib/session/title.js';
43
- import { filterTeamSessions, shouldShowTeamSessions, safeTeamText, groupSessionsByTeam, NO_TEAM_GROUP_KEY, } from '../lib/session/team-filter.js';
44
- import { parseSession } from '../lib/session/parse.js';
32
+ import { filterTeamSessions, shouldShowTeamSessions, safeTeamText, groupSessionsByTeam, NO_TEAM_GROUP_KEY, } from '@phnx-labs/sessions-cli/reader';
33
+ import { parseSession } from '@phnx-labs/sessions-cli/reader';
45
34
  import { runRemoteSessions, buildForwardedArgs, ensureWholeIndex } from '../lib/session/remote.js';
46
- import { formatRelativeTime, formatCompactAge, sessionAgeParts } from '../lib/session/relative-time.js';
47
- import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList, linkPath, linkUrl, shortenModel, formatTokenCount } from '../lib/session/render.js';
48
- import { linearIssueUrl } from '../lib/session/linear.js';
35
+ import { formatRelativeTime, sessionAgeParts } from '../lib/session/relative-time.js';
36
+ import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList, linkPath, linkUrl, shortenModel, formatTokenCount } from '@phnx-labs/sessions-cli/reader';
37
+ import { linearIssueUrl } from '@phnx-labs/sessions-cli/reader';
49
38
  import { sessionOwnerDevice, RESUME_PINNED_ENV } from '../lib/session/resume-owner.js';
50
39
  import { renderMarkdown } from '../lib/markdown.js';
51
40
  import { AGENTS, colorAgent, resolveAgentName } from '../lib/agents.js';
@@ -59,8 +48,9 @@ import { listInstalledVersions, resolveVersionAliasLoose } from '../lib/installa
59
48
  import { getAgentsInvocation } from '../lib/daemon/daemon.js';
60
49
  import { sessionAgentSupportsResume, sessionRecoveryRunArgs } from '../lib/session/recovery.js';
61
50
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
62
- import { sessionPicker, buildPreview, loadSessionPreviewDigest, transcriptOnPeerOf, formatTodoCompact, githubRepoUrlFromCwd, } from './sessions-picker.js';
51
+ import { sessionPicker, buildPreview, loadSessionPreviewDigest, transcriptOnPeerOf, formatTodoCompact, } from './sessions-picker.js';
63
52
  import { setHelpSections } from '../lib/help.js';
53
+ import { cleanPreview, gatherActiveSessions, indexActiveBySessionId, isAwaitingUser, liveGlyphAndPreview, liveStatusWord, remoteHostsToDial, requestedLiveStatuses, resolveRoutineName, runLiveRoster, shouldIncludeLocal, signalBadges, statusColor, } from './ps-roster.js';
64
54
  import { registerSessionsTailCommand } from './sessions-tail.js';
65
55
  import { registerSessionsResumeCommand } from './sessions-resume.js';
66
56
  import { registerSessionsForkCommand } from './fork.js';
@@ -90,17 +80,7 @@ const SESSION_AGENT_FILTER_HELP = `Filter by agent, e.g. claude, codex, claude@2
90
80
  function collectQueryClause(value, previous) {
91
81
  return [...previous, value];
92
82
  }
93
- /**
94
- * The prioritized harnesses that get a boolean shorthand flag (e.g. `--claude`
95
- * === `--agent claude`). The rest stay reachable via `--agent <name>`, which
96
- * also carries version pins like `codex@0.116.0`.
97
- */
98
83
  const AGENT_SHORTHANDS = ['claude', 'codex', 'kimi', 'antigravity', 'grok', 'opencode'];
99
- /**
100
- * Resolve a per-agent shorthand (`--claude`, `--kimi`, …) into `options.agent`.
101
- * An explicit `--agent` wins; if two shorthands are passed we take the first and
102
- * ignore the rest (commander gives no ordering, so this is a best-effort alias).
103
- */
104
84
  function applyAgentShorthands(options) {
105
85
  if (options.agent)
106
86
  return;
@@ -108,10 +88,6 @@ function applyAgentShorthands(options) {
108
88
  if (hit)
109
89
  options.agent = hit;
110
90
  }
111
- /**
112
- * Treat a positional `agent@version` as a structured filter only when it names
113
- * a real installed version. Everything else remains ordinary free-text search.
114
- */
115
91
  export function parseInstalledAgentVersionQuery(query, installedVersions = (agent) => (agent in AGENTS ? listInstalledVersions(agent) : [])) {
116
92
  const trimmed = query?.trim();
117
93
  if (!trimmed)
@@ -149,7 +125,6 @@ function applyVersionFilters(query, options) {
149
125
  const CLAUDE_RESUME_MATCH_WINDOW_MS = 10 * 60_000;
150
126
  const LOAD_VERBS = ['Loading', 'Scanning', 'Gathering', 'Indexing', 'Reading'];
151
127
  const FIND_VERBS = ['Finding', 'Searching', 'Locating', 'Matching'];
152
- /** Build a spinner-backed progress tracker that cycles through verbs while scanning sessions. */
153
128
  function createScanProgressTracker(verbs, suffix, spinner) {
154
129
  const counts = new Map();
155
130
  let verbIndex = 0;
@@ -186,24 +161,11 @@ function createScanProgressTracker(verbs, suffix, spinner) {
186
161
  };
187
162
  }
188
163
  const PICKER_RECENT_COUNT = 15;
189
- /**
190
- * The `--limit` default, shared with its `.option()` registration. Commander fills
191
- * the default in, so `options.limit` is never falsy — code that wants to know
192
- * whether the USER set a limit has to compare against this rather than test
193
- * truthiness.
194
- */
195
164
  const DEFAULT_LIMIT = '50';
196
- /** Pool size for `--in-team`: one team's rows can sit anywhere in the history. */
197
165
  const WHOLE_TEAM_POOL_LIMIT = 5000;
198
- // The grouped default view ("overview"): fetch a generous recency-ordered pool
199
- // for accurate per-project totals, show each project's most-recent rows grouped
200
- // by project, newest-active project first.
201
- const OVERVIEW_ROWS_PER_PROJECT = 5; // recent rows shown per project before "· N more"
202
- const OVERVIEW_POOL_LIMIT = 1000; // fetch cap — accurate per-project totals up to this
203
- const OVERVIEW_MAX_PROJECTS = 12; // project groups shown before "+N more projects"
204
- /**
205
- * Resolve a path-like query to an absolute directory path.
206
- */
166
+ const OVERVIEW_ROWS_PER_PROJECT = 5;
167
+ const OVERVIEW_POOL_LIMIT = 1000;
168
+ const OVERVIEW_MAX_PROJECTS = 12;
207
169
  function resolvePathFilter(query) {
208
170
  const expanded = query.startsWith('~')
209
171
  ? path.join(os.homedir(), query.slice(1))
@@ -251,28 +213,6 @@ async function renderArtifactsForSession(session, listAll, name) {
251
213
  }
252
214
  console.log(chalk.gray(`\n${artifacts.length} artifact${artifacts.length !== 1 ? 's' : ''}.`));
253
215
  }
254
- function statusColor(status) {
255
- switch (status) {
256
- case 'running': return chalk.green;
257
- case 'idle': return chalk.gray;
258
- case 'queued': return chalk.blue;
259
- case 'input_required': return chalk.yellow;
260
- // Dead process: dimmed so it recedes from the live rows without being mistaken
261
- // for the gray `idle` (which reads as "done, waiting for you" — a live state).
262
- case 'closed': return chalk.dim;
263
- // Days-stale / dangling: red so a session nobody is driving stands out.
264
- case 'abandoned': return chalk.red;
265
- // The host window died and took the agent with it — an unclean exit, not the
266
- // dimmed `closed` of a normal one. Red-bright so a crash reads as an event.
267
- case 'crashed': return chalk.redBright;
268
- // Alive with nobody attached. Yellow, like `input_required`: both mean the
269
- // session is stuck waiting on a human, and this one has no human to wait for.
270
- case 'orphaned': return chalk.yellow;
271
- // Alive but un-introspectable (a harness whose transcript we can't parse).
272
- // Magenta so it never reads as the gray "idle" it used to be faked as.
273
- case 'unknown': return chalk.magenta;
274
- }
275
- }
276
216
  function contextColor(context) {
277
217
  switch (context) {
278
218
  case 'terminal': return chalk.magenta;
@@ -285,9 +225,6 @@ function shortCwd(cwd) {
285
225
  if (!cwd)
286
226
  return '-';
287
227
  const home = homeDir();
288
- // Compare in normalized form so the `~` shorthand also lands on Windows
289
- // (case-insensitive, backslash paths); on POSIX this is byte-identical to the
290
- // previous `cwd.startsWith(home)`. The displayed tail keeps original casing.
291
228
  return toComparablePath(cwd).startsWith(toComparablePath(home))
292
229
  ? '~' + cwd.slice(home.length)
293
230
  : cwd;
@@ -297,276 +234,13 @@ function formatStartedAt(startedAtMs) {
297
234
  return '-';
298
235
  return formatRelativeTime(new Date(startedAtMs).toISOString());
299
236
  }
300
- /**
301
- * Strip terminal/harness noise from a preview so the column stays a single line
302
- * of plain prose: OSC title escapes, CSI/SGR ANSI, and the harness wrapper tags
303
- * (`<local-command-stdout>`, `<task-notification>`, `<command-*>`) that leak from
304
- * a captured transcript tail. Collapses runs of whitespace.
305
- */
306
- export function cleanPreview(text) {
307
- // eslint-disable-next-line no-control-regex
308
- return text
309
- .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '') // OSC (title) sequences
310
- .replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '') // CSI / SGR ANSI
311
- .replace(/<\/?(?:local-command-stdout|command-name|command-message|command-args|task-notification|system-reminder)>/g, '')
312
- .replace(/\s+/g, ' ')
313
- .trim();
314
- }
315
- /**
316
- * Build the live description for an active session: checklist progress (when
317
- * present) plus the state engine's preview (the latest turn), a user label, or
318
- * the first-prompt topic. Used by both the flat listing's `doing` cell and as
319
- * the snippet half of the --active row (identity is layered on in printActiveRow).
320
- *
321
- * Covers every ActiveSession context: terminal (interactive), headless, teams,
322
- * cloud, and sub-agent rows that share the same ActiveSession.todos field.
323
- */
324
- export function buildSessionDescription(s) {
325
- const todo = formatTodoCompact(s.todos);
326
- if (s.context === 'cloud') {
327
- const base = s.preview || `${s.cloudProvider ?? ''}${s.cloudTaskId ? ` · ${s.cloudTaskId.slice(0, 12)}` : ''}`;
328
- return cleanPreview([todo, base].filter(Boolean).join(' · '));
329
- }
330
- if (s.context === 'teams') {
331
- // A teams row identifies its TEAM, then the teammate within it, then who
332
- // spun it up, then what it's working on — so several teams from one
333
- // orchestrator stay distinct and each shows its target, not just its slug.
334
- const parts = [s.teamName];
335
- // Teammate name (distinct from the team slug) — which member this row is.
336
- if (s.label && s.label !== s.teamName)
337
- parts.push(s.label);
338
- // Lineage: which orchestrator spun up this team. Prefer the resolved label,
339
- // else the short session id, so "by whom" is answerable at a glance.
340
- const orch = s.orchestratorLabel || (s.orchestratorSessionId ? s.orchestratorSessionId.slice(0, 8) : '');
341
- if (orch)
342
- parts.push(`by ${orch}`);
343
- if (todo)
344
- parts.push(todo);
345
- // Target: the live latest turn if working, else the assigned mission (the
346
- // team's task/target, shown even before the teammate has a transcript), else
347
- // the transcript topic.
348
- const target = s.preview || s.assignedTask || s.topic;
349
- if (target)
350
- parts.push(target);
351
- return cleanPreview(parts.filter(Boolean).join(' · '));
352
- }
353
- // Terminal, headless, or sub-agent: todos + live preview, then label, then topic.
354
- // ladder-exempt: the compact --active preview BASE (a live snippet), not the row's headline title.
355
- const base = s.preview || s.label || s.topic || '';
356
- return cleanPreview([todo, base].filter(Boolean).join(' · '));
357
- }
358
- /**
359
- * Identity + checklist + live snippet for an --active / cross-machine row.
360
- * Surfaces agent-adjacent identity the flat table already has (label, project)
361
- * with a clickable project when a GitHub URL is resolvable, then the checklist
362
- * tally and the latest-turn snippet.
363
- *
364
- * Free-text fields are cleaned individually; OSC 8 hyperlinks are applied
365
- * *after* cleaning so `cleanPreview` does not strip the clickable targets
366
- * (RUSH-2045 review). Ticket is clickable via {@link signalBadges}, not here,
367
- * so the id is not printed twice.
368
- */
369
- export function formatActiveRowDescription(s) {
370
- const parts = [];
371
- const pushText = (t) => {
372
- const c = t ? cleanPreview(t) : '';
373
- if (c)
374
- parts.push(c);
375
- };
376
- if (s.context === 'teams' && s.teamName)
377
- pushText(s.teamName);
378
- if (s.label)
379
- pushText(s.label);
380
- // Project = basename(cwd), same derivation as serializeActiveSessionsForJson.
381
- const project = s.cwd ? path.basename(s.cwd) : '';
382
- if (project && project !== s.label && project !== s.teamName) {
383
- const label = cleanPreview(project);
384
- const repoUrl = githubRepoUrlFromCwd(s.cwd);
385
- parts.push(repoUrl ? linkUrl(repoUrl, label) : label);
386
- }
387
- const todo = formatTodoCompact(s.todos);
388
- if (todo)
389
- parts.push(todo);
390
- // Latest-turn snippet — avoid repeating label/topic when already used as identity.
391
- // A cloud row with no preview yet says nothing here; its provider + task id
392
- // ride the locator badge instead (RUSH-2336), not duplicated on this line.
393
- if (s.preview) {
394
- pushText(s.preview);
395
- }
396
- else if (!s.label && s.topic) {
397
- pushText(s.topic);
398
- }
399
- return parts.filter(Boolean).join(' · ');
400
- }
401
- /** Short human word for a session's activity (falls back to the coarse status). */
402
- function activityLabel(s) {
403
- // Lifecycle status wins over any residual parsed activity — a dead/dangling
404
- // session must read `closed`/`abandoned`, not the `idle` its stale tail infers.
405
- // `crashed`/`orphaned` are the same kind of claim about the session as a whole,
406
- // and both outrank the `idle` its last parsed turn would otherwise show.
407
- if (s.status === 'closed' || s.status === 'abandoned')
408
- return s.status;
409
- if (s.status === 'crashed')
410
- return 'crashed';
411
- if (s.status === 'orphaned')
412
- return 'orphan';
413
- if (s.activity === 'waiting_input')
414
- return 'waiting';
415
- if (s.activity === 'working')
416
- return 'working';
417
- if (s.activity === 'idle')
418
- return 'idle';
419
- return s.status === 'input_required' ? 'waiting' : s.status;
420
- }
421
- /**
422
- * Index live sessions by their full session UUID so a historical `SessionMeta`
423
- * row (`meta.id`) can be matched to the session that is still running now.
424
- * Rows without a sessionId (some cloud/headless probes) are skipped — they
425
- * can't be correlated back to a transcript on disk.
426
- */
427
- export function indexActiveBySessionId(active) {
428
- const byId = new Map();
429
- for (const a of active) {
430
- if (a.sessionId)
431
- byId.set(a.sessionId, a);
432
- }
433
- return byId;
434
- }
435
- /**
436
- * The live decoration for a listing row: a status glyph and the latest-turn
437
- * preview, when the session is still running. `●` running / `◐` waiting on the
438
- * user / `○` idle, colored by the same `statusColor` the --active view uses.
439
- * Returns empty strings when there is no live match, so callers render the
440
- * plain historical row unchanged.
441
- */
442
- export function liveGlyphAndPreview(a) {
443
- if (!a)
444
- return { glyph: '', preview: '' };
445
- // `◌` (dotted) = alive but un-introspectable — visually distinct from `○` idle
446
- // so an opaque harness is never mistaken for a finished one. `⊘` = abandoned /
447
- // dangling; `×` = closed (dead pid) — both distinct from the `○` idle a live,
448
- // stopped session shows, so a gone session never masquerades as a resting one.
449
- // `✗` = crashed (died WITH its host window, uncleanly) — deliberately louder
450
- // than the `×` of a clean close. `◍` = orphaned (alive, nothing attached): a
451
- // filled ring, so it reads as "still burning" unlike the hollow `○` idle.
452
- if (a.status === 'abandoned')
453
- return { glyph: statusColor(a.status)('⊘'), preview: buildSessionDescription(a) };
454
- if (a.status === 'closed')
455
- return { glyph: statusColor(a.status)('×'), preview: buildSessionDescription(a) };
456
- if (a.status === 'crashed')
457
- return { glyph: statusColor(a.status)('✗'), preview: buildSessionDescription(a) };
458
- if (a.status === 'orphaned')
459
- return { glyph: statusColor(a.status)('◍'), preview: buildSessionDescription(a) };
460
- const waiting = a.status === 'input_required' || a.activity === 'waiting_input';
461
- const running = a.status === 'running' || a.activity === 'working';
462
- const unknown = a.status === 'unknown';
463
- const shape = waiting ? '◐'
464
- : running ? '●'
465
- : unknown ? '◌'
466
- : '○';
467
- return { glyph: statusColor(a.status)(shape), preview: buildSessionDescription(a) };
468
- }
469
- /**
470
- * The one-word live status for a listing row — `working` / `waiting` / `idle`,
471
- * the same three states the `--active` column shows, so the default list is no
472
- * longer just a glyph. `waiting` is the actionable "needs you" case (a question /
473
- * permission / plan-review), kept distinct from `idle` (stopped) and `working`.
474
- * Empty for a not-live row, and for the rare no-signal `unknown`. Pure +
475
- * exported for the row tests.
476
- */
477
- export function liveStatusWord(a) {
478
- if (!a)
479
- return '';
480
- // Lifecycle status is definitive — surface it ahead of any parsed activity.
481
- if (a.status === 'closed' || a.status === 'abandoned')
482
- return a.status;
483
- // Same rank: a lost host is a fact about the session, not about its last turn.
484
- if (a.status === 'crashed')
485
- return 'crashed';
486
- if (a.status === 'orphaned')
487
- return 'orphan';
488
- if (a.status === 'input_required' || a.activity === 'waiting_input')
489
- return 'waiting';
490
- if (a.status === 'running' || a.activity === 'working')
491
- return 'working';
492
- if (a.status === 'idle' || a.activity === 'idle')
493
- return 'idle';
494
- if (a.status === 'queued')
495
- return 'queued';
496
- return '';
497
- }
498
- /**
499
- * True when a session is blocked on a human — the `--waiting` contract.
500
- *
501
- * NOT `status === 'input_required'`. `foldHostLink` rewrites that status to
502
- * `orphaned` when nothing is attached, and a session waiting on a question with
503
- * NOBODY watching is the most acute case `--waiting` exists to surface, not one
504
- * it should drop. The underlying `activity` is never rewritten, so it is the
505
- * honest signal here.
506
- *
507
- * But `activity` is never rewritten for a DEAD session either: one that died
508
- * mid-question keeps `waiting_input` forever, and answering it is not a thing a
509
- * human can do — it needs a relaunch. `--waiting` is a scriptable gate ("does
510
- * anything need me?"), so a corpse must not trip it.
511
- *
512
- * `closed` and `crashed` are unconditionally dead, so they are excluded outright.
513
- * `abandoned` is NOT: it fires on transcript staleness before the liveness check,
514
- * so it also covers the live-but-forgotten case — an interactive session that
515
- * asked a question and sat untouched over a long weekend is still answerable, and
516
- * is exactly what this gate exists for. It is excluded only when we positively
517
- * know its process is gone; unknown liveness (an older peer, a row with no pid)
518
- * stays excluded rather than inventing a human who can answer.
519
- */
520
- export function isAwaitingUser(s) {
521
- if (s.status === 'crashed' || s.status === 'closed')
522
- return false;
523
- if (s.status === 'abandoned' && s.pidAlive !== true)
524
- return false;
525
- return s.status === 'input_required' || s.activity === 'waiting_input';
526
- }
527
- /**
528
- * Whether a row belongs in an unqualified `--active` view (RUSH-2336) — the
529
- * ONE canonical selector every bare-active surface shares: the CLI's grouped
530
- * table and `--json` (`renderActiveSessions`), the interactive browser's
531
- * `--active` filter ({@link applyFilters} in `sessions-browser.ts`), `focus`'s
532
- * attach gate (`isAttachableLiveSession`), and the menubar snapshot
533
- * ({@link computeMenubarSnapshot}).
534
- *
535
- * The live registry deliberately retains terminally-dead AND not-yet-started
536
- * rows long enough for `--closed` / `--crashed` / `--queued` and recovery to
537
- * find them. Presence in that registry therefore is not, by itself, evidence
538
- * that a session is CURRENTLY active. Explicit lifecycle filters bypass this
539
- * predicate and select those retained rows through {@link matchesLiveStatus}
540
- * instead — this function only decides the unqualified default.
541
- *
542
- * - `queued` / `closed` / `crashed` are never active here: queued hasn't
543
- * started yet, closed/crashed are unconditionally dead. All three stay
544
- * reachable through their explicit filter.
545
- * - A cloud row carries no local pid at all — it is active on the
546
- * provider's own word (`cloudProvider` + `cloudTaskId`), never a
547
- * fabricated pid.
548
- * - Every other row is a real OS process (terminal/tmux/headless/team). It
549
- * is active only when the row names the machine it runs on, carries a
550
- * positive pid, AND has POSITIVELY verified that pid is alive
551
- * (`pidAlive === true` — not merely "not known dead"). A live orphaned
552
- * row, or a live-but-stuck abandoned row, still qualifies this way since
553
- * both carry a genuinely alive pid; unknown liveness (an older peer's
554
- * row, or a pid that could never be resolved) does not.
555
- */
556
- /** Width of the live status column — `crashed` is the longest word it renders. */
557
237
  const LIVE_STATUS_W = 8;
558
- /** The colored, space-padded status cell for a listing row (empty when not live). */
559
238
  function liveStatusCell(live) {
560
239
  const word = liveStatusWord(live);
561
240
  if (!word || !live)
562
241
  return { cell: '', width: 0 };
563
242
  return { cell: statusColor(live.status)(padToWidth(word, LIVE_STATUS_W)), width: LIVE_STATUS_W };
564
243
  }
565
- /**
566
- * The tracker/PR ref for a session's dedicated column: the ticket id when known,
567
- * else `PR#<n>`, else empty. Pulled out of the trailing badge blob so refs align
568
- * into a scannable column instead of jamming against a truncated topic.
569
- */
570
244
  export function ticketLabel(s) {
571
245
  return s.ticketId ?? (s.prNumber ? `PR#${s.prNumber}` : '');
572
246
  }
@@ -585,7 +259,6 @@ export function linkCwdCell(s, label) {
585
259
  function modelLabel(model) {
586
260
  return model ? shortenModel(model) : '-';
587
261
  }
588
- /** Canonical JSON row consumed by resume pickers and other session UIs. */
589
262
  export function serializeSessionPickerRows(sessions, liveSessions, self = machineId()) {
590
263
  const liveById = new Map(liveSessions.filter((row) => row.sessionId).map((row) => [row.sessionId, row]));
591
264
  return sessions.map((session) => {
@@ -608,389 +281,6 @@ export function serializeSessionPickerRows(sessions, liveSessions, self = machin
608
281
  };
609
282
  });
610
283
  }
611
- /**
612
- * Compact, colour-coded badges for the durable/awaiting signals. Text-only (no
613
- * emoji, per repo convention): `plan` / `ask` / `perm` for why it's waiting,
614
- * `PR#N`, `wt:slug`, `TICKET-123`.
615
- */
616
- function signalBadges(s) {
617
- const parts = [];
618
- if (s.awaitingReason === 'plan_review')
619
- parts.push(chalk.yellow('plan'));
620
- else if (s.awaitingReason === 'question')
621
- parts.push(chalk.yellow('ask'));
622
- else if (s.awaitingReason === 'permission')
623
- parts.push(chalk.yellow('perm'));
624
- if (s.ticket) {
625
- // Clickable when Linear workspace is resolvable (same helper as the picker header).
626
- const url = linearIssueUrl(s.ticket.id);
627
- parts.push(chalk.cyan(url ? linkUrl(url, s.ticket.id) : s.ticket.id));
628
- }
629
- if (s.pr) {
630
- const label = `PR#${s.pr.number ?? '?'}`;
631
- parts.push(chalk.blue(s.pr.url ? linkUrl(s.pr.url, label) : label));
632
- }
633
- if (s.worktree)
634
- parts.push(chalk.magenta(`wt:${s.worktree.slug}`));
635
- return parts.join(' ');
636
- }
637
- /**
638
- * Compact locator badge: how to JUMP to the session, not what it's doing.
639
- * `ssh` flags a remote host. For tmux, prefer the resolved `session:window.pane`
640
- * (a real `tmux attach -t <session:window>` target) over the raw `%pane` id. For
641
- * a local Ghostty session we know the tab, show `tab N`. Local, unlocatable
642
- * sessions add nothing (the common case).
643
- *
644
- * RUSH-2336: every row also carries its raw process/provider handle — a
645
- * `machine:pid` for a real OS process (terminal/tmux/headless/team), or
646
- * `provider · taskId` for a cloud row with no local pid — so the human view
647
- * exposes the same locator `--json` now guarantees on every active row.
648
- */
649
- function locatorBadge(s) {
650
- const p = s.provenance;
651
- const parts = [];
652
- // An ssh-launched session shows where it was launched FROM when the client IP
653
- // resolves to a registered device (`ssh←zion`); bare `ssh` when it doesn't.
654
- if (p?.transport === 'ssh')
655
- parts.push(chalk.red(p.origin ? `ssh←${p.origin.device}` : 'ssh'));
656
- if (p?.mux?.kind === 'tmux' && (s.tmuxTarget || p.mux.pane)) {
657
- parts.push(chalk.green(s.tmuxTarget ?? p.mux.pane));
658
- // For a tmux-hosted session, say which app+tab is looking at it right now
659
- // (or that it's running detached). Only meaningful for tmux (the pane is the
660
- // durable handle; the viewer is transient).
661
- const label = viewingInLabel(s);
662
- if (label)
663
- parts.push(chalk.gray(label === 'detached' ? label : `viewing in ${label}`));
664
- }
665
- else if (p?.mux?.kind === 'screen') {
666
- parts.push(chalk.green('screen'));
667
- }
668
- if (s.ghosttyTab != null)
669
- parts.push(chalk.green(`tab ${s.ghosttyTab}`));
670
- if (s.context === 'cloud') {
671
- const bits = [s.cloudProvider, s.cloudTaskId ? s.cloudTaskId.slice(0, 12) : undefined].filter(Boolean);
672
- if (bits.length)
673
- parts.push(chalk.dim(bits.join(' · ')));
674
- }
675
- else if (typeof s.pid === 'number' && s.pid > 0) {
676
- parts.push(chalk.dim(`${s.machine ? `${s.machine}:` : ''}pid ${s.pid}`));
677
- }
678
- return parts.join(' ');
679
- }
680
- /**
681
- * The `created X · idle Y` time cell for a live row (RUSH-2205). `created` is the
682
- * age of the session start ({@link ActiveSession.startedAtMs}); `idle` is the age
683
- * of the last transcript write ({@link ActiveSession.lastActivityMs}) — i.e. how
684
- * long it has been quiet. Compact ("6d", "3h", "now") so the row stays width-safe;
685
- * either half is omitted when its epoch is unknown.
686
- */
687
- function activeTimeCell(s) {
688
- const parts = [];
689
- if (s.startedAtMs)
690
- parts.push(`created ${formatCompactAge(new Date(s.startedAtMs).toISOString())}`);
691
- if (s.lastActivityMs)
692
- parts.push(`idle ${formatCompactAge(new Date(s.lastActivityMs).toISOString())}`);
693
- return parts.join(' · ');
694
- }
695
- /** Column widths for line 1 of an active row (id · agent · version · status · owner). */
696
- const ROW_ID_W = 9;
697
- const ROW_AGENT_W = 8;
698
- const ROW_VERSION_W = 8;
699
- const ROW_STATUS_W = 9;
700
- const ROW_OWNER_W = 9;
701
- /**
702
- * Fit pre-styled content (which may carry SGR colour AND OSC-8 hyperlinks — the
703
- * clickable ticket/PR/project badges) into `room` display cells. Returns it raw
704
- * when it already fits, preserving colour and clickable links; otherwise falls
705
- * back to a width-safe truncation. `truncateToWidth` strips OSC-8 before cutting
706
- * (see width.ts), so a too-narrow cell drops the hyperlink cleanly rather than
707
- * slicing a hyperlink escape in half and corrupting the terminal (RUSH-2205
708
- * review). Never run pre-linked content through `truncateToWidth` directly — that
709
- * strips its links even when it fits; go through this helper.
710
- */
711
- function fitCell(content, room) {
712
- if (room <= 0)
713
- return '';
714
- return stringWidth(content) <= room ? content : truncateToWidth(content, room);
715
- }
716
- /**
717
- * Build the (one or two) rendered lines for a single active-session row.
718
- * Indent is the leading whitespace (2 spaces for flat groups, 4 inside a window
719
- * sub-group). Sized to `termW` so no line ever wraps under tmux/SSH (RUSH-2205):
720
- *
721
- * line 1: id · agent version · status · owner · created X · idle Y · ticket/PR
722
- * line 2: └ label/topic (+ checklist) · jump locator (ssh/tmux/detached)
723
- *
724
- * The label/topic gets its own line so it is no longer buried in a truncated grey
725
- * snippet, and the actionable ticket/PR badges ride line 1. Version and the
726
- * ticket/PR/label are backfilled onto the {@link ActiveSession} from the indexed
727
- * SessionMeta before this renders (a live process reports none of them). Pure +
728
- * exported so the row layout is unit-tested for content and width without a
729
- * captured stdout.
730
- */
731
- export function renderActiveRowLines(s, indent, termW) {
732
- const idCol = chalk.dim(padToWidth((s.sessionId?.slice(0, 8)) ?? '-', ROW_ID_W));
733
- const shownKind = sessionDisplayAgent({ agent: s.kind, harness: s.harness });
734
- const kindCol = colorAgent(shownKind)(padToWidth(truncateToWidth(shownKind, ROW_AGENT_W), ROW_AGENT_W + 1));
735
- const versionCol = chalk.gray(padToWidth(truncateToWidth(s.version ?? '', ROW_VERSION_W), ROW_VERSION_W + 1));
736
- const statusCol = statusColor(s.status)(padToWidth(truncateToWidth(activityLabel(s), ROW_STATUS_W - 1), ROW_STATUS_W));
737
- const ownerCol = chalk.cyan(padToWidth(truncateToWidth(ownerLabel(s), ROW_OWNER_W - 1), ROW_OWNER_W));
738
- const fixedCols = idCol + kindCol + versionCol + statusCol + ownerCol;
739
- const fixedW = stringWidth(indent) + ROW_ID_W + (ROW_AGENT_W + 1) + (ROW_VERSION_W + 1) + ROW_STATUS_W + ROW_OWNER_W;
740
- const remaining = Math.max(0, termW - fixedW - 1);
741
- // Line 1 right side: created/idle time + actionable badges (fork count,
742
- // plan/ask/perm, ticket, PR, worktree). Badges are the jump-to-work signal, so
743
- // they win the width fight — fitCell keeps them raw+clickable when they fit and
744
- // drops the link cleanly when narrow; the time cell takes whatever is left.
745
- const fork = s.pidCount && s.pidCount > 1 ? chalk.dim(`×${s.pidCount} `) : '';
746
- const badgesCell = fitCell(fork + signalBadges(s), remaining);
747
- const badgesW = stringWidth(badgesCell);
748
- const timeRoom = Math.max(0, remaining - (badgesW ? badgesW + 2 : 0));
749
- const timeCell = chalk.gray(truncateToWidth(activeTimeCell(s), timeRoom));
750
- let right = timeCell;
751
- if (badgesCell)
752
- right += (stringWidth(timeCell) ? ' ' : '') + badgesCell;
753
- // right's visible width is <= remaining by construction, so the assembled line
754
- // is <= termW-1 whenever the fixed columns fit; the clamp only bites a terminal
755
- // narrower than the fixed columns (where right is empty — no link to corrupt).
756
- let line1 = indent + fixedCols + right;
757
- if (stringWidth(line1) > termW)
758
- line1 = truncateToWidth(line1, termW);
759
- const lines = [line1];
760
- // Line 2: label/topic + checklist (the identity, no longer buried) then the
761
- // jump locator. Skipped entirely when there is nothing to say. desc may carry a
762
- // clickable project link, so it also goes through fitCell.
763
- const contIndent = indent + ' '.repeat(ROW_ID_W);
764
- const desc = formatActiveRowDescription(s);
765
- const loc = locatorBadge(s);
766
- if (desc || loc) {
767
- const room2 = Math.max(0, termW - stringWidth(contIndent) - 2);
768
- const locCell = fitCell(loc, room2);
769
- const locW = stringWidth(locCell);
770
- const descRoom = Math.max(0, room2 - (locW ? locW + 2 : 0));
771
- const descCell = chalk.white(fitCell(desc || '-', descRoom));
772
- let line2 = contIndent + chalk.dim('└ ') + descCell;
773
- if (locCell)
774
- line2 += ' ' + locCell;
775
- if (stringWidth(line2) > termW)
776
- line2 = truncateToWidth(line2, termW);
777
- lines.push(line2);
778
- }
779
- // Secondary line (PHNX-3797 owner feedback): surface the most important recent
780
- // agent message when it is a pending QUESTION or a NEEDS-YOU block — the urgency
781
- // a rolling activity preview buries. Plain `activity` is already the preview on
782
- // line 2, so it earns no extra line here; the full ranked message still rides
783
- // the JSON/mirror feed for AGI EXT via `s.importantMessage`.
784
- const important = s.importantMessage;
785
- if (important && (important.kind === 'question' || important.kind === 'needs_you')) {
786
- const glyph = important.kind === 'question' ? '? ' : '! ';
787
- const room3 = Math.max(0, termW - stringWidth(contIndent) - 2 - glyph.length);
788
- const msgCell = fitCell(cleanPreview(important.text), room3);
789
- if (msgCell) {
790
- let line3 = contIndent + chalk.dim(glyph + msgCell);
791
- if (stringWidth(line3) > termW)
792
- line3 = truncateToWidth(line3, termW);
793
- lines.push(line3);
794
- }
795
- }
796
- return lines;
797
- }
798
- /** Render a single agent-session row inside an already-printed group header. */
799
- function printActiveRow(s, indent) {
800
- for (const line of renderActiveRowLines(s, indent, terminalWidth()))
801
- console.log(line);
802
- }
803
- /**
804
- * Compact owner display for the `--active` owner column: the local-part of a
805
- * resolved actor email/login (`muqsit@getrush.ai` -> `muqsit`), the id as-is
806
- * when it has no `@`, and `-` when the actor is unresolved (`UNRESOLVED@<host>`)
807
- * or absent (a launch predating actor stamping). Honest by design — an
808
- * unresolved local run shows no owner rather than inventing one.
809
- */
810
- export function ownerLabel(s) {
811
- const owner = s.owner;
812
- if (!owner || owner.startsWith('UNRESOLVED@'))
813
- return '-';
814
- const at = owner.indexOf('@');
815
- return at > 0 ? owner.slice(0, at) : owner;
816
- }
817
- /**
818
- * Short label for an IDE window. The slice key in live-terminals.json is
819
- * `${vscode.env.sessionId}-${ext-host pid}`; the trailing pid is the cheap
820
- * stable disambiguator. We surface it as `ext-pid` so two windows on the
821
- * same repo are visibly different.
822
- */
823
- function shortWindowLabel(windowId) {
824
- const m = windowId.match(/-(\d+)$/);
825
- return m ? `ext-pid ${m[1]}` : `win ${windowId.slice(0, 8)}`;
826
- }
827
- /**
828
- * Group sessions by workspace, then split each workspace into IDE-window
829
- * sub-groups + a flat bucket. Pure function — no I/O — so the renderer's
830
- * grouping rules can be tested without mocking the session scanner.
831
- *
832
- * Sort order:
833
- * - workspaces: by session count descending, then key ascending
834
- * - windows within a workspace: by oldest startedAtMs ascending
835
- * - sessions within a window/flat bucket: input order preserved
836
- */
837
- export function groupActiveSessions(sessions) {
838
- const byWorkspace = new Map();
839
- for (const s of sessions) {
840
- const key = s.cwd ?? (s.context === 'cloud' ? '__cloud__' : '__unknown__');
841
- const list = byWorkspace.get(key) || [];
842
- list.push(s);
843
- byWorkspace.set(key, list);
844
- }
845
- const sortedKeys = Array.from(byWorkspace.keys()).sort((a, b) => {
846
- const aCount = byWorkspace.get(a).length;
847
- const bCount = byWorkspace.get(b).length;
848
- if (aCount !== bCount)
849
- return bCount - aCount;
850
- return a.localeCompare(b);
851
- });
852
- const workspaces = sortedKeys.map((key) => {
853
- const group = byWorkspace.get(key);
854
- const windowedSessions = [];
855
- const flat = [];
856
- for (const s of group) {
857
- if (s.context === 'terminal' && s.windowId)
858
- windowedSessions.push(s);
859
- else
860
- flat.push(s);
861
- }
862
- const byWindow = new Map();
863
- for (const s of windowedSessions) {
864
- const list = byWindow.get(s.windowId) || [];
865
- list.push(s);
866
- byWindow.set(s.windowId, list);
867
- }
868
- const windowKeys = Array.from(byWindow.keys()).sort((a, b) => {
869
- const aStart = Math.min(...byWindow.get(a).map(s => s.startedAtMs ?? Infinity));
870
- const bStart = Math.min(...byWindow.get(b).map(s => s.startedAtMs ?? Infinity));
871
- return aStart - bStart;
872
- });
873
- return {
874
- key,
875
- total: group.length,
876
- windows: windowKeys.map((wid) => ({ windowId: wid, sessions: byWindow.get(wid) })),
877
- flat,
878
- };
879
- });
880
- return { workspaces };
881
- }
882
- /**
883
- * The machine a session belongs to: an explicit tag (set when merging
884
- * cross-machine results) wins; else the process's provenance host (normalized
885
- * to the same id form); else the local machine. Never keys off `ActiveSession.host`
886
- * — that is the terminal *app* (code/tmux), not the computer.
887
- */
888
- /** Synthetic top-level group key for provider-sandboxed cloud tasks. */
889
- const CLOUD_MACHINE_KEY = 'cloud';
890
- function machineKeyFor(s, localMachine) {
891
- // Cloud tasks run in a provider sandbox, not on the machine they're attributed
892
- // to for reply routing (s.machine = the querier). Surface them as their own
893
- // top-level "cloud" group instead of nested under the local device.
894
- if (s.context === 'cloud')
895
- return CLOUD_MACHINE_KEY;
896
- if (s.machine)
897
- return s.machine;
898
- if (s.provenance?.host)
899
- return normalizeHost(s.provenance.host);
900
- return localMachine;
901
- }
902
- /**
903
- * Group active sessions by machine, then delegate each machine's sessions to the
904
- * existing workspace/window grouping. Local machine is pinned first and flagged;
905
- * the rest sort by session count descending, then name. Pure — `localMachine` is
906
- * injected so the function stays testable without reading os.hostname().
907
- */
908
- export function groupSessionsByMachine(sessions, localMachine) {
909
- const byMachine = new Map();
910
- for (const s of sessions) {
911
- const key = machineKeyFor(s, localMachine);
912
- (byMachine.get(key) ?? byMachine.set(key, []).get(key)).push(s);
913
- }
914
- const keys = Array.from(byMachine.keys()).sort((a, b) => {
915
- if (a === localMachine)
916
- return -1;
917
- if (b === localMachine)
918
- return 1;
919
- // The synthetic "cloud" category sorts after all real machines.
920
- if (a === CLOUD_MACHINE_KEY)
921
- return 1;
922
- if (b === CLOUD_MACHINE_KEY)
923
- return -1;
924
- const ac = byMachine.get(a).length, bc = byMachine.get(b).length;
925
- if (ac !== bc)
926
- return bc - ac;
927
- return a.localeCompare(b);
928
- });
929
- const machines = keys.map((machine) => ({
930
- machine,
931
- isLocal: machine === localMachine,
932
- total: byMachine.get(machine).length,
933
- layout: groupActiveSessions(byMachine.get(machine)),
934
- }));
935
- return { machines };
936
- }
937
- /**
938
- * Collapse duplicate sessions after a cross-machine merge. Two rows collapse
939
- * only when they share both machine and session UUID (the same host listed
940
- * twice, or a local/remote overlap); rows without a sessionId can't be
941
- * correlated, so they're all kept.
942
- */
943
- export function dedupeByMachineSession(sessions) {
944
- const seen = new Map();
945
- const out = [];
946
- for (const s of sessions) {
947
- if (!s.sessionId) {
948
- out.push(s);
949
- continue;
950
- }
951
- const key = `${s.machine ?? ''}:${s.sessionId}`;
952
- const at = seen.get(key);
953
- if (at === undefined) {
954
- seen.set(key, out.length);
955
- out.push(s);
956
- continue;
957
- }
958
- // Both rows now describe the same session on the same machine, so the tie is
959
- // between the EXECUTING box's own row and the dispatcher's shim row for a
960
- // host-dispatched run (they only started colliding once foldExecutionMachine
961
- // attributed both to the executing host — RUSH-2479). The shim carries no
962
- // transcript and a `[host/<peer>]` placeholder label, so keeping it first
963
- // would strip a real preview off the merged fleet view. Prefer the row that
964
- // is not an offload shim; otherwise the first one still wins.
965
- if (out[at].offloadedFrom && !s.offloadedFrom)
966
- out[at] = s;
967
- }
968
- return out;
969
- }
970
- /**
971
- * Narrow a gathered live set to the machines an explicit `--device`
972
- * scope named. The gather picks which boxes to ASK; this asserts what the answer
973
- * may contain, and the two are not the same question: a host-dispatched run is
974
- * reported by the box that dispatched it while executing somewhere else, so
975
- * `--device <dispatcher>` used to list sessions that were not running there at
976
- * all (RUSH-2479). No scope → unchanged. Pure; exported for unit testing.
977
- */
978
- export function filterActiveSessionsByHostScope(sessions, hosts, self) {
979
- if (!hosts || hosts.length === 0)
980
- return sessions;
981
- const wanted = new Set(hosts.map(hostToken));
982
- return sessions.filter((s) => wanted.has(s.machine ?? self));
983
- }
984
- /**
985
- * Order a merged listing so the local machine's sessions come first, then each
986
- * remote machine as a contiguous block (more sessions first, then name), with
987
- * every machine keeping its incoming order (timestamp) within the block. Also
988
- * dedupes: a session present both locally (a synced mirror copy) and via live
989
- * fan-out collapses to one, keyed by machine + session id. Rows are keyed by
990
- * `machine` (discover tags local rows with the local id; fan-out tags remote
991
- * rows with the peer id) falling back to `localMachine` when untagged. Pure —
992
- * `localMachine` is injected so the ordering is testable without os.hostname().
993
- */
994
284
  export function mergeLocalFirst(sessions, localMachine) {
995
285
  const byMachine = new Map();
996
286
  const seen = new Set();
@@ -1016,17 +306,7 @@ export function mergeLocalFirst(sessions, localMachine) {
1016
306
  });
1017
307
  return keys.flatMap((k) => byMachine.get(k));
1018
308
  }
1019
- /**
1020
- * Serialize a `SessionMeta[]` to the clean JSON shape the `--json` listing
1021
- * emits: strip the internal-only scoring/provenance fields (`_matchedTerms`,
1022
- * `_bm25Score`, `_remote`) that are search/fan-out bookkeeping, never part of
1023
- * the public record, then pretty-print as a 2-space array with a trailing
1024
- * newline. The single seam shared by the local `--json` path and the
1025
- * `--json --host` remote fan-out so both emit byte-identical row shapes.
1026
- */
1027
- /** The intentionally small metadata contract emitted by `sessions --resolve`.
1028
- * It includes only launch identity needed to route/resume. Transcript paths,
1029
- * extracted plans, costs, and content stay on the machine that owns them. */
309
+ // Peer resolution exposes launch identity only; paths, plans, costs, and content stay local.
1030
310
  export function serializeResolvedSessionsJson(sessions) {
1031
311
  const safe = sessions.map((session) => ({
1032
312
  id: session.id,
@@ -1045,30 +325,13 @@ export function serializeResolvedSessionsJson(sessions) {
1045
325
  }));
1046
326
  return JSON.stringify(safe, null, 2) + '\n';
1047
327
  }
1048
- /**
1049
- * `agents sessions --json --host <h>` — fan the RECENT (non-active) listing out
1050
- * to the named host(s) and emit ONE clean merged `SessionMeta[]` JSON array,
1051
- * the same shape the local `--json` path emits. Reuses `gatherRemoteList` (the
1052
- * exact SSH fan-out the interactive cross-machine listing already uses) and
1053
- * serializes the merged, machine-tagged rows — instead of `runRemoteSessions`,
1054
- * which streams each remote's raw stdout under a per-host banner and so can
1055
- * never be JSON.parsed. A dead host contributes `[]` (with a stderr note from
1056
- * the fan-out), so stdout is always a valid array and the exit stays 0.
1057
- */
1058
328
  async function runRemoteSessionsJson(hosts) {
1059
- // Forward the caller's own filters (query, --limit, --since, …) minus --host,
1060
- // and guarantee --json so each peer answers with a parseable array. Force
1061
- // whole-index scope: an explicit --host means "that box's index", not the
1062
- // slice that happens to sit under the peer's SSH-login home dir.
1063
329
  const forwarded = ensureWholeIndex(buildForwardedArgs(process.argv, new Set(hosts)));
1064
330
  if (!forwarded.includes('--json'))
1065
331
  forwarded.push('--json');
1066
332
  const { sessions } = await gatherRemoteList(forwarded, hosts);
1067
333
  process.stdout.write(serializeSessionsJson(sessions));
1068
334
  }
1069
- /** Parse one peer's computer-run array without accepting banners or partial
1070
- * JSON. The fan-out boundary supplies the machine name when an older peer row
1071
- * omitted it. */
1072
335
  export function parseRemoteComputerSessionRows(stdout, machine) {
1073
336
  try {
1074
337
  const value = JSON.parse(stdout);
@@ -1083,9 +346,6 @@ export function parseRemoteComputerSessionRows(stdout, machine) {
1083
346
  return { items: [], valid: false };
1084
347
  }
1085
348
  }
1086
- /** Collect computer history from named peers, or every automatic peer when
1087
- * hosts is omitted. Every peer is forced to JSON and receives the recursion
1088
- * guard through the shared fleet transport. */
1089
349
  async function gatherRemoteComputerSessionRows(hosts) {
1090
350
  const hostSet = new Set(hosts ?? []);
1091
351
  const forwarded = buildForwardedArgs(process.argv, hostSet);
@@ -1101,515 +361,31 @@ async function gatherRemoteComputerSessionRows(hosts) {
1101
361
  });
1102
362
  return result.items;
1103
363
  }
1104
- /**
1105
- * `running N · idle N · waiting N · queued N · closed N · abandoned N · unknown N`
1106
- * for a bucket of sessions (zero buckets omitted). Same bucketing as the summary so
1107
- * per-group counts reconcile with the `(total)` beside the header — the `unknown`
1108
- * bucket is what keeps an alive-but-opaque row from silently vanishing from the
1109
- * tally. Empty when nothing.
1110
- */
1111
- function groupTally(sessions) {
1112
- const running = sessions.filter(s => s.status === 'running').length;
1113
- const idle = sessions.filter(s => s.status === 'idle').length;
1114
- const waiting = sessions.filter(s => s.status === 'input_required').length;
1115
- // Counted by status, deliberately: an orphaned session gets its own bucket
1116
- // below, so counting it here too would double-count it in the tally.
1117
- const queued = sessions.filter(s => s.status === 'queued').length;
1118
- const closed = sessions.filter(s => s.status === 'closed').length;
1119
- const abandoned = sessions.filter(s => s.status === 'abandoned').length;
1120
- // Without these two the tally silently loses rows: every status must have a
1121
- // bucket or "N active (…)" stops adding up to what the list shows.
1122
- const orphaned = sessions.filter(s => s.status === 'orphaned').length;
1123
- const crashed = sessions.filter(s => s.status === 'crashed').length;
1124
- const unknown = sessions.filter(s => s.status === 'unknown').length;
1125
- const parts = [];
1126
- if (running)
1127
- parts.push(`${running} running`);
1128
- if (idle)
1129
- parts.push(`${idle} idle`);
1130
- if (waiting)
1131
- parts.push(`${waiting} waiting`);
1132
- if (queued)
1133
- parts.push(`${queued} queued`);
1134
- if (closed)
1135
- parts.push(`${closed} closed`);
1136
- if (abandoned)
1137
- parts.push(`${abandoned} abandoned`);
1138
- if (orphaned)
1139
- parts.push(`${orphaned} orphaned`);
1140
- if (crashed)
1141
- parts.push(`${crashed} crashed`);
1142
- if (unknown)
1143
- parts.push(`${unknown} unknown`);
1144
- return parts.join(' · ');
1145
- }
1146
- /** Print one machine's workspace tree, indented under its machine header. */
1147
- function renderWorkspaceLayout(layout, base, machineKey) {
1148
- let first = true;
1149
- for (const ws of layout.workspaces) {
1150
- if (!first)
1151
- console.log();
1152
- first = false;
1153
- // Under the top-level "cloud" machine group the __cloud__ workspace header is
1154
- // redundant ("▸ cloud" then "cloud") — render its rows flat under the machine
1155
- // header instead. Row indent collapses by one level to match.
1156
- const redundantCloud = ws.key === '__cloud__' && machineKey === CLOUD_MACHINE_KEY;
1157
- const rowBase = redundantCloud ? base : base + ' ';
1158
- if (!redundantCloud) {
1159
- const header = ws.key === '__cloud__'
1160
- ? chalk.magenta.bold('cloud')
1161
- : ws.key === '__unknown__'
1162
- ? chalk.gray.bold('unknown')
1163
- : chalk.cyan.bold(shortCwd(ws.key));
1164
- const wsSessions = [...ws.windows.flatMap(w => w.sessions), ...ws.flat];
1165
- const tally = groupTally(wsSessions);
1166
- console.log(`${base}${header} ${chalk.gray(`(${ws.total})`)}${tally ? chalk.gray(` ${tally}`) : ''}`);
1167
- }
1168
- for (const win of ws.windows) {
1169
- // Host is per-process, but every terminal in the same IDE window shares
1170
- // an ancestor — take the first non-empty host as the window's label.
1171
- const host = win.sessions.find((s) => s.host)?.host ?? 'terminal';
1172
- const winHeader = `${chalk.gray(host)} ${chalk.gray('·')} ${chalk.gray(shortWindowLabel(win.windowId))} ${chalk.gray(`(${win.sessions.length})`)}`;
1173
- console.log(rowBase + winHeader);
1174
- for (const s of win.sessions)
1175
- printActiveRow(s, rowBase + ' ');
1176
- }
1177
- for (const s of ws.flat)
1178
- printActiveRow(s, rowBase);
1179
- }
1180
- }
1181
- /** Machine header: `▸ <name> ← this machine` for the local box (cyan), matching
1182
- * the `ag devices list` treatment; a plain `▸ <name>` for remotes. */
1183
- function printMachineHeader(mg) {
1184
- // The synthetic "cloud" group isn't a device — tint it magenta (matching the
1185
- // cloud row/label styling) so it reads as a category, not a machine.
1186
- const isCloud = mg.machine === CLOUD_MACHINE_KEY;
1187
- const marker = mg.isLocal ? chalk.cyan('▸ ') : isCloud ? chalk.magenta('▸ ') : chalk.gray('▸ ');
1188
- const name = mg.isLocal ? chalk.bold.cyan(mg.machine) : isCloud ? chalk.bold.magenta(mg.machine) : chalk.bold(mg.machine);
1189
- const here = mg.isLocal ? chalk.cyan(' ← this machine') : '';
1190
- console.log(`${marker}${name} ${chalk.gray(`(${mg.total})`)}${here}`);
1191
- }
1192
- /**
1193
- * Attach display-only jump locators onto LOCAL sessions: the Ghostty tab number
1194
- * (one batched read-only osascript, only when a local ghostty session exists)
1195
- * and the tmux `session:window.pane` target (one `list-panes -a` per socket).
1196
- * Every step is best-effort and swallowed — a failure just leaves the raw pane
1197
- * id / no tab number, and the rows render as before. Mutates the sessions.
1198
- */
1199
- async function enrichLocalLocators(local) {
1200
- // Ghostty tab numbers.
1201
- try {
1202
- const ghostty = local.filter(s => s.host === 'ghostty' && s.provenance?.transport !== 'ssh');
1203
- if (ghostty.length > 0) {
1204
- const surfaces = await enumerateGhosttyTabs();
1205
- for (const [sess, tab] of assignGhosttyTabs(ghostty, surfaces))
1206
- sess.ghosttyTab = tab;
1207
- }
1208
- }
1209
- catch { /* non-fatal */ }
1210
- // One Ghostty enumeration shared across every socket's viewing-in resolve
1211
- // (a tmux client can be attached from a Ghostty tab).
1212
- await enrichTmuxLocators(local, await enumerateGhosttyTabsQuietly());
1213
- }
1214
- /** {@link enumerateGhosttyTabs}, best-effort — an osascript failure yields no surfaces. */
1215
- async function enumerateGhosttyTabsQuietly() {
1216
- try {
1217
- return await enumerateGhosttyTabs();
1218
- }
1219
- catch {
1220
- return [];
1221
- }
1222
- }
1223
- /**
1224
- * The tmux half of {@link enrichLocalLocators}: the `session:window.pane` attach
1225
- * target and "viewing in <app> tab N" / detached, one batched query per socket.
1226
- *
1227
- * Split out because it is the only locator the `--json` path can afford. It costs
1228
- * tmux queries and a `ps` read — no osascript — so scriptable output stays cheap
1229
- * while still answering the question a consumer actually needs: is anyone looking
1230
- * at this session, or is it running orphaned? Without `surfaces`, a Ghostty-attached
1231
- * client still resolves as attached, just without its tab number.
1232
- */
1233
- async function enrichTmuxLocators(local, surfaces = []) {
1234
- try {
1235
- const tmux = local.filter(s => s.provenance?.mux?.kind === 'tmux' && s.provenance.mux.pane);
1236
- if (tmux.length > 0) {
1237
- const sockets = new Set(tmux.map(s => s.provenance.mux.socket));
1238
- for (const socket of sockets) {
1239
- const paneMap = await mapPanesToTargets(socket);
1240
- if (paneMap.size === 0)
1241
- continue;
1242
- const clients = await listClients(socket);
1243
- for (const s of tmux) {
1244
- if (s.provenance.mux.socket !== socket)
1245
- continue;
1246
- const target = paneMap.get(s.provenance.mux.pane);
1247
- if (target)
1248
- s.tmuxTarget = target;
1249
- s.viewingIn = await resolveViewingIn(s, clients, { paneToTarget: paneMap, ghosttySurfaces: surfaces });
1250
- }
1251
- }
1252
- }
1253
- }
1254
- catch { /* non-fatal */ }
1255
- }
1256
- /** Normalize a `--device` token (`alias`, `user@host`, `host.domain`)
1257
- * to the machine id the fan-out and registry key off. */
1258
- function hostToken(h) {
1259
- return normalizeHost(h.split('@').pop() || h);
1260
- }
1261
- /**
1262
- * Whether the local machine's sessions belong in an `--active` view. Local is
1263
- * included by default; an explicit `--device` list scopes the view to
1264
- * exactly those machines, so local is dropped unless it is itself named (by
1265
- * alias or `user@host`, matched on the normalized machine id). Exported for
1266
- * unit testing without touching SSH or the live process table.
1267
- */
1268
- export function shouldIncludeLocal(hosts, self) {
1269
- if (!hosts || hosts.length === 0)
1270
- return true;
1271
- return hosts.some(h => hostToken(h) === self);
1272
- }
1273
- /**
1274
- * The peers to dial for an `--active` view. No `--device` → `undefined`, which
1275
- * tells `gatherRemoteActive` to sweep the registered online devices. An
1276
- * explicit list → exactly those, minus this machine (its sessions come from the
1277
- * local seed, so dialing self would be a wasted SSH and a spurious "unreachable"
1278
- * note). Returns `[]` when the only named host is self — the caller then skips
1279
- * the remote fan-out entirely rather than letting `[]` trigger the sweep.
1280
- * Exported for unit testing.
1281
- */
1282
- export function remoteHostsToDial(hosts, self) {
1283
- if (!hosts || hosts.length === 0)
1284
- return undefined;
1285
- return hosts.filter(h => hostToken(h) !== self);
1286
- }
1287
- /**
1288
- * The fleet-wide live-session set behind every `--active` surface. Local sessions
1289
- * come from `getActiveSessions()` and (unless `--local`) the registered online
1290
- * devices from `ag devices` are folded in over SSH. An explicit `--device`
1291
- * list SCOPES the sweep to exactly those machines — the local machine is included
1292
- * only when it is itself named — so `--device` is a filter, not an addition.
1293
- *
1294
- * This is the single gather: the static renderer AND the interactive browser both
1295
- * call it, so the browser can never disagree with `--active --json` about which
1296
- * sessions are live (it used to call the local-only `getActiveSessions()` directly
1297
- * and silently hid every remote session).
1298
- *
1299
- * RUSH-2062: default path is cache-first against the daemon-warmed shared
1300
- * snapshot (`session-cache.ts`). Menubar / Factory / watchdog / CLI share one
1301
- * warm result instead of each re-running the full SSH fan-out. `forceRefresh`
1302
- * (or `AGENTS_SESSIONS_FORCE_REFRESH=1`) re-gathers live; scoped `--device` lists
1303
- * always gather live so a filter never returns a wrong unscoped snapshot.
1304
- */
1305
- export async function gatherActiveSessions(opts = {}) {
1306
- const forceRefresh = opts.forceRefresh === true
1307
- || process.env.AGENTS_SESSIONS_FORCE_REFRESH === '1';
1308
- // Scoped host lists are not represented in the unscoped fleet/local snapshot
1309
- // — always gather live so a filter cannot return the wrong set.
1310
- const scoped = (opts.hosts?.length ?? 0) > 0;
1311
- if (opts.local && !scoped) {
1312
- const loaded = await loadLocalActiveSessions({
1313
- forceRefresh,
1314
- gather: async () => {
1315
- const rows = await getActiveSessions({ localOnly: true });
1316
- const self = machineId();
1317
- for (const s of rows)
1318
- if (!s.machine)
1319
- s.machine = self;
1320
- return rows;
1321
- },
1322
- });
1323
- return { sessions: loaded.sessions, remoteDeviceCount: 0 };
1324
- }
1325
- if (!opts.local && !scoped) {
1326
- const loaded = await loadFleetActiveSessions({
1327
- forceRefresh,
1328
- gather: () => gatherActiveSessionsLive({ local: false }),
1329
- });
1330
- return {
1331
- sessions: loaded.sessions,
1332
- remoteDeviceCount: loaded.remoteDeviceCount,
1333
- remoteSkipped: loaded.remoteSkipped,
1334
- remoteDiscoveryFailed: loaded.remoteDiscoveryFailed,
1335
- };
1336
- }
1337
- return gatherActiveSessionsLive(opts);
1338
- }
1339
- /**
1340
- * Live (uncached) gather — the work the daemon / force-refresh path pays once
1341
- * so other surfaces can share the snapshot. Kept separate so the cache layer
1342
- * never recursively re-enters itself.
1343
- */
1344
- async function gatherActiveSessionsLive(opts = {}) {
1345
- const self = machineId();
1346
- // An explicit --host/--device list scopes the view: seed local sessions only
1347
- // when no hosts are named, or when this machine is one of the named targets.
1348
- // `localOnly: opts.local` (RUSH-2118) keeps a `--local` query from dialing a
1349
- // remote-host teammate over ssh even for this machine's OWN local gather —
1350
- // "this machine only" must mean zero ssh, not just "skip the cross-machine
1351
- // device fan-out below".
1352
- const local = shouldIncludeLocal(opts.hosts, self)
1353
- ? await getActiveSessions({ localOnly: opts.local })
1354
- : [];
1355
- for (const s of local)
1356
- if (!s.machine)
1357
- s.machine = self;
1358
- let remoteDeviceCount = 0;
1359
- let remoteSkipped = [];
1360
- let remoteDiscoveryFailed = false;
1361
- let merged = local;
1362
- if (!opts.local) {
1363
- const remoteHosts = remoteHostsToDial(opts.hosts, self);
1364
- // An explicit list naming only self leaves nothing remote to dial — skip the
1365
- // fan-out rather than let an empty list fall through to the device sweep.
1366
- if (!opts.hosts?.length || (remoteHosts && remoteHosts.length > 0)) {
1367
- const remote = await gatherRemoteActive(remoteHosts);
1368
- remoteDeviceCount = remote.deviceCount;
1369
- remoteSkipped = remote.skipped;
1370
- remoteDiscoveryFailed = remote.discoveryFailed;
1371
- merged = dedupeByMachineSession([...local, ...remote.sessions]);
1372
- }
1373
- }
1374
- // Asking a box is not the same as a session running there: a host-dispatched
1375
- // run is reported by its dispatcher while executing on the peer. Scope on the
1376
- // machine the session runs on so `--device X` never lists someone else's work
1377
- // (RUSH-2479). Applied here, in the single gather, so the interactive browser
1378
- // gets the same answer as `--active --json`.
1379
- return {
1380
- sessions: filterActiveSessionsByHostScope(merged, opts.hosts, self),
1381
- remoteDeviceCount,
1382
- remoteSkipped,
1383
- remoteDiscoveryFailed,
1384
- };
1385
- }
1386
- /**
1387
- * Explain an empty `--active` result instead of the old flat
1388
- * "No active agent sessions." either way (RUSH-2507: a live fleet sweep found
1389
- * ~41 running agents while this printed the same line as a genuinely idle
1390
- * fleet). Re-probes local tmux health only now, on the empty path, and names
1391
- * any remote peer that went unheard rather than silently folding it into
1392
- * "nothing running".
1393
- */
1394
- async function describeEmptyActiveDiscovery(opts, remoteSkipped, remoteDiscoveryFailed) {
1395
- const parts = [];
1396
- if (!opts.hosts?.length || opts.local) {
1397
- const health = await describeActiveDiscoveryHealth();
1398
- if (health.degradedSources.length > 0) {
1399
- parts.push(`local discovery is degraded (${health.degradedSources.join(', ')} unreachable) — sessions may be hidden, run \`agents doctor\` to diagnose`);
1400
- }
1401
- }
1402
- if (remoteDiscoveryFailed) {
1403
- parts.push('the device list could not be loaded — no peer was reachable to sweep');
1404
- }
1405
- else if (remoteSkipped && remoteSkipped.length > 0) {
1406
- parts.push(`${remoteSkipped.length} peer(s) went unheard (${remoteSkipped.join(', ')}) — sessions there may be hidden`);
1407
- }
1408
- if (parts.length === 0)
1409
- return 'No active agent sessions.';
1410
- return `No active agent sessions found, but discovery was degraded: ${parts.join('; ')}.`;
1411
- }
1412
- /**
1413
- * Render the unified active-session view, grouped by machine. Scoping and the
1414
- * fleet sweep live in {@link gatherActiveSessions}; this owns the presentation
1415
- * (the `--waiting` gate, JSON, and the grouped table). A tip is shown when there
1416
- * are no other machines to include.
1417
- */
1418
- async function renderActiveSessions(asJson, waitingOnly = false, opts = {}) {
1419
- const self = machineId();
1420
- const gathered = await gatherActiveSessions(opts);
1421
- const { remoteDeviceCount, remoteSkipped, remoteDiscoveryFailed } = gathered;
1422
- // Backfill agent version, refs, created time, and routine provenance from the
1423
- // historical index. Routine filtering needs that provenance before it narrows
1424
- // the live pool; doing it here also enriches both JSON and human output.
1425
- backfillActiveRowsFromIndex(gathered.sessions);
1426
- // --bookmarks narrows the live view too. Applied HERE, not only in the
1427
- // browser: the browser is skipped for --json, --waiting, a pipe, a multi-host
1428
- // scope, and an SSH-fanout peer, and the flag silently did nothing on every
1429
- // one of those paths — including `--active --bookmarks --json`, which is
1430
- // exactly what the browser's own `y` copy-cmd hands to an agent.
1431
- const merged = opts.bookmarksOnly
1432
- ? gathered.sessions.filter((s) => !!s.sessionId && listBookmarks().has(s.sessionId))
1433
- : gathered.sessions;
1434
- const routineFiltered = filterActiveSessionsByRoutine(merged, opts.routine);
1435
- // Status flags form a union. --waiting additionally retains its scriptable
1436
- // gate: exit non-zero when the union contains a session awaiting the user.
1437
- const statusFiltered = opts.statuses?.length
1438
- ? routineFiltered.filter((session) => opts.statuses.some((status) => matchesLiveStatus(session, status)))
1439
- : routineFiltered.filter(isRunningLiveSession);
1440
- const sessions = statusFiltered;
1441
- if (asJson) {
1442
- // Resolve who is watching each local tmux pane before serializing: `viewingIn`
1443
- // is how a consumer distinguishes a session someone is looking at from one
1444
- // running orphaned after its terminal died. tmux-only (no osascript) so the
1445
- // scriptable path stays cheap — see enrichTmuxLocators.
1446
- await enrichTmuxLocators(sessions.filter(s => sessionProcessIsLocal(s, self)));
1447
- process.stdout.write(JSON.stringify(serializeActiveSessionsForJson(sessions), null, 2) + '\n');
1448
- if (waitingOnly && sessions.some(isAwaitingUser))
1449
- process.exitCode = 1;
1450
- return;
1451
- }
1452
- if (sessions.length === 0) {
1453
- if (waitingOnly) {
1454
- console.log(chalk.gray('No sessions waiting on input.'));
1455
- return;
1456
- }
1457
- console.log(chalk.gray(await describeEmptyActiveDiscovery(opts, remoteSkipped, remoteDiscoveryFailed)));
1458
- if (!opts.local && !opts.hosts?.length && remoteDeviceCount === 0)
1459
- printCrossMachineTip();
1460
- return;
1461
- }
1462
- // Enrich LOCAL sessions with jump locators (display-only, after the --json /
1463
- // --waiting gates so scriptable output stays osascript-free). Remote sessions
1464
- // keep their raw pane id — their tmux/Ghostty live on the other machine.
1465
- await enrichLocalLocators(sessions.filter(s => sessionProcessIsLocal(s, self)));
1466
- const grouped = groupSessionsByMachine(sessions, self);
1467
- let firstMachine = true;
1468
- for (const mg of grouped.machines) {
1469
- if (!firstMachine)
1470
- console.log();
1471
- firstMachine = false;
1472
- printMachineHeader(mg);
1473
- renderWorkspaceLayout(mg.layout, ' ', mg.machine);
1474
- }
1475
- const parts = groupTally(sessions).split(' · ').filter(Boolean);
1476
- // The synthetic "cloud" group is a category, not a machine — exclude it from the
1477
- // machine count and note it separately so the tally stays truthful.
1478
- const realMachines = grouped.machines.filter((m) => m.machine !== CLOUD_MACHINE_KEY).length;
1479
- const hasCloud = grouped.machines.some((m) => m.machine === CLOUD_MACHINE_KEY);
1480
- const machineWord = realMachines === 1 ? 'machine' : 'machines';
1481
- const cloudNote = hasCloud ? ' + cloud' : '';
1482
- console.log(chalk.gray(`\n${sessions.length} active (${parts.join(', ')}) across ${realMachines} ${machineWord}${cloudNote}.`));
1483
- // Tip only when nothing else could be included and the user didn't opt out.
1484
- if (!opts.local && !opts.hosts?.length && remoteDeviceCount === 0)
1485
- printCrossMachineTip();
1486
- // Scriptable gate: a non-zero exit when anything is waiting on the user.
1487
- if (waitingOnly && sessions.some(isAwaitingUser))
1488
- process.exitCode = 1;
1489
- }
1490
- /** Apply the routine selector to enriched live rows for every non-browser active view. */
1491
- export function filterActiveSessionsByRoutine(sessions, routine) {
1492
- if (!routine)
1493
- return sessions;
1494
- const routineSessions = sessions.filter((session) => session.origin === 'routine' || !!session.routineName);
1495
- if (typeof routine !== 'string')
1496
- return routineSessions;
1497
- const names = [...new Set(routineSessions.map((session) => session.routineName).filter((name) => !!name))];
1498
- const selected = resolveRoutineName(routine, names);
1499
- return selected
1500
- ? routineSessions.filter((session) => session.routineName === selected)
1501
- : [];
1502
- }
1503
- /** Match the status words users see, preserving activity's richer working signal. */
1504
- export function matchesLiveStatus(session, status) {
1505
- if (status === 'working')
1506
- return session.activity === 'working' || (!session.activity && session.status === 'running');
1507
- if (status === 'waiting')
1508
- return isAwaitingUser(session);
1509
- return session.status === status;
1510
- }
1511
- /** Resolve convenience flags once. Multiple flags intentionally form a union. */
1512
- export function requestedLiveStatuses(options) {
1513
- const statuses = [];
1514
- if (options.working)
1515
- statuses.push('working');
1516
- if (options.idle)
1517
- statuses.push('idle');
1518
- if (options.waiting)
1519
- statuses.push('waiting');
1520
- if (options.orphan || options.orphaned)
1521
- statuses.push('orphaned');
1522
- if (options.crashed)
1523
- statuses.push('crashed');
1524
- if (options.closed)
1525
- statuses.push('closed');
1526
- if (options.abandoned)
1527
- statuses.push('abandoned');
1528
- if (options.queued)
1529
- statuses.push('queued');
1530
- if (options.unknown)
1531
- statuses.push('unknown');
1532
- return [...new Set(statuses)];
1533
- }
1534
- /** Nudge shown when `--active` has no other machines to fold in. */
1535
- function printCrossMachineTip() {
1536
- console.log(chalk.gray("\nTip: include sessions from your other machines — register them with 'ag devices sync', then rerun. Use --local to skip."));
1537
- }
1538
- /**
1539
- * True when the interactive session browser should open instead of a printed
1540
- * listing: a real TTY, no `--json`, and `--no-interactive` not set. The bare
1541
- * listing and `--active` both default to it; scripts/pipes/agents fall through
1542
- * to the existing printed/JSON paths.
1543
- */
1544
364
  function useInteractiveBrowser(options) {
1545
365
  return options.interactive !== false && !options.json && isInteractiveTerminal();
1546
366
  }
1547
- /**
1548
- * A bare interactive fleet listing — no query, no render/filter flag — that the
1549
- * `runSessionBrowser` picker can represent. The single predicate shared by the
1550
- * bare-browser branch and the `--device` early-return guard so they can't drift:
1551
- * when this holds, an explicit `--device` scope is folded into the
1552
- * browser (preview-rich, selectable) instead of the legacy per-host raw stream.
1553
- */
1554
367
  function isBareBrowserListing(options, query) {
1555
368
  return (useInteractiveBrowser(options) &&
1556
- // A peer answering a fan-out must never open a TUI. It has no TTY either, so
1557
- // this is the explicit half of a guard that otherwise rests on the implicit
1558
- // invariant that peers are always dialed with --json (see remote-list.ts).
1559
369
  process.env.AGENTS_SESSIONS_LOCAL !== '1' &&
1560
370
  hasNoBrowserDisqualifyingFlags(options, query));
1561
371
  }
1562
- /**
1563
- * Pure flag-gate half of {@link isBareBrowserListing} (TTY-independent, so it is
1564
- * unit-testable): true when no query, render, or filter flag is present that the
1565
- * `runSessionBrowser` picker cannot represent.
1566
- */
1567
372
  export function hasNoBrowserDisqualifyingFlags(options, query) {
1568
373
  return (!query &&
1569
- // A named team view is a flat selectable pool; the bare --teams report
1570
- // stays grouped and printed because the browser cannot preserve its shape.
1571
374
  (!options.teams || !!options.inTeam) &&
1572
375
  !options.flat &&
1573
376
  !options.tree &&
1574
377
  !options.markdown &&
1575
378
  !options.until &&
1576
379
  !options.project &&
1577
- // The interactive browser picker is a fuzzy-search TUI over the discovered
1578
- // pool, not a SQL-filtered listing — it cannot represent a skill/plugin
1579
- // scope. Falling through to it here would silently drop the filter and
1580
- // show the unfiltered pool instead (same reasoning as --project/--sort).
1581
380
  !options.skill &&
1582
381
  !options.plugin &&
1583
382
  !options.sort &&
1584
- // --routine without a name owns a two-stage picker (routine -> run ->
1585
- // session), and a named routine owns the run-grouped overview. The generic
1586
- // browser can only flatten routine sessions into its ordinary row list, so
1587
- // routing this flag there silently bypasses both routine-specific views.
1588
383
  !options.routine &&
1589
384
  !options.artifacts &&
1590
385
  options.artifact === undefined &&
1591
- // --cloud lists a provider's tasks, not the transcript index, and
1592
- // runCloudSessions has no host scope — letting a `--device X --cloud` fall
1593
- // through to the browser gate would silently drop the X the user asked for.
1594
386
  !options.cloud &&
1595
- // The browser carries ONE device in its filter and `y` copies back exactly
1596
- // one --device, so a multi-host scope can't round-trip. Those stay on the
1597
- // legacy per-host stream, which prints each peer under its own banner.
1598
387
  (options.host?.length ?? 0) <= 1);
1599
388
  }
1600
- /** Resolve a typed routine selector by exact name, substring, or one unambiguous typo. */
1601
- export function resolveRoutineName(query, names) {
1602
- const normalized = query.trim().toLowerCase();
1603
- if (!normalized)
1604
- return null;
1605
- const exact = names.find((name) => name.toLowerCase() === normalized);
1606
- if (exact)
1607
- return exact;
1608
- const containing = names.filter((name) => name.toLowerCase().includes(normalized));
1609
- if (containing.length === 1)
1610
- return containing[0];
1611
- return fuzzyMatch(query, names, FUZZY_PRESETS.dynamic);
1612
- }
1613
389
  export function buildRoutineRunGroups(sessions) {
1614
390
  const byRun = new Map();
1615
391
  for (const session of sessions) {
@@ -1641,10 +417,6 @@ export function buildRoutineChoices(sessions, runOnlyNames = []) {
1641
417
  latestRunSessionCount: runs[0].sessions.length,
1642
418
  };
1643
419
  });
1644
- // Routines whose only attempts are pre-session (blocked/skipped/failed-before-
1645
- // spawn) have run records but no transcript. Surface them so the picker is
1646
- // built from definitions+runs, never only transcripts — otherwise a routine
1647
- // that has never produced a session reads as "No routines match" (the plan).
1648
420
  const seen = new Set(choices.map((c) => c.name));
1649
421
  for (const name of runOnlyNames) {
1650
422
  if (seen.has(name))
@@ -1683,7 +455,6 @@ async function selectRoutineName(sessions) {
1683
455
  });
1684
456
  return picked?.item.name ?? null;
1685
457
  }
1686
- /** `listJobsWithRuns` but never throws into the sessions reader (best-effort). */
1687
458
  function safeListJobsWithRuns() {
1688
459
  try {
1689
460
  return [...new Set([...listJobs().map((job) => job.name), ...listJobsWithRuns()])];
@@ -1692,17 +463,6 @@ function safeListJobsWithRuns() {
1692
463
  return [];
1693
464
  }
1694
465
  }
1695
- /**
1696
- * Resolve which routine the user is targeting for `--routine`. Returns `null` to
1697
- * ABORT the command (a bad `--routine <name>` printed an error, or the user
1698
- * cancelled the interactive pick). `{ name: null }` means "no routine was
1699
- * selected, don't filter" — the degenerate bare `--routine` piped to a
1700
- * non-interactive sink. `{ name }` is a chosen routine.
1701
- *
1702
- * The name universe is transcripts UNION every routine with a definition or a
1703
- * run record (`safeListJobsWithRuns`), so a command-only or never-ran routine —
1704
- * which produces no `SessionMeta` — still resolves and drills down.
1705
- */
1706
466
  async function selectRoutineTarget(sessions, routine, interactive) {
1707
467
  const routineNames = [...new Set([
1708
468
  ...sessions.map((session) => session.routineName).filter((name) => !!name),
@@ -1742,10 +502,6 @@ export function executionKind(meta) {
1742
502
  return 'agent';
1743
503
  return 'command';
1744
504
  }
1745
- /**
1746
- * Reconcile a routine's canonical run records (`listRuns`, local disk) with the
1747
- * indexed sessions for that routine (fleet-wide, keyed by `routineRunId`).
1748
- */
1749
505
  function buildRoutineDrilldown(name, sessions) {
1750
506
  const runs = listRuns(name);
1751
507
  const forRoutine = sessions.filter((s) => s.routineName === name);
@@ -1760,7 +516,6 @@ function buildRoutineDrilldown(name, sessions) {
1760
516
  meta,
1761
517
  sessions: (byRun.get(meta.runId) ?? []).sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)),
1762
518
  }))
1763
- // Newest run first: `startedAt`, then run id (an ISO-derived slot) as a tie-break.
1764
519
  .sort((a, b) => {
1765
520
  const at = a.meta.startedAt || a.meta.runId;
1766
521
  const bt = b.meta.startedAt || b.meta.runId;
@@ -1773,13 +528,9 @@ function buildRoutineDrilldown(name, sessions) {
1773
528
  orphanSessions,
1774
529
  runRecordCount: runs.length,
1775
530
  linkedSessionCount: forRoutine.length,
1776
- // "Agent routine" = anything that isn't purely command runs. A workflow run
1777
- // carries no `agent` field but still expects a session, so key off
1778
- // executionKind, not `agent` alone.
1779
531
  isAgentRoutine: runs.some((r) => executionKind(r) !== 'command') || forRoutine.length > 0,
1780
532
  };
1781
533
  }
1782
- /** Glyph + word for a run's terminal status. */
1783
534
  function runStatusCell(status) {
1784
535
  switch (status) {
1785
536
  case 'completed': return `${chalk.green('✓')} ${chalk.green('completed')}`;
@@ -1792,7 +543,6 @@ function runStatusCell(status) {
1792
543
  default: return chalk.gray(status);
1793
544
  }
1794
545
  }
1795
- /** Where a run's body executed: a host, a cloud provider, or this machine. */
1796
546
  function runPlacement(meta) {
1797
547
  if (meta.host)
1798
548
  return `host:${meta.host}`;
@@ -1800,13 +550,11 @@ function runPlacement(meta) {
1800
550
  return `cloud:${meta.cloudProvider}`;
1801
551
  return 'local';
1802
552
  }
1803
- /** The trigger clause: kind + who triggered it (dropping the noisy UNRESOLVED). */
1804
553
  function runTrigger(meta) {
1805
554
  const kind = meta.triggerKind ?? 'schedule';
1806
555
  const who = meta.triggeredBy && !meta.triggeredBy.startsWith('UNRESOLVED') ? ` by ${meta.triggeredBy}` : '';
1807
556
  return `${kind}${who}`;
1808
557
  }
1809
- /** The tail clause explaining a run's outcome (exit code / error / skip reason). */
1810
558
  function runOutcomeDetail(meta) {
1811
559
  if (meta.status === 'skipped' && meta.skipReason) {
1812
560
  const ref = meta.activeRunId ? ` → ${meta.activeRunId}` : '';
@@ -1819,13 +567,10 @@ function runOutcomeDetail(meta) {
1819
567
  return 'daemon down at fire time';
1820
568
  if (meta.errorMessage)
1821
569
  return meta.errorMessage;
1822
- // A clean `exit 0` is already implied by the green "completed" status — only a
1823
- // non-zero code carries information worth a tail clause.
1824
570
  if (typeof meta.exitCode === 'number' && meta.exitCode !== 0)
1825
571
  return `exit ${meta.exitCode}`;
1826
572
  return '';
1827
573
  }
1828
- /** Compact per-session metadata line under a linked session row. */
1829
574
  function linkedSessionMeta(session) {
1830
575
  const parts = [];
1831
576
  const shown = sessionDisplayAgent(session);
@@ -1846,19 +591,11 @@ function linkedSessionMeta(session) {
1846
591
  parts.push(`${session.toolCallCount} tools`);
1847
592
  return chalk.gray(' ' + parts.join(' · '));
1848
593
  }
1849
- /**
1850
- * Render the routine drilldown: canonical run history first, each run linked to
1851
- * its indexed agent session(s). This is the run/session seam made visible — a
1852
- * command routine shows its runs and states plainly that no session is expected;
1853
- * an agent routine shows the same runs plus each run's session metadata.
1854
- */
1855
594
  export function printRoutineDrilldown(drill, liveIndex, opts = {}) {
1856
595
  const runWord = drill.runRecordCount === 1 ? 'run record' : 'run records';
1857
596
  const sessWord = drill.linkedSessionCount === 1 ? 'linked session' : 'linked sessions';
1858
597
  console.log(`${chalk.cyan.bold(drill.name)} ` +
1859
598
  chalk.gray(`${drill.runRecordCount} ${runWord} · ${drill.linkedSessionCount} ${sessWord}`));
1860
- // A routine with no session ever produced is a command routine — say so up
1861
- // front so the empty session column reads as expected, not as missing data.
1862
599
  if (!drill.isAgentRoutine) {
1863
600
  console.log(chalk.gray('Command routine — runs execute a shell command; no agent session is produced.'));
1864
601
  }
@@ -1881,9 +618,6 @@ export function printRoutineDrilldown(drill, liveIndex, opts = {}) {
1881
618
  if (outcome)
1882
619
  detailParts.push(outcome);
1883
620
  console.log(chalk.gray(' ' + detailParts.join(' · ')));
1884
- // Log/report access — both are children of the run dir that a pre-execution
1885
- // terminal (blocked/skipped/missed) never wrote, so gate each on existence
1886
- // rather than pointing at a file that isn't there.
1887
621
  const runDir = getRunDir(drill.name, m.runId);
1888
622
  const logHints = [];
1889
623
  if (fs.existsSync(path.join(runDir, 'stdout.log')))
@@ -1899,8 +633,6 @@ export function printRoutineDrilldown(drill, liveIndex, opts = {}) {
1899
633
  }
1900
634
  }
1901
635
  else if (kind !== 'command') {
1902
- // An agent run that produced no session — say why (the run terminal
1903
- // explains it), never fake a session row.
1904
636
  console.log(chalk.gray(' no agent session archived for this run'));
1905
637
  }
1906
638
  console.log();
@@ -1918,16 +650,11 @@ export function printRoutineDrilldown(drill, liveIndex, opts = {}) {
1918
650
  }
1919
651
  }
1920
652
  console.log(chalk.gray(`Run history from .history/runs/${drill.name}/ · resume a session with agents sessions resume <id>.`));
1921
- // Every listing path must say what it dropped (sessions.ts invariant). Under
1922
- // --routine team-origin sessions are shown (hiddenCount is 0), but unmanaged
1923
- // installs may still be hidden — surface both footers when non-zero.
1924
653
  if (opts.hiddenCount && opts.hiddenCount > 0)
1925
654
  console.log(chalk.gray(formatTeamHiddenFooter(opts.hiddenCount)));
1926
655
  if (opts.hiddenUnmanaged && opts.hiddenUnmanaged > 0)
1927
656
  console.log(chalk.gray(formatUnmanagedHiddenFooter(opts.hiddenUnmanaged)));
1928
657
  }
1929
- /** The canonical `ag sessions …` command for a set of flags — the twin of the
1930
- * browser's `y` hotkey (see --print-cmd). Normalizes to the stable flag form. */
1931
658
  function canonicalSessionsCommand(query, options) {
1932
659
  const a = ['sessions'];
1933
660
  if (options.active)
@@ -1984,31 +711,8 @@ function canonicalSessionsCommand(query, options) {
1984
711
  a.push(JSON.stringify(q));
1985
712
  return 'ag ' + a.join(' ');
1986
713
  }
1987
- /** Bound on the `details.messages` array and on each message's `text` — this
1988
- * rides a cached JSON payload (and, for the remote fast path, an SSH capture
1989
- * with its own byte cap), so it is capped independently of either. */
1990
714
  const SESSION_DETAIL_MAX_MESSAGES = 8;
1991
715
  const SESSION_DETAIL_MESSAGE_MAX_CHARS = 4_000;
1992
- /**
1993
- * The additive `details` block on `sessions preview --json` (PHNX-3999):
1994
- * `request`/`timeline`/`files` are read FIRST from the existing
1995
- * `session_timelines` projection (`readSessionTimelineAny` — the daemon's own
1996
- * bounded, incrementally-folded cache, `SessionTimelineProjection` in
1997
- * `lib/session/db.ts`), so a session the daemon has already reached costs
1998
- * nothing extra here. When that projection is missing (daemon hasn't reached
1999
- * this session yet), this does an ON-DEMAND fold using the EXACT same pure,
2000
- * already-redacting pipeline the daemon uses (`foldTimeline` /
2001
- * `projectTimeline` / `projectSessionFiles`, `timeline.ts`) over whatever
2002
- * events are cheaply available: the fresh-parse `events` from
2003
- * `loadSessionPreviewDigest` when there is one, else a bounded 128 KiB tail
2004
- * read (`readSessionTail`, Claude/Codex only — the same bounded reader the
2005
- * local preview's over-cap fallback uses) so a WARM cache hit (no fresh parse)
2006
- * still gets real, on-demand content instead of falling back to a 2-message
2007
- * digest summary. This is genuinely bounded either way: it folds only events
2008
- * already in hand or a fixed 128 KiB window, never a fresh whole-file parse.
2009
- * `partial`/`reason` say when this happened (`daemon-pending: on-demand
2010
- * bounded fold`) versus a session with no transcript at all.
2011
- */
2012
716
  function sessionTranscriptStamp(session) {
2013
717
  try {
2014
718
  if (!session.filePath)
@@ -2025,13 +729,6 @@ export function buildSessionDetailBlock(session, digest, events, sourceStamp) {
2025
729
  const stamp = sourceStamp ?? sessionTranscriptStamp(session);
2026
730
  const canDateEvents = sourceStamp !== undefined || events.length === 0;
2027
731
  const daemonProjection = stamp ? readSessionTimelineAny(session.id, stamp) : undefined;
2028
- // A bounded tail read backs `messages` on EVERY warm cache hit (no fresh
2029
- // parse this call), REGARDLESS of whether the daemon projection is already
2030
- // present — this is what fixes a real bug review caught: a warm digest hit
2031
- // whose daemon timeline HAD landed still fell through to the 2-message
2032
- // digest summary below, because the tail-read used to be gated on the
2033
- // on-demand-fold branch only. `messages` and the on-demand fold are two
2034
- // independent uses of the same cheap, bounded events window.
2035
732
  let foldEvents = events;
2036
733
  if (foldEvents.length === 0 && session.filePath) {
2037
734
  foldEvents = readSessionTail(session.filePath, session.agent);
@@ -2079,22 +776,7 @@ export function buildSessionDetailBlock(session, digest, events, sourceStamp) {
2079
776
  reason,
2080
777
  };
2081
778
  }
2082
- /** Resolve a session by id/query globally and print its compact preview (no pager).
2083
- * Backs `--preview` — the fast path for the "peek before resume" hot loop. */
2084
779
  export async function renderSessionPreview(query, scope) {
2085
- // Exact ID + exactly one named, non-local device: the canonical bounded
2086
- // preview loader (PHNX-3999) answers in ONE ssh hop plus a durable local
2087
- // cache, instead of the general resolver's metadata fan-out followed by a
2088
- // second render hop. Scoped tightly on purpose — a short id/label is not a
2089
- // stable cache key across devices, and this never touches local discovery.
2090
- //
2091
- // Gated on `isCompleteSessionId`, NOT a bare-UUID regex: measured over this
2092
- // fleet's own index, a "full session id" is a bare UUID for most harnesses
2093
- // but `session_<uuid>` for kimi/rush and `ses_<ulid>` for opencode
2094
- // (discover.ts). A bare-UUID-only check would silently skip this fast path
2095
- // (falling through to the slower general resolver, never wrong, but wrong
2096
- // to claim as "the fast path" for those harnesses) for a real, exact,
2097
- // caller-supplied full id on those three.
2098
780
  if (scope.json && !scope.local && scope.hosts?.length === 1
2099
781
  && scope.hosts[0] !== machineId() && isCompleteSessionId(query.trim())) {
2100
782
  const { getRemoteSessionPreview } = await import('../lib/session/remote-preview-cache.js');
@@ -2106,37 +788,15 @@ export async function renderSessionPreview(query, scope) {
2106
788
  console.log(JSON.stringify({
2107
789
  schemaVersion: 1,
2108
790
  session: envelope?.session ?? null,
2109
- // `active` is a live snapshot the PEER computed at the moment it ran
2110
- // `sessions preview --local --json` — meaningful only for that instant.
2111
- // When this response is served from the durable cache (`cache.source ===
2112
- // 'cache'`, which can be up to 45s old, or indefinitely old under a
2113
- // matched --revision), presenting that stale snapshot under the same
2114
- // `active` key would read as the session's CURRENT working/idle status
2115
- // when it may no longer be. Null it out rather than relabel it with an
2116
- // age stamp: the caller already has `cache.fetchedAt`/`cache.stale` to
2117
- // reason about staleness, and getting a fresh `active` read costs
2118
- // another SSH round trip this fast path exists specifically to avoid.
2119
791
  active: result.cache.source === 'live' ? (envelope?.active ?? null) : null,
2120
792
  preview: envelope?.preview ?? null,
2121
793
  error: envelope?.error ?? (envelope ? null : result.cache.reason),
2122
- // The peer's own `sessions preview --local --json` already computed
2123
- // `details` (request/timeline/files/messages) with its own bounded reader
2124
- // (see buildSessionDetailBlock) — this hop just forwards it verbatim,
2125
- // never re-derives it, so a metadata-only/no-transcript peer row's
2126
- // explicit unavailable reason survives the hop unchanged.
2127
794
  details: envelope?.details ?? null,
2128
795
  cache: result.cache,
2129
796
  }));
2130
797
  return;
2131
798
  }
2132
799
  let outcome = await resolveSessionMetadataValue(query, scope);
2133
- // resolveSessionMetadataValue already consults the live registry for a running
2134
- // session with no transcript row (RUSH-2682), so a live session resolves above.
2135
- // This block still repairs the residual case — a session whose transcript is on
2136
- // disk but not yet indexed (e.g. one that just ended). `waitForScan` makes the
2137
- // repair honest: when another process holds the scan claim it waits for that
2138
- // scan instead of returning the stale read. Also preserves Claude history-alias
2139
- // discovery.
2140
800
  if (outcome.kind !== 'resolved'
2141
801
  && (!scope.hosts?.length || shouldIncludeLocal(scope.hosts, machineId()))) {
2142
802
  const discovered = applyScopeFilters(await discoverSessions({ all: true, cwd: process.cwd(), limit: 5000, waitForScan: true }), scope);
@@ -2151,10 +811,6 @@ export async function renderSessionPreview(query, scope) {
2151
811
  }
2152
812
  }
2153
813
  if (outcome.kind === 'partial') {
2154
- // RUSH-2492: an unreachable peer is a warning, not a hard failure. The
2155
- // resolver already resolves an id found on the reachable fleet (SES-9a),
2156
- // so reaching here means the session was not found on any device we COULD
2157
- // reach — it may live on an unreachable peer, which we could not check.
2158
814
  const offline = outcome.failedPeers;
2159
815
  console.error(chalk.yellow(`Warning: ${offline.length} device(s) unreachable, not checked: ${offline.join(', ')}`));
2160
816
  console.error(chalk.red(`No session matching "${query}" on any reachable device (${offline.length} unreachable, not checked).`));
@@ -2191,17 +847,12 @@ export async function renderSessionPreview(query, scope) {
2191
847
  }
2192
848
  return;
2193
849
  }
2194
- // Lead with the live status when the session is still running, so the preview
2195
- // says working / waiting / idle up front — not just the historical transcript.
2196
- // The shared snapshot is accepted for at most 15 seconds. The durable preview
2197
- // below contains no live status, so a long-lived process can never keep a
2198
- // stale working/waiting headline in its transcript cache.
2199
850
  let live;
2200
851
  try {
2201
852
  const loaded = await loadLocalActiveSessions();
2202
853
  live = indexActiveBySessionId(loaded.sessions).get(session.id);
2203
854
  }
2204
- catch { /* plain preview on any probe failure */ }
855
+ catch { }
2205
856
  if (scope.json) {
2206
857
  const sourceStamp = sessionTranscriptStamp(session);
2207
858
  const { digest, error, events } = loadSessionPreviewDigest(session);
@@ -2249,27 +900,12 @@ export async function renderSessionPreview(query, scope) {
2249
900
  console.log(headline);
2250
901
  console.log(buildPreview(session));
2251
902
  }
2252
- /**
2253
- * The one-line live status banner shown above a session preview: the glyph, the
2254
- * status word, and — when the session needs a human or has LOST one — a plain
2255
- * sentence saying so. Shared by `--preview` and the interactive browser's preview
2256
- * pane so both explain a state the same way.
2257
- *
2258
- * `crashed` and `orphaned` are the states a glyph alone cannot carry: nobody
2259
- * reads "orphan" and knows it means "still running in tmux with no window
2260
- * attached", so those two spell it out.
2261
- */
2262
903
  export function formatLiveStatusHeadline(live, bookmarked = false) {
2263
904
  const star = bookmarked ? chalk.yellow('★ ') : '';
2264
- // With no live row there is no status to lead with, so the star has to say
2265
- // what it means on its own — a bare `★` above a preview reads as noise.
2266
905
  if (!live)
2267
906
  return bookmarked ? chalk.yellow('★ bookmarked') : '';
2268
907
  const { glyph } = liveGlyphAndPreview(live);
2269
908
  const word = liveStatusWord(live) || live.status;
2270
- // One definition, shared with `--waiting`. A second local copy drifted: the
2271
- // preview said "needs you" for a dead session that `--waiting` had (rightly)
2272
- // stopped counting, so the human and the script disagreed about the same row.
2273
909
  const needsYou = isAwaitingUser(live);
2274
910
  const reason = live.awaitingReason ? ` (${live.awaitingReason.replace('_', ' ')})` : '';
2275
911
  let suffix = needsYou ? chalk.yellow(` ← needs you${reason}`) : '';
@@ -2277,16 +913,12 @@ export function formatLiveStatusHeadline(live, bookmarked = false) {
2277
913
  suffix = chalk.redBright(' ← the host app or connection went away and took the agent with it');
2278
914
  }
2279
915
  else if (live.status === 'orphaned') {
2280
- // Keep the needs-you half. A session sitting on a real question with nobody
2281
- // attached is strictly worse than either fact alone, and replacing the
2282
- // question with the generic orphan line undersells exactly that case.
2283
916
  suffix = needsYou
2284
917
  ? chalk.yellow(` ← waiting on you${reason}, and no client is attached to answer it`)
2285
918
  : chalk.yellow(' ← still running, but no client is attached — nothing is showing it');
2286
919
  }
2287
920
  return `${star}${glyph} ${statusColor(live.status)(word)}${suffix}`;
2288
921
  }
2289
- /** Merge local and peer envelopes without changing the versioned JSON shape. */
2290
922
  export function mergeToolSearchEnvelopes(local, remotes) {
2291
923
  const all = [local, ...remotes];
2292
924
  const sessions = new Map();
@@ -2333,7 +965,6 @@ export function mergeToolProgramCountEnvelopes(local, remotes) {
2333
965
  machines: all.flatMap((envelope) => envelope.machines),
2334
966
  };
2335
967
  }
2336
- /** Partition a fleet query by transcript origin so synced mirrors cannot duplicate results. */
2337
968
  export function toolOriginSessions(sessions, machine, originOnly) {
2338
969
  return originOnly
2339
970
  ? sessions.filter((session) => (session.machine ?? machine) === machine)
@@ -2355,7 +986,6 @@ export function toolSearchFleetSortError(sort, spansDevices) {
2355
986
  return undefined;
2356
987
  return 'Tool search across devices supports only --sort recent; cost and duration are local-only.';
2357
988
  }
2358
- /** Compact grouped evidence for humans; JSON retains every bounded field. */
2359
989
  function printToolSearch(envelope) {
2360
990
  for (const session of envelope.sessions) {
2361
991
  const machineName = session.machine
@@ -2394,14 +1024,7 @@ function printToolSearch(envelope) {
2394
1024
  console.log(chalk.yellow(`Tool index coverage is partial: ${envelope.coverage.remainingFiles} transcript${envelope.coverage.remainingFiles === 1 ? '' : 's'} remain.${skipped}${limited}${retry}`));
2395
1025
  }
2396
1026
  }
2397
- /** Main action handler for `agents sessions`. Routes to picker, table, or single-session render. */
2398
- async function sessionsAction(query, options,
2399
- /**
2400
- * Where commander got `--limit` from: 'cli'/'env' when the user supplied it,
2401
- * 'default' when it filled in its own. Truthiness can't tell those apart,
2402
- * because the default arrives as a string like any typed value.
2403
- */
2404
- limitSource) {
1027
+ async function sessionsAction(query, options, limitSource) {
2405
1028
  const queryClauses = options.query ?? [];
2406
1029
  const liveStatuses = requestedLiveStatuses(options);
2407
1030
  const liveOnly = options.active === true || liveStatuses.length > 0;
@@ -2418,8 +1041,6 @@ limitSource) {
2418
1041
  process.exitCode = 1;
2419
1042
  return;
2420
1043
  }
2421
- // Outside tool evidence mode, --query retains its original positional-search
2422
- // meaning. The collector only makes it repeatable for tool clauses.
2423
1044
  query = query ?? queryClauses[0];
2424
1045
  }
2425
1046
  if (options.fleet && !toolEvidenceMode) {
@@ -2468,9 +1089,6 @@ limitSource) {
2468
1089
  return;
2469
1090
  }
2470
1091
  }
2471
- // Normalize convenience flags before any routing reads them: per-agent
2472
- // shorthands fold into --agent; --device and --devices (both
2473
- // resolve against the same device registry).
2474
1092
  applyAgentShorthands(options);
2475
1093
  try {
2476
1094
  query = applyVersionFilters(query, options);
@@ -2480,32 +1098,17 @@ limitSource) {
2480
1098
  process.exitCode = 1;
2481
1099
  return;
2482
1100
  }
2483
- // --device / --devices values are merged into options.host for internal routing. A bare `all` / `fleet` sentinel means
2484
- // "search every peer" — which is already the default for `--active` and the
2485
- // interactive listing — so it resolves to no explicit host set rather than
2486
- // erroring on a device literally named "all".
2487
1101
  const rawDeviceTargets = [...(options.device ?? []), ...(options.devices ?? [])];
2488
- // But the HISTORICAL `--json` listing does NOT fan out by default (it stays a
2489
- // deterministic local slice for scripts), so the sentinel would otherwise be
2490
- // silently dropped and the fleet never reached (PHNX-2673). Remember it so that
2491
- // path can sweep every peer, exactly as the interactive listing already does.
2492
1102
  const fleetWide = rawDeviceTargets.some(t => t.toLowerCase() === 'all' || t.toLowerCase() === 'fleet');
2493
1103
  const deviceTargets = rawDeviceTargets
2494
1104
  .filter(t => t.toLowerCase() !== 'all' && t.toLowerCase() !== 'fleet');
2495
1105
  if (deviceTargets.length > 0) {
2496
1106
  options.host = [...(options.host ?? []), ...deviceTargets];
2497
1107
  }
2498
- // --print-cmd: echo the canonical `ag sessions …` for the given flags and exit.
2499
- // The non-interactive twin of the browser's `y` hotkey — lets an agent compose
2500
- // (or a human copy) the exact command a view maps to.
2501
1108
  if (options.printCmd) {
2502
1109
  process.stdout.write(canonicalSessionsCommand(query, options) + '\n');
2503
1110
  return;
2504
1111
  }
2505
- // --roots: emit the local session-scan directories, per agent, as JSON. A pure
2506
- // machine-readable query (no listing/render) — external watchers (the Factory
2507
- // extension's fs.watch) read it to track the same dirs the CLI scans, instead
2508
- // of hardcoding `~/.claude|.codex|.gemini`. Always local; ignores other flags.
2509
1112
  if (options.roots) {
2510
1113
  process.stdout.write(JSON.stringify(getSessionRoots(), null, 2) + '\n');
2511
1114
  return;
@@ -2521,9 +1124,6 @@ limitSource) {
2521
1124
  process.stdout.write(JSON.stringify({ launchId, sessionId: record?.session_id ?? null }) + '\n');
2522
1125
  return;
2523
1126
  }
2524
- // --resolve is the metadata-only contract for downstream context workflows.
2525
- // It reads indexed SessionMeta rows and never calls renderSession, buildPreview,
2526
- // parseSession, or any other transcript renderer/parser.
2527
1127
  if (options.resolve !== undefined || options.resolveSafeV1 !== undefined) {
2528
1128
  if (options.resolveSafeV1 !== undefined && process.env[NO_FANOUT_ENV] !== '1') {
2529
1129
  console.error(chalk.red('--resolve-safe-v1 is an internal fleet protocol.'));
@@ -2560,16 +1160,7 @@ limitSource) {
2560
1160
  console.error(chalk.gray('Drop one — `--device <box>` to read that machine, `--local` to stay on this one.'));
2561
1161
  process.exit(1);
2562
1162
  }
2563
- // --host WITHOUT --active. `--json` fans the recent listing out and emits ONE
2564
- // clean merged SessionMeta[] array (same shape as the local --json path), for
2565
- // scripts/extensions that JSON.parse a remote's history. Without --json it
2566
- // keeps the legacy per-host stream (each remote's raw stdout under a
2567
- // `── host ──` banner). With --active, the hosts are folded into the merged
2568
- // machine-grouped view instead (handled below).
2569
1163
  if (options.host && options.host.length > 0 && !liveOnly && !toolEvidenceMode) {
2570
- // --local means "skip the SSH fan-out"; --host means "look only over there".
2571
- // Together they ask for a peer's sessions without dialing the peer, which can
2572
- // only ever be empty — so say that instead of rendering a blank list.
2573
1164
  if (options.local === true && !shouldIncludeLocal(options.host, machineId())) {
2574
1165
  console.error(chalk.red('--local and --device name opposite scopes: --local skips the SSH fan-out that --device needs.'));
2575
1166
  console.error(chalk.gray('Drop one — `--device <box>` to read that machine, `--local` to stay on this one.'));
@@ -2579,11 +1170,6 @@ limitSource) {
2579
1170
  await runRemoteSessionsJson(options.host);
2580
1171
  return;
2581
1172
  }
2582
- // A bare interactive `--device`/`--device <box>` listing falls through to the
2583
- // fleet browser below, which folds the named host(s) into the same merged,
2584
- // preview-rich, selectable view as the local listing (via gatherRemoteList).
2585
- // A query, a render/filter flag, or a non-interactive caller keeps the legacy
2586
- // per-host raw stream under a `── host ──` banner.
2587
1173
  if (!isBareBrowserListing(options, query)) {
2588
1174
  try {
2589
1175
  runRemoteSessions(options.host);
@@ -2595,9 +1181,6 @@ limitSource) {
2595
1181
  return;
2596
1182
  }
2597
1183
  }
2598
- // --preview <id/query>: resolve one session and print its compact preview, then
2599
- // exit — checked before --active so `--active --preview <id>` peeks the id
2600
- // rather than being swallowed by the active listing.
2601
1184
  if (options.preview) {
2602
1185
  if (!query) {
2603
1186
  console.error(chalk.red('--preview requires a session id or query.'));
@@ -2607,63 +1190,18 @@ limitSource) {
2607
1190
  return;
2608
1191
  }
2609
1192
  if (liveOnly) {
2610
- // The running view is built from the live scan, which carries no team lineage
2611
- // (that comes off the transcript index and the teams meta dir), so --in-team
2612
- // has nothing to match on here. Say so rather than ignoring the flag.
2613
1193
  if (options.inTeam) {
2614
1194
  console.error(chalk.red('--in-team does not apply to --active: the running view carries no team lineage.'));
2615
1195
  console.error(chalk.gray('Drop --active to filter by team, or use `agents teams status <name>` for a live team.'));
2616
1196
  process.exit(1);
2617
1197
  }
2618
- // On a TTY (and not a scripting path), open the interactive browser seeded to
2619
- // running-only. --json / --waiting / --no-interactive / a peer fan-out keep the
2620
- // static dump untouched, so scripts and agents are unaffected. An explicit
2621
- // --since seeds the window; --until / --project (no browser field) or a
2622
- // multi-host scope fall through to the static dump that already honors them.
2623
- if (useInteractiveBrowser(options) &&
2624
- liveStatuses.length === 0 &&
2625
- !options.until &&
2626
- !options.project &&
2627
- !options.sort &&
2628
- (options.host?.length ?? 0) <= 1 &&
2629
- process.env.AGENTS_SESSIONS_LOCAL !== '1') {
2630
- const { runSessionBrowser, activeBrowserSeed } = await import('./sessions-browser.js');
2631
- await runSessionBrowser(activeBrowserSeed({
2632
- teams: options.teams,
2633
- agent: options.agent,
2634
- host: options.host,
2635
- since: options.since,
2636
- all: options.all,
2637
- bookmarks: options.bookmarks,
2638
- routine: options.routine,
2639
- }), { local: options.local === true, hosts: options.host });
2640
- return;
2641
- }
2642
- // AGENTS_SESSIONS_LOCAL is set by a parent fan-out invocation (see
2643
- // remote-active.ts) so a peer answers for itself without recursing.
2644
- const forceLocal = options.local === true || process.env.AGENTS_SESSIONS_LOCAL === '1';
2645
- await renderActiveSessions(options.json === true, options.waiting === true, {
2646
- local: forceLocal,
2647
- hosts: options.host,
2648
- bookmarksOnly: options.bookmarks === true,
2649
- statuses: liveStatuses,
2650
- routine: options.routine,
2651
- });
1198
+ await runLiveRoster(options);
2652
1199
  return;
2653
1200
  }
2654
1201
  if (options.cloud) {
2655
1202
  await runCloudSessions(query, options);
2656
1203
  return;
2657
1204
  }
2658
- // Bare interactive listing → the interactive fleet browser (humans). A query,
2659
- // a render/filter flag, --flat/--tree, --json, --until, --project (a named-project
2660
- // filter the browser can't represent), or --no-interactive keep the existing
2661
- // printed/render paths (agents and scripts unaffected). An explicit --since seeds
2662
- // the browser's window so the flag is honored, not swallowed.
2663
- //
2664
- // Bare `--teams` still diverts to the printed team-grouped report because the
2665
- // flat picker cannot preserve that shape. `--in-team <name> --teams` is one
2666
- // flat lineage, so it qualifies through hasNoBrowserDisqualifyingFlags.
2667
1205
  if (isBareBrowserListing(options, query)) {
2668
1206
  const { runSessionBrowser, bareBrowserSeed } = await import('./sessions-browser.js');
2669
1207
  await runSessionBrowser(bareBrowserSeed({
@@ -2687,7 +1225,6 @@ limitSource) {
2687
1225
  process.exit(1);
2688
1226
  }
2689
1227
  const { agent, version } = parseAgentFilter(options.agent);
2690
- // Path-like queries filter by project directory instead of text search.
2691
1228
  let pathFilter;
2692
1229
  let searchQuery;
2693
1230
  if (query && looksLikePath(query)) {
@@ -2702,53 +1239,28 @@ limitSource) {
2702
1239
  else {
2703
1240
  searchQuery = query;
2704
1241
  }
2705
- // Artifact flags require a session query.
2706
1242
  if ((options.artifacts || options.artifact !== undefined) && !query) {
2707
1243
  console.error(chalk.red('--artifacts and --artifact require a session ID or query.'));
2708
1244
  process.exit(1);
2709
1245
  }
2710
1246
  const mode = resolveViewMode(options, filterOpts);
2711
- // --markdown or any filter flag forces single-session render.
2712
1247
  const wantsRender = !toolEvidenceMode && (mode === 'markdown' || hasAnyFilter(filterOpts));
2713
- // Artifact-list or artifact-read paths: widen scope and resolve session globally.
2714
1248
  if ((options.artifacts || options.artifact !== undefined) && searchQuery) {
2715
1249
  await renderArtifactsGlobal(searchQuery, options.artifacts ?? false, options.artifact, artifactLookupScope(options.agent, options.project, options.routine));
2716
1250
  return;
2717
1251
  }
2718
- // A supplied ID is already the lookup key. Resolve it directly from SQLite
2719
- // and, when needed, the peer indexes before any broad transcript discovery.
2720
1252
  if (!toolEvidenceMode && searchQuery && looksLikeSessionId(searchQuery)) {
2721
1253
  await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, routine: options.routine, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
2722
1254
  return;
2723
1255
  }
2724
- // When the user explicitly asks to render (via mode flag), resolve the
2725
- // query globally so sessions outside the default cwd/30d window are found.
2726
1256
  if (wantsRender && searchQuery) {
2727
1257
  await renderOneSession(searchQuery, mode, { agent: options.agent, project: options.project, routine: options.routine, filter: filterOpts, redact: options.redact, local: options.local, hosts: options.host });
2728
1258
  return;
2729
1259
  }
2730
- // Interactive picker loads a deep pool but shows only recent sessions
2731
- // until the user starts typing. Non-interactive/JSON uses the explicit limit.
2732
1260
  const isInteractive = !options.json && isInteractiveTerminal();
2733
- // The grouped project overview is the default for a bare interactive listing:
2734
- // no query, no path drill-in, not explicitly --flat/--tree. It drops the silent
2735
- // cwd-scope + 50-cap + 30-day window that hide most of a large index.
2736
1261
  const wantsOverview = isInteractive && !searchQuery && !pathFilter && !options.flat && !options.tree;
2737
- // --in-team asks for ONE team's whole lineage, which is a handful of rows that
2738
- // can sit anywhere in history. Bounding it by the default top-50 / 30-day window
2739
- // silently returns nothing for any team older than that — so the flag widens its
2740
- // own scope, the way --all does, unless the caller set an explicit --limit.
2741
1262
  const wantsWholeTeam = !!options.inTeam;
2742
- // A routine is not tied to the shell's cwd: its archived sessions retain the
2743
- // working directory in which the scheduled agent actually ran. Scope the
2744
- // routine catalog across directories so invoking it from ~/.agents/.system,
2745
- // a project repo, or $HOME yields the same routine history.
2746
1263
  const wantsWholeRoutine = !!options.routine;
2747
- // `--limit` has a commander default, so an untouched flag still arrives as a
2748
- // string and truthiness can't tell it from a typed one. Commander records where
2749
- // each value came from, which is the only signal that distinguishes an explicit
2750
- // `--limit 50` from no flag at all — a script asking for 50 must get 50, not the
2751
- // whole-team pool.
2752
1264
  const userSetLimit = limitSource === 'cli' || limitSource === 'env';
2753
1265
  const limit = wantsOverview
2754
1266
  ? OVERVIEW_POOL_LIMIT
@@ -2758,8 +1270,6 @@ limitSource) {
2758
1270
  process.exitCode = 1;
2759
1271
  return;
2760
1272
  }
2761
- // Overview: recency order across the whole index, no default window; an explicit
2762
- // --since still narrows. Non-overview keeps the prior interactive-30d default.
2763
1273
  const since = wantsOverview
2764
1274
  ? options.since
2765
1275
  : (options.since ?? (isInteractive && !options.all && !wantsWholeTeam && !wantsWholeRoutine ? '30d' : undefined));
@@ -2774,23 +1284,12 @@ limitSource) {
2774
1284
  const spinner = options.json ? null : ora().start();
2775
1285
  const tracker = createScanProgressTracker(LOAD_VERBS, 'sessions', spinner);
2776
1286
  try {
2777
- // Team-origin filter is pushed down to SQL so the LIMIT applies AFTER it.
2778
- // Without this, a dev dir with heavy SDK spawn activity (Task subagents,
2779
- // `agents run`, team agents) can fill the top-N window entirely with
2780
- // hidden rows and make real CLI sessions appear to vanish.
2781
- // 'recent' is the user-facing alias for the default timestamp sort.
2782
1287
  const sortBy = options.sort === 'cost' ? 'cost' : options.sort === 'duration' ? 'duration' : 'timestamp';
2783
1288
  const scope = {
2784
1289
  agent,
2785
1290
  version,
2786
- // --in-team spans directories by construction: a team's teammates run in
2787
- // their own worktrees, so scoping to the current cwd hides most of the
2788
- // lineage the flag exists to show.
2789
1291
  all: pathFilter ? undefined : options.all || wantsWholeTeam || wantsWholeRoutine || toolSpansDevices,
2790
1292
  cwd: process.cwd(),
2791
- // Default overview scopes to the current repo SUBTREE (prefix match), so a
2792
- // monorepo shows its sub-projects grouped instead of collapsing to the one
2793
- // exact-cwd project. `--all` clears the prefix and spans the whole index.
2794
1293
  cwdPrefix: pathFilter ?? (wantsOverview && !options.all && !wantsWholeTeam && !wantsWholeRoutine && !toolSpansDevices ? process.cwd() : undefined),
2795
1294
  project: options.project,
2796
1295
  since,
@@ -2835,22 +1334,10 @@ limitSource) {
2835
1334
  }
2836
1335
  tracker.stop();
2837
1336
  spinner?.stop();
2838
- // Version filter is pushed down to SQL via scope.version above; no
2839
- // post-filter needed. Defensive: the team-origin SQL filter covers the
2840
- // ~100% case, but classifyTeamSession also recognizes sessions with a
2841
- // meta.json in ~/.agents/teams/agents whose is_team_origin flag was
2842
- // never set (legacy rows). Keep the in-memory pass so those are still
2843
- // enriched/hidden.
2844
1337
  const { visible: visibleSessions } = filterTeamSessions(sessions, shouldShowTeamSessions(options));
2845
1338
  sessions = visibleSessions;
2846
- // --in-team spans both ends of the lineage, so it can't be one SQL predicate:
2847
- // the orchestrator matches on the scan-derived `spawnedTeam` column, while a
2848
- // teammate only knows its team from the meta.json filterTeamSessions just
2849
- // read. Match either, after that pass has populated `teamOrigin`.
2850
1339
  if (options.inTeam)
2851
1340
  sessions = sessions.filter((s) => matchesTeam(s, options.inTeam));
2852
- // --bookmarks narrows to the bookmarked set. Applied here, before the JSON
2853
- // emit, so `--bookmarks --json` is the machine-readable twin of the `b` key.
2854
1341
  if (options.bookmarks) {
2855
1342
  const bookmarks = listBookmarks();
2856
1343
  sessions = sessions.filter((s) => bookmarks.has(s.id));
@@ -2911,28 +1398,15 @@ limitSource) {
2911
1398
  }
2912
1399
  return;
2913
1400
  }
2914
- // Under --in-team the visible list is one team, so the whole-index team-origin
2915
- // count would be a non-sequitur next to it.
2916
1401
  const hiddenCount = shouldShowTeamSessions(options) || options.inTeam
2917
1402
  ? 0
2918
1403
  : countSessionsInScope({ ...scope, onlyTeamOrigin: true });
2919
- // A typed routine scope must apply before smart ID routing. Otherwise an ID
2920
- // from another routine resolves and renders before the routine filter below.
2921
1404
  if (typeof options.routine === 'string') {
2922
1405
  const filteredByRoutine = await filterSessionsByRoutine(sessions, options.routine, false);
2923
1406
  if (!filteredByRoutine)
2924
1407
  return;
2925
1408
  sessions = filteredByRoutine;
2926
1409
  }
2927
- // Smart ID routing: a bare query that resolves to one session renders
2928
- // directly. If nothing matches in the scoped window and the query looks
2929
- // like a session ID, widen to global scope (incl. Claude /resume history).
2930
- //
2931
- // Exception: a PEER answering a parent's `--json` locate sweep
2932
- // (AGENTS_SESSIONS_LOCAL=1) must return the SessionMeta[] ROW for the id, not
2933
- // render its transcript — the sweep parses an array (parseRemoteList). So when
2934
- // we are that peer and --json is set, skip the single-session short-circuit and
2935
- // fall through to the array-emitting --json block below.
2936
1410
  const answeringJsonSweep = options.json === true && process.env[NO_FANOUT_ENV] === '1';
2937
1411
  if (searchQuery && !answeringJsonSweep) {
2938
1412
  const idMatches = resolveSessionById(sessions, searchQuery);
@@ -2952,25 +1426,11 @@ limitSource) {
2952
1426
  return;
2953
1427
  sessions = filteredByRoutine;
2954
1428
  }
2955
- // An id-shaped query resolves by id ONLY — the same rule the render path
2956
- // uses (resolveSessionQuery). Without this a `--json <uuid>` (as issued by
2957
- // the fleet locate sweep, which runs `sessions <uuid> --json --local` on
2958
- // each peer) would fall to FTS content search and return every transcript
2959
- // that merely MENTIONS the id, defeating exact remote resolution. A genuine
2960
- // search phrase keeps the ranked metadata+content path.
2961
1429
  let filtered = searchQuery
2962
1430
  ? resolveSessionQuery(sessions, searchQuery, {
2963
1431
  scope: { agent: options.agent, project: options.project, routine: options.routine },
2964
1432
  }).matches
2965
1433
  : sessions;
2966
- // `--device all`/`--fleet` on a historical `--json` query fans out to every
2967
- // peer and merges their rows in — the SAME SSH sweep the interactive listing
2968
- // below uses, and the whole-fleet twin of the explicit `--device <host>
2969
- // --json` path (`runRemoteSessionsJson`). Without this the documented
2970
- // "search the whole fleet" flag returned local rows only (PHNX-2673). Guarded
2971
- // to the sentinel so a bare `--json` stays a deterministic local slice for
2972
- // scripts, and skipped under `--local`/the peer recursion guard. Dead peers
2973
- // are skipped by the fan-out, never fatal — enrichment, not a dependency.
2974
1434
  const forceLocalJson = options.local === true || process.env[NO_FANOUT_ENV] === '1';
2975
1435
  if (fleetWide && !forceLocalJson) {
2976
1436
  const forwarded = ensureWholeIndex(buildForwardedArgs(process.argv, new Set(options.host ?? [])));
@@ -2984,15 +1444,11 @@ limitSource) {
2984
1444
  }
2985
1445
  }
2986
1446
  catch {
2987
- // fan-out is an enrichment, never a hard dependency
2988
1447
  }
2989
1448
  finally {
2990
1449
  fanSpinner?.stop();
2991
1450
  }
2992
1451
  }
2993
- // JSON is the canonical picker contract: enrich the durable rows once
2994
- // from the shared live cache so every consumer gets lifecycle/recovery
2995
- // metadata without performing its own join or transcript scan.
2996
1452
  const live = await gatherActiveSessions({
2997
1453
  local: forceLocalJson,
2998
1454
  hosts: options.host,
@@ -3000,19 +1456,8 @@ limitSource) {
3000
1456
  process.stdout.write(serializeSessionsJson(serializeSessionPickerRows(filtered, live.sessions)));
3001
1457
  return;
3002
1458
  }
3003
- // Cross-machine fan-out: unless --local (or we ARE a peer answering a
3004
- // parent's sweep), fold in other online machines' sessions live over SSH so
3005
- // the list spans the fleet without any sync — each remote row carries the
3006
- // machine it came from, and the picker/table label + group by it. Only the
3007
- // interactive picker and the printed table get this; --json and single-id
3008
- // resolution above stay local (a peer answers for itself; scripts get a
3009
- // deterministic local slice). Best-effort: a fan-out failure leaves the
3010
- // local list intact rather than erroring the whole command.
3011
1459
  const forceLocal = options.local === true || process.env[NO_FANOUT_ENV] === '1';
3012
1460
  if (!forceLocal) {
3013
- // Pass the hosts set so a variadic `--host a b` never leaks a host as a
3014
- // query (defensive: the --host-without-active early return above already
3015
- // means we only get here in auto-discovery mode, with no --host in argv).
3016
1461
  const forwarded = buildForwardedArgs(process.argv, new Set(options.host ?? []));
3017
1462
  if (!forwarded.includes('--json'))
3018
1463
  forwarded.push('--json');
@@ -3024,7 +1469,6 @@ limitSource) {
3024
1469
  }
3025
1470
  }
3026
1471
  catch {
3027
- // fan-out is an enrichment, never a hard dependency
3028
1472
  }
3029
1473
  finally {
3030
1474
  fanSpinner?.stop();
@@ -3033,32 +1477,21 @@ limitSource) {
3033
1477
  if (options.routine) {
3034
1478
  const target = await selectRoutineTarget(sessions, options.routine, isInteractive);
3035
1479
  if (!target)
3036
- return; // bad --routine <name> or cancelled pick (message printed)
1480
+ return;
3037
1481
  if (target.name) {
3038
1482
  sessions = sessions.filter((s) => s.routineName === target.name);
3039
- // A routine browse renders the run-first drilldown — canonical run history
3040
- // plus each run's linked session — instead of dead-ending a
3041
- // command/pre-session routine into the generic empty copy. An explicit
3042
- // --flat/--tree or a search query keeps the scoped session listing/picker,
3043
- // so `--routine x --flat` prints the plain table and `--routine x <id>`
3044
- // resolves a specific session within the routine.
3045
1483
  if (!searchQuery && !options.flat && !options.tree) {
3046
1484
  const liveIndex = await maybeLiveIndex(options);
3047
1485
  printRoutineDrilldown(buildRoutineDrilldown(target.name, sessions), liveIndex, { hiddenCount, hiddenUnmanaged });
3048
1486
  return;
3049
1487
  }
3050
1488
  }
3051
- // target.name === null: bare `--routine` piped to a non-interactive sink —
3052
- // no routine chosen, fall through to the normal listing unfiltered.
3053
1489
  }
3054
1490
  if (sessions.length === 0) {
3055
1491
  if (pathFilter) {
3056
1492
  console.log(chalk.gray(`No sessions found for ${pathFilter}.`));
3057
1493
  }
3058
1494
  else if (options.routine) {
3059
- // A routine scoped to a flat/tree/query listing with no matching session
3060
- // (e.g. a command routine has none) — say so in routine terms, not the
3061
- // generic cwd/--all copy, and point at the run-history drilldown.
3062
1495
  console.log(chalk.gray('No indexed agent sessions for this routine. Drop --flat/--tree to see its run history.'));
3063
1496
  }
3064
1497
  else {
@@ -3072,27 +1505,16 @@ limitSource) {
3072
1505
  }
3073
1506
  return;
3074
1507
  }
3075
- // `--teams` prints the team-grouped report: teammates under their team (with
3076
- // spawner + spawn time), bare SDK spawns in their own bucket. --flat/--tree
3077
- // keep their inline table rendering; a search query keeps the picker. Placed
3078
- // before the project overview and picker so it wins the bare `--teams` case.
3079
1508
  if (options.teams && !options.flat && !options.tree && !searchQuery && !pathFilter) {
3080
1509
  const liveIndex = await maybeLiveIndex(options);
3081
1510
  printTeamsView(sessions, liveIndex, hiddenUnmanaged);
3082
1511
  return;
3083
1512
  }
3084
- // The grouped project overview is the bare interactive default: a scannable
3085
- // dashboard of the whole fleet grouped by project, newest-active first.
3086
- // Interact/resume via `agents sessions <project>` or `agents sessions resume`.
3087
1513
  if (wantsOverview) {
3088
1514
  const liveIndex = await maybeLiveIndex(options);
3089
- // Per-project row cap is fixed (--limit carries a default of 50 and drives
3090
- // the fetch pool, not the display); `--all` expands every group instead.
3091
1515
  printSessionOverview(sessions, hiddenCount, liveIndex, { perProjectCap: OVERVIEW_ROWS_PER_PROJECT, expand: !!options.all, hiddenUnmanaged });
3092
1516
  return;
3093
1517
  }
3094
- // --tree / --flat are printed listings, not an interactive pick — render them
3095
- // directly even in a TTY. A search query keeps the interactive picker.
3096
1518
  if (isInteractiveTerminal() && !options.tree && !options.flat) {
3097
1519
  const message = pathFilter
3098
1520
  ? `Search sessions (${path.basename(pathFilter)}):`
@@ -3104,7 +1526,6 @@ limitSource) {
3104
1526
  }
3105
1527
  return;
3106
1528
  }
3107
- // Non-interactive fallback (piped output) or --flat/--tree.
3108
1529
  const filtered = searchQuery
3109
1530
  ? filterSessionsByQuery(sessions, searchQuery, {
3110
1531
  agent: options.agent,
@@ -3114,8 +1535,6 @@ limitSource) {
3114
1535
  : sessions;
3115
1536
  const liveIndex = await maybeLiveIndex(options);
3116
1537
  printSessionTable(filtered, hiddenCount, options.tree === true, liveIndex);
3117
- // Every listing path must say what it dropped — a hidden default that stays
3118
- // silent in one render mode is the failure this footer exists to prevent.
3119
1538
  if (hiddenUnmanaged > 0)
3120
1539
  console.log(chalk.gray(formatUnmanagedHiddenFooter(hiddenUnmanaged)));
3121
1540
  }
@@ -3126,13 +1545,6 @@ limitSource) {
3126
1545
  process.exit(1);
3127
1546
  }
3128
1547
  }
3129
- /**
3130
- * Prefix marking a row as somebody's teammate: `[<team>/<handle>] `, falling back
3131
- * to the handle alone when the record predates team-name capture. The mode is
3132
- * deliberately dropped here (it survives in the preview pane) — this tag is
3133
- * folded into the topic cell, whose floor is 16 columns, so every character it
3134
- * takes is one the actual prompt loses.
3135
- */
3136
1548
  function teamTag(session) {
3137
1549
  const origin = session.teamOrigin;
3138
1550
  if (!origin)
@@ -3143,32 +1555,14 @@ function teamTag(session) {
3143
1555
  return `[${team}${handle ? `/${handle}` : ''}] `;
3144
1556
  return handle ? `[${handle}] ` : '[team] ';
3145
1557
  }
3146
- /**
3147
- * Whether a session belongs to `team`, from either end: it spawned the team, or
3148
- * it is one of the team's teammates. Case-insensitive, matching the SQL
3149
- * predicate behind `querySessions({ spawnedTeam })`.
3150
- */
3151
1558
  export function matchesTeam(session, team) {
3152
- // The needle is peer-derived in the browser: `f.team` comes off the team cycle,
3153
- // which is built from rows another machine sent. Guard it the same way as the
3154
- // fields it is compared against, so a non-string can't throw out of a filter
3155
- // that runs over every row.
3156
1559
  const want = safeTeamText(team)?.trim().toLowerCase();
3157
1560
  if (!want)
3158
1561
  return true;
3159
1562
  return (safeTeamText(session.spawnedTeam)?.toLowerCase() === want ||
3160
1563
  safeTeamText(session.teamOrigin?.team)?.toLowerCase() === want);
3161
1564
  }
3162
- /** Longest team name rendered in the `team:` row badge before truncation. */
3163
1565
  const TEAM_BADGE_MAX = 10;
3164
- /**
3165
- * The `team:<name>` badge for a session that SPAWNED a team — the orchestrator
3166
- * end of the lineage, from the scan-derived `spawnedTeam`. Returned as a plain
3167
- * (uncolored) string plus its display width so callers can reserve the width
3168
- * from the topic budget and color it as their own segment: folding it into the
3169
- * topic string would lose the color, since renderTopicCell strips ANSI and
3170
- * re-whitens every slice.
3171
- */
3172
1566
  export function teamBadge(session) {
3173
1567
  const team = safeTeamText(session.spawnedTeam);
3174
1568
  if (!team)
@@ -3181,7 +1575,6 @@ function originTag(session) {
3181
1575
  return '';
3182
1576
  return `[routine${session.routineName ? ` · ${session.routineName}` : ''}] `;
3183
1577
  }
3184
- /** Adapt a SessionMeta's persisted signals to the badge renderer's shape. */
3185
1578
  function metaSignals(s) {
3186
1579
  return {
3187
1580
  pr: s.prUrl ? { url: s.prUrl, number: s.prNumber } : undefined,
@@ -3189,23 +1582,7 @@ function metaSignals(s) {
3189
1582
  ticket: s.ticketId ? { id: s.ticketId } : undefined,
3190
1583
  };
3191
1584
  }
3192
- /**
3193
- * Narrowest topic a row is willing to render. The time cell drops its creation
3194
- * field rather than squeeze the topic past this — a row that wraps is worse than
3195
- * a row missing one field.
3196
- */
3197
1585
  const MIN_TOPIC_W = 16;
3198
- /**
3199
- * The trailing time cell, as two fields: `3d → 2 hours ago` — when the session
3200
- * was created, then when it was last active. One label answers neither "is this
3201
- * the old session I'm looking for" nor "how long did it run", since a session
3202
- * touched an hour ago may have started last week.
3203
- *
3204
- * Collapses to last-activity alone when the session ran under a minute (both
3205
- * halves would name the same moment) or when `topicSlack` — the columns the row
3206
- * has left for its topic — cannot spare the extra width. `extraW` is what the
3207
- * creation field cost, for the caller to take off the topic budget.
3208
- */
3209
1586
  function timeCell(age, topicSlack) {
3210
1587
  const lastOnly = { plain: age.last, text: chalk.gray(age.last), extraW: 0 };
3211
1588
  if (!age.created)
@@ -3216,12 +1593,6 @@ function timeCell(age, topicSlack) {
3216
1593
  return lastOnly;
3217
1594
  return { plain: prefix + age.last, text: chalk.dim(prefix) + chalk.gray(age.last), extraW };
3218
1595
  }
3219
- /** One flat table row:
3220
- * shortId · agent · version · model · project · [glyph] label·doing · [ticket] · [wt] · time
3221
- * `doing` is the live preview when running, else the topic. The `ticket` column
3222
- * (tracker/PR ref, pulled out of the badge blob so refs align) is only rendered
3223
- * when `showTicket` — otherwise a listing with no refs would waste a column of
3224
- * dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
3225
1596
  export function flatSessionRow(session, live, showTicket = false, cols = {}, bookmarked = false) {
3226
1597
  const shown = sessionDisplayAgent(session);
3227
1598
  const agentColor = colorAgent(shown);
@@ -3230,18 +1601,12 @@ export function flatSessionRow(session, live, showTicket = false, cols = {}, boo
3230
1601
  const tag = originTag(session) || teamTag(session);
3231
1602
  const label = session.label;
3232
1603
  const { glyph, preview } = liveGlyphAndPreview(live);
3233
- // A running session's live preview (via liveGlyphAndPreview → buildSessionDescription)
3234
- // already folds in ActiveSession.todos. For resting / not-live rows, surface
3235
- // SessionMeta.todos when the scan attached it.
3236
1604
  const restingTodo = !live ? formatTodoCompact(session.todos) : '';
3237
1605
  const topicBase = tag ? `${tag}${session.topic ?? ''}` : session.topic;
3238
1606
  const doing = [restingTodo, preview || topicBase].filter(Boolean).join(' · ') || undefined;
3239
1607
  const wt = session.worktreeSlug ? chalk.magenta(`wt:${session.worktreeSlug}`) : '';
3240
1608
  const team = teamBadge(session);
3241
1609
  const teamSeg = team.plain ? chalk.green(team.plain) : '';
3242
- // The machine column only earns its width when the listing spans more than one
3243
- // box (i.e. the cross-machine fan-out folded remotes in) — same rule and
3244
- // pool-derived width as the picker.
3245
1610
  const machineColW = cols.machineWidth ?? PICKER_MACHINE_W;
3246
1611
  const machineCell = cols.showMachine
3247
1612
  ? chalk.gray(padToWidth(truncateToWidth((cols.machineLabel?.(session.machine ?? '') ?? session.machine ?? '') || '-', machineColW - 1), machineColW))
@@ -3250,8 +1615,6 @@ export function flatSessionRow(session, live, showTicket = false, cols = {}, boo
3250
1615
  const ticketCell = showTicket
3251
1616
  ? chalk.blue(linkTicketCell(session, padToWidth(truncateToWidth(ticketLabel(session) || '-', TICKET_W), TICKET_W + 1)))
3252
1617
  : '';
3253
- // Live status word (working / waiting / idle) next to the glyph — the default
3254
- // list is no longer a bare glyph. Empty (zero width) for resting rows.
3255
1618
  const { cell: statusCell, width: statusW } = liveStatusCell(live);
3256
1619
  const glyphW = glyph ? 2 : 0;
3257
1620
  const machineW = cols.showMachine ? machineColW : 0;
@@ -3259,11 +1622,8 @@ export function flatSessionRow(session, live, showTicket = false, cols = {}, boo
3259
1622
  const wtW = wt ? stringWidth(wt) + 1 : 0;
3260
1623
  const width = terminalWidth();
3261
1624
  const requestedModelW = cols.showModel ? (cols.modelWidth ?? PICKER_MODEL_MAX) : 0;
3262
- // Same conditional 2 cells as the picker's marker, for the same reason.
3263
1625
  const bookmarkW = cols.showBookmark ? 2 : 0;
3264
1626
  const bookmarkCell = cols.showBookmark ? (bookmarked ? chalk.yellow('★ ') : ' ') : '';
3265
- // Sized against the last-activity label alone, so the creation field is an
3266
- // additive decision the row makes only once it knows what space is left.
3267
1627
  const fixedW = bookmarkW + (10 + 9 + 8 + 16) + glyphW + statusW + machineW + ticketW + wtW + team.width + stringWidth(age.last) + 1;
3268
1628
  const modelSlack = width - fixedW - MIN_TOPIC_W;
3269
1629
  const modelW = requestedModelW <= modelSlack
@@ -3286,7 +1646,6 @@ export function flatSessionRow(session, live, showTicket = false, cols = {}, boo
3286
1646
  (wt ? wt + ' ' : '') +
3287
1647
  when.text);
3288
1648
  }
3289
- /** One tree-mode row (grouped under a dir header): id · agent · badges · topic · time. No version/project column. */
3290
1649
  function treeSessionRow(session, live) {
3291
1650
  const shown = sessionDisplayAgent(session);
3292
1651
  const agentColor = colorAgent(shown);
@@ -3294,8 +1653,6 @@ function treeSessionRow(session, live) {
3294
1653
  const tag = originTag(session) || teamTag(session);
3295
1654
  const label = session.label;
3296
1655
  const { glyph, preview } = liveGlyphAndPreview(live);
3297
- // Match flatSessionRow: live preview already folds ActiveSession.todos; resting
3298
- // rows surface SessionMeta.todos when present (RUSH-2045).
3299
1656
  const restingTodo = !live ? formatTodoCompact(session.todos) : '';
3300
1657
  const topicBase = preview || (tag ? `${tag}${session.topic ?? ''}` : session.topic);
3301
1658
  const topic = [restingTodo, topicBase].filter(Boolean).join(' · ') || '-';
@@ -3319,38 +1676,16 @@ function treeSessionRow(session, live) {
3319
1676
  padToWidth(chalk.white(truncateToWidth(head, topicW)), topicW) +
3320
1677
  ' ' + when.text);
3321
1678
  }
3322
- /**
3323
- * Live-session index for enriching the default listing, or undefined when
3324
- * enrichment is off (`--no-live`) or irrelevant (`--json`, which serializes
3325
- * SessionMeta). Full detection (incl. the headless `ps` scan) is deliberate:
3326
- * bare-CLI and tmux agents are the common case here, and skipping them would
3327
- * leave the glyph almost never showing. The listing is a one-shot user action,
3328
- * not a hot loop, so the `ps`/`lsof` cost is acceptable; `--no-live` is the
3329
- * escape hatch. Never throws — a probe failure just yields a plain listing.
3330
- */
3331
1679
  export async function maybeLiveIndex(options) {
3332
1680
  if (options.live === false || options.json)
3333
1681
  return undefined;
3334
1682
  try {
3335
- // `--local` promises "this machine only" for the default listing too (see
3336
- // the `--local` help text), so it must gate the live-enrichment probe the
3337
- // same way `--active --local` does — never dial a remote-host teammate
3338
- // over ssh here either (RUSH-2118).
3339
1683
  return indexActiveBySessionId(await getActiveSessions({ localOnly: options.local === true }));
3340
1684
  }
3341
1685
  catch {
3342
1686
  return undefined;
3343
1687
  }
3344
1688
  }
3345
- /**
3346
- * Group key for the overview: resolve the cwd through the same canonical
3347
- * resolver the `agents feed` timeline groups by — a defined project's name
3348
- * when `defs` contains the cwd (multi-repo projects read as one group), else
3349
- * the repo-level key, so a monorepo subdir (`<repo>/apps/cli`) reads as
3350
- * `<repo>` in both views. Falls back to the indexed project name (stamped at
3351
- * scan time) when the cwd carries nothing usable, e.g. a remote path this
3352
- * machine cannot see still folds by basename through the same resolver.
3353
- */
3354
1689
  export function overviewProjectKey(s, defs) {
3355
1690
  const resolved = defs?.length ? resolveProjectNameForCwd(s.cwd, defs) : resolveProjectKey(s.cwd);
3356
1691
  if (resolved)
@@ -3359,12 +1694,6 @@ export function overviewProjectKey(s, defs) {
3359
1694
  return s.project.trim();
3360
1695
  return '(no project)';
3361
1696
  }
3362
- /**
3363
- * Turn a recency-descending pool into project groups: each group shows its
3364
- * `perProjectCap` most-recent sessions (the rest become `· N more`), and groups
3365
- * are ordered by their most-recent session so the newest-active project leads.
3366
- * `perProjectCap = Infinity` expands every group. Pure — unit-tested.
3367
- */
3368
1697
  export function buildOverviewGroups(pool, perProjectCap, defs) {
3369
1698
  const byKey = new Map();
3370
1699
  for (const s of pool) {
@@ -3374,17 +1703,12 @@ export function buildOverviewGroups(pool, perProjectCap, defs) {
3374
1703
  const cap = Math.max(1, perProjectCap);
3375
1704
  const groups = [];
3376
1705
  for (const [key, rows] of byKey) {
3377
- const shown = rows.slice(0, cap); // rows are recency-desc (pool was sorted)
1706
+ const shown = rows.slice(0, cap);
3378
1707
  groups.push({ key, total: rows.length, shown, more: rows.length - shown.length, maxTs: rows[0].lastActivity ?? rows[0].timestamp });
3379
1708
  }
3380
1709
  groups.sort((a, b) => (a.maxTs < b.maxTs ? 1 : a.maxTs > b.maxTs ? -1 : a.key.localeCompare(b.key)));
3381
1710
  return { groups, projectCount: byKey.size };
3382
1711
  }
3383
- /**
3384
- * The grouped project overview — the bare interactive default. Shows the latest
3385
- * sessions grouped under their project, newest-active project first, with a
3386
- * `· N more` per project and a `+N more projects` when the list is capped.
3387
- */
3388
1712
  function printSessionOverview(pool, hiddenCount, liveIndex, opts) {
3389
1713
  const { groups } = buildOverviewGroups(pool, opts.expand ? Infinity : opts.perProjectCap, listProjectDefs());
3390
1714
  const shownGroups = opts.expand ? groups : groups.slice(0, OVERVIEW_MAX_PROJECTS);
@@ -3419,8 +1743,6 @@ function printSessionOverview(pool, hiddenCount, liveIndex, opts) {
3419
1743
  }
3420
1744
  function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
3421
1745
  if (tree) {
3422
- // Group by directory; drop the id/version columns from view. The short id
3423
- // stays as each row's leading handle (the address to read/resume it).
3424
1746
  const byDir = new Map();
3425
1747
  for (const s of sessions) {
3426
1748
  const key = s.cwd || s.project || 'unknown';
@@ -3448,9 +1770,6 @@ function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
3448
1770
  console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
3449
1771
  return;
3450
1772
  }
3451
- // Only show the ticket column when at least one row carries a ref — otherwise
3452
- // it's a column of dashes that steals width from every topic. The machine
3453
- // column (and its compact labels) is computed the same way the picker does it.
3454
1773
  const showTicket = sessions.some((s) => ticketLabel(s) !== '');
3455
1774
  const cols = pickerColumnsFor(sessions);
3456
1775
  const bookmarks = listBookmarks();
@@ -3463,17 +1782,8 @@ function printSessionTable(sessions, hiddenCount = 0, tree = false, liveIndex) {
3463
1782
  console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
3464
1783
  }
3465
1784
  }
3466
- /** Width of the mode cell (plan/edit/auto/skip) in a team-member row. */
3467
1785
  const TEAM_MODE_W = 5;
3468
- /** Longest teammate handle folded into a team-member row before it truncates. */
3469
1786
  const TEAM_HANDLE_W = 16;
3470
- /**
3471
- * One row under a team group: `shortId · agent · mode · handle · doing · time`.
3472
- * Unlike {@link flatSessionRow}/{@link treeSessionRow} it drops the
3473
- * `[team/handle]` topic prefix — the group header already names the team — and
3474
- * promotes the teammate's mode and handle to their own cells, the richer
3475
- * identity the `--teams` view exists to show (RUSH-1997).
3476
- */
3477
1787
  function teamMemberRow(session, live) {
3478
1788
  const shown = sessionDisplayAgent(session);
3479
1789
  const agentColor = colorAgent(shown);
@@ -3482,8 +1792,6 @@ function teamMemberRow(session, live) {
3482
1792
  const handle = safeTeamText(origin?.handle) ?? session.shortId;
3483
1793
  const mode = safeTeamText(origin?.mode) ?? '';
3484
1794
  const { glyph, preview } = liveGlyphAndPreview(live);
3485
- // Match flatSessionRow: a live preview already folds ActiveSession.todos; a
3486
- // resting row surfaces SessionMeta.todos when the scan attached it.
3487
1795
  const restingTodo = !live ? formatTodoCompact(session.todos) : '';
3488
1796
  const doing = [restingTodo, preview || session.topic || ''].filter(Boolean).join(' · ');
3489
1797
  const shownHandle = truncateToWidth(handle, TEAM_HANDLE_W);
@@ -3506,14 +1814,9 @@ function teamMemberRow(session, live) {
3506
1814
  ' ' +
3507
1815
  when.text);
3508
1816
  }
3509
- /** The header line for one team group (or the residual no-team bucket). */
3510
1817
  function teamGroupHeader(g, glyph, labelById) {
3511
1818
  const count = chalk.gray(`(${g.sessions.length})`);
3512
1819
  if (g.kind === 'noTeam') {
3513
- // These carry the isTeamOrigin flag but no teammate meta.json — a Task
3514
- // sub-agent's transcript is never indexed, so in practice this bucket is
3515
- // headless `agents run` spawns or teammates whose meta aged out. Named
3516
- // honestly rather than claimed as real teammates.
3517
1820
  return (chalk.magenta('▸') + ' ' + chalk.magenta.bold(NO_TEAM_GROUP_KEY) + ' ' + count +
3518
1821
  ' ' + chalk.gray('team-flagged spawns with no team record (`agents run`, or aged-out teammates)'));
3519
1822
  }
@@ -3527,14 +1830,6 @@ function teamGroupHeader(g, glyph, labelById) {
3527
1830
  bits.push(chalk.gray(`spawned ${formatRelativeTime(g.firstSpawnTs)}`));
3528
1831
  return bits.join(' ');
3529
1832
  }
3530
- /**
3531
- * The `agents sessions --teams` view: team-origin sessions grouped by team, the
3532
- * richer display RUSH-1997 asks for. Each named team names its spawner (the
3533
- * orchestrator session that created it) and spawn time, with its teammates'
3534
- * mode + handle under it; team-flagged spawns that carry no teammate record fall
3535
- * into a trailing no-team bucket, so a real teammate and a bare spawn are never
3536
- * conflated. A printed report like `--tree`, not an interactive pick.
3537
- */
3538
1833
  function printTeamsView(pool, liveIndex, hiddenUnmanaged = 0) {
3539
1834
  const groups = groupSessionsByTeam(pool);
3540
1835
  if (groups.length === 0) {
@@ -3542,9 +1837,6 @@ function printTeamsView(pool, liveIndex, hiddenUnmanaged = 0) {
3542
1837
  console.log(chalk.gray('Team sessions are spawned by `agents teams` — see `agents teams status`.'));
3543
1838
  return;
3544
1839
  }
3545
- // Resolve a spawner session id to its human label from the pool. The
3546
- // orchestrator may or may not be in view; fall back to the short id, the same
3547
- // degradation the --active teams rows use (active.ts resolveOrchestratorLabels).
3548
1840
  const labelById = new Map();
3549
1841
  for (const s of pool) {
3550
1842
  const label = sessionHeadline(s);
@@ -3604,11 +1896,6 @@ function buildFilterOptions(options) {
3604
1896
  function hasAnyFilter(opts) {
3605
1897
  return !!(opts.include?.length || opts.exclude?.length || opts.first !== undefined || opts.last !== undefined);
3606
1898
  }
3607
- /**
3608
- * Default is summary. Any explicit format flag wins. When filters are present
3609
- * without a format, default to markdown since summary is an aggregate view
3610
- * that filters don't meaningfully narrow.
3611
- */
3612
1899
  function resolveViewMode(options, filters) {
3613
1900
  if (options.markdown)
3614
1901
  return 'markdown';
@@ -3618,35 +1905,13 @@ function resolveViewMode(options, filters) {
3618
1905
  return 'markdown';
3619
1906
  return 'summary';
3620
1907
  }
3621
- /**
3622
- * Render a resolved session to stdout — the non-follow view behind
3623
- * `agents logs <sessionId>`. Defaults to the concise `summary` digest (same as
3624
- * `agents sessions <id>`); pass `'markdown'` for the full transcript
3625
- * (`agents logs <id> --full`). Reuses the shared `renderSession` renderer.
3626
- */
3627
1908
  export async function renderSessionLog(session, mode = 'summary') {
3628
1909
  await renderSession(session, mode, {});
3629
1910
  }
3630
- /**
3631
- * Emit one session exactly as `agents sessions <id> --json` does — the same
3632
- * redact-by-default `{ session, events }` shape — so `agents logs <id> --json`
3633
- * shares one machine-readable session contract instead of inventing another.
3634
- */
3635
1911
  export async function renderSessionLogJson(session) {
3636
1912
  await renderSession(session, 'json', {});
3637
1913
  }
3638
- /**
3639
- * Render a file-gone (archived) session from the local DB (RUSH-2436). The
3640
- * transcript file is deleted, but the user turns survive in session_text and the
3641
- * last-computed digest in session_preview_cache — serve those so `agents sessions
3642
- * <id>` still shows the prompts instead of an empty metadata dump. User-turn text
3643
- * is the durable content every harness stores; assistant text survives only as
3644
- * the cached digest's `lastAssistant` snippet, shown when present.
3645
- */
3646
1914
  function renderArchivedSession(session, mode, options = {}) {
3647
- // Redact by default, mirroring the live render path (render.ts renderConversationMarkdown):
3648
- // session_text stores raw user text, so masking is render-time. `--no-redact`
3649
- // opts out, same as the live path.
3650
1915
  const redact = (text) => options.redact !== false ? redactSecrets(text) : text;
3651
1916
  const content = redact((readSessionContent(session.id) ?? '').trim());
3652
1917
  const digestRaw = readArchivedSessionPreview(session.id);
@@ -3654,12 +1919,6 @@ function renderArchivedSession(session, mode, options = {}) {
3654
1919
  ? { ...digestRaw, lastAssistant: redact(digestRaw.lastAssistant ?? '') }
3655
1920
  : undefined;
3656
1921
  if (mode === 'json') {
3657
- // Minimal machine shape for a file-gone session: the durable user content
3658
- // plus the cached preview digest, tagged archived. Deliberately NOT the full
3659
- // { session, events } contract — there are no events to parse. Redact the
3660
- // first-message-excerpt meta fields (topic/label/plan) too, matching the live
3661
- // single-session --json (render.ts redactJson), so `--json` masks them unless
3662
- // --no-redact is passed.
3663
1922
  console.log(JSON.stringify({
3664
1923
  session: {
3665
1924
  ...session,
@@ -3699,13 +1958,8 @@ function renderArchivedSession(session, mode, options = {}) {
3699
1958
  async function renderSession(session, mode, filters, options = {}) {
3700
1959
  const { hydrateSessionTranscript, findLocalSessionTranscripts } = await import('../lib/session/discover.js');
3701
1960
  session = await hydrateSessionTranscript(session);
3702
- // OpenCode stores sessions in SQLite; filePath is "db_path#session_id"
3703
1961
  const realPath = session.filePath.split('#')[0];
3704
1962
  if (!fs.existsSync(realPath)) {
3705
- // The transcript file is gone, but the session's user turns are stored
3706
- // durably in the local DB (session_text), so serve those instead of a bare
3707
- // metadata dump (RUSH-2436). This is what makes an archived session still
3708
- // render its prompts after `agents remove` / rm / a box reimage.
3709
1963
  const archivedContent = readSessionContent(session.id);
3710
1964
  if (archivedContent && archivedContent.trim() !== '') {
3711
1965
  renderArchivedSession(session, mode, options);
@@ -3735,9 +1989,6 @@ async function renderSession(session, mode, filters, options = {}) {
3735
1989
  const modelStr = stats.models.length > 0 ? chalk.yellow(` ${stats.models.join(', ')}`) : '';
3736
1990
  const branchStr = session.gitBranch ? chalk.gray(` (${session.gitBranch})`) : '';
3737
1991
  const absTime = formatAbsoluteTime(session.timestamp);
3738
- // Auto-inferred title headline (user /rename > Claude ai-title >
3739
- // daemon-generated title > first-prompt topic) — the fastest way to
3740
- // recognize which task this session is.
3741
1992
  const title = sessionHeadline(session);
3742
1993
  if (title) {
3743
1994
  const badges = signalBadges(metaSignals(session));
@@ -3766,13 +2017,6 @@ async function renderSession(session, mode, filters, options = {}) {
3766
2017
  process.stdout.write(renderMarkdown(renderConversationMarkdown(events, { redact: options.redact !== false })));
3767
2018
  return;
3768
2019
  }
3769
- // json — normalized events plus the durable session signals from the state
3770
- // engine (plan text, PR, worktree, ticket). Pre-1.20.51 emitted a bare event
3771
- // array; consumers that JSON.parse this now read `output.events` for the
3772
- // array. See issue #743 (plan surfaced) and CHANGELOG for the shape change.
3773
- // `todos` (RUSH-1503) is computed from the UNFILTERED transcript so the
3774
- // checklist reflects true session state regardless of any `--include` filter;
3775
- // it lets the Factory panel read the CLI's checklist instead of re-parsing.
3776
2020
  const todos = inferSessionState(parsedEvents, { cwd: session.cwd }).todos;
3777
2021
  process.stdout.write(renderJson(events, todos ? { ...session, todos } : session, { redact: options.redact !== false }));
3778
2022
  }
@@ -3781,8 +2025,6 @@ function renderTopicCell(label, topic, query, visibleWidth, paddedWidth) {
3781
2025
  const tpc = (topic ?? '').trim();
3782
2026
  const sep = ' · ';
3783
2027
  const raw = lbl && tpc ? `${lbl}${sep}${tpc}` : (lbl || tpc);
3784
- // Width-aware: measure/truncate/pad by display cells, not String.length, so
3785
- // ANSI escapes and wide (CJK/emoji) glyphs don't drift the column.
3786
2028
  const visible = truncateToWidth(raw, visibleWidth);
3787
2029
  const padding = ' '.repeat(Math.max(0, paddedWidth - stringWidth(visible)));
3788
2030
  const labelEnd = lbl ? Math.min(lbl.length, visible.length) : 0;
@@ -3817,17 +2059,11 @@ function renderTopicCell(label, topic, query, visibleWidth, paddedWidth) {
3817
2059
  }
3818
2060
  return out + padding;
3819
2061
  }
3820
- /** Fallback machine-column width when a pool-derived width isn't supplied.
3821
- * `pickerColumnsFor` normally computes `machineWidth` sized to the actual
3822
- * hostnames, floored/capped by these bounds so common ids like `yosemite-s0`
3823
- * (11) fit whole while a pathological hostname can't devour the topic column. */
3824
2062
  const PICKER_MACHINE_W = 11;
3825
2063
  const PICKER_MACHINE_MIN = 8;
3826
2064
  const PICKER_MACHINE_MAX = 18;
3827
2065
  const PICKER_MODEL_MIN = 6;
3828
2066
  const PICKER_MODEL_MAX = 13;
3829
- /** Column width that shows every compacted hostname in `machines` whole (one
3830
- * trailing space for separation), bounded by MIN/MAX. */
3831
2067
  function machineColumnWidth(machines, label) {
3832
2068
  const widest = machines.reduce((w, m) => Math.max(w, stringWidth(label(m))), 0);
3833
2069
  return Math.min(PICKER_MACHINE_MAX, Math.max(PICKER_MACHINE_MIN, widest + 1));
@@ -3836,12 +2072,6 @@ function modelColumnWidth(sessions) {
3836
2072
  const widest = sessions.reduce((width, session) => (Math.max(width, session.model ? stringWidth(modelLabel(session.model)) : 0)), 0);
3837
2073
  return Math.min(PICKER_MODEL_MAX, Math.max(PICKER_MODEL_MIN, widest + 1));
3838
2074
  }
3839
- /**
3840
- * Compact display form for machine ids: strip the longest shared dash-delimited
3841
- * prefix so "yosemite-s0"/"yosemite-s1" read as "s0"/"s1" while unrelated ids
3842
- * ("zion", "mac-mini") stay whole. Stripping a *common* prefix can't collide,
3843
- * and at least one segment is always kept.
3844
- */
3845
2075
  export function machineLabeler(machines) {
3846
2076
  const uniq = [...new Set(machines.filter(Boolean))];
3847
2077
  if (uniq.length < 2)
@@ -3858,11 +2088,6 @@ export function machineLabeler(machines) {
3858
2088
  return p.length > shared ? p.slice(shared).join('-') : m;
3859
2089
  };
3860
2090
  }
3861
- /**
3862
- * Column flags for a picker, computed once over the whole pool so every row
3863
- * aligns: the machine column only earns its width when the listing spans more
3864
- * than one box, the ticket column only when some row carries a PR/ticket ref.
3865
- */
3866
2091
  export function pickerColumnsFor(sessions) {
3867
2092
  const machines = sessions.map((s) => s.machine).filter((m) => !!m);
3868
2093
  const distinct = [...new Set(machines)];
@@ -3875,22 +2100,12 @@ export function pickerColumnsFor(sessions) {
3875
2100
  modelWidth: modelColumnWidth(sessions),
3876
2101
  showTicket: sessions.some((s) => ticketLabel(s) !== ''),
3877
2102
  showBookmark: (() => {
3878
- // One read of the store per pool, not one per row.
3879
2103
  const bookmarks = listBookmarks();
3880
2104
  return bookmarks.size > 0 && sessions.some((s) => bookmarks.has(s.id));
3881
2105
  })(),
3882
2106
  };
3883
2107
  }
3884
- /** Width of the host-program column (`tmux→ghostty` is the long realistic case). */
3885
2108
  const PICKER_HOST_W = 14;
3886
- /**
3887
- * Which program a live session is running in, for the picker's host column: the
3888
- * immediate host app (`codium`, `ghostty`, `tmux`, `iterm`, …) and — when a tmux
3889
- * session is currently being watched through a different app — the app it is
3890
- * viewed in, as `tmux→ghostty`. A tmux session with no attached client stays a
3891
- * bare `tmux`, which is exactly "running detached". Empty when the session has no
3892
- * resolvable host (cloud rows, an unreadable process env).
3893
- */
3894
2109
  export function liveHostLabel(a) {
3895
2110
  if (!a?.host)
3896
2111
  return '';
@@ -3902,16 +2117,9 @@ export function formatPickerLabel(s, query, cols = {}, ssh, host = '', bookmarke
3902
2117
  const agentColor = colorAgent(shown);
3903
2118
  const age = sessionAgeParts(s.timestamp, s.lastActivity);
3904
2119
  const project = s.project || '-';
3905
- // SSH-launch origin (live rows only): mirrors the flat listing's `ssh←<device>`
3906
- // badge. Rendered as its OWN red segment before the topic cell — folding it into
3907
- // the topic string loses the colour, because renderTopicCell strips ANSI and
3908
- // re-whitens every slice. Its width is reserved from the topic budget below
3909
- // (exactly like `wt`), so the fixed-width columns stay aligned.
3910
2120
  const sshPlain = ssh ? (ssh.device ? `ssh←${ssh.device} ` : 'ssh ') : '';
3911
2121
  const sshSeg = sshPlain ? chalk.red(sshPlain) : '';
3912
2122
  const sshW = sshPlain ? stringWidth(sshPlain) : 0;
3913
- // Orchestrator badge — same own-segment treatment as `ssh` above, for the same
3914
- // reason: renderTopicCell would strip its colour if it rode inside the topic.
3915
2123
  const team = teamBadge(s);
3916
2124
  const teamSeg = team.plain ? chalk.green(team.plain) : '';
3917
2125
  const tag = originTag(s) || teamTag(s);
@@ -3927,42 +2135,23 @@ export function formatPickerLabel(s, query, cols = {}, ssh, host = '', bookmarke
3927
2135
  const ticketCell = cols.showTicket
3928
2136
  ? chalk.blue(padRight(truncate(ticketLabel(s) || '-', TICKET_W), TICKET_W + 1))
3929
2137
  : '';
3930
- // Which terminal/editor the session runs in — a tab in Ghostty vs a VS Code
3931
- // panel vs a detached tmux pane is the thing you need to know to go find it.
3932
2138
  const hostCell = cols.showHost
3933
2139
  ? chalk.gray(padRight(truncate(host || '-', PICKER_HOST_W - 1), PICKER_HOST_W))
3934
2140
  : '';
3935
- // The picker prepends a gutter (cursor, plus a checkbox in multi-select mode);
3936
- // reserve it, plus the conditional columns, so the topic shrinks to fit and
3937
- // rows never wrap.
3938
2141
  const gutter = cols.gutter ?? 2;
3939
2142
  const machineColW = cols.showMachine ? machineW : 0;
3940
2143
  const ticketW = cols.showTicket ? TICKET_W + 1 : 0;
3941
2144
  const hostW = cols.showHost ? PICKER_HOST_W : 0;
3942
2145
  const wtW = wt ? stringWidth(wt) + 1 : 0;
3943
- // Within a pool that HAS bookmarked rows the marker holds its 2 cells whether
3944
- // this row is bookmarked or not, so the columns after it never jog; a pool
3945
- // with none drops the column entirely (`showBookmark`) and costs nothing.
3946
2146
  const bookmarkW = cols.showBookmark ? 2 : 0;
3947
2147
  const bookmarkCell = cols.showBookmark ? (bookmarked ? chalk.yellow('★ ') : ' ') : '';
3948
- // The same status word the flat listing shows, so a session that is `orphan`
3949
- // or `crashed` reads that way in the browser too — not only in its preview.
3950
- // Constant width whenever the column is on. `liveStatusCell` already pads its
3951
- // word to LIVE_STATUS_W but returns an EMPTY cell (width 0) for a row with no
3952
- // live match — blanks fill in for those, so the topic column does not jog on
3953
- // exactly the rows that are not running.
3954
2148
  const status = cols.showStatus ? liveStatusCell(live) : { cell: '', width: 0 };
3955
2149
  const statusW = cols.showStatus ? LIVE_STATUS_W : 0;
3956
2150
  const statusCell = cols.showStatus ? (status.cell || ' '.repeat(LIVE_STATUS_W)) : '';
3957
- // Sized against the last-activity label alone; the creation field is then an
3958
- // additive decision made against whatever width is left (see the flat listing).
3959
2151
  const baseTopicW = terminalWidth() - gutter - bookmarkW - statusW - (10 + 9 + 8 + 16) - machineColW - hostW - ticketW - wtW - sshW - team.width - stringWidth(age.last) - 1;
3960
2152
  const when = timeCell(age, baseTopicW);
3961
2153
  const topicW = Math.max(MIN_TOPIC_W, baseTopicW - when.extraW);
3962
2154
  return (bookmarkCell +
3963
- // Truncated, not just padded: an indexed shortId is always 8 chars, but a
3964
- // live row with no session id is named by its pid or cloud task, which can
3965
- // run past the column and shunt every later column out of alignment.
3966
2155
  chalk.white(padRight(truncate(s.shortId, 9), 10)) +
3967
2156
  agentColor(padRight(truncate(shown, 8), 9)) +
3968
2157
  chalk.yellow(padRight(truncate(versionStr, 7), 8)) +
@@ -3977,24 +2166,16 @@ export function formatPickerLabel(s, query, cols = {}, ssh, host = '', bookmarke
3977
2166
  (wt ? wt + ' ' : '') +
3978
2167
  when.text);
3979
2168
  }
3980
- /** Hints rotated above the picker so the flags/features stay discoverable. */
3981
2169
  const PICKER_TIPS = [
3982
2170
  'Tip: narrow with -a/--agent (e.g. -a codex), or --project <name> for another folder.',
3983
2171
  "Tip: --all searches every directory; -D/--device <machine> folds in another box's sessions.",
3984
2172
  'Tip: just type to fuzzy-search prompts and responses; press space to preview a session.',
3985
2173
  'Tip: --since 2d / --until <date> bound the time window; pass a session id to open it directly.',
3986
2174
  ];
3987
- /**
3988
- * Pick a hint to show above the picker. Deterministic (keys off the pool size)
3989
- * so it stays fixed across the picker's re-renders within a single run.
3990
- */
3991
2175
  export function formatPickerTip(sessions) {
3992
2176
  return chalk.gray(PICKER_TIPS[sessions.length % PICKER_TIPS.length]);
3993
2177
  }
3994
2178
  export async function pickSessionInteractive(sessions, message = 'Search sessions:', initialSearch, hiddenCount = 0, enterHint, scope) {
3995
- // The hidden-session footer is console.log'd above the Inquirer prompt, so it
3996
- // scrolls the viewport the picker can't measure; tell the picker to reserve for
3997
- // it (see pickerPageSize) so the preview and the footer stay on screen together.
3998
2179
  let linesAbovePrompt = 0;
3999
2180
  if (hiddenCount > 0) {
4000
2181
  console.log(chalk.gray(formatTeamHiddenFooter(hiddenCount)));
@@ -4007,8 +2188,6 @@ export async function pickSessionInteractive(sessions, message = 'Search session
4007
2188
  subtitle: formatPickerTip(sessions),
4008
2189
  sessions,
4009
2190
  filter: (query) => {
4010
- // No query: show the full pool (picker viewport still paginates via pageSize).
4011
- // Typing: search the full pool.
4012
2191
  if (!query.trim())
4013
2192
  return sessions;
4014
2193
  return filterSessionsByQuery(sessions, query, scope);
@@ -4026,36 +2205,21 @@ export async function pickSessionInteractive(sessions, message = 'Search session
4026
2205
  throw err;
4027
2206
  }
4028
2207
  }
4029
- /** True when the peer wasn't a dialable device; prints one clear line so a
4030
- * remote pick never dead-ends silently. */
4031
2208
  function warnNoPeerTarget(machine, session) {
4032
2209
  console.log(chalk.yellow(`Session ${session.shortId} lives on ${machine}, which isn't a reachable device right now.`));
4033
2210
  console.log(chalk.gray(`Register/wake it (ag devices), or run there: agents ssh ${machine}`));
4034
2211
  }
4035
- /**
4036
- * Row-id prefix for a live session whose agent has not reported a session id yet
4037
- * (a booting harness, a queued teammate). The browser lists these so nothing live
4038
- * is hidden, but they address no transcript — {@link isIdlessLiveRow} is what the
4039
- * pick handler checks before trying to read or resume one.
4040
- */
4041
2212
  export const LIVE_ROW_PREFIX = 'live:';
4042
2213
  function isIdlessLiveRow(s) {
4043
2214
  return s.id.startsWith(LIVE_ROW_PREFIX);
4044
2215
  }
4045
2216
  export async function handlePickedSession(picked) {
4046
- // A live row with no session id has no transcript to open and no id to resume
4047
- // by; say where the process is instead of dead-ending on an unreadable path.
4048
2217
  if (isIdlessLiveRow(picked.session)) {
4049
2218
  const where = picked.session.machine ? ` on ${picked.session.machine}` : '';
4050
2219
  console.log(chalk.yellow(`This session hasn't reported a session id yet — nothing to open${where}.`));
4051
2220
  console.log(chalk.gray(`Watch for it with: agents sessions --active${picked.session.machine ? ` --device ${picked.session.machine}` : ''}`));
4052
2221
  return;
4053
2222
  }
4054
- // Reading and resuming are on DIFFERENT machines' terms, and conflating them
4055
- // is what RUSH-2022 was. Reading follows the FILE: a synced mirror sits on
4056
- // this disk, so a peer-owned row with no readable local transcript has to be
4057
- // read on the peer. Resuming follows the HARNESS STATE, which is on the
4058
- // owning machine whatever the transcript's location — a mirror included.
4059
2223
  if (picked.action === 'view') {
4060
2224
  const readFrom = transcriptOnPeerOf(picked.session);
4061
2225
  if (readFrom) {
@@ -4071,22 +2235,11 @@ export async function handlePickedSession(picked) {
4071
2235
  return;
4072
2236
  await resumeSessionInPlace(picked.session);
4073
2237
  }
4074
- /**
4075
- * Resume `session` on the machine that owns it, when that is not this one.
4076
- * Returns true when it handled the resume (hopped, or reported an unreachable
4077
- * owner), false when the session is local and the caller should take over here.
4078
- *
4079
- * The one hop for a foreground, one-session-at-a-time resume — shared by the
4080
- * `agents sessions` picker and by `sessions resume`'s no-tab-backend path, which
4081
- * would otherwise reach `resumeSessionInPlace` and be refused (RUSH-2022).
4082
- */
4083
2238
  async function resumeOnOwnerIfRemote(session) {
4084
2239
  const owner = sessionOwnerDevice(session);
4085
2240
  if (!owner)
4086
2241
  return false;
4087
2242
  console.log(chalk.gray(`Resuming ${session.shortId} on ${owner} over SSH...`));
4088
- // `agents sessions resume <id>` is the strict single-session path — one pick, one
4089
- // resume.
4090
2243
  const rc = await runOnPeer(['sessions', 'resume', session.id], owner, {
4091
2244
  tty: true,
4092
2245
  env: { [RESUME_PINNED_ENV]: '1' },
@@ -4096,21 +2249,7 @@ async function resumeOnOwnerIfRemote(session) {
4096
2249
  warnNoPeerTarget(owner, session);
4097
2250
  return true;
4098
2251
  }
4099
- /**
4100
- * Resume a session in the current terminal — a foreground takeover of this
4101
- * process. Used by the single-select picker and by `sessions resume` when the
4102
- * chosen destination is "in place" (unknown emulator / off-macOS, single pick).
4103
- * Falls back to the same resume invocation against the current version when the
4104
- * version-pinned launcher is genuinely missing.
4105
- */
4106
2252
  export async function resumeSessionInPlace(session) {
4107
- // This function is the LOCAL takeover, and every caller is responsible for
4108
- // routing a peer-owned session before it gets here (the picker above,
4109
- // `agents sessions resume`). Reaching it with one anyway means a
4110
- // caller skipped that step, so refuse rather than start the harness against
4111
- // state this box does not have — the `fs.existsSync(session.cwd)` fallback
4112
- // just below is exactly how that used to pass unnoticed, quietly swapping in
4113
- // `process.cwd()` when the recorded directory was a peer's path (RUSH-2022).
4114
2253
  const owner = sessionOwnerDevice(session);
4115
2254
  if (owner) {
4116
2255
  console.error(chalk.red(`Session ${session.shortId} belongs to ${owner} — it cannot resume on this machine.`));
@@ -4125,12 +2264,6 @@ export async function resumeSessionInPlace(session) {
4125
2264
  console.log(chalk.gray(`Resuming: ${resume.join(' ')} (cwd: ${cwd})`));
4126
2265
  await spawnResumeCommand(resume, cwd);
4127
2266
  }
4128
- /**
4129
- * Relaunch this CLI's one recovery path. The owning device resolves account
4130
- * health and either performs exact-home native resume or same-harness
4131
- * `/continue`; callers never guess which version home can see the transcript.
4132
- * `portable` is for an SSH/terminal-engine command that executes on a peer.
4133
- */
4134
2267
  export function buildSessionRecoveryCommand(session, portable = false) {
4135
2268
  const args = sessionRecoveryRunArgs(session);
4136
2269
  if (portable)
@@ -4138,17 +2271,6 @@ export function buildSessionRecoveryCommand(session, portable = false) {
4138
2271
  const invocation = getAgentsInvocation(args);
4139
2272
  return [invocation.command, ...invocation.args];
4140
2273
  }
4141
- /**
4142
- * Map a resume argv to the spawn(command, args, {shell}) triple.
4143
- *
4144
- * On Windows the agent launcher is a `.cmd`/PATHEXT shim and needs shell:true.
4145
- * With shell:true, Node concatenates args into the cmd.exe line unescaped
4146
- * (DEP0190 + injection). A session.id derived from a filename can carry
4147
- * metacharacters (`&|<>`); compose a fully-quoted line and pass an EMPTY args
4148
- * array so cmd.exe cannot reparse them (RUSH-1753). See composeWin32CommandLine.
4149
- *
4150
- * `platform` is injectable so the win32 shell path is unit-testable on any host.
4151
- */
4152
2274
  export function resumeSpawnInvocation(cmd, platform = process.platform) {
4153
2275
  const shell = needsWindowsShell(cmd[0], platform);
4154
2276
  if (shell) {
@@ -4160,17 +2282,6 @@ export function resumeSpawnInvocation(cmd, platform = process.platform) {
4160
2282
  }
4161
2283
  return { command: cmd[0], args: cmd.slice(1), shell: false };
4162
2284
  }
4163
- /**
4164
- * Spawn a resume command as a foreground takeover (inherited stdio), resolving
4165
- * when it exits. On Windows the agent launcher is a `.cmd`/PATHEXT shim that
4166
- * `spawn` can't exec directly — a bare-name `shell:false` spawn throws
4167
- * `EFTYPE`/`ENOENT` there — so we go through the shell via `needsWindowsShell`.
4168
- * When shell:true, argv is composed via resumeSpawnInvocation (quoted line +
4169
- * empty args) so an untrusted session.id cannot inject through cmd.exe.
4170
- * The spawn is guarded because such a failure can be thrown synchronously;
4171
- * without the guard it would surface under an unrelated "Failed to discover
4172
- * sessions" catch upstream instead of a truthful launch error.
4173
- */
4174
2285
  function spawnResumeCommand(cmd, cwd) {
4175
2286
  return new Promise((resolve) => {
4176
2287
  let child;
@@ -4197,40 +2308,15 @@ function spawnResumeCommand(cmd, cwd) {
4197
2308
  child.on('close', () => resolve());
4198
2309
  });
4199
2310
  }
4200
- /**
4201
- * Build the shell command that resumes a picked session.
4202
- *
4203
- * When the session's originating version is known, uses the version-pinned
4204
- * binary (e.g. `claude@2.1.138`) so the resume always runs in the same
4205
- * isolated HOME where the JSONL was written — regardless of which version is
4206
- * currently the default. Falls back to the bare shim when version is unknown.
4207
- *
4208
- */
4209
- /**
4210
- * The agent's own resume invocation, given whichever launcher we resolved.
4211
- * Keeping the verb in one place is what lets the version-pinned and fallback
4212
- * paths stay in agreement — they previously drifted into `/continue`, which is
4213
- * not a command either CLI has.
4214
- */
4215
2311
  function resumeArgv(agent, id, launcher) {
4216
2312
  switch (agent) {
4217
2313
  case 'claude': return [launcher, '--resume', id];
4218
2314
  case 'codex': return [launcher, 'resume', id];
4219
2315
  case 'opencode': return [launcher, '--session', id];
4220
- // Muse interactive resume is a subcommand: `muse resume <uuid>`.
4221
2316
  case 'muse': return [launcher, 'resume', id];
4222
2317
  default: return null;
4223
2318
  }
4224
2319
  }
4225
- /**
4226
- * Absolute path of the on-disk versioned alias, or null when it isn't there.
4227
- *
4228
- * Resume must not resolve `<cli>@<version>` by bare name. The shims directory is
4229
- * deliberately absent from PATH for an isolated install — that is precisely what
4230
- * `--isolated` promises — so a PATH lookup can never find the alias, and resume
4231
- * degraded to the fallback 100% of the time for isolated copies. Mirrors the
4232
- * resolution `buildExecCommand` already does for `agents run`.
4233
- */
4234
2320
  function versionedAliasIfPresent(agent, version) {
4235
2321
  const cli = AGENTS[agent]?.cliCommand ?? agent;
4236
2322
  const base = path.join(getShimsDir(), `${cli}@${version}`);
@@ -4244,8 +2330,6 @@ export function buildResumeCommand(session) {
4244
2330
  if (!sessionAgentSupportsResume(session.agent))
4245
2331
  return null;
4246
2332
  switch (session.agent) {
4247
- // opencode sessions are shared across versions, so resume is deliberately NOT
4248
- // version-pinned — it always goes through the plain launcher.
4249
2333
  case 'opencode':
4250
2334
  return resumeArgv('opencode', session.id, 'opencode');
4251
2335
  case 'claude':
@@ -4254,8 +2338,6 @@ export function buildResumeCommand(session) {
4254
2338
  const cli = AGENTS[session.agent]?.cliCommand ?? session.agent;
4255
2339
  if (session.version) {
4256
2340
  const alias = versionedAliasIfPresent(session.agent, session.version);
4257
- // Absolute path when the alias exists; otherwise the bare versioned name,
4258
- // which still resolves for a non-isolated install whose shims are on PATH.
4259
2341
  return resumeArgv(session.agent, session.id, alias ?? `${cli}@${session.version}`);
4260
2342
  }
4261
2343
  return resumeArgv(session.agent, session.id, cli);
@@ -4264,15 +2346,6 @@ export function buildResumeCommand(session) {
4264
2346
  return null;
4265
2347
  }
4266
2348
  }
4267
- // ---------------------------------------------------------------------------
4268
- // Cloud session source (--cloud)
4269
- // ---------------------------------------------------------------------------
4270
- /**
4271
- * Handle `agents sessions --cloud [id] [filters]`.
4272
- * - Without id: list captured cloud-runs, optionally as JSON.
4273
- * - With id: fetch the jsonl, parse with the recorded format, render via
4274
- * the same pipeline as local sessions (summary / markdown / json).
4275
- */
4276
2349
  async function runCloudSessions(query, options) {
4277
2350
  const { discoverCloudSessions, ensureCloudSessionCached } = await import('../lib/session/cloud.js');
4278
2351
  let filterOpts;
@@ -4331,11 +2404,8 @@ async function runCloudSessions(query, options) {
4331
2404
  process.exit(1);
4332
2405
  }
4333
2406
  cachedSpinner?.stop();
4334
- // Ensure the SessionMeta points at the local cache path for renderSession.
4335
2407
  await renderSession({ ...meta, filePath: cachedPath }, mode, filterOpts, options);
4336
2408
  }
4337
- /** Resolve the harness portion of a sessions selector with the CLI's canonical
4338
- * alias and single-typo rules. Kept pure so focus and the browser cannot drift. */
4339
2409
  export function resolveSessionAgentName(name) {
4340
2410
  const normalized = name.toLowerCase();
4341
2411
  if (SESSION_AGENTS.includes(normalized)) {
@@ -4368,41 +2438,18 @@ function formatSearchMessage(options) {
4368
2438
  return 'Search sessions:';
4369
2439
  return `Search sessions (${filters.join(', ')}):`;
4370
2440
  }
4371
- /**
4372
- * The single entry point for turning a `sessions <query>` argument into rows.
4373
- *
4374
- * An id-shaped query — a complete id OR a hex short-id/prefix (looksLikeSessionId)
4375
- * — resolves by id alone, through the index, and never falls back to content
4376
- * search (a bare id must not surface every transcript that merely mentions it).
4377
- * A genuine search phrase keeps the ranked metadata+content search.
4378
- */
4379
2441
  export function resolveSessionQuery(pool, query, options = {}) {
4380
- // Normalize ONCE here. isCompleteSessionId trims but resolveSessionById does
4381
- // not, so a padded id ("<uuid> ", e.g. pasted from a terminal) would classify
4382
- // as complete and then miss the id lookup — reporting a session that IS on
4383
- // this machine as absent.
4384
2442
  const normalized = query.trim();
4385
2443
  const completeId = isCompleteSessionId(normalized);
4386
2444
  const byIdMatches = resolveSessionById(pool, normalized);
4387
2445
  if (byIdMatches.length > 0)
4388
2446
  return { matches: byIdMatches, byId: true, completeId };
4389
2447
  if (looksLikeSessionId(normalized)) {
4390
- // Any id-shaped query — a complete id OR a bare hex short-id/prefix —
4391
- // resolves by id ONLY, never by content. The pool is a minority of the
4392
- // index (measured: 2,798 of 7,614 rows) because it re-reads live agent homes
4393
- // and skips whole classes of indexed session, so a pool miss isn't absence:
4394
- // ask the index directly, the same authoritative lookup `fork` and `exec`
4395
- // use. And an id that resolves to nothing must report "no session with that
4396
- // id" — NOT fall back to fuzzy content search. A short id like "d3470b57"
4397
- // otherwise surfaces every transcript that merely MENTIONS the string (a
4398
- // resume prompt echoes the parent id into the body of many later sessions).
4399
2448
  const matches = options.indexFallback === false ? [] : findSessionsById(normalized);
4400
2449
  return { matches, byId: true, completeId };
4401
2450
  }
4402
2451
  return { matches: filterSessionsByQuery(pool, normalized, options.scope), byId: false, completeId };
4403
2452
  }
4404
- /** Explain an ambiguous resolution. Only a short id can be lengthened: a complete
4405
- * id is already maximal, and a search phrase was never an id to begin with. */
4406
2453
  function ambiguityHint(byId, completeId) {
4407
2454
  if (completeId)
4408
2455
  return 'That is already a complete id — these rows share it as a prefix.';
@@ -4410,18 +2457,9 @@ function ambiguityHint(byId, completeId) {
4410
2457
  ? 'Pass a longer ID to narrow it down.'
4411
2458
  : 'That matched on text, not an id. Pass a session id, or narrow the search.';
4412
2459
  }
4413
- /** Explain a complete-id miss, which no local rephrasing can fix. Echoes the
4414
- * normalized id so a pasted, padded argument doesn't produce an unrunnable hint.
4415
- * Used only where NO fleet sweep ran (a `--local` lookup, an `--artifacts`
4416
- * listing, or a peer answering a parent's sweep) — the fleet-swept miss uses
4417
- * {@link fleetNotFoundMessage}. Never tells the user to pass `--device`: fleet
4418
- * search is already the default, so a device flag can't find what the id missed. */
4419
2460
  function notFoundByIdMessage(query) {
4420
2461
  return [chalk.red(`No session with id ${query.trim()} on this machine.`)];
4421
2462
  }
4422
- /** Honest result of a fleet sweep that found nothing: it says what the sweep
4423
- * actually did — how many peers answered, which were unreachable — instead of
4424
- * dead-ending on "search with --device <host>" after the search already ran. */
4425
2463
  export function fleetNotFoundMessage(query, deviceCount, unreachable) {
4426
2464
  const id = query.trim();
4427
2465
  if (deviceCount === 0) {
@@ -4437,7 +2475,6 @@ export function fleetNotFoundMessage(query, deviceCount, unreachable) {
4437
2475
  }
4438
2476
  return lines;
4439
2477
  }
4440
- /** Filter and rank sessions by a multi-term search query across metadata and content. */
4441
2478
  export function filterSessionsByQuery(sessions, query, scope) {
4442
2479
  const trimmed = query?.trim().toLowerCase() || '';
4443
2480
  if (!trimmed)
@@ -4448,20 +2485,12 @@ export function filterSessionsByQuery(sessions, query, scope) {
4448
2485
  return sessions.filter((session) => session.agent === agent && session.version === version);
4449
2486
  }
4450
2487
  const terms = trimmed.split(/\s+/).filter(Boolean);
4451
- // Hydrate FTS hits missing from the pool (PHNX-2767), then drop hits that
4452
- // fail --project/--agent/--routine so those flags are not undone by the union.
4453
2488
  const contentIndex = scopedContentIndex(sessions, trimmed, scope);
4454
- // If the query exactly matches a session label, short-circuit the structural
4455
- // scorer (which would otherwise surface every session whose topic happens to
4456
- // contain the same words) and return only the label hits.
4457
2489
  const EXACT_LABEL_SCORE = 1_000_000;
4458
2490
  const exactLabelHits = [...contentIndex.values()].filter(s => (s._bm25Score ?? 0) >= EXACT_LABEL_SCORE);
4459
2491
  if (exactLabelHits.length > 0) {
4460
2492
  return exactLabelHits.sort((a, b) => (b._bm25Score ?? 0) - (a._bm25Score ?? 0));
4461
2493
  }
4462
- // The listing pool is a page of the index. FTS hits the whole index. Union
4463
- // so a grep-visible transcript is returned even when it missed the page
4464
- // (PHNX-2767).
4465
2494
  const poolById = new Map(sessions.map(s => [s.id, s]));
4466
2495
  for (const [id, hit] of contentIndex) {
4467
2496
  if (!poolById.has(id))
@@ -4470,7 +2499,6 @@ export function filterSessionsByQuery(sessions, query, scope) {
4470
2499
  return [...poolById.values()]
4471
2500
  .map(session => ({ session, score: scoreSessionQuery(session, terms) }))
4472
2501
  .filter(entry => {
4473
- // Include if scored by topic/project/etc, or matched by content search
4474
2502
  if (entry.score > 0)
4475
2503
  return true;
4476
2504
  const contentMatch = contentIndex.get(entry.session.id);
@@ -4491,7 +2519,6 @@ export function filterSessionsByQuery(sessions, query, scope) {
4491
2519
  return new Date(b.session.timestamp).getTime() - new Date(a.session.timestamp).getTime();
4492
2520
  })
4493
2521
  .map(entry => {
4494
- // Attach content match terms for highlighting
4495
2522
  const cm = contentIndex.get(entry.session.id);
4496
2523
  if (cm && cm._matchedTerms) {
4497
2524
  return { ...cm };
@@ -4539,17 +2566,6 @@ function scoreSessionQuery(session, terms) {
4539
2566
  }
4540
2567
  return score;
4541
2568
  }
4542
- /**
4543
- * Narrow a session list by --project and --agent before search resolution.
4544
- * Without this, a query like "scoped search" could match sessions in BOTH
4545
- * the project you specified AND elsewhere, producing an ambiguity error
4546
- * even though the user already pointed at the correct scope.
4547
- *
4548
- * PHNX-2767 hydrates FTS hits missing from the listing pool *after* this
4549
- * runs, so callers that search must also pass the same scope into
4550
- * `filterSessionsByQuery` / `scopedContentIndex` or the union
4551
- * reintroduces the rows this function just dropped.
4552
- */
4553
2569
  export function applyScopeFilters(sessions, scope) {
4554
2570
  let filtered = sessions;
4555
2571
  if (scope.project) {
@@ -4561,7 +2577,6 @@ export function applyScopeFilters(sessions, scope) {
4561
2577
  });
4562
2578
  }
4563
2579
  if (scope.agent) {
4564
- // Accept "claude" or "claude@2.1.112" / "claude@default" / "claude@latest". Version suffix narrows further.
4565
2580
  const [wantAgent, rawVersion] = scope.agent.split('@');
4566
2581
  const resolvedAgent = resolveAgentName(wantAgent);
4567
2582
  const wantVersion = resolvedAgent ? resolveVersionAliasLoose(resolvedAgent, rawVersion) : rawVersion;
@@ -4585,12 +2600,6 @@ export function applyScopeFilters(sessions, scope) {
4585
2600
  }
4586
2601
  return filtered;
4587
2602
  }
4588
- /**
4589
- * PHNX-2767 hydrates FTS hits missing from the listing pool. That union is
4590
- * unscoped on purpose (cwd/limit are a page of the index, not a filter).
4591
- * `--project` / `--agent` / `--routine` ARE filters: drop hydrated hits that
4592
- * fail them so content search cannot undo a scope the user already set.
4593
- */
4594
2603
  function scopedContentIndex(sessions, query, scope) {
4595
2604
  const hits = searchContentIndex(sessions, query);
4596
2605
  if (!scope || (!scope.agent && !scope.project && !scope.routine))
@@ -4651,19 +2660,12 @@ async function renderOneSession(query, mode, scope) {
4651
2660
  if (looksLikeSessionId(query)) {
4652
2661
  const outcome = await resolveSessionMetadataValue(query, scope);
4653
2662
  if (outcome.kind === 'partial') {
4654
- // RUSH-2492: an unreachable peer is a warning, not a hard failure. The
4655
- // resolver already resolves an id found on the reachable fleet (SES-9a),
4656
- // so reaching here means the session was not found on any device we
4657
- // COULD reach — it may live on an unreachable peer we could not check.
4658
2663
  const offline = outcome.failedPeers;
4659
2664
  console.error(chalk.yellow(`Warning: ${offline.length} device(s) unreachable, not checked: ${offline.join(', ')}`));
4660
2665
  console.error(chalk.red(`No session matching "${query}" on any reachable device (${offline.length} unreachable, not checked).`));
4661
2666
  console.error(chalk.gray(' If it lives on an offline box, wake it (agents devices) or run there: agents ssh <device>'));
4662
2667
  process.exit(1);
4663
2668
  }
4664
- // An index miss can be a transcript created since the last incremental
4665
- // scan, or a Claude history alias. Fall through to the established
4666
- // discovery/history resolver below only on that cold miss.
4667
2669
  if (outcome.kind === 'ambiguous') {
4668
2670
  console.error(chalk.red(`Multiple sessions match "${query}" across the fleet:`));
4669
2671
  for (const candidate of outcome.candidates) {
@@ -4719,13 +2721,6 @@ async function renderOneSession(query, mode, scope) {
4719
2721
  let queryMatches = resolution.matches;
4720
2722
  let byId = resolution.byId;
4721
2723
  const completeId = resolution.completeId;
4722
- // Widen to the transcript content index only for a genuine search phrase.
4723
- // ANY id-shaped query (a complete id OR a hex short-id/prefix) names a
4724
- // specific session; widening could only surface a DIFFERENT session that
4725
- // happens to MENTION the id — which is what made `sessions <uuid>` render an
4726
- // unrelated transcript and `sessions <shortid>` list every session that
4727
- // echoes the id in a resume prompt. Gate on looksLikeSessionId, not just
4728
- // completeId, so a short id resolves to "no match" rather than fuzzy content.
4729
2724
  if (queryMatches.length === 0 && !looksLikeSessionId(query)) {
4730
2725
  const contentResults = scopedContentIndex(allSessions, query, scope);
4731
2726
  if (contentResults.size > 0) {
@@ -4754,22 +2749,12 @@ async function renderOneSession(query, mode, scope) {
4754
2749
  }
4755
2750
  }
4756
2751
  else if (byId) {
4757
- // Not on this machine. A UUID names ONE session, so before giving up ask
4758
- // the fleet — the session may live only on a peer (the whole point of
4759
- // RUSH-2024). Skip the sweep when the caller pinned --local, or when we
4760
- // are ourselves a peer answering a parent's sweep (AGENTS_SESSIONS_LOCAL),
4761
- // so a locate never recurses. On a single remote hit we hand rendering to
4762
- // that peer; a multi-host hit surfaces the conflict; a miss keeps the
4763
- // local not-found message. No FTS fallback either way.
4764
2752
  if (shouldFanOutForId(query, scope.local)) {
4765
2753
  const outcome = await resolveSessionAcrossFleet(query, mode, scope.hosts);
4766
2754
  if (outcome.kind === 'rendered')
4767
2755
  return;
4768
2756
  if (outcome.kind === 'conflict')
4769
2757
  process.exit(1);
4770
- // The fleet sweep already ran and found nothing — report what actually
4771
- // happened, never "search with --device <host>" (fleet search is the
4772
- // default, so a device flag would only re-run the same miss).
4773
2758
  fleetNotFoundMessage(query, outcome.deviceCount, outcome.unreachable).forEach(l => console.error(l));
4774
2759
  process.exit(1);
4775
2760
  }
@@ -4811,19 +2796,6 @@ async function renderOneSession(query, mode, scope) {
4811
2796
  process.exit(1);
4812
2797
  }
4813
2798
  }
4814
- /**
4815
- * Whether a missed local id lookup should widen to a cross-machine sweep.
4816
- *
4817
- * Gate (all must hold):
4818
- * - the query is id-shaped (`looksLikeSessionId`) — only an identifier resolves
4819
- * across the fleet; a search phrase never does.
4820
- * - not `--local` — the caller opted out of cross-machine lookup (deterministic
4821
- * local behavior for scripts, RUSH-2024 acceptance: "--local still restricts").
4822
- * - `AGENTS_SESSIONS_LOCAL` is unset — we are not ourselves a peer answering a
4823
- * parent's sweep, so a locate can never recurse (RUSH-2024: avoid double-fan-out).
4824
- *
4825
- * Pure + exported so the gate is unit-tested without driving discovery / SSH.
4826
- */
4827
2799
  function shouldFanOutForId(query, local) {
4828
2800
  if (local === true)
4829
2801
  return false;
@@ -4831,45 +2803,15 @@ function shouldFanOutForId(query, local) {
4831
2803
  return false;
4832
2804
  return looksLikeSessionId(query);
4833
2805
  }
4834
- /** The render-mode flag a peer's `agents sessions <id>` must carry so the remote
4835
- * summary matches the mode the user asked for. `summary` is the peer's default,
4836
- * so it needs no flag; the others map 1:1 to a CLI flag. */
4837
2806
  function modeFlag(mode) {
4838
2807
  if (mode === 'markdown')
4839
2808
  return '--markdown';
4840
2809
  if (mode === 'json')
4841
2810
  return '--json';
4842
- return undefined; // summary — the peer's default render
2811
+ return undefined;
4843
2812
  }
4844
2813
  const FULL_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4845
- /** Width of `SessionMeta.shortId` / the hex a tmux alias embeds (`deriveShortId`). */
4846
2814
  const SHORT_SESSION_ID_RE = /^[0-9a-f]{8}$/i;
4847
- /**
4848
- * A match unique enough to resolve on the FIRST peer that returns it and cancel
4849
- * the rest of the fleet sweep: a full session UUID (globally unique), a live
4850
- * tmux alias, or a bare 8-hex short id — all three name at most one session PER
4851
- * ANSWERING peer, so the first reachable hit is enough (PHNX-3292: the alias/
4852
- * short-id case is exactly what `agents tmux ls` prints, and it used to stay
4853
- * all-settle right alongside a genuine keyword search).
4854
- *
4855
- * A session **label** is deliberately NOT definitive here even on an exact
4856
- * match: labels are free-form and can collide, so a distinct session may carry
4857
- * the same label on a peer that has not answered yet — early-exiting on the
4858
- * first would silently, and nondeterministically, resume the wrong session
4859
- * (whichever peer replied first). Labels therefore stay all-settle so a
4860
- * cross-machine label conflict surfaces as an ambiguity, and a short-id PREFIX
4861
- * *shorter* than the full 8-hex width stays all-settle for the same reason
4862
- * (RUSH-2203).
4863
- *
4864
- * This is about cancelling a sweep that is still IN FLIGHT, where a peer that
4865
- * has not answered yet is still expected to. It is deliberately stricter than
4866
- * `isUniqueEnoughSelector`, which decides what to do once the sweep is OVER and
4867
- * a peer has definitively not answered — see SES-9a. A collision between two
4868
- * peers that BOTH answer before the abort lands is still caught by the ordinary
4869
- * uniqueness gate in `metadataResolveOutcome`/`fleetCandidatesByQuery`; only a
4870
- * peer that has not answered yet, at the moment of the FIRST hit, is the
4871
- * accepted risk (documented in the PHNX-3292 plan).
4872
- */
4873
2815
  export function isDefinitiveMatch(session, selector) {
4874
2816
  const trimmed = selector.trim();
4875
2817
  if (FULL_SESSION_ID_RE.test(trimmed)) {
@@ -4878,27 +2820,11 @@ export function isDefinitiveMatch(session, selector) {
4878
2820
  const shortId = shortIdFromName(trimmed) ?? (SHORT_SESSION_ID_RE.test(trimmed) ? trimmed : undefined);
4879
2821
  return !!shortId && session.shortId.toLowerCase() === shortId.toLowerCase();
4880
2822
  }
4881
- /**
4882
- * Whether a selector may enable early-exit on the cancellable fan-out: a full
4883
- * UUID, a tmux alias, or an exact 8-hex short id — every shape that is globally
4884
- * unique enough for the first hit to be the only hit worth waiting for. Labels,
4885
- * keywords, and short-id prefixes narrower than 8 hex stay all-settle: their
4886
- * uniqueness (or conflict) is only knowable once every peer has answered. See
4887
- * {@link isDefinitiveMatch}.
4888
- */
4889
2823
  export function selectorAllowsEarlyExit(selector) {
4890
2824
  const trimmed = selector.trim();
4891
2825
  return FULL_SESSION_ID_RE.test(trimmed) || isAgentTmuxAlias(trimmed) || SHORT_SESSION_ID_RE.test(trimmed);
4892
2826
  }
4893
- /** Resolve a fleet sweep through the same canonical full-id / prefix resolver as
4894
- * local lookups, then group copies by logical session id. Synced mirrors of one
4895
- * session therefore stay one candidate even when several machines report them;
4896
- * distinct ids sharing a prefix remain distinct ambiguity candidates. */
4897
2827
  export function fleetCandidatesByQuery(rows, query, trustResolvedRows = false) {
4898
- // An id selector must still be prefix-filtered defensively against older peers
4899
- // returning content mentioners. Keyword rows, however, were already matched by
4900
- // each peer's own FTS index; the parent does not own that transcript/index and
4901
- // must not re-run metadata-only filtering that could discard a content hit.
4902
2828
  const matched = !trustResolvedRows && looksLikeSessionId(query)
4903
2829
  ? resolveSessionQuery(rows, query, { indexFallback: false }).matches
4904
2830
  : rows;
@@ -4906,7 +2832,7 @@ export function fleetCandidatesByQuery(rows, query, trustResolvedRows = false) {
4906
2832
  for (const session of matched) {
4907
2833
  const machine = session.machine;
4908
2834
  if (!machine)
4909
- continue; // an untagged row can't be routed back to a peer
2835
+ continue;
4910
2836
  const logicalId = session.id.toLowerCase();
4911
2837
  let byMachine = byId.get(logicalId);
4912
2838
  if (!byMachine) {
@@ -4921,7 +2847,6 @@ export function fleetCandidatesByQuery(rows, query, trustResolvedRows = false) {
4921
2847
  return { id: hits[0].session.id, hits };
4922
2848
  });
4923
2849
  }
4924
- /** Resolve through the canonical metadata+content union used by keyword search. */
4925
2850
  function resolveIndexedMetadataRows(indexed, selector, scope) {
4926
2851
  const alias = resolveSessionAlias(selector);
4927
2852
  if (alias.kind === 'resolved') {
@@ -4933,18 +2858,12 @@ function resolveIndexedMetadataRows(indexed, selector, scope) {
4933
2858
  }
4934
2859
  return resolveSessionQuery(indexed, selector, { indexFallback: false, scope }).matches;
4935
2860
  }
4936
- /**
4937
- * ID-shaped selectors go straight to SQLite's primary-key/short-id lookup.
4938
- * Keyword and label selectors still need the broader metadata pool.
4939
- */
4940
2861
  function indexedRowsForSelector(selector, scope) {
4941
2862
  const indexed = looksLikeSessionId(selector)
4942
2863
  ? findSessionsById(selector)
4943
2864
  : querySessions();
4944
2865
  return applyScopeFilters(indexed, scope);
4945
2866
  }
4946
- /** Fixed peer argv for the metadata resolver. Scope flags compose identically on
4947
- * every host; `--all` removes the SSH login cwd/time window, not agent/project filters. */
4948
2867
  export function metadataResolveForwardedArgs(selector, scope) {
4949
2868
  const args = ['sessions', '--resolve-safe-v1', selector, '--json', '--all', '--local'];
4950
2869
  if (scope.agent)
@@ -4953,7 +2872,6 @@ export function metadataResolveForwardedArgs(selector, scope) {
4953
2872
  args.push('--project', scope.project);
4954
2873
  return args;
4955
2874
  }
4956
- /** Build the local-only peer argv for a distributed tool search. */
4957
2875
  export function toolSearchForwardedArgs(argv, hosts) {
4958
2876
  const args = ensureWholeIndex(buildForwardedArgs(argv, new Set(hosts)).filter((arg) => arg !== '--fleet'));
4959
2877
  if (!args.includes('--json'))
@@ -4962,19 +2880,7 @@ export function toolSearchForwardedArgs(argv, hosts) {
4962
2880
  args.push('--local');
4963
2881
  return args;
4964
2882
  }
4965
- /** Width of the short id the CLI prints everywhere (`SessionMeta.shortId`):
4966
- * 8 hex chars / 32 bits. A selector shorter than this is treated as a keyword,
4967
- * not an identifier. */
4968
2883
  const SHORT_SESSION_ID_WIDTH = 8;
4969
- /**
4970
- * Whether a selector names ONE session precisely enough to resolve from the
4971
- * reachable fleet alone when some peer did not answer (SES-9a).
4972
- *
4973
- * Deliberately stricter than `looksLikeSessionId`, which accepts any 6+ char
4974
- * `[0-9a-f-]` run and so matches ordinary words (`facade`, `decade`, `beaded`).
4975
- * Those are keywords a user typed as a search, not identifiers, and they must
4976
- * keep waiting for every peer.
4977
- */
4978
2884
  export function isUniqueEnoughSelector(selector) {
4979
2885
  const trimmed = selector.trim();
4980
2886
  if (isCompleteSessionId(trimmed))
@@ -4982,35 +2888,8 @@ export function isUniqueEnoughSelector(selector) {
4982
2888
  return /^[0-9a-f-]+$/i.test(trimmed)
4983
2889
  && trimmed.replace(/-/g, '').length >= SHORT_SESSION_ID_WIDTH;
4984
2890
  }
4985
- /** Resolution fails closed for keywords and labels when a peer did not answer.
4986
- * An id-shaped selector at least `SHORT_SESSION_ID_WIDTH` hex chars wide (or a
4987
- * complete id) resolves from a single reachable hit even when an unrelated
4988
- * registered device is offline — see SES-9a for the accepted collision risk. */
4989
2891
  export function metadataResolveOutcome(localMatches, remote, selector) {
4990
- // Every peer answered through --resolve-safe-v1, so its native-id row is
4991
- // already the result of resolving the original selector. Re-filtering by the
4992
- // selector would discard alias suffixes such as c1f3d813 because the native
4993
- // UUID intentionally has a different prefix.
4994
2892
  const candidates = fleetCandidatesByQuery([...localMatches, ...remote.sessions], selector, true);
4995
- // One hit on the REACHABLE fleet resolves a precise-enough id selector even
4996
- // when a peer is offline (SES-9a). This is a deliberate, bounded trade, not a
4997
- // claim that prefixes cannot collide — they can, which is why the `ambiguous`
4998
- // branch below still exists for peers that DID answer:
4999
- // - refusing costs 100% of lookups on any fleet with a permanently offline
5000
- // device (9 of them here), to avoid a prefix collision that ALSO has to
5001
- // land on the one box that did not answer. Rate depends on the id type:
5002
- // a UUIDv4 short id is unique in practice, while a time-ordered
5003
- // UUIDv7/ULID prefix is largely a timestamp and collides much more
5004
- // readily (see deriveShortId in session/db.ts);
5005
- // - `isUniqueEnoughSelector` keeps this off keyword-shaped queries, so a
5006
- // 6-char word like `facade` or `decade` is not treated as an identifier.
5007
- // A label stays fail-closed at any length: labels are free-form and collide by
5008
- // design, so one hit there really is a guess.
5009
- // (RUSH-2203's early-exit — see isDefinitiveMatch — cancels the sweep
5010
- // mid-flight, where a peer that has not answered YET is still expected to
5011
- // answer, so it stays narrower than this post-sweep gate even though
5012
- // PHNX-3292 widened it past UUID to alias/8-hex too. Here the sweep is
5013
- // already over.)
5014
2893
  if (isUniqueEnoughSelector(selector) && candidates.length === 1) {
5015
2894
  return { kind: 'resolved', session: candidates[0].hits[0].session };
5016
2895
  }
@@ -5022,74 +2901,17 @@ export function metadataResolveOutcome(localMatches, remote, selector) {
5022
2901
  return { kind: 'ambiguous', candidates };
5023
2902
  return { kind: 'resolved', session: candidates[0].hits[0].session };
5024
2903
  }
5025
- /**
5026
- * Whether a local candidate answers a full-UUID selector **definitively** — i.e.
5027
- * whether finding it on this box is the whole answer, so the fleet fan-out can be
5028
- * skipped (PHNX-3890).
5029
- *
5030
- * Two shapes qualify, and both are claims this box can actually back:
5031
- *
5032
- * - **A real transcript on this disk** (`filePath`). It renders locally, whoever
5033
- * owns it — a local session or a fleet-synced mirror — so no SSH hop is needed.
5034
- * - **A genuine peer attribution** (`machine` names another box). The row already
5035
- * routes the read to its owner through `transcriptOnPeerOf`, so the peer that
5036
- * would answer the fan-out is the peer the render already dials.
5037
- *
5038
- * What does NOT qualify is the launcher-shim shape: a transcript-less live row
5039
- * whose `machine` is this box only because it DEFAULTED there
5040
- * (`activeSessionToSessionMeta`'s `active.machine ?? self`,
5041
- * `computeLocalMetadataMatches`'s `machine || localMachine`). A dispatcher holds
5042
- * exactly that row for a session whose agent and transcript live on a peer: the
5043
- * launch process is here, the conversation is not. Treating it as definitive is
5044
- * what dead-ended `agents sessions preview <full-uuid>` on the local
5045
- * "Live session — full transcript not indexed here." stub while a passive peer —
5046
- * which has no local row at all, so it fans out — rendered the real digest.
5047
- * Process locality is not transcript locality.
5048
- */
5049
2904
  export function isLocallyDefinitiveMatch(session, self) {
5050
2905
  if (session.filePath)
5051
2906
  return true;
5052
2907
  return !!session.machine && session.machine !== self;
5053
2908
  }
5054
- /**
5055
- * Drop the launcher-shim local rows that a peer has since answered for
5056
- * (PHNX-3890). `fleetCandidatesByQuery` groups a logical session's copies per
5057
- * machine and every consumer reads `hits[0]`, with local rows passed first — so
5058
- * simply fanning out is not enough: the self-defaulted shim would still win the
5059
- * attribution and route the read back to a box with no transcript. When the peer
5060
- * that actually owns the session has answered the sweep, its row is the strictly
5061
- * better one, and the shim carries no information the candidate loses (same
5062
- * logical id, so the candidate — and any ambiguity it is part of — survives
5063
- * through the remote hit).
5064
- *
5065
- * A row that is {@link isLocallyDefinitiveMatch} is never dropped, so a locally
5066
- * readable transcript or a synced mirror still renders here with no SSH hop. When
5067
- * no peer answered for that id, the shim is kept: with no fleet evidence, a
5068
- * transcript-less self-attributed row is indistinguishable from a session THIS box
5069
- * just started, and dropping it would re-break the cold-index lookup RUSH-2682
5070
- * fixed.
5071
- */
5072
2909
  export function preferOwnerAttribution(localMatches, remoteSessions, self) {
5073
2910
  if (remoteSessions.length === 0)
5074
2911
  return localMatches;
5075
2912
  const answeredByPeer = new Set(remoteSessions.map(session => session.id.toLowerCase()));
5076
2913
  return localMatches.filter(session => isLocallyDefinitiveMatch(session, self) || !answeredByPeer.has(session.id.toLowerCase()));
5077
2914
  }
5078
- /**
5079
- * Local metadata candidates for a selector: the indexed SQLite rows, plus — when
5080
- * an id-shaped selector misses the index entirely — the live-session registry
5081
- * (RUSH-2682).
5082
- *
5083
- * Indexing is lazy (it only runs inside `discoverSessions`), so a session THIS
5084
- * box just started is "running" in `agents sessions --active` minutes before its
5085
- * transcript reaches the local index. Every id resolver (`preview`, `resume`,
5086
- * `focus`, and the fan-out peer that answers `--resolve-safe-v1`) therefore
5087
- * consults the SAME live registry `--active` reads before declaring a running
5088
- * session non-existent. It reshapes process state the registry already computed;
5089
- * it parses no transcript and renders nothing. The live path is entered ONLY on a
5090
- * cold id miss, so an indexed hit keeps its richer row and keyword resolution is
5091
- * unchanged.
5092
- */
5093
2915
  export async function computeLocalMetadataMatches(selector, scope, deps = {}) {
5094
2916
  const localMachine = machineId();
5095
2917
  const includeLocal = !scope.hosts?.length || shouldIncludeLocal(scope.hosts, localMachine);
@@ -5126,22 +2948,9 @@ export async function computeLocalMetadataMatches(selector, scope, deps = {}) {
5126
2948
  }
5127
2949
  return hydrated;
5128
2950
  }
5129
- /**
5130
- * Resolve an id-shaped selector against the local live-session registry
5131
- * (RUSH-2682). Reads the daemon-warmed active snapshot first (cheap); if that
5132
- * snapshot predates a just-started session and misses, it forces ONE fresh
5133
- * gather before conceding, so a session started seconds ago resolves
5134
- * synchronously. Never throws — a degraded registry read yields no candidates,
5135
- * so the caller falls through to the ordinary not-found path.
5136
- */
5137
2951
  export async function liveMetadataMatches(selector, scope, self, deps = {}) {
5138
2952
  const load = deps.loadActive ?? loadLocalActiveSessions;
5139
2953
  const loadFleet = deps.loadFleetActive ?? (() => readActiveSessionsCache('fleet')?.sessions ?? []);
5140
- // A launcher-shim row whose `machine` self-defaulted to this box gets its true
5141
- // EXECUTION host from the fleet snapshot, so a session dispatched to a peer is
5142
- // read on that peer instead of dead-ending on the local stub (PHNX-3890). The
5143
- // read is best-effort: an absent/cold snapshot leaves the row untouched and the
5144
- // fleet fan-out in resolveSessionMetadataValue supplies the owner instead.
5145
2954
  let fleetExecMachine;
5146
2955
  try {
5147
2956
  fleetExecMachine = fleetExecutionMachineById(loadFleet());
@@ -5162,23 +2971,9 @@ export async function liveMetadataMatches(selector, scope, self, deps = {}) {
5162
2971
  return [];
5163
2972
  }
5164
2973
  }
5165
- /** Reusable local-first resolver for `agents run --resume` and `agents sessions resume`.
5166
- * Full UUIDs hit the local SQLite index without any SSH fan-out. */
5167
2974
  export async function resolveSessionMetadataValue(selector, scope = {}, deps = { gatherRemoteList }) {
5168
2975
  const localMatches = await computeLocalMetadataMatches(selector, scope, deps);
5169
2976
  const localMachine = machineId();
5170
- // A full-UUID local hit resolves with ZERO SSH: a UUID is globally unique, so
5171
- // finding it locally is the whole answer and no peer is dialed. (A label is
5172
- // NOT resolved local-only here — it can collide with a same-label session on a
5173
- // peer, so it must consult the fleet; see isDefinitiveMatch, RUSH-2203.) The
5174
- // local index lookup is synchronous and completes before any fan-out spawns,
5175
- // so this local hit never waits on a peer.
5176
- //
5177
- // "Found it locally" must mean this box can actually ANSWER for it, though —
5178
- // see isLocallyDefinitiveMatch. A transcript-less launcher shim for a session
5179
- // executing on a peer is a local row that is not a local answer, and
5180
- // short-circuiting on it skipped the one fan-out that could have named the
5181
- // owner (PHNX-3890).
5182
2977
  if (FULL_SESSION_ID_RE.test(selector)) {
5183
2978
  const localOutcome = metadataResolveOutcome(localMatches, { sessions: [], unreachable: [] }, selector);
5184
2979
  if (localOutcome.kind === 'resolved' && isLocallyDefinitiveMatch(localOutcome.session, localMachine)) {
@@ -5189,10 +2984,6 @@ export async function resolveSessionMetadataValue(selector, scope = {}, deps = {
5189
2984
  return metadataResolveOutcome(localMatches, { sessions: [], unreachable: [] }, selector);
5190
2985
  try {
5191
2986
  const forwarded = metadataResolveForwardedArgs(selector, scope);
5192
- // Definitive lookups (full UUID / exact label) opt into early-exit: the
5193
- // first peer holding the match resolves the sweep and SIGTERMs the rest, so
5194
- // a fast peer's hit is not bounded by the slowest/unreachable peer. Short-id
5195
- // prefixes stay all-settle (ambiguity is only known once every peer answers).
5196
2987
  const remote = await deps.gatherRemoteList(forwarded, scope.hosts, selectorAllowsEarlyExit(selector)
5197
2988
  ? { isDefinitive: (session) => isDefinitiveMatch(session, selector) }
5198
2989
  : undefined);
@@ -5202,30 +2993,14 @@ export async function resolveSessionMetadataValue(selector, scope = {}, deps = {
5202
2993
  return metadataResolveOutcome(localMatches, { sessions: [], unreachable: [error?.message ?? 'fleet fan-out'] }, selector);
5203
2994
  }
5204
2995
  }
5205
- /** Resolve one selector to indexed metadata across the fleet without reading or
5206
- * rendering transcript events. A peer answering the parent sweep returns every
5207
- * local candidate; the parent performs the one logical-session uniqueness gate.
5208
- * Backs `agents sessions --resolve <selector> --json`. Exported (deps already
5209
- * injectable) so tests can drive this real call site against a fake fan-out
5210
- * instead of only the pure resolver it wraps. */
5211
2996
  export async function resolveSessionMetadata(selector, scope, deps = { gatherRemoteList }) {
5212
- // Same local candidate set as resolveSessionMetadataValue, so the peer
5213
- // answering a fan-out (`--resolve-safe-v1`, NO_FANOUT) surfaces a running
5214
- // session its own index has not caught yet — the cross-device half of the
5215
- // RUSH-2682 fix, since the parent's fan-out reads this box's answer.
5216
2997
  const localMatches = await computeLocalMetadataMatches(selector, scope, deps);
5217
- // A peer is already inside gatherRemoteList. Return all local candidates so
5218
- // the parent can distinguish a fleet-wide unique match from an ambiguity.
5219
2998
  if (process.env[NO_FANOUT_ENV] === '1') {
5220
2999
  process.stdout.write(serializeResolvedSessionsJson(localMatches));
5221
3000
  return;
5222
3001
  }
5223
3002
  const outcome = await resolveSessionMetadataValue(selector, scope, deps);
5224
3003
  if (outcome.kind === 'partial') {
5225
- // RUSH-2492: an unreachable peer is a warning, not a hard failure. The
5226
- // resolver already resolves an id found on the reachable fleet (SES-9a),
5227
- // so reaching here means the session was not found on any device we
5228
- // COULD reach — it may live on an unreachable peer we could not check.
5229
3004
  const offline = outcome.failedPeers;
5230
3005
  console.error(chalk.yellow(`Warning: ${offline.length} device(s) unreachable, not checked: ${offline.join(', ')}`));
5231
3006
  console.error(chalk.red(`No session matching "${selector}" on any reachable device (${offline.length} unreachable, not checked).`));
@@ -5254,11 +3029,6 @@ async function resolveSessionAcrossFleet(query, mode, hosts, deps = { gatherRemo
5254
3029
  let deviceCount = 0;
5255
3030
  let unreachable = [];
5256
3031
  try {
5257
- // Force whole-index scope (--all): the peer runs in its SSH-login home dir,
5258
- // whose cwd would otherwise silently narrow the lookup and hide the row.
5259
- // --json so each peer answers a parseable array; --local so it answers for
5260
- // itself and never re-fans-out (belt-and-suspenders with the parent's
5261
- // AGENTS_SESSIONS_LOCAL, which remote-list also sets on the peer).
5262
3032
  const forwarded = ['sessions', query, '--json', '--all', '--local'];
5263
3033
  const remote = await deps.gatherRemoteList(forwarded, hosts, selectorAllowsEarlyExit(query)
5264
3034
  ? { isDefinitive: (session) => isDefinitiveMatch(session, query) }
@@ -5268,8 +3038,6 @@ async function resolveSessionAcrossFleet(query, mode, hosts, deps = { gatherRemo
5268
3038
  unreachable = remote.unreachable;
5269
3039
  }
5270
3040
  catch {
5271
- // A fan-out failure is not an exact resolution — treat as not-found so the
5272
- // caller prints the honest message rather than a half-answer.
5273
3041
  candidates = [];
5274
3042
  }
5275
3043
  finally {
@@ -5290,10 +3058,6 @@ async function resolveSessionAcrossFleet(query, mode, hosts, deps = { gatherRemo
5290
3058
  }
5291
3059
  const candidate = candidates[0];
5292
3060
  const { machine } = candidate.hits[0];
5293
- // Render the remote summary by re-running `sessions <id>` ON the peer. --local
5294
- // keeps that render on the peer (it owns the transcript); the mode flag matches
5295
- // the mode the user asked for. No TTY: a summary/markdown/json render is a
5296
- // one-shot capture, not an interactive resume.
5297
3061
  const peerArgs = ['sessions', candidate.id, '--local'];
5298
3062
  const flag = modeFlag(mode);
5299
3063
  if (flag)
@@ -5302,11 +3066,10 @@ async function resolveSessionAcrossFleet(query, mode, hosts, deps = { gatherRemo
5302
3066
  if (result === 'no-target') {
5303
3067
  console.error(chalk.red(`Session ${candidate.id} is on ${machine}, but it is not a reachable registered device.`));
5304
3068
  console.error(chalk.gray('Register it with `agents devices` or run the command on that machine.'));
5305
- return { kind: 'conflict' }; // a definitive answer (found, un-renderable) — do NOT fall to the local not-found line
3069
+ return { kind: 'conflict' };
5306
3070
  }
5307
3071
  return { kind: 'rendered' };
5308
3072
  }
5309
- /** Register the `agents sessions` command with all its options and help text. */
5310
3073
  export function registerSessionsCommands(program) {
5311
3074
  const sessionsCmd = program
5312
3075
  .command('sessions')
@@ -5480,15 +3243,10 @@ export function registerSessionsCommands(program) {
5480
3243
  });
5481
3244
  sessionsCmd.action(async (query, options, command) => {
5482
3245
  if (options.browser) {
5483
- // Alias for `agents browser sessions`: a profile positional narrows to one
5484
- // profile. `--no-interactive` is already a top-level sessionsCmd option
5485
- // (options.interactive), so it carries through for free.
5486
3246
  await runBrowserSessionsCommand({ profile: query, json: options.json, interactive: options.interactive });
5487
3247
  return;
5488
3248
  }
5489
3249
  if (options.computer) {
5490
- // Alias for `agents computer sessions`: a machine positional narrows to
5491
- // one machine. `--no-interactive` carries through the same way.
5492
3250
  const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
5493
3251
  const deviceArgs = [...(options.device ?? []), ...(options.devices ?? [])];
5494
3252
  const allFleet = deviceArgs.some((host) => ['all', 'fleet'].includes(host.toLowerCase()));
@@ -5668,13 +3426,11 @@ function findClaudeSessionsInProject(sessions, historyEntry) {
5668
3426
  function findClaudeProjectSessions(sessions, historyEntry) {
5669
3427
  if (!historyEntry.project)
5670
3428
  return [];
5671
- // Resolve symlinks (e.g. macOS /var -> /private/var) so we match sessions
5672
- // whose cwd was canonicalized at scan time.
5673
3429
  let projectRoot = historyEntry.project;
5674
3430
  try {
5675
3431
  projectRoot = fs.realpathSync(projectRoot);
5676
3432
  }
5677
- catch { /* dir gone */ }
3433
+ catch { }
5678
3434
  return sessions.filter(session => session.agent === 'claude' &&
5679
3435
  typeof session.cwd === 'string' &&
5680
3436
  isWithinProject(session.cwd, projectRoot));
@@ -5742,8 +3498,6 @@ function findClaudeResumeTimestamp(filePath, targetTimestampMs) {
5742
3498
  }
5743
3499
  }
5744
3500
  function isWithinProject(sessionCwd, projectRoot) {
5745
- // Compare separator- and case-normalized (Windows folds `\`→`/` and lowercases)
5746
- // so a backslash session cwd matches a forward-slash project root and vice versa.
5747
3501
  const cwd = toComparablePath(sessionCwd);
5748
3502
  const root = toComparablePath(projectRoot);
5749
3503
  return cwd === root || cwd.startsWith(root + '/');
@@ -5756,9 +3510,6 @@ function sessionDistance(session, historyEntry) {
5756
3510
  return Number.MAX_SAFE_INTEGER;
5757
3511
  return Math.abs(sessionTime - historyEntry.timestampMs);
5758
3512
  }
5759
- // ---------------------------------------------------------------------------
5760
- // Formatting helpers
5761
- // ---------------------------------------------------------------------------
5762
3513
  function formatAbsoluteTime(isoTimestamp) {
5763
3514
  const d = new Date(isoTimestamp);
5764
3515
  if (isNaN(d.getTime()))