@phnx-labs/agents-cli 1.22.119 → 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 +236 -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 +20 -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,74 +1,27 @@
1
- /**
2
- * SQLite-backed session index and full-text search.
3
- *
4
- * Stores session metadata and user-prompt text in a WAL-mode SQLite database
5
- * at ~/.agents/.history/sessions/sessions.db. Provides incremental upsert, scan-stamp
6
- * ledger (mtime/size tracking to skip unchanged files), FTS5 search with
7
- * BM25 ranking, and label-first search for /rename'd sessions.
8
- */
9
1
  import * as fs from 'fs';
10
2
  import * as path from 'path';
11
3
  import Database from '../sqlite.js';
12
- import { parseSession, sessionFilePathContainer } from './parse.js';
13
- import { extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from './state.js';
4
+ import { claudeSubagentFiles, resolvedSubAgentCount } from './glance-files.js';
5
+ import { parseSession, sessionFilePathContainer } from '@phnx-labs/sessions-cli/reader';
6
+ import { extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from '@phnx-labs/sessions-cli/reader';
14
7
  import { getSessionsDir, getSessionsDbPath } from '../state.js';
15
8
  import { query as queryEvents, queryToolUsageForSessions } from '../feed/events.js';
16
9
  import { machineForSessionFile } from '../origin-machine.js';
17
10
  import { loadSessionActorIndex, readSessionActorRecord } from './actor-sidecar.js';
18
- import { scanEventToolCalls } from './tool-calls.js';
11
+ import { scanEventToolCalls } from '@phnx-labs/sessions-cli/reader';
19
12
  import { persistToolCalls, planEventToolResume, purgeToolCalls, toolEvidenceSourcePath } from './tool-store.js';
20
13
  import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
21
- import { extractBackgroundShells, extractSkills, extractSlashCommands, harnessTracksBackgroundShells, isSubAgentTool, } from './highlights.js';
14
+ import { extractBackgroundShells, extractSkills, extractSlashCommands, harnessTracksBackgroundShells, isSubAgentTool, } from '@phnx-labs/sessions-cli/reader';
22
15
  import { resolveResource } from '../resources.js';
23
16
  import { discoverPlugins } from '../plugins/plugins.js';
24
17
  import { machineId } from '../machine-id.js';
25
- import { firstUserMessageFromEvents, lastUserMessageFromEvents } from './prompt.js';
26
- import { emptyTimelineState, TIMELINE_EXTRACTOR_VERSION } from './timeline.js';
18
+ import { firstUserMessageFromEvents, lastUserMessageFromEvents } from '@phnx-labs/sessions-cli/reader';
19
+ import { emptyTimelineState, TIMELINE_EXTRACTOR_VERSION } from '@phnx-labs/sessions-cli/reader';
27
20
  const SESSIONS_DIR = getSessionsDir();
28
21
  const DB_PATH = getSessionsDbPath();
29
- /** Current schema version; bumped when migrations are added. Exported so tests
30
- * assert against the constant instead of hardcoding a number that every bump
31
- * then has to chase (docs/sessions.md calls the constant the source of truth). */
32
22
  export const SCHEMA_VERSION = 51;
33
- /**
34
- * Bump to force the content extractor (assistant-answer text, alongside the
35
- * user-prompt text every harness already accumulates) to re-derive on every
36
- * session's next scan. Unlike RESOURCE_INDEX_VERSION this is read by the
37
- * change-detector itself (`filterChangedEntries`, discover.ts) via
38
- * `scan_ledger.extractor_version` — a stored version below this one is treated
39
- * as "changed" even when the file's (mtime, size) are unchanged, so bumping it
40
- * here backfills every existing session's assistant text on its next scan
41
- * without a `DELETE FROM scan_ledger` (which would also throw away the
42
- * resumable parser_state/content_text for Claude/Codex).
43
- *
44
- * v3 (PHNX-3621) invalidates OpenCode rows that merged v2 stamped before its
45
- * genuine first-user-turn extractor was added.
46
- * v4 (PHNX-3621 leftover) invalidates Grok rows stamped before the bounded
47
- * chat_history.jsonl prefix read filled firstUserMessage.
48
- * v5 (PHNX-3939) restores first-meta Codex fork ownership, including cold files.
49
- * v6 (PHNX-3999) invalidates rows whose stored `label` is harness scaffolding — a
50
- * `<bash-input>` shell echo, a `<command-name>` wrapper, a bare `/clear` — which
51
- * `cleanGeneratedSessionLabel` now rejects. The label is written at index time, so
52
- * without this bump every already-indexed session keeps showing the junk title
53
- * until its transcript happens to change.
54
- */
55
23
  export const CONTENT_INDEX_VERSION = 6;
56
- /**
57
- * Bump to force `agents sessions backfill resources` to re-derive every
58
- * session's skill/slash-command tallies on its next run (resource_scan_ledger
59
- * rows with a lower version are treated as stale — the same mechanism
60
- * TOOL_INDEX_VERSION gives the tool backfill).
61
- */
62
24
  const RESOURCE_INDEX_VERSION = 1;
63
- /**
64
- * Canonicalize a file path for use as a scan_ledger key. The same physical
65
- * session file is reachable via multiple aliases — `~/.claude/projects/x.jsonl`
66
- * (when `~/.claude` is a symlink to a versioned home) and
67
- * `~/.agents/versions/claude/<v>/home/.claude/projects/x.jsonl`. Keying the
68
- * ledger by the raw path means switching between these aliases (e.g. via
69
- * `agents use`) misses the cache and forces a full re-parse. Realpath collapses
70
- * all aliases to one stable key.
71
- */
72
25
  function canonicalLedgerKey(filePath) {
73
26
  if (!filePath)
74
27
  return filePath;
@@ -79,16 +32,7 @@ function canonicalLedgerKey(filePath) {
79
32
  return filePath;
80
33
  }
81
34
  }
82
- // BM25 column weights for session_text: label > topic > project > content >
83
- // assistant. Higher weights make matches in that column rank higher.
84
- // `assistant` (the agent's own answers) is weighted BELOW `content` (the
85
- // user's prompts): a user's own words are a stronger signal of "this is the
86
- // session I meant" than the agent echoing/paraphrasing them back, so an
87
- // assistant-only match still surfaces but ranks behind an equivalent
88
- // user-prompt match.
89
- /** BM25 column weights for FTS5: label > topic > project > content > assistant. */
90
35
  const BM25_WEIGHTS = [5.0, 2.0, 1.5, 1.0, 0.5];
91
- /** DDL for the sessions database (tables, indexes, FTS5 virtual table). */
92
36
  const SCHEMA = `
93
37
  CREATE TABLE IF NOT EXISTS sessions (
94
38
  id TEXT PRIMARY KEY,
@@ -562,40 +506,18 @@ CREATE TABLE IF NOT EXISTS session_remote_preview_cache (
562
506
  );
563
507
  CREATE INDEX IF NOT EXISTS idx_remote_preview_cache_fetched ON session_remote_preview_cache(fetched_at DESC);
564
508
  `;
565
- /**
566
- * Bumping this invalidates every cached facet row without touching the schema
567
- * version, so a change to the extraction logic (a new metric, a corrected bucket)
568
- * re-derives on the next `agents insights` instead of silently reporting stale
569
- * numbers alongside fresh ones. Same role as RESOURCE_INDEX_VERSION.
570
- */
571
- /** Bump when facet extraction changes so cached rows recompute (shell-command-by-binary v7). */
572
509
  export const INSIGHTS_EXTRACTOR_VERSION = 7;
573
- /** Bump when classifyTopic's output changes so cached topics recompute (human task taxonomy v2). */
574
510
  export const SESSION_TOPIC_EXTRACTOR_VERSION = 2;
575
- // Bumped to 2 (PHNX-2973): the digest now carries `changedFiles` (per-file
576
- // paths). Bumping invalidates v1 cache rows so a fresh recompute populates the
577
- // new field instead of serving a stale digest that predates it.
578
511
  const PREVIEW_EXTRACTOR_VERSION = 3;
579
- /** Bump when classifyPhenotype's output changes so cached phenotypes recompute (PHNX-3327 v1). */
580
512
  export const SESSION_PHENOTYPE_EXTRACTOR_VERSION = 1;
581
- /** Bump when the summarizer output shape changes so cached summaries recompute (PHNX-3939 v1). */
582
513
  export const SESSION_SUMMARY_EXTRACTOR_VERSION = 1;
583
514
  let dbInstance = null;
584
- /**
585
- * Apply schema migrations from `fromVersion` → SCHEMA_VERSION. The new
586
- * `CREATE IF NOT EXISTS` at SCHEMA doesn't help when column sets or FTS
587
- * column definitions change — those need explicit migration here.
588
- */
589
515
  function migrateSchema(db, fromVersion) {
590
516
  if (fromVersion < 2) {
591
- // v1 → v2: add `label` column to sessions and switch session_text from
592
- // single `content` column to multi-column (label, topic, project, content).
593
517
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
594
518
  if (!cols.some(c => c.name === 'label')) {
595
519
  db.exec(`ALTER TABLE sessions ADD COLUMN label TEXT`);
596
520
  }
597
- // FTS5 virtual tables can't be ALTERed — drop and recreate. Scan ledger
598
- // is cleared so every file gets re-parsed on next run, repopulating FTS5.
599
521
  db.exec(`
600
522
  DROP TABLE IF EXISTS session_text;
601
523
  CREATE VIRTUAL TABLE session_text USING fts5(
@@ -610,15 +532,9 @@ function migrateSchema(db, fromVersion) {
610
532
  `);
611
533
  }
612
534
  if (fromVersion < 3) {
613
- // v2 → v3: topic extraction now strips team-spawn wrapper prompts
614
- // (HEADLESS PLAN MODE prefix + summary suffix). Force a rescan so cached
615
- // topics like "You are running in HEADLESS PLAN MODE..." get re-extracted.
616
535
  db.exec(`DELETE FROM scan_ledger;`);
617
536
  }
618
537
  if (fromVersion < 4) {
619
- // v3 → v4: team-origin is now captured structurally from the JSONL
620
- // `entrypoint` field at scan time. Add the column and force a rescan so
621
- // every existing Claude session gets its flag populated.
622
538
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
623
539
  if (!cols.some(c => c.name === 'is_team_origin')) {
624
540
  db.exec(`ALTER TABLE sessions ADD COLUMN is_team_origin INTEGER DEFAULT 0`);
@@ -626,16 +542,9 @@ function migrateSchema(db, fromVersion) {
626
542
  db.exec(`DELETE FROM scan_ledger;`);
627
543
  }
628
544
  if (fromVersion < 5) {
629
- // v4 → v5: ledger is now keyed by realpath instead of the as-discovered
630
- // path, so symlink/version-relative aliases for the same physical file
631
- // collapse to one row. Old aliased rows are dropped — next scan will
632
- // repopulate under canonical keys.
633
545
  db.exec(`DELETE FROM scan_ledger;`);
634
546
  }
635
547
  if (fromVersion < 6) {
636
- // v5 → v6: cost ($) and wall-clock duration are now computed at scan time
637
- // from raw per-model token usage. Add the columns and force a full rescan
638
- // so every existing session gets its cost_usd / duration_ms populated.
639
548
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
640
549
  if (!cols.some(c => c.name === 'cost_usd')) {
641
550
  db.exec(`ALTER TABLE sessions ADD COLUMN cost_usd REAL`);
@@ -646,9 +555,6 @@ function migrateSchema(db, fromVersion) {
646
555
  db.exec(`DELETE FROM scan_ledger;`);
647
556
  }
648
557
  if (fromVersion < 7) {
649
- // v6 → v7: the session-state engine now persists durable signals (PR opened,
650
- // worktree, tracker ticket) at scan time. Add the columns and force a full
651
- // rescan so every existing session gets them populated.
652
558
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
653
559
  if (!cols.some(c => c.name === 'pr_url'))
654
560
  db.exec(`ALTER TABLE sessions ADD COLUMN pr_url TEXT`);
@@ -661,10 +567,6 @@ function migrateSchema(db, fromVersion) {
661
567
  db.exec(`DELETE FROM scan_ledger;`);
662
568
  }
663
569
  if (fromVersion < 8) {
664
- // v7 → v8: the listing now sorts and labels by last-activity (last message
665
- // time) instead of creation time. Add the column, seed it to `timestamp` so
666
- // no row sorts as NULL before the rescan lands, then force a full rescan so
667
- // every session gets its true last_activity (from lastTsMs) repopulated.
668
570
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
669
571
  if (!cols.some(c => c.name === 'last_activity'))
670
572
  db.exec(`ALTER TABLE sessions ADD COLUMN last_activity TEXT`);
@@ -672,21 +574,11 @@ function migrateSchema(db, fromVersion) {
672
574
  db.exec(`DELETE FROM scan_ledger;`);
673
575
  }
674
576
  if (fromVersion < 9) {
675
- // v8 → v9: `agents run --name <slug>` gives a run a durable launch handle,
676
- // resolvable via `agents sessions <name>`. Additive column; NO rescan — the
677
- // name is set at run time (host sidecar / run-name sidecar), not parsed from
678
- // transcripts, so existing rows stay valid with a NULL name.
679
577
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
680
578
  if (!cols.some(c => c.name === 'name'))
681
579
  db.exec(`ALTER TABLE sessions ADD COLUMN name TEXT`);
682
580
  }
683
581
  if (fromVersion < 10) {
684
- // v9 → v10: `name` and `label` unify into a single `label`. `--name` now
685
- // SEEDS the label at launch (refined later by an agent-generated title)
686
- // instead of living in a separate immutable `name` column. Fold any existing
687
- // name into label where the label is empty, mirror it into the FTS row, then
688
- // drop the redundant column. Seeds re-apply from the run-name sidecars every
689
- // scan (seedLabelsFromNames), so no rescan is required.
690
582
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
691
583
  if (cols.some(c => c.name === 'name')) {
692
584
  db.exec(`UPDATE sessions SET label = name
@@ -697,29 +589,18 @@ function migrateSchema(db, fromVersion) {
697
589
  }
698
590
  }
699
591
  if (fromVersion < 11) {
700
- // v10 → v11: the Claude scanner now captures the ExitPlanMode plan markdown
701
- // at scan time so `agents sessions --json` can surface it without forcing
702
- // consumers (the Factory NEEDS-YOU panel) to re-read raw JSONL. Additive
703
- // column; rescan to backfill.
704
592
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
705
593
  if (!cols.some(c => c.name === 'plan'))
706
594
  db.exec(`ALTER TABLE sessions ADD COLUMN plan TEXT`);
707
595
  db.exec(`DELETE FROM scan_ledger;`);
708
596
  }
709
597
  if (fromVersion < 12) {
710
- // v11 → v12: `output_tokens` — the real generated-token count, kept separate
711
- // from `token_count` (which sums cache-read/-write and so is dominated by
712
- // cheap re-counted context). This is the honest "output" metric powering
713
- // `agents insights output`. Additive column; rescan to backfill from transcripts.
714
598
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
715
599
  if (!cols.some(c => c.name === 'output_tokens'))
716
600
  db.exec(`ALTER TABLE sessions ADD COLUMN output_tokens INTEGER`);
717
601
  db.exec(`DELETE FROM scan_ledger;`);
718
602
  }
719
603
  if (fromVersion < 13) {
720
- // v12 → v13: routine runs archive their sandboxed transcript into the run
721
- // directory and get indexed as origin='routine', linked by routine_name and
722
- // routine_run_id. Existing rows are normal CLI-origin sessions.
723
604
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
724
605
  if (!cols.some(c => c.name === 'origin'))
725
606
  db.exec(`ALTER TABLE sessions ADD COLUMN origin TEXT DEFAULT 'cli'`);
@@ -731,12 +612,6 @@ function migrateSchema(db, fromVersion) {
731
612
  db.exec(`DELETE FROM scan_ledger;`);
732
613
  }
733
614
  if (fromVersion < 14) {
734
- // v13 → v14: the discovery scan now short-circuits the readdir + per-file
735
- // stat of leaf transcript dirs whose (mtime, entry_count) is unchanged,
736
- // caching that snapshot in the new `dir_ledger` table. Create it, and clear
737
- // scan_ledger so the first post-upgrade scan does a clean full walk — that
738
- // walk seeds BOTH ledgers correctly, so a cold/empty dir_ledger degrades to
739
- // today's full behavior.
740
615
  db.exec(`
741
616
  CREATE TABLE IF NOT EXISTS dir_ledger (
742
617
  dir_path TEXT PRIMARY KEY,
@@ -748,11 +623,6 @@ function migrateSchema(db, fromVersion) {
748
623
  `);
749
624
  }
750
625
  if (fromVersion < 15) {
751
- // v14 → v15: the Claude scan becomes resumable (B-1). scan_ledger gains a
752
- // `parser_state` continuation blob (offset + accumulator snapshot) and a
753
- // `content_text` cache of the accumulated user doc. Add both columns, then
754
- // clear scan_ledger so the first post-upgrade scan does a clean full walk
755
- // that reseeds the cursor from byte 0.
756
626
  const cols = db.prepare(`PRAGMA table_info(scan_ledger)`).all();
757
627
  if (!cols.some(c => c.name === 'parser_state'))
758
628
  db.exec(`ALTER TABLE scan_ledger ADD COLUMN parser_state TEXT`);
@@ -761,20 +631,9 @@ function migrateSchema(db, fromVersion) {
761
631
  db.exec(`DELETE FROM scan_ledger;`);
762
632
  }
763
633
  if (fromVersion < 16) {
764
- // v15 → v16: repair rows poisoned by the empty-shortId bug (now fixed at the
765
- // source in deriveShortId). A session id that was only a known prefix — a bare
766
- // `session_` dir, an id of exactly `api-` or `ses_` — derived to '' via
767
- // `id.replace(prefix, '').slice(0, 8)`, passed the `short_id TEXT NOT NULL`
768
- // column (empty string is not NULL), yet matched nothing in the
769
- // `short_id LIKE ?` picker lookups. The parser no longer produces '', but
770
- // existing rows don't self-heal: an empty short_id is not re-parsed unless its
771
- // file changes, and an orphaned row (file gone) never re-parses at all. Repair
772
- // in place — `substr(id, 1, 8)` is non-empty because `id` is the non-empty
773
- // primary key — so every corrupt row becomes addressable. No rescan needed.
774
634
  db.exec(`UPDATE sessions SET short_id = substr(id, 1, 8) WHERE short_id IS NULL OR short_id = ''`);
775
635
  }
776
636
  if (fromVersion < 17) {
777
- // v16 → v17 (main): todos / recent dirs / linear project metadata.
778
637
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
779
638
  if (!cols.some(c => c.name === 'todos'))
780
639
  db.exec(`ALTER TABLE sessions ADD COLUMN todos TEXT`);
@@ -787,7 +646,6 @@ function migrateSchema(db, fromVersion) {
787
646
  db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
788
647
  }
789
648
  if (fromVersion < 18) {
790
- // v17 → v18: persist origin machine for smart-launch affinity GROUP BY machine.
791
649
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
792
650
  if (!cols.some(c => c.name === 'machine')) {
793
651
  db.exec(`ALTER TABLE sessions ADD COLUMN machine TEXT`);
@@ -800,18 +658,11 @@ function migrateSchema(db, fromVersion) {
800
658
  .prepare(`SELECT id, agent, file_path FROM sessions WHERE machine IS NULL OR machine = ''`)
801
659
  .all();
802
660
  const upd = db.prepare(`UPDATE sessions SET machine = ? WHERE id = ?`);
803
- // migrateSchema runs inside getDB's schema transaction, so these writes
804
- // deliberately share that transaction instead of opening a nested one.
805
661
  for (const row of rows) {
806
662
  upd.run(machineForSessionFile(row.file_path, row.agent), row.id);
807
663
  }
808
664
  }
809
665
  if (fromVersion < 19) {
810
- // v18 → v19: actor provenance (RUSH-2018) — who initiated the session and
811
- // the actor's kind. Populated at write time from the resolved actor (the
812
- // pid-registry/spawn path), not derivable from the transcript, so there is
813
- // no ledger wipe here: a rescan can't backfill these, and forcing a full
814
- // re-parse would be pure churn. Existing rows stay NULL until re-created.
815
666
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
816
667
  if (!cols.some(c => c.name === 'actor'))
817
668
  db.exec(`ALTER TABLE sessions ADD COLUMN actor TEXT`);
@@ -819,42 +670,24 @@ function migrateSchema(db, fromVersion) {
819
670
  db.exec(`ALTER TABLE sessions ADD COLUMN initiated_by TEXT`);
820
671
  }
821
672
  if (fromVersion < 20) {
822
- // v19 → v20: persist the transcript's model for the static session list.
823
673
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
824
674
  if (!cols.some(c => c.name === 'model'))
825
675
  db.exec(`ALTER TABLE sessions ADD COLUMN model TEXT`);
826
676
  db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
827
677
  }
828
678
  if (fromVersion < 21) {
829
- // v20 → v21: persist the team a session SPAWNED (`agents teams create/add`).
830
- // The value was already derived at scan time (discover.ts detectSpawnedTeam)
831
- // and set on SessionMeta, but had no column — so it was dropped at the write
832
- // and no consumer ever saw it. Wipe BOTH ledgers, not just scan_ledger: with
833
- // dir_ledger intact, collectChangedFilesInLeafDirs treats every non-live-root
834
- // dir as unchanged and derives its hot set from the scan stamps just deleted,
835
- // so archived dirs would never be re-parsed and would stay NULL forever.
836
679
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
837
680
  if (!cols.some(c => c.name === 'spawned_team'))
838
681
  db.exec(`ALTER TABLE sessions ADD COLUMN spawned_team TEXT`);
839
682
  db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
840
683
  }
841
684
  if (fromVersion < 22) {
842
- // v21 → v22: persist the transcript's aggregate tool-call count.
843
685
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
844
686
  if (!cols.some(c => c.name === 'tool_call_count'))
845
687
  db.exec(`ALTER TABLE sessions ADD COLUMN tool_call_count INTEGER`);
846
688
  db.exec(`DELETE FROM scan_ledger; DELETE FROM dir_ledger;`);
847
689
  }
848
690
  if (fromVersion < 23) {
849
- // v22 → v23: persist usedBrowser/usedComputer (#11) so the sessions picker
850
- // preview can trust a positive detection instead of re-deriving it from a
851
- // transcript regex on every render. Computed from a sessionId-scoped read
852
- // of the events log (see detectToolUsage), not from the parsed transcript,
853
- // so no ledger wipe is needed here — a rescan re-derives them regardless.
854
- //
855
- // Deliberately NO DEFAULT (NULL on ALTER, for every pre-existing row):
856
- // NULL means "not yet computed by this scanner" (a legacy row), distinct
857
- // from a real, computed 0/false.
858
691
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
859
692
  if (!cols.some(c => c.name === 'used_browser'))
860
693
  db.exec(`ALTER TABLE sessions ADD COLUMN used_browser INTEGER`);
@@ -862,9 +695,6 @@ function migrateSchema(db, fromVersion) {
862
695
  db.exec(`ALTER TABLE sessions ADD COLUMN used_computer INTEGER`);
863
696
  }
864
697
  if (fromVersion < 24) {
865
- // v23 → v24: session_resource_usage (#12) — skill/slash-command usage per
866
- // session, joined against the currently-installed resource/plugin for
867
- // provenance. No ledger wipe: writeResourceUsage() owns this table.
868
698
  db.exec(`
869
699
  CREATE TABLE IF NOT EXISTS session_resource_usage (
870
700
  session_id TEXT NOT NULL,
@@ -882,9 +712,6 @@ function migrateSchema(db, fromVersion) {
882
712
  `);
883
713
  }
884
714
  if (fromVersion < 25) {
885
- // v24 → v25: tool-call evidence uses an independent ledger. Do not clear
886
- // scan_ledger or dir_ledger: normal session listing stays warm, while tool
887
- // history is filled once on demand.
888
715
  db.exec(`
889
716
  CREATE TABLE IF NOT EXISTS tool_calls (
890
717
  call_key TEXT PRIMARY KEY,
@@ -933,8 +760,6 @@ function migrateSchema(db, fromVersion) {
933
760
  `);
934
761
  }
935
762
  if (fromVersion < 26) {
936
- // v25 → v26: make append accounting O(changed calls), including empty
937
- // deltas, instead of reading every historical evidence row per append.
938
763
  const callCols = db.prepare(`PRAGMA table_info(tool_calls)`).all();
939
764
  if (!callCols.some((column) => column.name === 'evidence_bytes')) {
940
765
  db.exec(`ALTER TABLE tool_calls ADD COLUMN evidence_bytes INTEGER NOT NULL DEFAULT 0`);
@@ -943,15 +768,9 @@ function migrateSchema(db, fromVersion) {
943
768
  if (!ledgerCols.some((column) => column.name === 'evidence_bytes')) {
944
769
  db.exec(`ALTER TABLE tool_scan_ledger ADD COLUMN evidence_bytes INTEGER NOT NULL DEFAULT 0`);
945
770
  }
946
- // The first tool schema existed only in prerelease development builds. Force its tool
947
- // evidence through one bounded rebuild rather than trusting zeroed totals.
948
771
  db.exec(`DELETE FROM tool_scan_ledger`);
949
772
  }
950
773
  if (fromVersion < 27) {
951
- // v26 → v27: the original unicode word tokenizer could not prefilter the
952
- // substring semantics promised by input/output/error queries. Rebuild only
953
- // the derived FTS table from already-redacted call rows; transcripts and
954
- // both scan ledgers remain warm.
955
774
  db.exec(`
956
775
  DROP TABLE IF EXISTS tool_call_text;
957
776
  CREATE VIRTUAL TABLE tool_call_text USING fts5(
@@ -968,9 +787,6 @@ function migrateSchema(db, fromVersion) {
968
787
  `);
969
788
  }
970
789
  if (fromVersion < 28) {
971
- // v27 → v28: retain every static program occurrence instead of only the
972
- // distinct program set. The source transcript is rebuilt only by the
973
- // explicit tools backfill; normal session and directory ledgers stay warm.
974
790
  db.exec(`
975
791
  CREATE TABLE IF NOT EXISTS tool_program_occurrences (
976
792
  call_key TEXT NOT NULL,
@@ -985,9 +801,6 @@ function migrateSchema(db, fromVersion) {
985
801
  `);
986
802
  }
987
803
  if (fromVersion < 29) {
988
- // v28 → v29: coverage and query planning address the independent tool
989
- // ledger by session id. Rebuild only this derived ledger so a tool query
990
- // never has to resolve or stat transcript paths.
991
804
  db.exec(`
992
805
  DROP TABLE tool_scan_ledger;
993
806
  CREATE TABLE tool_scan_ledger (
@@ -1003,9 +816,6 @@ function migrateSchema(db, fromVersion) {
1003
816
  `);
1004
817
  }
1005
818
  if (fromVersion < 30) {
1006
- // v29 → v30: prerelease tool-index builds temporarily used schema versions
1007
- // later owned by independent main migrations. Repair from the physical
1008
- // schema because a v29 marker alone cannot prove these columns are present.
1009
819
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((column) => column.name));
1010
820
  if (!cols.has('tool_call_count')) {
1011
821
  db.exec(`
@@ -1020,10 +830,6 @@ function migrateSchema(db, fromVersion) {
1020
830
  db.exec(`ALTER TABLE sessions ADD COLUMN used_computer INTEGER`);
1021
831
  }
1022
832
  if (fromVersion < 31) {
1023
- // v30 → v31: independent ledger for the explicit resource-usage backfill
1024
- // (agents sessions backfill resources). Do NOT wipe scan_ledger — normal
1025
- // session listing stays warm; historical resource rows are filled once on
1026
- // demand, exactly like the tool-index ledger (v25).
1027
833
  db.exec(`
1028
834
  CREATE TABLE IF NOT EXISTS resource_scan_ledger (
1029
835
  session_id TEXT PRIMARY KEY,
@@ -1037,25 +843,11 @@ function migrateSchema(db, fromVersion) {
1037
843
  `);
1038
844
  }
1039
845
  if (fromVersion < 32) {
1040
- // v31 → v32: persist the effective managed launch mode so resume can
1041
- // restore the same permission boundary instead of falling back to a CLI
1042
- // default that may be more or less permissive.
1043
846
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((column) => column.name));
1044
847
  if (!cols.has('mode'))
1045
848
  db.exec(`ALTER TABLE sessions ADD COLUMN mode TEXT`);
1046
849
  }
1047
850
  if (fromVersion < 33) {
1048
- // v32 → v33: attribute each Claude session to the account that produced it.
1049
- // Until now `account` held ONE email resolved process-globally and stamped on
1050
- // every row of a scan, so a machine with several signed-in accounts reported all
1051
- // of its history under whichever resolved first.
1052
- //
1053
- // Do NOT wipe scan_ledger. Attribution is a pure function of (file_path,
1054
- // version) — both already stored — so existing rows are repaired in place with
1055
- // no transcript re-parsed. Adding `DELETE FROM scan_ledger` here to match the
1056
- // other migrations would force a full re-parse of every indexed transcript to
1057
- // recompute something derivable from two columns. The v31 migration sets the
1058
- // same precedent.
1059
851
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((column) => column.name));
1060
852
  if (!cols.has('account_key'))
1061
853
  db.exec(`ALTER TABLE sessions ADD COLUMN account_key TEXT`);
@@ -1065,26 +857,6 @@ function migrateSchema(db, fromVersion) {
1065
857
  backfillClaudeAccounts(db);
1066
858
  }
1067
859
  if (fromVersion < 34) {
1068
- // v33 -> v34: claude-opus-5 and claude-sonnet-5 were missing from the pricing
1069
- // table. `getModelPricing` matches on a dash-bounded prefix, so neither could fall
1070
- // back to its Claude 4 entry: both resolved to null and every session using them
1071
- // priced to $0 -- silently, because an unpriced model contributes nothing rather
1072
- // than raising. On one real index that was 526 sessions, 478 of them the current
1073
- // default model.
1074
- //
1075
- // Adding the prices alone fixes nothing already indexed: cost_usd is computed at
1076
- // scan time, and the scanner skips any transcript whose (file_mtime_ms, file_size)
1077
- // is unchanged. Nor can those rows be repaired in place -- the row stores
1078
- // token_count and output_tokens, not the uncached-input / cache-read / cache-write
1079
- // split the price table needs -- so the figure has to come from re-reading the
1080
- // transcript.
1081
- //
1082
- // Flush ONLY the affected transcripts, not the whole ledger. A blanket
1083
- // `DELETE FROM scan_ledger` (what v5 -> v6 did when cost was introduced) would
1084
- // re-parse every session on the next scan and break the contract the other
1085
- // migrations here are tested against: adding a column must not invalidate warm
1086
- // session ledgers. Scoping it to the rows that actually mispriced keeps every
1087
- // other ledger entry warm and still guarantees the numbers correct themselves.
1088
860
  db.exec(`
1089
861
  DELETE FROM scan_ledger
1090
862
  WHERE file_path IN (
@@ -1096,35 +868,9 @@ function migrateSchema(db, fromVersion) {
1096
868
  `);
1097
869
  }
1098
870
  if (fromVersion < 35) {
1099
- // v34 -> v35: the default listing sort was `ORDER BY IFNULL(last_activity,
1100
- // timestamp) DESC` — wrapping the column in IFNULL() makes SQLite unable to
1101
- // satisfy it from idx_sessions_last_activity, so every list/resume query did
1102
- // a full table sort instead of an index walk (RUSH-2211). Every upsert path
1103
- // already writes a non-NULL last_activity (resolveLastActivity falls back to
1104
- // `timestamp`, itself NOT NULL) — the only rows that can still be NULL here
1105
- // are ones written before the v8 migration that somehow slipped the backfill,
1106
- // or seeded directly by a test. Backfill them so the column is unconditionally
1107
- // NOT NULL, then querySessions can sort on the bare column and use the index.
1108
871
  db.exec(`UPDATE sessions SET last_activity = timestamp WHERE last_activity IS NULL`);
1109
872
  }
1110
873
  if (fromVersion < 36) {
1111
- // v35 -> v36: make the tool index incremental, and stop paying a full FTS
1112
- // scan per deleted call.
1113
- //
1114
- // (a) tool_scan_ledger gains a resume point (parser_state + parsed_offset).
1115
- // Existing rows get NULLs, which read as "no resume point": the next
1116
- // scan of each session re-reads it once from byte 0 and records a resume
1117
- // point, so every scan after that is incremental. No ledger is wiped.
1118
- //
1119
- // (b) tool_call_text is rebuilt so its rowid mirrors tool_calls.rowid. The
1120
- // old rows were inserted with FTS5-assigned rowids and are only
1121
- // addressable by the UNINDEXED call_key, i.e. a full index scan per
1122
- // delete. There is no ALTER for that, and the rowids cannot be repaired
1123
- // in place, so the table is dropped and repopulated from tool_calls --
1124
- // the same non-destructive derived-table rebuild v27 did (the source of
1125
- // truth is tool_calls, which is untouched). The rebuild also lands the
1126
- // content as one merged segment, which is the compaction
1127
- // optimizeSessionSearchIndex would otherwise have to do afterwards.
1128
874
  const ledgerCols = new Set(db.prepare(`PRAGMA table_info(tool_scan_ledger)`).all()
1129
875
  .map((column) => column.name));
1130
876
  if (!ledgerCols.has('parser_state'))
@@ -1147,14 +893,6 @@ function migrateSchema(db, fromVersion) {
1147
893
  `);
1148
894
  }
1149
895
  if (fromVersion < 37) {
1150
- // v36 -> v37: persist the burn SPLIT (uncached input / cache-read /
1151
- // cache-write) and a second "no-cache" cost per session, so `agents insights output`
1152
- // can report the token split and a --pricing no-cache scenario (RUSH-2287).
1153
- // These are new nullable columns — do NOT flush scan_ledger (adding a column
1154
- // must keep warm session ledgers warm, the contract the v33->v34 note above
1155
- // states). Pre-upgrade rows stay NULL for the split until their transcript is
1156
- // re-scanned; `agents insights output` reports the split only where it is present, so
1157
- // an absent split reads as "not available for this session", never as zero.
1158
896
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1159
897
  if (!cols.has('input_tokens'))
1160
898
  db.exec(`ALTER TABLE sessions ADD COLUMN input_tokens INTEGER`);
@@ -1166,23 +904,11 @@ function migrateSchema(db, fromVersion) {
1166
904
  db.exec(`ALTER TABLE sessions ADD COLUMN cost_usd_nocache REAL`);
1167
905
  }
1168
906
  if (fromVersion < 38) {
1169
- // v37 -> v38: archived_at (RUSH-2436). Makes the local DB authoritative for
1170
- // content: a session whose transcript file is gone but whose user turns still
1171
- // live in session_text is kept (flagged archived) instead of dropped from
1172
- // listings. New nullable column, so no ledger flush — it is populated lazily
1173
- // by querySessions the first time it confirms a scanned file is gone.
1174
907
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
1175
908
  if (!cols.some((c) => c.name === 'archived_at'))
1176
909
  db.exec(`ALTER TABLE sessions ADD COLUMN archived_at INTEGER`);
1177
910
  }
1178
911
  if (fromVersion < 39) {
1179
- // v38 -> v39: durable tool-session metadata (RUSH-2549). Browser task identity
1180
- // lived only in the daemon's tasks.json, which is rewritten from the live task
1181
- // map -- so stopping a task erased the link to the agent session that drove it
1182
- // and every finished task listed as "unlinked". Computer-use had the identity
1183
- // right but wrote it to the 7-day event ledger, so its history expired instead.
1184
- // Both now persist metadata here. Pure additions, so no ledger flush: nothing
1185
- // already indexed is invalidated, and the tables populate going forward.
1186
912
  db.exec(`
1187
913
  CREATE TABLE IF NOT EXISTS browser_sessions (
1188
914
  task TEXT NOT NULL,
@@ -1220,19 +946,6 @@ function migrateSchema(db, fromVersion) {
1220
946
  `);
1221
947
  }
1222
948
  if (fromVersion < 40) {
1223
- // v39 -> v40: persist the fan-out a session left behind — sub-agents spawned
1224
- // and shells backgrounded (RUSH-3091/3095). Both were recomputed per render
1225
- // from the transcript, so a REMOTE or unindexed row (rendered from
1226
- // SessionMeta alone by sessions-picker's formatMetaOnlyBody, which has no
1227
- // events to derive from) showed neither. Columns are what make them
1228
- // renderable there.
1229
- //
1230
- // New nullable columns, so no ledger flush — the v33->v34 contract that
1231
- // adding a column keeps warm session ledgers warm. Pre-upgrade rows stay
1232
- // NULL until re-scanned, and NULL is deliberately distinct from 0: NULL is
1233
- // "not computed / harness cannot report it", 0 is "scanned, none found".
1234
- // The renders omit the segment for both, so neither can be misread as a
1235
- // positive "nothing is running" claim.
1236
949
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1237
950
  if (!cols.has('sub_agent_count'))
1238
951
  db.exec(`ALTER TABLE sessions ADD COLUMN sub_agent_count INTEGER`);
@@ -1241,32 +954,11 @@ function migrateSchema(db, fromVersion) {
1241
954
  }
1242
955
  }
1243
956
  if (fromVersion < 41) {
1244
- // v40 -> v41: persist the custom-harness / profile name a run was launched
1245
- // as (PHNX-2935). Transcript discovery still keys `agent` on the HOST
1246
- // (the file lives under the host's session dir), so without this column
1247
- // `agents sessions` cannot tell `agents run deepseek` from a native claude
1248
- // run. Additive, no ledger flush — the name is launch metadata joined from
1249
- // the actor sidecar, not parsed from the transcript. Pre-upgrade rows stay
1250
- // NULL (native / unknown) until a sidecar-backed rescan fills them.
1251
957
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1252
958
  if (!cols.has('harness'))
1253
959
  db.exec(`ALTER TABLE sessions ADD COLUMN harness TEXT`);
1254
960
  }
1255
961
  if (fromVersion < 42) {
1256
- // v41 -> v42: index the agent's ANSWERS, not just the user's prompts.
1257
- // session_text gains an `assistant` column (own FTS5 column, own lower
1258
- // BM25 weight — see BM25_WEIGHTS) and scan_ledger gains `extractor_version`
1259
- // so the change-detector can force a re-extract independent of
1260
- // (mtime, size). FTS5 can't ALTER a virtual table's column set, but unlike
1261
- // v1->v2 (which dropped the table and forced a blind full rescan of
1262
- // everything), the existing label/topic/project/content in every row is
1263
- // still exactly right — only `assistant` is missing. Rename the old table
1264
- // out of the way, create the new 6-column one, and copy the old rows back
1265
- // in (assistant defaults to '' until the extractor_version lever backfills
1266
- // it on that row's next scan) — search over label/topic/project/content
1267
- // never blacks out for the transient window it takes existing sessions to
1268
- // get rescanned, which can be a long time for a session whose transcript
1269
- // is otherwise cold.
1270
962
  db.exec(`ALTER TABLE session_text RENAME TO session_text_v41`);
1271
963
  db.exec(`
1272
964
  CREATE VIRTUAL TABLE session_text USING fts5(
@@ -1288,52 +980,15 @@ function migrateSchema(db, fromVersion) {
1288
980
  if (!ledgerCols.some(c => c.name === 'extractor_version')) {
1289
981
  db.exec(`ALTER TABLE scan_ledger ADD COLUMN extractor_version INTEGER`);
1290
982
  }
1291
- // No `DELETE FROM scan_ledger` — the new column is NULL on every existing
1292
- // row, which already never equals CONTENT_INDEX_VERSION, so every session
1293
- // re-extracts on its next scan while parser_state/content_text (the
1294
- // Claude/Codex resumable continuation) stay intact for rows that don't
1295
- // need a full reparse for any OTHER reason.
1296
983
  }
1297
984
  if (fromVersion < 43) {
1298
- // v42 -> v43: persist a per-tool-call END timestamp (PHNX-3437). The traces
1299
- // insight engine could only book a failed call's wasted time from the
1300
- // bounded gap to the NEXT call — so a call that BLOCKED for minutes and was
1301
- // the last in its session (or was followed quickly by an unrelated call)
1302
- // registered as ~0 waste. The call's result record already carried its own
1303
- // timestamp at ingestion; this column persists it so `end_timestamp -
1304
- // timestamp` (the call's own blocking duration) becomes the primary
1305
- // attribution, with the gap heuristic kept as the fallback for NULL rows.
1306
- //
1307
- // Additive, nullable column — no ledger flush (the v33->v34 contract that
1308
- // adding a column keeps warm session ledgers warm). Pre-upgrade rows stay
1309
- // NULL until re-indexed, and `insights.ts` degrades a NULL end back to the
1310
- // bounded-gap behavior, so nothing crashes or yields NaN. The paired
1311
- // TOOL_INDEX_VERSION bump (7 -> 8) is what re-derives it on a re-index; the
1312
- // tool index is deliberately independent of SCHEMA_VERSION and is never
1313
- // force-rescanned by a migration (only by the explicit tool backfill or a
1314
- // normal incremental append), so a bare ALTER here would otherwise leave
1315
- // existing rows dark forever.
1316
985
  const cols = new Set(db.prepare(`PRAGMA table_info(tool_calls)`).all().map((c) => c.name));
1317
986
  if (!cols.has('end_timestamp'))
1318
987
  db.exec(`ALTER TABLE tool_calls ADD COLUMN end_timestamp TEXT`);
1319
988
  }
1320
989
  if (fromVersion < 44) {
1321
- // v43 -> v44: backfill duration_ms for sessions whose harness scan extractor
1322
- // never derived it (PHNX-3457). rush/grok/kimi/cursor/muse/antigravity/hermes/
1323
- // openclaw left duration_ms NULL — 52% of the corpus, 100% of rush — so the
1324
- // console median was computed over only the ~48% that carried it, skewing it
1325
- // short. Going forward resolveDurationMs() populates it at every upsert; this
1326
- // repairs already-indexed rows in place from the timestamps they already store
1327
- // (last_activity, itself resolved from the last-message time else file mtime,
1328
- // minus the creation timestamp), so no transcript is re-parsed. julianday is
1329
- // avoided because it does not accept a trailing 'Z'; the arithmetic is done in
1330
- // JS with the exact Date.parse resolveDurationMs uses, keeping the backfill and
1331
- // the live path consistent. Only rows with a positive span are touched; a NULL
1332
- // that cannot be resolved stays NULL rather than becoming a fabricated 0.
1333
990
  const nullDurationRows = db.prepare(`SELECT id, timestamp, last_activity FROM sessions
1334
991
  WHERE duration_ms IS NULL AND last_activity IS NOT NULL`).all();
1335
- // Runs inside migrateSchema's own transaction (db.ts:1468), so no nested
1336
- // db.transaction() here — that would raise "transaction within a transaction".
1337
992
  const update = db.prepare(`UPDATE sessions SET duration_ms = ? WHERE id = ?`);
1338
993
  for (const row of nullDurationRows) {
1339
994
  const startMs = Date.parse(row.timestamp);
@@ -1344,69 +999,33 @@ function migrateSchema(db, fromVersion) {
1344
999
  }
1345
1000
  }
1346
1001
  if (fromVersion < 45) {
1347
- // v44 -> v45: retain the full first genuine user turn separately from the
1348
- // one-line topic. The CONTENT_INDEX_VERSION bump re-parses every transcript
1349
- // through its normal incremental scan path so existing rows backfill without
1350
- // a second consumer-side transcript reader.
1351
1002
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1352
1003
  if (!cols.has('first_user_message'))
1353
1004
  db.exec(`ALTER TABLE sessions ADD COLUMN first_user_message TEXT`);
1354
1005
  }
1355
1006
  if (fromVersion < 46) {
1356
- // v45 -> v46: mirror provenance for fleet-synced peer session digests
1357
- // (PHNX-3792). Additive columns, no ledger wipe: they are written only by the
1358
- // session-mirror consume path (never a transcript scan), so re-parsing every
1359
- // indexed transcript would be pure churn — a genuine local row keeps them NULL.
1360
1007
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1361
1008
  if (!cols.has('mirror_synced_at'))
1362
1009
  db.exec(`ALTER TABLE sessions ADD COLUMN mirror_synced_at INTEGER`);
1363
1010
  if (!cols.has('mirror_source'))
1364
1011
  db.exec(`ALTER TABLE sessions ADD COLUMN mirror_source TEXT`);
1365
- // The idx_sessions_mirror_synced index is created unconditionally after this
1366
- // block (fresh DBs skip migrations), alongside idx_sessions_last_activity.
1367
1012
  }
1368
1013
  if (fromVersion < 47) {
1369
- // v46 -> v47: carry the actor's Phoenix id (PHNX-3798). Additive, write-once
1370
- // launch metadata joined from the actor sidecar — never transcript-derived,
1371
- // exactly like actor/initiated_by (v19) — so there is no ledger wipe: a rescan
1372
- // can't backfill it and forcing a full re-parse would be pure churn. Existing
1373
- // rows stay NULL until the sidecar-join fills them (the ON CONFLICT COALESCEs).
1374
1014
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1375
1015
  if (!cols.has('phoenix_id'))
1376
1016
  db.exec(`ALTER TABLE sessions ADD COLUMN phoenix_id TEXT`);
1377
1017
  }
1378
1018
  if (fromVersion < 48) {
1379
- // v47 -> v48: retain the LATEST genuine user turn beside the first
1380
- // (PHNX-3939). For a /continue, a redirect, or an interrupted-and-restated
1381
- // session the operative request is the last thing the user said, and the row
1382
- // title / sidebar Request card show that.
1383
- //
1384
- // Deliberately WITHOUT a CONTENT_INDEX_VERSION bump, unlike the v45
1385
- // first_user_message column. That bump forces a re-parse of every indexed
1386
- // transcript on the next scan; measured on this fleet it pinned the daemon
1387
- // long enough to blow the session-index and session-state tick deadlines and
1388
- // park both services. It also buys nothing here: the value can only CHANGE
1389
- // when the transcript changes, so the ordinary incremental scan fills it in
1390
- // for exactly the sessions where it matters, and the sidebar's request comes
1391
- // from the daemon-folded `session_timelines` projection rather than this
1392
- // column. An old row simply keeps NULL until its next write.
1393
1019
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1394
1020
  if (!cols.has('last_user_message'))
1395
1021
  db.exec(`ALTER TABLE sessions ADD COLUMN last_user_message TEXT`);
1396
1022
  }
1397
1023
  if (fromVersion < 49) {
1398
- // v48 -> v49: account-first storage (PHNX-3940).
1399
1024
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map(c => c.name));
1400
1025
  if (!cols.has('account_id'))
1401
1026
  db.exec(`ALTER TABLE sessions ADD COLUMN account_id TEXT`);
1402
1027
  }
1403
1028
  if (fromVersion < 50) {
1404
- // v49 -> v50: the daemon-generated session title (PHNX-3797). Additive
1405
- // columns, no ledger wipe: nothing in a transcript produces them — the
1406
- // session-title service writes them and the scanner's upsert never names
1407
- // them — so re-parsing every indexed transcript would be pure churn. Rows
1408
- // stay NULL until the titler reaches them, which the recap ladder handles by
1409
- // falling back to the user's own first message.
1410
1029
  const cols = new Set(db.prepare(`PRAGMA table_info(sessions)`).all().map((c) => c.name));
1411
1030
  if (!cols.has('generated_title'))
1412
1031
  db.exec(`ALTER TABLE sessions ADD COLUMN generated_title TEXT`);
@@ -1416,18 +1035,6 @@ function migrateSchema(db, fromVersion) {
1416
1035
  db.exec(`ALTER TABLE sessions ADD COLUMN generated_title_at INTEGER`);
1417
1036
  }
1418
1037
  if (fromVersion < 51) {
1419
- // v50 -> v51: session_text rows move to the rowid of the sessions row they
1420
- // describe (PHNX-4154), the repair v36 made to tool_call_text. The only key
1421
- // was the UNINDEXED session_id, so every delete and content read was a full
1422
- // scan of the FTS content — 669 MB and 0.2-0.5 s per statement on a real
1423
- // box — and the mirror ingest issues one per mirrored row (2,298 there)
1424
- // inside a daemon tick: the event loop froze for minutes, every service
1425
- // blew its deadline, and browser verbs timed out behind it.
1426
- //
1427
- // Same shape as v42: rename, recreate, copy back keyed by sessions.rowid,
1428
- // drop — search never blacks out. OR REPLACE keeps the newest of any
1429
- // duplicate session_id rows. A text row whose session is gone has no rowid
1430
- // to sit at and is dropped; nothing could reach it anyway.
1431
1038
  db.exec(`ALTER TABLE session_text RENAME TO session_text_v50`);
1432
1039
  db.exec(`
1433
1040
  CREATE VIRTUAL TABLE session_text USING fts5(
@@ -1449,17 +1056,7 @@ function migrateSchema(db, fromVersion) {
1449
1056
  db.exec(`DROP TABLE session_text_v50`);
1450
1057
  }
1451
1058
  }
1452
- /**
1453
- * Stamp `account_key` / `account_org` / `account` on every Claude row from its
1454
- * `file_path` and recorded `version`. Used by the v33 migration; idempotent, so it is
1455
- * safe to re-run.
1456
- */
1457
1059
  function backfillClaudeAccounts(db, scope = 'all') {
1458
- // 'unresolved' exists so the getDB repair touches ONLY rows that are actually
1459
- // broken. Re-resolving every Claude row on an unrelated trigger would silently
1460
- // downgrade a correct row whose version home has since been uninstalled and its
1461
- // trash snapshot pruned — the row would go from attributed to dark for no reason
1462
- // the user caused. The migration wants 'all'; the repair does not.
1463
1060
  const where = scope === 'all'
1464
1061
  ? `agent = 'claude'`
1465
1062
  : `agent = 'claude' AND (account_key IS NULL
@@ -1468,44 +1065,23 @@ function backfillClaudeAccounts(db, scope = 'all') {
1468
1065
  const rows = db.prepare(`SELECT id, file_path, version FROM sessions WHERE ${where}`).all();
1469
1066
  if (rows.length === 0)
1470
1067
  return;
1471
- // `account` is overwritten, not COALESCEd. Every pre-v33 row carries the wrong
1472
- // globally-resolved email; keeping it on a row we could not attribute would leave a
1473
- // known-false address on display (commands/sessions.ts prints it, and its fuzzy
1474
- // matcher scores on it) and would disagree with the scan path, which writes
1475
- // `account = excluded.account` unconditionally. A dark row reads NULL.
1476
1068
  const update = db.prepare(`UPDATE sessions SET account_key = ?, account_org = ?, account = ? WHERE id = ?`);
1477
1069
  for (const row of rows) {
1478
1070
  const bucket = resolveClaudeAccount(index, row.file_path ?? '', row.version, readSessionActorRecord(row.id)?.accountId);
1479
1071
  update.run(bucket.key, bucket.orgName, bucket.email, row.id);
1480
1072
  }
1481
1073
  }
1482
- /** Open (or return the cached) sessions database, applying migrations as needed. */
1483
1074
  export function getDB(initialBusyTimeoutMs = 30_000) {
1484
1075
  if (dbInstance)
1485
1076
  return dbInstance;
1486
1077
  fs.mkdirSync(SESSIONS_DIR, { recursive: true });
1487
1078
  const db = new Database(DB_PATH);
1488
- // Wait up to 30s instead of failing immediately on SQLITE_BUSY. Install the
1489
- // handler before journal_mode: concurrent first opens can race for its schema
1490
- // lock, before a later busy_timeout would have a chance to wait. Multiple
1491
- // agents (CLIs, skills, hooks) open this DB concurrently. The first scan of
1492
- // a new version home can take longer than 10s; concurrent callers need enough
1493
- // headroom to wait. The ledger-recheck in upsertSessionsBatch makes
1494
- // subsequent writers near-instant, so 30s is a rarely-reached safety net.
1495
1079
  try {
1496
1080
  db.pragma(`busy_timeout = ${Math.max(0, Math.trunc(initialBusyTimeoutMs))}`);
1497
1081
  db.pragma('journal_mode = WAL');
1498
1082
  db.pragma('synchronous = NORMAL');
1499
1083
  db.pragma('temp_store = MEMORY');
1500
1084
  db.exec(SCHEMA);
1501
- // `session_remote_preview_cache` is a lazy cache table (like the others
1502
- // below), independent of SCHEMA_VERSION — but it shipped once already
1503
- // without `last_caller_revision` before this column was added, so a DB
1504
- // that already ran that earlier version has the table WITHOUT the column,
1505
- // and `CREATE TABLE IF NOT EXISTS` above is a no-op against it. Guard with
1506
- // the same PRAGMA-table_info pattern the versioned `sessions` migrations
1507
- // use, so an existing cache DB gains the column instead of every read/write
1508
- // throwing "no such column".
1509
1085
  const remotePreviewCacheCols = db.prepare(`PRAGMA table_info(session_remote_preview_cache)`).all();
1510
1086
  if (!remotePreviewCacheCols.some(c => c.name === 'last_caller_revision')) {
1511
1087
  db.exec(`ALTER TABLE session_remote_preview_cache ADD COLUMN last_caller_revision TEXT`);
@@ -1519,8 +1095,6 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1519
1095
  db.prepare(`INSERT OR IGNORE INTO meta(key, value) VALUES ('schema_version', ?)`).run(String(SCHEMA_VERSION));
1520
1096
  }
1521
1097
  else if (currentVersion < SCHEMA_VERSION) {
1522
- // Re-read after BEGIN IMMEDIATE acquires the writer lock. A second process
1523
- // may have completed the migration while this connection was waiting.
1524
1098
  const migrate = db.transaction(() => {
1525
1099
  const lockedVersion = readSchemaVersion();
1526
1100
  if (lockedVersion === undefined || lockedVersion >= SCHEMA_VERSION)
@@ -1530,21 +1104,12 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1530
1104
  });
1531
1105
  migrate();
1532
1106
  }
1533
- // Index last_activity only after the column is guaranteed to exist — fresh DBs
1534
- // get it from CREATE TABLE above, existing pre-v8 DBs from the migration just
1535
- // run. It must NOT live in SCHEMA (executed before migration) or an existing
1536
- // DB would fail the index build on a column it doesn't have yet.
1537
1107
  db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_last_activity ON sessions(last_activity DESC)`);
1538
1108
  db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_origin ON sessions(origin)`);
1539
- // Same fresh-vs-migrated rule: the column is guaranteed above (fresh from
1540
- // CREATE TABLE, existing from migration v46), so index the mirror pruner's
1541
- // scan column here rather than in SCHEMA (PHNX-3792).
1542
1109
  db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_mirror_synced ON sessions(mirror_synced_at)`);
1543
1110
  db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_routine_run_id ON sessions(routine_run_id)`);
1544
1111
  const sessionColumns = db.prepare('PRAGMA table_info(sessions)').all();
1545
1112
  if (['account_id', 'phoenix_id'].some(name => !sessionColumns.some(column => column.name === name))) {
1546
- // Partial upgrades can stamp the current version before every column exists.
1547
- // Recheck under the writer lock so concurrent openers cannot add it twice.
1548
1113
  db.transaction(() => {
1549
1114
  const columns = db.prepare('PRAGMA table_info(sessions)').all();
1550
1115
  if (!columns.some(column => column.name === 'account_id'))
@@ -1553,18 +1118,7 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1553
1118
  db.exec('ALTER TABLE sessions ADD COLUMN phoenix_id TEXT');
1554
1119
  })();
1555
1120
  }
1556
- // Account attribution repair. Two ways a Claude row ends up wrong even at v33:
1557
- // an older CLI (whose INSERT does not name the column) writes NULL, and a DB
1558
- // migrated by a build that predates the "clear the stale email on a dark row" fix
1559
- // keeps a known-wrong address. The v33 migration cannot fix either — it never runs
1560
- // again. Cheap guard first so the common case is one indexed lookup, then repair.
1561
- // Same shape as the `machine` repair below, for the same reason.
1562
1121
  {
1563
- // Column guard FIRST, like the `machine` repair below. schema_version can be
1564
- // stamped at SCHEMA_VERSION without the column existing — getDB writes the marker
1565
- // for any DB whose meta has no row (a hand-built or partially-created index), and
1566
- // migrateSchema never runs in that path. Querying account_key unguarded would
1567
- // then throw "no such column" and take down every command that opens the index.
1568
1122
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
1569
1123
  if (cols.some((c) => c.name === 'account_key') && cols.some((c) => c.name === 'account_org')) {
1570
1124
  const needsRepair = db.prepare(`
@@ -1578,9 +1132,6 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1578
1132
  db.transaction(() => backfillClaudeAccounts(db, 'unresolved'))();
1579
1133
  }
1580
1134
  }
1581
- // machine column + indexes: only after the column is guaranteed present.
1582
- // Fresh SCHEMA (v17) includes the column; older DBs get it from migrate v17.
1583
- // If a partial upgrade left schema_version ahead of the column, repair here.
1584
1135
  {
1585
1136
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
1586
1137
  if (!cols.some((c) => c.name === 'machine')) {
@@ -1599,23 +1150,12 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1599
1150
  db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_machine_ts ON sessions(machine, timestamp DESC)`);
1600
1151
  db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_agent_ts ON sessions(agent, timestamp DESC)`);
1601
1152
  }
1602
- // harness column: only after the column is guaranteed present.
1603
- // Fresh SCHEMA (v41) includes the column; older DBs get it from migrate v41.
1604
- // schema_version can be stamped at SCHEMA_VERSION without the column existing
1605
- // — getDB writes the marker for any DB whose meta has no row (a hand-built
1606
- // or partially-created index), and migrateSchema never runs in that path
1607
- // (`currentVersion === undefined`). If a partial upgrade left schema_version
1608
- // ahead of the column, repair here so the next upsertSession INSERT naming
1609
- // `harness` does not throw (PHNX-2935). Same shape as the `machine` repair
1610
- // above, for the same reason.
1611
1153
  {
1612
1154
  const cols = db.prepare(`PRAGMA table_info(sessions)`).all();
1613
1155
  if (!cols.some((c) => c.name === 'harness')) {
1614
1156
  db.exec(`ALTER TABLE sessions ADD COLUMN harness TEXT`);
1615
1157
  }
1616
1158
  }
1617
- // One-shot cleanup of the pre-SQLite JSONL indexes. Safe — nothing reads
1618
- // them anymore. Guarded by a meta flag so we only try once.
1619
1159
  const cleaned = db.prepare(`SELECT value FROM meta WHERE key = 'legacy_indexes_removed'`).get();
1620
1160
  if (!cleaned) {
1621
1161
  for (const p of [
@@ -1627,7 +1167,7 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1627
1167
  if (fs.existsSync(p))
1628
1168
  fs.unlinkSync(p);
1629
1169
  }
1630
- catch { /* ignore */ }
1170
+ catch { }
1631
1171
  }
1632
1172
  db.prepare(`INSERT OR IGNORE INTO meta(key, value) VALUES ('legacy_indexes_removed', '1')`).run();
1633
1173
  }
@@ -1640,7 +1180,6 @@ export function getDB(initialBusyTimeoutMs = 30_000) {
1640
1180
  throw error;
1641
1181
  }
1642
1182
  }
1643
- /** Bound synchronous cache contention without changing other database callers. */
1644
1183
  export function withSessionDBTimeout(timeoutMs, operation) {
1645
1184
  const db = getDB(timeoutMs);
1646
1185
  const previous = db.prepare('PRAGMA busy_timeout').get().timeout;
@@ -1652,35 +1191,16 @@ export function withSessionDBTimeout(timeoutMs, operation) {
1652
1191
  db.pragma(`busy_timeout = ${previous}`);
1653
1192
  }
1654
1193
  }
1655
- /** Close the cached database connection. */
1656
1194
  export function closeDB() {
1657
1195
  clearSessionExistenceCache();
1658
1196
  if (dbInstance) {
1659
1197
  dbInstance.close();
1660
1198
  dbInstance = null;
1661
- // Closing the connection finalizes every prepared statement it owns. Drop
1662
- // the cached upsert/FTS statements too, so the next getDB() rebuilds them
1663
- // against the fresh connection instead of re-running a finalized statement
1664
- // (which throws "statement has been finalized" on the first upsert).
1665
1199
  cachedStmts = {};
1666
1200
  }
1667
1201
  }
1668
- /**
1669
- * Compact the session + tool-call full-text-search indexes.
1670
- *
1671
- * FTS5 appends a new segment on every insert and leaves a tombstone on every
1672
- * delete. The scanner delete+inserts a session's docs on each rescan (and on
1673
- * every extractor-version bump), and FTS5 never merges those segments on its
1674
- * own — so `tool_call_text_data` / `session_text_data` accumulate hundreds of
1675
- * thousands of unmerged segments, gigabytes of index for tens of MB of content,
1676
- * and `agents sessions` queries slow to a crawl. The FTS5 `'optimize'` command
1677
- * merges every segment into one and purges tombstones. Non-destructive: no
1678
- * searchable content is lost. Reclaimed space becomes reusable free pages inside
1679
- * the DB file; run VACUUM (with the daemon stopped) to return it to the OS.
1680
- */
1681
1202
  export function optimizeSessionSearchIndex() {
1682
1203
  const db = getDB();
1683
- // Hardcoded literals — never interpolate caller input into an identifier.
1684
1204
  const tables = ['tool_call_text', 'session_text'];
1685
1205
  const segments = (table) => db.prepare(`SELECT count(*) AS n FROM ${table}_data`).get().n;
1686
1206
  return tables.map((table) => {
@@ -1689,37 +1209,11 @@ export function optimizeSessionSearchIndex() {
1689
1209
  return { table, segmentsBefore, segmentsAfter: segments(table) };
1690
1210
  });
1691
1211
  }
1692
- /**
1693
- * Segment count above which a scan pays for a slice of merge work. Below it the
1694
- * index is small enough that querying it is not the bottleneck and merging is
1695
- * pure overhead on every scan.
1696
- */
1697
1212
  const FTS_MAINTENANCE_SEGMENT_THRESHOLD = 512;
1698
- /**
1699
- * Page budget for one incremental merge. FTS5's `'merge'` command does at most
1700
- * this much work and returns — it is not `'optimize'`, which merges the whole
1701
- * index in one unbounded pass. That bound is why this can run on the scan path:
1702
- * the cost per scan is fixed, and repeated scans converge the index instead of
1703
- * one scan stalling on a multi-gigabyte compaction.
1704
- */
1705
1213
  const FTS_MAINTENANCE_MERGE_PAGES = 64;
1706
- /**
1707
- * Keep the FTS indexes from degrading on the normal scan path.
1708
- *
1709
- * `optimizeSessionSearchIndex` is the full, unbounded compaction behind
1710
- * `agents sessions optimize`. Leaving it as the ONLY compaction meant the index
1711
- * degraded until a human happened to run that command, which is how
1712
- * `tool_call_text_data` reached gigabytes for tens of MB of content. This is the
1713
- * automatic counterpart: bounded, threshold-gated, and safe to call after every
1714
- * batch of writes. Non-destructive — merging never changes what is searchable.
1715
- *
1716
- * Returns one result per table it actually merged (empty when every table is
1717
- * under the threshold, which is the common case on a warm index).
1718
- */
1719
1214
  export function maintainSessionSearchIndex(db = getDB(), options = {}) {
1720
1215
  const threshold = options.segmentThreshold ?? FTS_MAINTENANCE_SEGMENT_THRESHOLD;
1721
1216
  const pages = options.mergePages ?? FTS_MAINTENANCE_MERGE_PAGES;
1722
- // Hardcoded literals — never interpolate caller input into an identifier.
1723
1217
  const tables = ['tool_call_text', 'session_text'];
1724
1218
  const segments = (table) => db.prepare(`SELECT count(*) AS n FROM ${table}_data`).get().n;
1725
1219
  const results = [];
@@ -1732,11 +1226,7 @@ export function maintainSessionSearchIndex(db = getDB(), options = {}) {
1732
1226
  }
1733
1227
  return results;
1734
1228
  }
1735
- // ---------------------------------------------------------------------------
1736
- // Scan coordinator — prevents concurrent full scans across processes
1737
- // ---------------------------------------------------------------------------
1738
- /** How long a scan claim is trusted before it's considered stale (ms). */
1739
- const SCAN_CLAIM_TTL_MS = 120_000; // 2 minutes
1229
+ const SCAN_CLAIM_TTL_MS = 120_000;
1740
1230
  function isProcessAlive(pid) {
1741
1231
  if (!pid || isNaN(pid))
1742
1232
  return false;
@@ -1748,18 +1238,6 @@ function isProcessAlive(pid) {
1748
1238
  return false;
1749
1239
  }
1750
1240
  }
1751
- /**
1752
- * Try to claim the right to run the incremental scan. Returns true if this
1753
- * process should proceed with scanning, false if another live process is
1754
- * already scanning (caller should skip the scan and serve from the DB).
1755
- *
1756
- * Uses the `meta` table so it survives crashes — dead PIDs are detected via
1757
- * process.kill(pid, 0), stale entries via TTL. No external lock files needed.
1758
- *
1759
- * Wrapped in db.transaction() (BEGIN IMMEDIATE) so the read-then-write is
1760
- * atomic and busy_timeout retries correctly — bare auto-commit DML in WAL
1761
- * mode can return SQLITE_BUSY_SNAPSHOT which bypasses the busy handler.
1762
- */
1763
1241
  export function tryClaimScan(pid) {
1764
1242
  const db = getDB();
1765
1243
  const txn = db.transaction(() => {
@@ -1772,9 +1250,8 @@ export function tryClaimScan(pid) {
1772
1250
  const existingTs = parseInt(parts[1], 10);
1773
1251
  const ageMs = Date.now() - existingTs;
1774
1252
  if (isProcessAlive(existingPid) && ageMs < SCAN_CLAIM_TTL_MS) {
1775
- return false; // another live process is scanning — skip
1253
+ return false;
1776
1254
  }
1777
- // Dead PID or expired TTL — take over below
1778
1255
  }
1779
1256
  db.prepare(`INSERT OR REPLACE INTO meta (key, value) VALUES ('scan_in_progress', ?)`)
1780
1257
  .run(`${pid}:${Date.now()}`);
@@ -1782,13 +1259,6 @@ export function tryClaimScan(pid) {
1782
1259
  });
1783
1260
  return txn();
1784
1261
  }
1785
- /**
1786
- * Read-only probe: is a scan in progress right now, held by a LIVE process
1787
- * within the TTL? (RUSH-2682). Unlike {@link tryClaimScan} it never writes, so a
1788
- * caller that lost the claim can wait for the in-flight scan to finish rather
1789
- * than returning the pre-scan DB snapshot as if it were the answer. Returns
1790
- * false for a dead-PID or expired claim — that scan is not actually running.
1791
- */
1792
1262
  export function scanInProgressByLivePid() {
1793
1263
  const db = getDB();
1794
1264
  const existing = db
@@ -1801,10 +1271,6 @@ export function scanInProgressByLivePid() {
1801
1271
  const ts = parseInt(parts[1], 10);
1802
1272
  return isProcessAlive(pid) && Date.now() - ts < SCAN_CLAIM_TTL_MS;
1803
1273
  }
1804
- /**
1805
- * Release the scan claim written by tryClaimScan. Only deletes the entry
1806
- * if it still belongs to this process (guards against TTL takeovers).
1807
- */
1808
1274
  export function releaseScan(pid) {
1809
1275
  const db = getDB();
1810
1276
  const txn = db.transaction(() => {
@@ -1820,14 +1286,9 @@ export function releaseScan(pid) {
1820
1286
  });
1821
1287
  txn();
1822
1288
  }
1823
- /** Return the absolute path to the sessions database file. */
1824
1289
  export function getDBPath() {
1825
1290
  return DB_PATH;
1826
1291
  }
1827
- /**
1828
- * Look up the file stat stamp we stored the last time we scanned a given file path.
1829
- * Callers compare this to the current fs.stat to decide whether to rescan.
1830
- */
1831
1292
  export function getScanStampByPath(filePath) {
1832
1293
  const db = getDB();
1833
1294
  const row = db
@@ -1837,19 +1298,11 @@ export function getScanStampByPath(filePath) {
1837
1298
  ? { fileMtimeMs: row.file_mtime_ms, fileSize: row.file_size, scannedAt: row.scanned_at, extractorVersion: row.extractor_version }
1838
1299
  : null;
1839
1300
  }
1840
- /**
1841
- * Bulk-load the stamp ledger for a set of file paths in a single SQL query.
1842
- * This is the fast path used by the incremental scanner — avoids N+1 queries.
1843
- */
1844
1301
  export function getScanStampsForPaths(filePaths) {
1845
1302
  const result = new Map();
1846
1303
  if (filePaths.length === 0)
1847
1304
  return result;
1848
1305
  const db = getDB();
1849
- // Multiple input paths can resolve to the same canonical key (e.g. the same
1850
- // session JSONL reachable via `~/.claude/...` and `~/.agents/versions/...`).
1851
- // We query DB by canonical key, then fan results back out to every original
1852
- // alias so callers can `.get(filePath)` with the path they passed in.
1853
1306
  const canonicalToOriginals = new Map();
1854
1307
  for (const fp of filePaths) {
1855
1308
  const canonical = canonicalLedgerKey(fp);
@@ -1860,7 +1313,6 @@ export function getScanStampsForPaths(filePaths) {
1860
1313
  canonicalToOriginals.set(canonical, [fp]);
1861
1314
  }
1862
1315
  const canonicalKeys = [...canonicalToOriginals.keys()];
1863
- // SQLite parameter limit is typically 999 / 32766 — chunk defensively.
1864
1316
  const CHUNK = 500;
1865
1317
  for (let i = 0; i < canonicalKeys.length; i += CHUNK) {
1866
1318
  const chunk = canonicalKeys.slice(i, i + CHUNK);
@@ -1886,14 +1338,6 @@ export function getScanStampsForPaths(filePaths) {
1886
1338
  }
1887
1339
  return result;
1888
1340
  }
1889
- /**
1890
- * Bulk-load the resumable-parse continuation (parser_state + content_text) plus
1891
- * the stamp for a set of file paths in a single chunked query. Mirrors
1892
- * {@link getScanStampsForPaths}: keys by canonical path and fans results back to
1893
- * every original alias so callers can `.get(filePath)` with the path they passed.
1894
- * The Claude incremental scan uses this to fetch each changed file's prior
1895
- * continuation without an N+1 of {@link getScanStampByPath}.
1896
- */
1897
1341
  export function getParserStatesForPaths(filePaths) {
1898
1342
  const result = new Map();
1899
1343
  if (filePaths.length === 0)
@@ -1936,10 +1380,6 @@ export function getParserStatesForPaths(filePaths) {
1936
1380
  }
1937
1381
  return result;
1938
1382
  }
1939
- /**
1940
- * Record scan stamps for files we've looked at. Covers both files that produced
1941
- * a session and files we looked at but chose not to index (e.g. malformed).
1942
- */
1943
1383
  export function recordScans(entries) {
1944
1384
  if (entries.length === 0)
1945
1385
  return;
@@ -1956,23 +1396,11 @@ export function recordScans(entries) {
1956
1396
  const now = Date.now();
1957
1397
  const txn = db.transaction((items) => {
1958
1398
  for (const { filePath, scan } of items) {
1959
- // Stamped at the CURRENT content extractor version even for a file that
1960
- // yielded no session (malformed / no id): we ran today's extractor over
1961
- // it and it produced nothing, so it is current, not stale — otherwise a
1962
- // permanently-unparseable file would re-trigger "changed" on every scan
1963
- // forever once CONTENT_INDEX_VERSION is bumped.
1964
1399
  stmt.run(canonicalLedgerKey(filePath), scan.fileMtimeMs, scan.fileSize, now, CONTENT_INDEX_VERSION);
1965
1400
  }
1966
1401
  });
1967
1402
  txn(entries);
1968
1403
  }
1969
- /**
1970
- * Bulk-load the dir ledger for a set of leaf directories in a single SQL query.
1971
- * Mirrors {@link getScanStampsForPaths}: keys by canonical path (so a dir
1972
- * reachable via a symlinked version home and its realpath collapse to one row)
1973
- * and fans the result back out to every original alias so callers can
1974
- * `.get(dirPath)` with the path they passed in.
1975
- */
1976
1404
  export function getDirLedgerForPaths(dirs) {
1977
1405
  const result = new Map();
1978
1406
  if (dirs.length === 0)
@@ -2008,11 +1436,6 @@ export function getDirLedgerForPaths(dirs) {
2008
1436
  }
2009
1437
  return result;
2010
1438
  }
2011
- /**
2012
- * Upsert dir-scan stamps. Recorded after a full readdir of a leaf transcript
2013
- * dir so the next scan can skip that dir when its (mtime, entry_count) is
2014
- * unchanged. Mirrors {@link recordScans}.
2015
- */
2016
1439
  export function recordDirScans(entries) {
2017
1440
  if (entries.length === 0)
2018
1441
  return;
@@ -2167,19 +1590,6 @@ const upsertSessionStmt = (db) => db.prepare(`
2167
1590
  ELSE sessions.mirror_source
2168
1591
  END
2169
1592
  `);
2170
- /**
2171
- * Did this session emit at least one browser/computer-automation event?
2172
- * A scoped, sessionId-filtered read of the events log — NOT a re-scan of the
2173
- * (potentially huge) transcript — since browser.navigate / browser.screenshot
2174
- * / computer.action are recorded there via events.ts's emit(), keyed by the
2175
- * same session id that is this row's `meta.id` (the provenance floor stamps
2176
- * AGENT_SESSION_ID/AGENTS_SESSION_ID on every such event at emit time).
2177
- *
2178
- * Called independently of {@link enrichCachedSessionMeta} (which SKIPS
2179
- * claude/codex entries in the batch path because their caller already parsed
2180
- * the transcript for todos/recentDirectoriesTouched) — this never touches the
2181
- * transcript, so it must run for every agent, every time.
2182
- */
2183
1593
  function detectToolUsage(sessionId) {
2184
1594
  const usedBrowser = queryEvents({ sessionId, eventTypes: ['browser.navigate', 'browser.screenshot'], limit: 1 }).length > 0;
2185
1595
  const usedComputer = queryEvents({ sessionId, eventTypes: ['computer.action'], limit: 1 }).length > 0;
@@ -2190,17 +1600,6 @@ const insertResourceUsageStmt = (db) => db.prepare(`
2190
1600
  INSERT INTO session_resource_usage (session_id, kind, name, plugin, source, repo_root, snapshot_sha, count)
2191
1601
  VALUES (@session_id, @kind, @name, @plugin, @source, @repo_root, @snapshot_sha, @count)
2192
1602
  `);
2193
- /**
2194
- * Resolve a skill/slash-command's provenance for `session_resource_usage`
2195
- * (#12). A flat (non-namespaced) resource resolves via resolveResource()'s
2196
- * project/user/system/extra-repo scan. A namespaced one (`plugin:name`, e.g.
2197
- * `rush:design` — the shape both a plugin skill's `args.skill` and a plugin
2198
- * command's SessionEvent.slashCommand carry) is plugin-owned and lives under
2199
- * the plugin's own directory, invisible to resolveResource()'s flat scan —
2200
- * resolved instead against the already-discovered plugin list. Neither found
2201
- * (renamed or uninstalled since the session ran) returns all-undefined
2202
- * rather than a stale guess.
2203
- */
2204
1603
  function resolveResourceProvenance(kind, name, cwd, plugins) {
2205
1604
  const listOf = (p) => (kind === 'skills' ? p.skills : p.commands);
2206
1605
  const colonIdx = name.indexOf(':');
@@ -2220,28 +1619,6 @@ function resolveResourceProvenance(kind, name, cwd, plugins) {
2220
1619
  return {};
2221
1620
  return { plugin: plugin.name, source: plugin.marketplace, repoRoot: plugin.repoRoot, snapshotSha: plugin.snapshotSha };
2222
1621
  }
2223
- /**
2224
- * Persist already-computed skill/slash-command tallies for a session (#12)
2225
- * into `session_resource_usage`, replacing any prior rows for it (a rescan's
2226
- * usage supersedes the old — same replace-on-upsert shape as the FTS text
2227
- * below). `discoverPlugins()` (real I/O: manifest + directory reads) only
2228
- * runs when there is actually a skill/command to resolve, so a session with
2229
- * neither pays nothing beyond the DELETE.
2230
- *
2231
- * Split from {@link writeResourceUsage} so a caller that already has the
2232
- * tallies (claude/codex's incremental accumulator — see
2233
- * ClaudeParseState.skillEvents/slashCommandEvents, threaded onto
2234
- * SessionMeta.skillsUsed/slashCommandsUsed) can write without re-parsing the
2235
- * transcript to re-derive them.
2236
- *
2237
- * Deliberately NOT wrapped in its own `db.transaction()`: better-sqlite3
2238
- * (this repo's wrapper included) does not support nested transactions, and
2239
- * `upsertSessionsBatch` calls this from INSIDE its own outer transaction. A
2240
- * standalone caller (enrichCachedSessionMeta, outside any transaction) still
2241
- * gets each statement committed individually — a crash between the DELETE
2242
- * and an INSERT self-heals on the next rescan, the same risk profile as any
2243
- * other un-batched write in this file.
2244
- */
2245
1622
  function writeResourceUsageFromTallies(sessionId, skills, commands, cwd) {
2246
1623
  const db = getDB();
2247
1624
  const del = deleteResourceUsageStmt(db);
@@ -2270,27 +1647,10 @@ function writeResourceUsageFromTallies(sessionId, skills, commands, cwd) {
2270
1647
  ins.run(bind);
2271
1648
  }
2272
1649
  }
2273
- /**
2274
- * Persist skill/slash-command usage for a session (#12) by deriving the
2275
- * tallies from a full parsed transcript. Used by {@link enrichCachedSessionMeta}
2276
- * (every `upsertSession()` call, and `upsertSessionsBatch` for every harness
2277
- * EXCEPT claude/codex, which pre-compute skillsUsed/slashCommandsUsed via
2278
- * their incremental accumulator instead — see writeResourceUsageFromTallies
2279
- * and the call site in upsertSessionsBatch).
2280
- */
2281
1650
  function writeResourceUsage(sessionId, events, cwd) {
2282
1651
  writeResourceUsageFromTallies(sessionId, extractSkills(events), extractSlashCommands(events), cwd);
2283
1652
  }
2284
- /**
2285
- * Fan-out the session left behind, from an ALREADY-parsed transcript (never a
2286
- * re-parse — every caller here has `events` in hand).
2287
- *
2288
- * `backgroundShellCount` is `undefined`, not 0, for a harness that cannot report
2289
- * backgrounded shells. That distinction is the whole point: 0 asserts "none were
2290
- * started", while undefined says "this harness does not record it" — and the
2291
- * renders omit the segment for both, so neither can read as "nothing is running".
2292
- */
2293
- function fanOutCounts(events, agent) {
1653
+ function fanOutCounts(events, agent, sessionFile) {
2294
1654
  let subAgentCount = 0;
2295
1655
  for (const e of events) {
2296
1656
  if (e.type !== 'tool_use' || e._local)
@@ -2298,14 +1658,14 @@ function fanOutCounts(events, agent) {
2298
1658
  if (isSubAgentTool(e.tool || '', e.command || ''))
2299
1659
  subAgentCount++;
2300
1660
  }
1661
+ const children = agent === 'claude' && sessionFile ? claudeSubagentFiles(sessionFile) : undefined;
2301
1662
  return {
2302
- subAgentCount,
1663
+ subAgentCount: resolvedSubAgentCount(children, subAgentCount),
2303
1664
  backgroundShellCount: harnessTracksBackgroundShells(agent)
2304
1665
  ? extractBackgroundShells(events).length
2305
1666
  : undefined,
2306
1667
  };
2307
1668
  }
2308
- /** Fold transcript-derived metadata that every parser can supply uniformly. */
2309
1669
  function enrichMetaFromEvents(meta, events) {
2310
1670
  return {
2311
1671
  ...meta,
@@ -2313,7 +1673,7 @@ function enrichMetaFromEvents(meta, events) {
2313
1673
  lastUserMessage: meta.lastUserMessage ?? lastUserMessageFromEvents(events),
2314
1674
  todos: extractTodoProgressFromEvents(events),
2315
1675
  recentDirectoriesTouched: extractRecentDirectoriesTouched(events, meta.cwd),
2316
- ...fanOutCounts(events, meta.agent),
1676
+ ...fanOutCounts(events, meta.agent, meta.filePath),
2317
1677
  };
2318
1678
  }
2319
1679
  function enrichCachedSessionMeta(meta) {
@@ -2325,22 +1685,12 @@ function enrichCachedSessionMeta(meta) {
2325
1685
  return enrichMetaFromEvents(meta, events);
2326
1686
  }
2327
1687
  catch {
2328
- // Synthetic/cloud rows can intentionally name a transcript that is not local.
2329
1688
  return meta;
2330
1689
  }
2331
1690
  }
2332
- /**
2333
- * The one way to address a session's FTS row: its rowid is the sessions rowid
2334
- * (schema comment on session_text). A `session_id = ?` predicate scans the whole
2335
- * index instead — the PHNX-4154 daemon freeze.
2336
- */
2337
1691
  const SESSION_TEXT_ROWID = `(SELECT rowid FROM sessions WHERE id = ?)`;
2338
1692
  const deleteTextStmt = (db) => db.prepare(`DELETE FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`);
2339
- // Binds the session id twice: once for the rowid lookup, once for the column.
2340
1693
  const insertTextStmt = (db) => db.prepare(`INSERT INTO session_text (rowid, session_id, label, topic, project, content, assistant) VALUES (${SESSION_TEXT_ROWID}, ?, ?, ?, ?, ?, ?)`);
2341
- // Read back the label the upsert actually stored (which may be the preserved
2342
- // one, not the incoming blank) so the FTS label column stays consistent with
2343
- // sessions.label after a bare rescan.
2344
1694
  const readLabelStmt = (db) => db.prepare(`SELECT label FROM sessions WHERE id = ?`);
2345
1695
  let cachedStmts = {};
2346
1696
  function stmts(db) {
@@ -2354,33 +1704,17 @@ function stmts(db) {
2354
1704
  }
2355
1705
  return cachedStmts;
2356
1706
  }
2357
- /**
2358
- * Return the label stored for a session, as text for the FTS index (never NULL).
2359
- * Called inside the upsert transaction, AFTER the row upsert, so it reflects the
2360
- * preserve-non-empty-label rule in the ON CONFLICT clause rather than the raw
2361
- * incoming label.
2362
- */
2363
1707
  function storedFtsLabel(readLabel, id) {
2364
1708
  const row = readLabel.get(id);
2365
1709
  return row?.label ?? '';
2366
1710
  }
2367
- /** Resolve origin machine for a row: prefer caller-stamped meta, else path. */
2368
1711
  function resolveMachine(meta) {
2369
1712
  if (meta.machine && meta.machine.trim())
2370
1713
  return meta.machine.trim();
2371
1714
  return machineForSessionFile(meta.filePath, meta.agent);
2372
1715
  }
2373
- /**
2374
- * Upsert a session row and replace its FTS5 content in a single transaction.
2375
- * `content` is the tokenizable user-prompt text; pass '' to leave the row unsearchable.
2376
- */
2377
1716
  export function upsertSession(meta, content, scan, assistantContent = '') {
2378
1717
  meta = enrichCachedSessionMeta(meta);
2379
- // Join the durable sessionId -> actor sidecar (RUSH-2019) when the caller
2380
- // didn't already carry an actor, so a scanned transcript still attributes to a
2381
- // person. The ON CONFLICT COALESCEs actor/initiated_by, so this fills a fresh
2382
- // row AND backfills one indexed null-first (before its sidecar existed), while a
2383
- // rescan carrying no actor still keeps the stored owner.
2384
1718
  const actorRec = meta.actor && meta.accountId ? undefined : readSessionActorRecord(meta.id);
2385
1719
  const toolUsage = detectToolUsage(meta.id);
2386
1720
  const db = getDB();
@@ -2393,10 +1727,6 @@ export function upsertSession(meta, content, scan, assistantContent = '') {
2393
1727
  origin: meta.origin ?? 'cli',
2394
1728
  routine_name: meta.routineName ?? null,
2395
1729
  routine_run_id: meta.routineRunId ?? null,
2396
- // Backfill the origin version from the launch-time sidecar when the transcript
2397
- // scan couldn't derive one (codex's `.codex-homes/<version>/` home) — the same
2398
- // write-once join as mode/harness, so native resume stops falling back to
2399
- // `/continue` for a missing recorded version (PHNX-3626).
2400
1730
  version: meta.version ?? actorRec?.version ?? null,
2401
1731
  account: meta.account ?? null,
2402
1732
  account_key: meta.accountKey ?? null,
@@ -2450,17 +1780,10 @@ export function upsertSession(meta, content, scan, assistantContent = '') {
2450
1780
  const txn = db.transaction(() => {
2451
1781
  upsert.run(row);
2452
1782
  delText.run(meta.id);
2453
- insText.run(meta.id, meta.id,
2454
- // Use the label the upsert actually stored (preserve-non-empty rule),
2455
- // not the raw incoming one, so FTS label ranking survives a bare rescan.
2456
- storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
1783
+ insText.run(meta.id, meta.id, storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
2457
1784
  });
2458
1785
  txn();
2459
1786
  }
2460
- /** A verified Codex rollout has one native owner. An older last-meta scan may
2461
- * have indexed its child bytes under the parent id. Reconcile only paths that
2462
- * were successfully written, after the whole batch, so a real parent rollout
2463
- * repaired in the same transaction keeps its own row regardless of scan order. */
2464
1787
  function reconcileCodexFileOwners(db, metas) {
2465
1788
  const owners = new Map(metas.filter(meta => meta.agent === 'codex' && meta.filePath).map(meta => [meta.filePath, meta.id]));
2466
1789
  const paths = [...owners.keys()];
@@ -2471,39 +1794,22 @@ function reconcileCodexFileOwners(db, metas) {
2471
1794
  for (const row of rows) {
2472
1795
  if (owners.get(row.file_path) === row.id)
2473
1796
  continue;
2474
- // The text row is keyed by the sessions rowid, so it goes before the row.
2475
1797
  db.prepare(`DELETE FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`).run(row.id);
2476
1798
  db.prepare('DELETE FROM sessions WHERE id = ?').run(row.id);
2477
1799
  for (const table of ['session_preview_cache', 'session_summaries', 'session_insights', 'session_topics', 'session_phenotypes', 'session_resource_usage', 'resource_scan_ledger']) {
2478
1800
  db.prepare(`DELETE FROM ${table} WHERE session_id = ?`).run(row.id);
2479
1801
  }
2480
- // The tool ledger has a unique file path too; release the wrong binding
2481
- // before the child evidence is persisted below. Transcript files stay put.
2482
1802
  purgeToolCalls(db, row.id);
2483
1803
  }
2484
1804
  }
2485
1805
  }
2486
- /** Batch-upsert sessions with their FTS5 content and scan stamps in a single transaction. */
2487
1806
  export function upsertSessionsBatch(entries) {
2488
1807
  if (entries.length === 0)
2489
1808
  return;
2490
1809
  const db = getDB();
2491
1810
  const { upsert, delText, insText, readLabel } = stmts(db);
2492
1811
  const now = Date.now();
2493
- // One directory read for the whole batch: join the durable sessionId -> actor
2494
- // sidecar (RUSH-2019) so scanned transcripts attribute to a person. Only used
2495
- // for entries whose meta carries no actor; the ON CONFLICT COALESCEs the column,
2496
- // so this fills fresh rows AND backfills null-first ones, never clobbering a
2497
- // stored owner on rescan.
2498
1812
  const actorIndex = loadSessionActorIndex();
2499
- // Persist the Claude resumable-parse continuation (parser_state + content_text)
2500
- // alongside the stamp, plus the CURRENT content extractor version — a caller
2501
- // that reached this batch write ran today's extractor over the file, so the
2502
- // ledger row is current regardless of which branch (full/incremental)
2503
- // produced it. On a full/incremental Claude parse the caller passes the
2504
- // serialized newState + accumulated user doc so the NEXT scan can resume from
2505
- // the persisted offset (B-2). Other scanners pass neither, leaving both columns
2506
- // NULL exactly as before — their ledger rows are unaffected.
2507
1813
  const ledger = db.prepare(`
2508
1814
  INSERT INTO scan_ledger (file_path, file_mtime_ms, file_size, scanned_at, parser_state, content_text, extractor_version)
2509
1815
  VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -2515,43 +1821,19 @@ export function upsertSessionsBatch(entries) {
2515
1821
  content_text = excluded.content_text,
2516
1822
  extractor_version = excluded.extractor_version
2517
1823
  `);
2518
- // Build a lookup from canonical file path → entry, used inside the write
2519
- // transaction to re-check the ledger AFTER acquiring the lock. When a
2520
- // concurrent process already committed the same files between our
2521
- // filterChangedFiles call and now, the ledger will have matching (mtime, size)
2522
- // rows — we skip those entries, making the second writer's transaction a
2523
- // near-instant no-op rather than redundant work.
2524
1824
  const byPath = new Map(entries
2525
1825
  .filter(e => e.scan && e.meta.filePath)
2526
1826
  .map(e => [canonicalLedgerKey(e.meta.filePath), e]));
2527
1827
  const enrichedEntries = entries.map(entry => {
2528
1828
  if (entry.meta.agent === 'claude' || entry.meta.agent === 'codex' || !entry.meta.filePath) {
2529
- // claude/codex keep their resumable-parse optimization (no transcript read
2530
- // here). When their scanner already handed us normalized events, the counts
2531
- // are free — take them. Otherwise leave them undefined, which persists NULL
2532
- // and means "not computed yet", and the next scan that carries events fills
2533
- // it. Never collapse that to 0: see fanOutCounts.
2534
1829
  return entry.events
2535
1830
  ? { ...entry, meta: enrichMetaFromEvents(entry.meta, entry.events) }
2536
1831
  : entry;
2537
1832
  }
2538
- // Harnesses whose scanners produce no events AND whose parseSession reads a
2539
- // potentially large flat transcript file (not a compact SQLite DB). Calling
2540
- // parseSession on the warm tick for an active large session wedges the Node
2541
- // event loop for seconds, making browser IPC miss its connection window
2542
- // (PHNX-3411). Defer their tool-call indexing to runDeferredToolIndex, which
2543
- // uses ensureToolIndex with tool_scan_ledger stamps and byte/file budget caps.
2544
- // NOT opencode — parseOpenCode issues a targeted SQLite query, so it is fast
2545
- // even for large sessions and its results populate recentDirectoriesTouched.
2546
1833
  const LARGE_TRANSCRIPT_AGENTS = new Set(['kimi', 'grok']);
2547
1834
  if (!entry.events && LARGE_TRANSCRIPT_AGENTS.has(entry.meta.agent)) {
2548
1835
  return entry;
2549
1836
  }
2550
- // Enrich the entry. When the scanner already provided events, use them
2551
- // directly (no transcript re-read). When it didn't — e.g. OpenCode whose
2552
- // scanner produces only metadata but whose parseOpenCode is a fast SQLite
2553
- // query — fall back to parseSession. Agents that would call an expensive
2554
- // flat-file parse already returned above.
2555
1837
  try {
2556
1838
  const toolSourcePath = toolEvidenceSourcePath(entry.meta.filePath, entry.meta.agent);
2557
1839
  const toolScan = toolSourcePath === entry.meta.filePath
@@ -2562,17 +1844,6 @@ export function upsertSessionsBatch(entries) {
2562
1844
  })();
2563
1845
  const events = entry.events ?? parseSession(entry.meta.filePath, entry.meta.agent);
2564
1846
  writeResourceUsage(entry.meta.id, events, entry.meta.cwd);
2565
- // Resume the tool index from the last scan of this append-only stream when
2566
- // it is safe to (PHNX-3411). A live session's transcript grows every tick,
2567
- // so a full re-derive re-sanitizes its ENTIRE tool history each time — the
2568
- // synchronous O(session) work that wedged the daemon event loop for the
2569
- // 11 non-streaming harnesses. `planEventToolResume` returns the prior
2570
- // snapshot only when the file is still an append of what was scanned
2571
- // before; otherwise `prior` is null and this is a full replace from
2572
- // event 0 (identical index, just re-derived).
2573
- // No tool stamp (a scanner that carried no scan record) means the resume
2574
- // point cannot be size-guarded, so full-scan rather than trust a stale
2575
- // offset. persistToolCalls below also skips a resume-less write.
2576
1847
  const prior = toolScan
2577
1848
  ? planEventToolResume(db, entry.meta.id, toolSourcePath, toolScan, events.length)
2578
1849
  : null;
@@ -2580,13 +1851,9 @@ export function upsertSessionsBatch(entries) {
2580
1851
  return {
2581
1852
  ...entry,
2582
1853
  meta: enrichMetaFromEvents(entry.meta, events),
2583
- // The CHANGED calls only. On a resume these are the newly appended tail
2584
- // (append-safe upsert); on a full scan they are the whole history.
2585
1854
  toolCalls: scanned.calls,
2586
1855
  toolScan,
2587
1856
  toolIndexMode: (prior ? 'append' : 'replace'),
2588
- // Persist where the NEXT scan resumes: the collector snapshot + how many
2589
- // events this scan folded.
2590
1857
  toolResume: { parserState: JSON.stringify(scanned.snapshot), parsedOffset: scanned.eventCount },
2591
1858
  };
2592
1859
  }
@@ -2595,16 +1862,9 @@ export function upsertSessionsBatch(entries) {
2595
1862
  }
2596
1863
  });
2597
1864
  const writtenEntries = [];
2598
- // Pre-compute browser/computer usage for all sessions outside the write
2599
- // transaction. detectToolUsage scans all event log files (O(files) I/O
2600
- // per call) and holding the SQLite write lock during that scan is what
2601
- // causes the "DB locked" errors (RUSH-2006). One pass for the whole batch
2602
- // costs O(files) total instead of O(N × files) inside the lock.
2603
1865
  const toolUsageBySession = queryToolUsageForSessions(new Set(enrichedEntries.map(e => e.meta.id)));
2604
1866
  const txn = db.transaction((items) => {
2605
- // Re-read the ledger now that we hold the write lock. Any file committed
2606
- // by a concurrent process since our pre-scan is visible here.
2607
- const CHUNK = 500; // stay under SQLite's 999-variable limit
1867
+ const CHUNK = 500;
2608
1868
  const alreadyIndexed = new Set();
2609
1869
  const paths = [...byPath.keys()];
2610
1870
  for (let i = 0; i < paths.length; i += CHUNK) {
@@ -2615,12 +1875,6 @@ export function upsertSessionsBatch(entries) {
2615
1875
  .all(...chunk);
2616
1876
  for (const row of rows) {
2617
1877
  const entry = byPath.get(row.file_path);
2618
- // A concurrent writer's row only makes THIS entry redundant when it is
2619
- // current at CONTENT_INDEX_VERSION too — otherwise a (mtime, size) match
2620
- // alone would make the version lever a no-op: the very reason this batch
2621
- // was scheduled (a stale extractor_version) would be silently skipped as
2622
- // "someone else already indexed it", when what they indexed predates the
2623
- // current extractor.
2624
1878
  if (entry &&
2625
1879
  row.file_mtime_ms === entry.scan.fileMtimeMs &&
2626
1880
  row.file_size === entry.scan.fileSize &&
@@ -2633,28 +1887,11 @@ export function upsertSessionsBatch(entries) {
2633
1887
  const { meta, content, assistantContent, scan, parserState, contentText } = entry;
2634
1888
  if (alreadyIndexed.has(meta.id))
2635
1889
  continue;
2636
- // Per-row guard: one malformed session (e.g. a required field that resolves to
2637
- // NULL) must not abort the whole batch and take down the entire `agents sessions`
2638
- // listing. A constraint error uses SQLite's ABORT resolution — it reverts only the
2639
- // failing statement, not the transaction — and the db.transaction wrapper only rolls
2640
- // back when the error escapes `fn`, so catching + skipping here leaves the txn valid
2641
- // and committable. We deliberately do NOT stamp the ledger for a skipped row, so the
2642
- // next scan re-tries it (self-healing once the underlying parser is fixed).
2643
1890
  const toolUsage = toolUsageBySession.get(meta.id) ?? { usedBrowser: false, usedComputer: false };
2644
- // claude/codex skip enrichCachedSessionMeta above (preserving their
2645
- // resumable-parse optimization) — write their pre-computed
2646
- // skillsUsed/slashCommandsUsed (folded incrementally by discover.ts's
2647
- // accumulator) here instead. Every other harness already got this
2648
- // write from enrichCachedSessionMeta() in the .map() above.
2649
1891
  if (meta.agent === 'claude' || meta.agent === 'codex') {
2650
1892
  writeResourceUsageFromTallies(meta.id, meta.skillsUsed ?? [], meta.slashCommandsUsed ?? [], meta.cwd);
2651
1893
  }
2652
1894
  try {
2653
- // Typed, not a bare literal: bun binds named parameters in strict mode, where a
2654
- // MISSING key throws (node binds NULL instead). A silently dropped key therefore
2655
- // breaks only the shipped binary's runtime, and the per-row catch below swallows
2656
- // it — the exact shape of the bug that shipped account_key unbound. Annotating
2657
- // against SessionRow makes tsc reject the next omission.
2658
1895
  const row = {
2659
1896
  id: meta.id,
2660
1897
  short_id: meta.shortId,
@@ -2663,8 +1900,6 @@ export function upsertSessionsBatch(entries) {
2663
1900
  origin: meta.origin ?? 'cli',
2664
1901
  routine_name: meta.routineName ?? null,
2665
1902
  routine_run_id: meta.routineRunId ?? null,
2666
- // Backfill the origin version from the launch-time sidecar when the scan
2667
- // couldn't derive one — see the single-row upsert above (PHNX-3626).
2668
1903
  version: meta.version ?? actorIndex.get(meta.id)?.version ?? null,
2669
1904
  account: meta.account ?? null,
2670
1905
  account_key: meta.accountKey ?? null,
@@ -2717,10 +1952,7 @@ export function upsertSessionsBatch(entries) {
2717
1952
  };
2718
1953
  upsert.run(row);
2719
1954
  delText.run(meta.id);
2720
- insText.run(meta.id, meta.id,
2721
- // Mirror upsertSession: index the label the upsert actually stored
2722
- // (preserve-non-empty rule), not the raw incoming one.
2723
- storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
1955
+ insText.run(meta.id, meta.id, storedFtsLabel(readLabel, meta.id), meta.topic ?? '', meta.project ?? '', content ?? '', assistantContent ?? '');
2724
1956
  if (scan && meta.filePath) {
2725
1957
  ledger.run(canonicalLedgerKey(meta.filePath), scan.fileMtimeMs, scan.fileSize, now, parserState ?? null, contentText ?? null, CONTENT_INDEX_VERSION);
2726
1958
  }
@@ -2735,38 +1967,21 @@ export function upsertSessionsBatch(entries) {
2735
1967
  reconcileCodexFileOwners(db, writtenEntries.map(entry => entry.meta));
2736
1968
  });
2737
1969
  txn(enrichedEntries);
2738
- // Tool evidence shares the transcript parse above but owns an independent
2739
- // transaction/ledger. If this write fails, the normal session row remains
2740
- // valid and ensureToolIndex retries from the missing tool ledger later.
2741
1970
  for (const entry of writtenEntries) {
2742
1971
  const toolScan = entry.toolScan ?? entry.scan;
2743
1972
  if (!toolScan || !entry.toolCalls)
2744
1973
  continue;
2745
1974
  try {
2746
- // `resume` is set only by the full-file harness path above; claude/codex
2747
- // pass none, so their tool ledger keeps carrying no event-offset resume
2748
- // point (their resume rides the content-scan ledger instead) — unchanged.
2749
1975
  persistToolCalls(db, entry.meta, entry.toolCalls, toolScan, {
2750
1976
  mode: entry.toolIndexMode ?? 'replace',
2751
1977
  resume: entry.toolResume,
2752
1978
  });
2753
1979
  }
2754
1980
  catch {
2755
- // Boundary is intentionally retryable via tool_scan_ledger.
2756
1981
  }
2757
1982
  }
2758
- // Every batch appends FTS segments (session_text always, tool_call_text for the
2759
- // harnesses indexed above). Pay a bounded slice of the merge here so the
2760
- // scan path keeps its own index healthy instead of leaving all compaction to
2761
- // the manual `agents sessions optimize` (RUSH-2208). Threshold-gated, so a
2762
- // small index costs two counts and nothing else.
2763
1983
  maintainSessionSearchIndex(db);
2764
1984
  }
2765
- /**
2766
- * Sync labels for a set of sessions. For each id in the map, if the stored
2767
- * label differs, update both `sessions.label` and the FTS5 label column.
2768
- * Leaves FTS5 content/topic/project untouched — cheap to call every run.
2769
- */
2770
1985
  export function syncLabels(labelMap) {
2771
1986
  if (labelMap.size === 0)
2772
1987
  return 0;
@@ -2782,8 +1997,6 @@ export function syncLabels(labelMap) {
2782
1997
  .all(...chunk);
2783
1998
  for (const row of rows) {
2784
1999
  const live = labelMap.get(row.id)?.trim() || null;
2785
- // A missing/empty live label means "no refinement yet", not "erase the
2786
- // generated title or launch handle already stored for this session".
2787
2000
  if (live && live !== (row.label ?? '')) {
2788
2001
  updates.push({ id: row.id, label: live });
2789
2002
  }
@@ -2802,23 +2015,6 @@ export function syncLabels(labelMap) {
2802
2015
  txn(updates);
2803
2016
  return updates.length;
2804
2017
  }
2805
- /**
2806
- * Seed session labels from `agents run --name` handles, keyed by session id.
2807
- *
2808
- * `--name` is the universal launch-time way to set a session's label — the same
2809
- * field an agent later refines with a generated title (`syncLabels`) or the user
2810
- * with `/rename`. The seed's source of truth lives outside the transcript (host
2811
- * task sidecars, run-name sidecars written at launch), so it is re-applied by id
2812
- * every scan rather than parsed per-file. It only fills a label that is still
2813
- * EMPTY — an agent-generated title always wins over the seed, so a Claude run's
2814
- * `--name` shows until Claude titles it, and a non-Claude run keeps its `--name`
2815
- * as the label. Writes both `sessions.label` and the FTS5 label column so a
2816
- * seeded name is fuzzy-searchable. Cheap to call every run; returns rows updated.
2817
- *
2818
- * Ordering matters: this runs AFTER the per-agent scans (which apply
2819
- * agent-generated titles via {@link syncLabels}), so it never overwrites a real
2820
- * title — it only backfills the gap the seed was meant to cover.
2821
- */
2822
2018
  export function seedLabelsFromNames(nameMap) {
2823
2019
  if (nameMap.size === 0)
2824
2020
  return 0;
@@ -2834,7 +2030,6 @@ export function seedLabelsFromNames(nameMap) {
2834
2030
  .all(...chunk);
2835
2031
  for (const row of rows) {
2836
2032
  const seed = nameMap.get(row.id);
2837
- // Only fill an empty label; a real agent title (non-empty) always wins.
2838
2033
  if (seed && !(row.label ?? '').trim()) {
2839
2034
  updates.push({ id: row.id, label: seed });
2840
2035
  }
@@ -2853,15 +2048,6 @@ export function seedLabelsFromNames(nameMap) {
2853
2048
  txn(updates);
2854
2049
  return updates.length;
2855
2050
  }
2856
- /**
2857
- * Sync topics (session titles) for a set of sessions, keyed by id. For agents
2858
- * whose human-readable title lives in a side index that updates independently
2859
- * of the transcript (Codex `session_index.jsonl`), the per-file scan can't see
2860
- * a title that lands later. This applies those titles by id, updating both
2861
- * `sessions.topic` and the FTS5 topic column. Only ever sets a non-empty title
2862
- * and only when it differs from the stored value — cheap to call every run.
2863
- * Returns the number of rows updated.
2864
- */
2865
2051
  export function syncTopics(topicMap) {
2866
2052
  if (topicMap.size === 0)
2867
2053
  return 0;
@@ -2895,7 +2081,6 @@ export function syncTopics(topicMap) {
2895
2081
  txn(updates);
2896
2082
  return updates.length;
2897
2083
  }
2898
- /** Convert a raw database row into a SessionMeta object. */
2899
2084
  function rowToMeta(row) {
2900
2085
  return {
2901
2086
  id: row.id,
@@ -2948,18 +2133,10 @@ function rowToMeta(row) {
2948
2133
  linearProjectUrl: row.linear_project_url ?? undefined,
2949
2134
  machine: row.machine ?? undefined,
2950
2135
  actor: row.actor ?? undefined,
2951
- // Narrow the free-text column to the known kinds; an unexpected value maps
2952
- // to undefined rather than being asserted as a valid kind.
2953
2136
  initiatedBy: row.initiated_by === 'human' || row.initiated_by === 'agent' ? row.initiated_by : undefined,
2954
2137
  phoenixId: row.phoenix_id ?? undefined,
2955
- // NULL = never computed by this scanner (legacy row) — leave undefined so
2956
- // the sessions picker knows to fall back to the transcript-regex detection
2957
- // instead of trusting a false "never used browser/computer".
2958
2138
  usedBrowser: row.used_browser === null ? undefined : row.used_browser === 1,
2959
2139
  usedComputer: row.used_computer === null ? undefined : row.used_computer === 1,
2960
- // A stamped archived_at means the transcript file is gone but the session's
2961
- // user turns still live in session_text — the row is served from the DB and
2962
- // flagged, never dropped (RUSH-2436). NULL leaves both undefined (live row).
2963
2140
  archivedAt: row.archived_at ?? undefined,
2964
2141
  archived: row.archived_at != null ? true : undefined,
2965
2142
  mirrorSyncedAt: row.mirror_synced_at ?? undefined,
@@ -2979,14 +2156,6 @@ function parseJsonColumn(value) {
2979
2156
  return undefined;
2980
2157
  }
2981
2158
  }
2982
- /**
2983
- * The recency signal used to sort and label the listing: last-message time when
2984
- * a parser computed it (`meta.lastActivity` from `lastTsMs`), else the file's
2985
- * mtime (its last write), else creation time. Guarded on `filePath` so synthetic
2986
- * / cloud rows (no local file) fall to their creation timestamp rather than a
2987
- * bogus scan-time mtime. Always an ISO string, so it sorts lexicographically
2988
- * against `timestamp` and feeds `formatRelativeTime` unchanged.
2989
- */
2990
2159
  function resolveLastActivity(meta, scan) {
2991
2160
  if (meta.lastActivity)
2992
2161
  return meta.lastActivity;
@@ -2994,26 +2163,6 @@ function resolveLastActivity(meta, scan) {
2994
2163
  return new Date(scan.fileMtimeMs).toISOString();
2995
2164
  return meta.timestamp;
2996
2165
  }
2997
- /**
2998
- * The persisted wall-clock span for a session (PHNX-3457).
2999
- *
3000
- * `durationMs` is canonically `lastTs − firstTs`. Some harness scan extractors
3001
- * derive it themselves from per-event timestamps (claude/codex/droid/gemini/
3002
- * opencode set `meta.durationMs`); the rest (rush/grok/kimi/cursor/muse/
3003
- * antigravity/hermes/openclaw) never did, so `duration_ms` landed NULL for them —
3004
- * 52% of the corpus, including 100% of the dominant `rush` usage — and the
3005
- * console median was computed over only the ~48% that happened to carry it,
3006
- * skewing it short and misleading.
3007
- *
3008
- * This closes the gap at the single write boundary every harness funnels
3009
- * through, so parity is automatic rather than per-extractor: when the extractor
3010
- * already computed a precise span, keep it; otherwise derive it from the same
3011
- * `timestamp` (creation) and `resolveLastActivity` (last event, itself resolved
3012
- * from the harness's last-message time, else file mtime) that the row already
3013
- * stores. Returns null only when no positive span can be established (a single
3014
- * timestamped event, or a clock that runs backwards), which reads as NULL rather
3015
- * than a fabricated 0.
3016
- */
3017
2166
  function resolveDurationMs(meta, scan) {
3018
2167
  if (meta.durationMs != null)
3019
2168
  return meta.durationMs;
@@ -3029,17 +2178,10 @@ export function isSessionActivityFresh(row, maxAgeMs, nowMs) {
3029
2178
  const activityMs = Number.isFinite(parsedActivityMs) ? parsedActivityMs : row.file_mtime_ms ?? undefined;
3030
2179
  return activityMs != null && nowMs - activityMs <= maxAgeMs;
3031
2180
  }
3032
- /** Persist a lazily resolved Linear project without reparsing the transcript. */
3033
2181
  export function cacheLinearProject(sessionId, project, projectUrl) {
3034
2182
  getDB().prepare(`UPDATE sessions SET linear_project = ?, linear_project_url = ? WHERE id = ?`)
3035
2183
  .run(project, projectUrl, sessionId);
3036
2184
  }
3037
- /**
3038
- * Newest indexed session file for an agent working in `cwd`. Lets the live
3039
- * `--active` scanner locate a Codex transcript (whose files are date-partitioned,
3040
- * not cwd-keyed like Claude's) by reusing the index. Returns undefined if the
3041
- * session hasn't been scanned yet — the caller degrades to no live state.
3042
- */
3043
2185
  export function latestSessionFileForCwd(agent, cwd, options) {
3044
2186
  if (!cwd)
3045
2187
  return undefined;
@@ -3047,7 +2189,7 @@ export function latestSessionFileForCwd(agent, cwd, options) {
3047
2189
  try {
3048
2190
  normalized = fs.realpathSync(cwd);
3049
2191
  }
3050
- catch { /* use as-is */ }
2192
+ catch { }
3051
2193
  const db = getDB();
3052
2194
  const row = db
3053
2195
  .prepare(`SELECT file_path, last_activity, timestamp, file_mtime_ms
@@ -3064,7 +2206,6 @@ export function latestSessionFileForCwd(agent, cwd, options) {
3064
2206
  }
3065
2207
  return row.file_path;
3066
2208
  }
3067
- /** Build a parameterized WHERE clause from query options. */
3068
2209
  function buildSessionWhere(options) {
3069
2210
  const where = [];
3070
2211
  const params = [];
@@ -3089,12 +2230,6 @@ function buildSessionWhere(options) {
3089
2230
  params.push(options.cwd);
3090
2231
  }
3091
2232
  if (options.cwdPrefix) {
3092
- // A LOCAL stored cwd uses the host path separator (normalizeCwd runs
3093
- // path.normalize on it), so the subdir wildcard must too — a hardcoded '/'
3094
- // never matches a Windows `C:\a\b` subpath and the listing comes back empty.
3095
- // A cwd recorded on another machine keeps its own separators, so this
3096
- // wildcard does not match foreign subpaths; both sides go through
3097
- // normalizeCwd, so the exact `cwd = ?` comparison still holds for them.
3098
2233
  where.push('(cwd = ? OR cwd LIKE ?)');
3099
2234
  params.push(options.cwdPrefix, options.cwdPrefix + path.sep + '%');
3100
2235
  }
@@ -3106,10 +2241,6 @@ function buildSessionWhere(options) {
3106
2241
  where.push('machine = ? COLLATE NOCASE');
3107
2242
  params.push(options.machine);
3108
2243
  }
3109
- // id lookup. SQLite's LIKE is case-insensitive for ASCII, so a lowercased
3110
- // pattern matches mixed-case ids; the `=` exact compare adds COLLATE NOCASE
3111
- // for the same reason. short_id carries its own index (idx_sessions_short_id);
3112
- // id is the PRIMARY KEY.
3113
2244
  if (options.idExact) {
3114
2245
  where.push('(id = ? COLLATE NOCASE OR short_id = ? COLLATE NOCASE OR routine_run_id = ? COLLATE NOCASE)');
3115
2246
  params.push(options.idExact, options.idExact, options.idExact);
@@ -3119,7 +2250,6 @@ function buildSessionWhere(options) {
3119
2250
  params.push(`${options.idPrefix}%`, `${options.idPrefix}%`, `${options.idPrefix}%`);
3120
2251
  }
3121
2252
  if (typeof options.sinceMs === 'number') {
3122
- // Compare as strings; ISO 8601 timestamps sort lexicographically.
3123
2253
  where.push('timestamp >= ?');
3124
2254
  params.push(new Date(options.sinceMs).toISOString());
3125
2255
  }
@@ -3133,10 +2263,6 @@ function buildSessionWhere(options) {
3133
2263
  if (options.onlyTeamOrigin) {
3134
2264
  where.push('IFNULL(is_team_origin, 0) = 1');
3135
2265
  }
3136
- // #12: join against session_resource_usage. A subquery IN, not a real JOIN
3137
- // on the base SELECT, keeps `SELECT * FROM sessions` untouched for every
3138
- // other caller of buildSessionWhere() (countSessions, the usage rollup, …)
3139
- // that never wants a skill/plugin filter.
3140
2266
  if (options.skill) {
3141
2267
  where.push(`id IN (
3142
2268
  SELECT session_id FROM session_resource_usage
@@ -3154,23 +2280,14 @@ function buildSessionWhere(options) {
3154
2280
  const directoryMembershipCache = new Map();
3155
2281
  let directoryMembershipSweepCount = 0;
3156
2282
  const DIRECTORY_MTIME_SETTLE_MS = 2_000;
3157
- /** Process-local diagnostics for the real-filesystem existence-cache tests. */
3158
2283
  export function getSessionExistenceCacheStats() {
3159
2284
  return { sweeps: directoryMembershipSweepCount };
3160
2285
  }
3161
- /** Clear process-local directory membership state when the session DB closes. */
3162
2286
  function clearSessionExistenceCache() {
3163
2287
  directoryMembershipCache.clear();
3164
2288
  directoryMembershipSweepCount = 0;
3165
2289
  }
3166
2290
  function findMissingFilePaths(filePaths) {
3167
- // Existence is decided on the CONTAINER file, never the raw stored path. A
3168
- // composite `file_path` (`<container>#<id>`, e.g. OpenCode's `opencode.db#ses_…`)
3169
- // names a row INSIDE a shared file — its basename is never a directory entry,
3170
- // so a dirname/basename membership check on the composite string classified
3171
- // every such row as deleted and pruned it (RUSH-2357). Group the original
3172
- // paths under the container we actually stat, and map the verdict back so the
3173
- // returned set still holds the original `file_path` strings the caller keys on.
3174
2291
  const byDir = new Map();
3175
2292
  for (const p of filePaths) {
3176
2293
  const container = sessionFilePathContainer(p);
@@ -3212,9 +2329,6 @@ function findMissingFilePaths(filePaths) {
3212
2329
  }
3213
2330
  catch {
3214
2331
  directoryMembershipCache.delete(dir);
3215
- // Directory itself is gone (or unreadable) — every file in it is missing.
3216
- // Also covers the race where readdir loses to a concurrent delete: fall
3217
- // back to a direct stat rather than assuming existence.
3218
2332
  for (const [base, originals] of bases) {
3219
2333
  const filePath = path.join(dir, base);
3220
2334
  if (!fs.existsSync(filePath))
@@ -3229,25 +2343,12 @@ function findMissingFilePaths(filePaths) {
3229
2343
  }
3230
2344
  return missing;
3231
2345
  }
3232
- /** Query sessions from the database, applying filters and ordering by last-activity descending (default). */
3233
2346
  export function querySessions(options = {}) {
3234
2347
  const db = getDB();
3235
2348
  const { clause, params } = buildSessionWhere(options);
3236
- // When a LIMIT is in play, we still need to filter stale rows AFTER the query,
3237
- // so over-fetch a small buffer. Without this, a page of 50 rows where the first
3238
- // 5 are stale would return only 45 to the caller even when there are more.
3239
2349
  const limitClause = options.limit
3240
2350
  ? `LIMIT ${Math.max(1, Math.floor(options.limit)) + 16}`
3241
2351
  : '';
3242
- // NULLs last so unpriced / duration-less rows never crowd out real data when
3243
- // sorting by cost or duration. timestamp is never null (NOT NULL column).
3244
- // Default sort is the bare `last_activity` column, not `IFNULL(last_activity,
3245
- // timestamp)` — the v35 migration backfills every row so last_activity is
3246
- // never NULL, and every upsert path (resolveLastActivity) keeps it that way
3247
- // going forward. Wrapping the column in IFNULL() defeats
3248
- // idx_sessions_last_activity (SQLite can't use an index on an expression that
3249
- // isn't the bare column); the bare column lets the planner walk the index
3250
- // instead of sorting the whole result set (RUSH-2211).
3251
2352
  const orderClause = options.sortBy === 'cost'
3252
2353
  ? 'ORDER BY cost_usd IS NULL, cost_usd DESC, timestamp DESC'
3253
2354
  : options.sortBy === 'duration'
@@ -3259,19 +2360,6 @@ export function querySessions(options = {}) {
3259
2360
  const trimmed = options.limit ? rows.slice(0, options.limit) : rows;
3260
2361
  return trimmed.map(rowToMeta);
3261
2362
  }
3262
- // A row whose transcript file is gone from disk is one of two things, and the
3263
- // local DB is now authoritative for telling them apart (RUSH-2436):
3264
- // - ARCHIVED — its user turns still live in session_text. The session is
3265
- // real; the file was just removed (agents remove trash, a manual rm, a
3266
- // .history rotation, a box reimage). Keep it, stamped `archived`, so it
3267
- // still lists and renders from the DB instead of silently vanishing.
3268
- // - PHANTOM — a stale/moved file_path with NO cached content (#136), e.g. a
3269
- // path a scan forgot to rewrite. There is nothing durable to serve, so it
3270
- // stays suppressed exactly as before.
3271
- // We NO LONGER purge tool-call evidence here: merely listing a file-gone
3272
- // session used to DELETE its redacted tool calls (purgeToolCalls), destroying
3273
- // durable data on a read. Synthetic rows (OpenClaw channels/cron — see
3274
- // scanOpenClawIncremental) carry an empty file_path and are exempt.
3275
2363
  const missingPaths = findMissingFilePaths(rows.map(r => r.file_path).filter((p) => !!p));
3276
2364
  const missing = rows.filter(r => r.file_path && missingPaths.has(r.file_path));
3277
2365
  const missingIds = new Set(missing.map(r => r.id));
@@ -3284,8 +2372,6 @@ export function querySessions(options = {}) {
3284
2372
  for (const row of missing) {
3285
2373
  const content = readContent.get(row.id)?.content;
3286
2374
  if (content && content.trim() !== '') {
3287
- // Genuine archived session: stamp archived_at the first time we confirm
3288
- // the file is gone, and reflect it on the in-memory row we return.
3289
2375
  if (row.archived_at == null) {
3290
2376
  markArchived.run(now, row.id);
3291
2377
  row.archived_at = now;
@@ -3298,10 +2384,6 @@ export function querySessions(options = {}) {
3298
2384
  });
3299
2385
  classify();
3300
2386
  }
3301
- // Un-archive a row whose file came back (a recoverable-trash restore, a re-sync):
3302
- // it is present on disk now, so it must not keep reporting `archived` to a
3303
- // machine consumer reading the --json listing. Rare, so the write only fires when
3304
- // such a row actually exists (RUSH-2436).
3305
2387
  const resurrected = rows.filter(r => r.archived_at != null && !missingIds.has(r.id));
3306
2388
  if (resurrected.length > 0) {
3307
2389
  const clearArchived = db.prepare(`UPDATE sessions SET archived_at = NULL WHERE id = ?`);
@@ -3317,15 +2399,6 @@ export function querySessions(options = {}) {
3317
2399
  const trimmed = options.limit ? live.slice(0, options.limit) : live;
3318
2400
  return trimmed.map(rowToMeta);
3319
2401
  }
3320
- /**
3321
- * Cheap query for the daemon's deferred tool-index pass (PHNX-3411).
3322
- *
3323
- * Returns the most-recently-active sessions whose parseSession reads a large
3324
- * flat transcript (kimi: wire.jsonl, grok: chat_history.jsonl). Their scanners
3325
- * produce no events, so upsertSessionsBatch skips them in the warm tick to
3326
- * avoid wedging the event loop. ensureToolIndex uses tool_scan_ledger stamps
3327
- * to skip already-current rows and applies byte/file budget caps.
3328
- */
3329
2402
  export function querySessionsForDeferredToolIndex(limit) {
3330
2403
  const db = getDB();
3331
2404
  const rows = db.prepare(`
@@ -3337,7 +2410,6 @@ export function querySessionsForDeferredToolIndex(limit) {
3337
2410
  `).all(limit);
3338
2411
  return rows.map(rowToMeta);
3339
2412
  }
3340
- /** Count sessions matching the given filter options. */
3341
2413
  export function countSessions(options = {}) {
3342
2414
  const db = getDB();
3343
2415
  const { clause, params } = buildSessionWhere(options);
@@ -3345,21 +2417,12 @@ export function countSessions(options = {}) {
3345
2417
  const row = db.prepare(sql).get(...params);
3346
2418
  return row ? row.n : 0;
3347
2419
  }
3348
- /** What to group a usage rollup by. */
3349
- /**
3350
- * Read cached facets for the given sessions, dropping any row that is stale.
3351
- *
3352
- * Staleness is decided in SQL against the session's own `file_mtime_ms` / `file_size`,
3353
- * the same pair the scanner maintains — so the cache cannot disagree with the index,
3354
- * `IS` rather than `=` so a source with no statable file — NULL on both sides — is a
3355
- * cache HIT rather than a permanent miss that re-parses it on every run.
3356
- */
3357
2420
  export function readSessionInsights(ids) {
3358
2421
  const db = getDB();
3359
2422
  const out = new Map();
3360
2423
  if (ids.length === 0)
3361
2424
  return out;
3362
- const CHUNK = 400; // chunk.length + 1 binds, well under SQLite's 999-variable limit
2425
+ const CHUNK = 400;
3363
2426
  for (let i = 0; i < ids.length; i += CHUNK) {
3364
2427
  const chunk = ids.slice(i, i + CHUNK);
3365
2428
  const phs = chunk.map(() => '?').join(',');
@@ -3377,22 +2440,11 @@ export function readSessionInsights(ids) {
3377
2440
  out.set(row.id, JSON.parse(row.facets));
3378
2441
  }
3379
2442
  catch {
3380
- // A corrupt cache row is not a reason to fail the report; recompute it.
3381
2443
  }
3382
2444
  }
3383
2445
  }
3384
2446
  return out;
3385
2447
  }
3386
- /**
3387
- * Persist freshly computed facets against the stamp of the bytes actually parsed.
3388
- *
3389
- * The caller passes the stat it observed when it read the file. Re-reading the stamp
3390
- * from the sessions table inside this INSERT would race: a concurrent rescan between
3391
- * the parse and the write (the cold path flushes in batches, so the window is minutes
3392
- * wide, and this module treats concurrent access as a design assumption) stamps NEW
3393
- * bytes onto OLD facets — a permanent false cache hit until the file changes again.
3394
- * tool-index.ts sets the precedent: stat at parse time, carry the stamp into the write.
3395
- */
3396
2448
  export function writeSessionInsights(entries) {
3397
2449
  if (entries.length === 0)
3398
2450
  return;
@@ -3415,11 +2467,9 @@ export function writeSessionInsights(entries) {
3415
2467
  }
3416
2468
  })();
3417
2469
  }
3418
- /** Drop every cached facet row. Backs `agents insights --refresh`. */
3419
2470
  export function clearSessionInsights() {
3420
2471
  getDB().exec(`DELETE FROM session_insights`);
3421
2472
  }
3422
- /** Read cached trace topics only when their transcript byte stamps still match. */
3423
2473
  export function readSessionTopics(ids) {
3424
2474
  const db = getDB();
3425
2475
  const out = new Map();
@@ -3443,13 +2493,11 @@ export function readSessionTopics(ids) {
3443
2493
  out.set(row.id, JSON.parse(row.topicJson));
3444
2494
  }
3445
2495
  catch {
3446
- // Invalid derived cache data is a miss and self-heals on the next write.
3447
2496
  }
3448
2497
  }
3449
2498
  }
3450
2499
  return out;
3451
2500
  }
3452
- /** Persist trace topics against the exact transcript bytes used to classify them. */
3453
2501
  export function writeSessionTopics(entries) {
3454
2502
  if (entries.length === 0)
3455
2503
  return;
@@ -3472,7 +2520,6 @@ export function writeSessionTopics(entries) {
3472
2520
  }
3473
2521
  })();
3474
2522
  }
3475
- /** Read cached failure phenotypes only when their transcript byte stamps still match. */
3476
2523
  export function readSessionPhenotypes(ids) {
3477
2524
  const db = getDB();
3478
2525
  const out = new Map();
@@ -3496,13 +2543,11 @@ export function readSessionPhenotypes(ids) {
3496
2543
  out.set(row.id, JSON.parse(row.phenotypeJson));
3497
2544
  }
3498
2545
  catch {
3499
- // Invalid derived cache data is a miss and self-heals on the next write.
3500
2546
  }
3501
2547
  }
3502
2548
  }
3503
2549
  return out;
3504
2550
  }
3505
- /** Persist failure phenotypes against the exact transcript bytes used to classify them. */
3506
2551
  export function writeSessionPhenotypes(entries) {
3507
2552
  if (entries.length === 0)
3508
2553
  return;
@@ -3525,7 +2570,6 @@ export function writeSessionPhenotypes(entries) {
3525
2570
  }
3526
2571
  })();
3527
2572
  }
3528
- /** Read one derived preview only when it matches the transcript bytes on disk. */
3529
2573
  export function readSessionPreviewCache(id, sourceStamp) {
3530
2574
  const row = getDB().prepare(`
3531
2575
  SELECT pc.preview_json AS previewJson
@@ -3544,7 +2588,6 @@ export function readSessionPreviewCache(id, sourceStamp) {
3544
2588
  return undefined;
3545
2589
  }
3546
2590
  }
3547
- /** Persist normalized preview data against the exact transcript bytes parsed. */
3548
2591
  export function writeSessionPreviewCache(entry) {
3549
2592
  getDB().prepare(`
3550
2593
  INSERT INTO session_preview_cache
@@ -3558,38 +2601,10 @@ export function writeSessionPreviewCache(entry) {
3558
2601
  preview_json = excluded.preview_json
3559
2602
  `).run(entry.id, entry.fileMtimeMs, entry.fileSize, PREVIEW_EXTRACTOR_VERSION, Date.now(), JSON.stringify(entry.preview));
3560
2603
  }
3561
- /** Bump when the cached remote preview envelope shape changes so cached rows recompute (PHNX-3999 v1). */
3562
2604
  export const REMOTE_PREVIEW_SCHEMA_VERSION = 1;
3563
- /** Cap on distinct (device, sessionId) rows this cache holds — a requester box
3564
- * previews many peers' sessions over a long uptime, so this bounds growth the
3565
- * same way the fleet session mirror bounds itself to 200 rows (db.ts, PHNX-3792):
3566
- * oldest-by-last-fetch is evicted first. Row count alone is not a real bound —
3567
- * every field the cached envelope carries is already bounded (digest fields,
3568
- * `detail.messages` <= 8 x 4000 chars, `detail.timeline` <= 8 steps, etc.), but
3569
- * 500 rows x an unbounded per-row size is still unbounded storage, so this is
3570
- * paired with a total-byte budget below. */
3571
2605
  const REMOTE_PREVIEW_CACHE_MAX_ROWS = 500;
3572
- /**
3573
- * Total on-disk budget for `envelope_json` across every cached row, enforced
3574
- * ALONGSIDE the row cap (whichever evicts more aggressively wins). At the
3575
- * 500-row cap this is a ~32 KiB/row average, which comfortably fits the
3576
- * bounded envelope shape above — a single row is refused entry above
3577
- * {@link REMOTE_PREVIEW_ENVELOPE_MAX_BYTES} rather than being allowed to
3578
- * consume the whole budget by itself.
3579
- */
3580
2606
  const REMOTE_PREVIEW_CACHE_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
3581
- /**
3582
- * Per-envelope cache-write cap. The bounded fields above make a well-formed
3583
- * envelope small; this exists to refuse an outlier (a version-skewed peer, or
3584
- * a future field that forgets to bound itself) rather than let ONE session
3585
- * blow the total budget. A refused write is NOT an error — the caller still
3586
- * gets the live envelope for this call, it simply is not persisted, so the
3587
- * next call re-fetches instead of silently caching a giant blob.
3588
- */
3589
2607
  export const REMOTE_PREVIEW_ENVELOPE_MAX_BYTES = 512 * 1024;
3590
- /** Read the durable cached remote preview row for one (device, sessionId) pair,
3591
- * regardless of whether it currently holds a successful envelope. Undefined
3592
- * means this box has never attempted (or recorded) a fetch for that pair. */
3593
2608
  export function readRemotePreviewCache(device, sessionId) {
3594
2609
  const row = getDB().prepare(`
3595
2610
  SELECT fetched_at AS fetchedAt, ok, envelope_json AS envelopeJson, failure_reason AS failureReason,
@@ -3606,7 +2621,6 @@ export function readRemotePreviewCache(device, sessionId) {
3606
2621
  envelope = JSON.parse(row.envelopeJson);
3607
2622
  }
3608
2623
  catch {
3609
- // Corrupt cache row: treat as no payload rather than throwing.
3610
2624
  }
3611
2625
  }
3612
2626
  return {
@@ -3619,14 +2633,6 @@ export function readRemotePreviewCache(device, sessionId) {
3619
2633
  lastCallerRevision: row.lastCallerRevision ?? undefined,
3620
2634
  };
3621
2635
  }
3622
- /**
3623
- * Record the caller's own `--revision` cursor for one (device, sessionId)
3624
- * pair, independent of any fetch. A no-op if no row exists yet (nothing to
3625
- * compare against on a session this box has never fetched). This is what lets
3626
- * "same revision as last time" serve the durable cache with zero SSH
3627
- * indefinitely — the comparison is against THIS value, never against the
3628
- * envelope's own `details.sourceRevision` (which may be a different format).
3629
- */
3630
2636
  export function writeRemotePreviewCallerRevision(device, sessionId, revision) {
3631
2637
  getDB().prepare(`
3632
2638
  UPDATE session_remote_preview_cache
@@ -3642,8 +2648,6 @@ function pruneRemotePreviewCache(maxRows = REMOTE_PREVIEW_CACHE_MAX_ROWS) {
3642
2648
  SELECT rowid FROM session_remote_preview_cache ORDER BY fetched_at DESC LIMIT ?
3643
2649
  )
3644
2650
  `).run(maxRows);
3645
- // Total-byte budget, independent of row count: walk newest-first, keep
3646
- // rows until the running total would exceed the budget, drop the rest.
3647
2651
  const rows = db.prepare(`
3648
2652
  SELECT rowid AS rowid,
3649
2653
  envelope_bytes + length(CAST(COALESCE(failure_reason, '') AS BLOB))
@@ -3663,13 +2667,6 @@ function pruneRemotePreviewCache(maxRows = REMOTE_PREVIEW_CACHE_MAX_ROWS) {
3663
2667
  db.prepare(`DELETE FROM session_remote_preview_cache WHERE rowid IN (${placeholders})`).run(...evict);
3664
2668
  }
3665
2669
  }
3666
- /**
3667
- * Record a successful remote fetch: replaces the payload and resets backoff.
3668
- * An envelope over {@link REMOTE_PREVIEW_ENVELOPE_MAX_BYTES} is refused —
3669
- * this is a write-path bound, not a request failure: the caller already has
3670
- * the live envelope for this call from the fetch that produced it, this only
3671
- * decides whether it is worth persisting.
3672
- */
3673
2670
  export function writeRemotePreviewCacheSuccess(device, sessionId, envelope, fetchedAt = Date.now(), revision) {
3674
2671
  const envelopeJson = JSON.stringify(envelope);
3675
2672
  const envelopeBytes = Buffer.byteLength(envelopeJson, 'utf8');
@@ -3695,23 +2692,12 @@ export function writeRemotePreviewCacheSuccess(device, sessionId, envelope, fetc
3695
2692
  });
3696
2693
  write();
3697
2694
  }
3698
- /** Cap on a stored `failure_reason` string. Some reasons interpolate
3699
- * peer-controlled content (`validateEnvelope`'s mismatched-id message
3700
- * embeds the peer's own claimed session id) — bound it so a misbehaving or
3701
- * malicious peer can't inflate a cache row's stored text unboundedly. */
3702
2695
  const REMOTE_PREVIEW_FAILURE_REASON_MAX_CHARS = 300;
3703
2696
  function boundFailureReason(reason) {
3704
2697
  return reason.length > REMOTE_PREVIEW_FAILURE_REASON_MAX_CHARS
3705
2698
  ? reason.slice(0, REMOTE_PREVIEW_FAILURE_REASON_MAX_CHARS) + '…'
3706
2699
  : reason;
3707
2700
  }
3708
- /**
3709
- * Record a failed remote fetch attempt with exponential backoff, WITHOUT
3710
- * discarding a prior good envelope — a session that answered once and is now
3711
- * offline still degrades to that last-good payload (read back via
3712
- * {@link readRemotePreviewCache}'s `ok`/`envelope`), annotated stale by the
3713
- * caller, rather than losing it the moment one attempt fails.
3714
- */
3715
2701
  export function writeRemotePreviewCacheFailure(device, sessionId, reason, backoffMs, now = Date.now()) {
3716
2702
  const boundedReason = boundFailureReason(reason);
3717
2703
  const db = getDB();
@@ -3734,36 +2720,14 @@ export function writeRemotePreviewCacheFailure(device, sessionId, reason, backof
3734
2720
  consecutive_failures = excluded.consecutive_failures,
3735
2721
  next_attempt_at = excluded.next_attempt_at
3736
2722
  `).run(device, sessionId, REMOTE_PREVIEW_SCHEMA_VERSION, existing?.fetchedAt ?? now, keepOk, envelopeJson, envelopeBytes, boundedReason, consecutiveFailures, nextAttemptAt);
3737
- // A failure-only row still occupies a slot in the row/byte-bounded cache —
3738
- // it must be pruned like any other write, or a persistently-offline peer's
3739
- // failure rows accumulate without bound (they were previously exempt from
3740
- // this call entirely).
3741
2723
  pruneRemotePreviewCache();
3742
2724
  });
3743
2725
  write();
3744
2726
  }
3745
- /**
3746
- * The session's durable user-turn text, as stored in the `session_text` FTS
3747
- * `content` column at scan time (all harnesses, keyed by session_id). This is
3748
- * the content that survives the transcript file being deleted (RUSH-2436): the
3749
- * render path reads it so a file-gone session still shows its user prompts, and
3750
- * querySessions uses its presence to tell a genuine archived session (has
3751
- * content) from a phantom (a stale/moved file_path with none). Returns undefined
3752
- * when no row exists; an empty string when a row exists but carried no content.
3753
- */
3754
2727
  export function readSessionContent(id) {
3755
2728
  const row = getDB().prepare(`SELECT content FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`).get(id);
3756
2729
  return row?.content;
3757
2730
  }
3758
- /**
3759
- * Read the last-computed preview digest for a session by id, validated against
3760
- * the session row's OWN stored file stamp rather than a live `fs.stat` — the
3761
- * transcript file is gone, so there is nothing on disk to stat, but the preview
3762
- * cache was written with the same `file_mtime_ms`/`file_size` the sessions row
3763
- * still records, so the join re-establishes that stamp. Backs the file-gone
3764
- * render/preview path (RUSH-2436). Returns undefined when no cached digest
3765
- * survives for this session.
3766
- */
3767
2731
  export function readArchivedSessionPreview(id) {
3768
2732
  const row = getDB().prepare(`
3769
2733
  SELECT pc.preview_json AS previewJson
@@ -3783,11 +2747,6 @@ export function readArchivedSessionPreview(id) {
3783
2747
  return undefined;
3784
2748
  }
3785
2749
  }
3786
- /**
3787
- * Read one summary only when it matches the transcript bytes on disk (the
3788
- * reuse-or-recompute stamp the SessionSummarizerService gates on). Mirrors
3789
- * {@link readSessionPreviewCache}.
3790
- */
3791
2750
  export function readSessionSummary(id, sourceStamp) {
3792
2751
  const row = getDB().prepare(`
3793
2752
  SELECT summary_json AS summaryJson
@@ -3806,14 +2765,6 @@ export function readSessionSummary(id, sourceStamp) {
3806
2765
  return undefined;
3807
2766
  }
3808
2767
  }
3809
- /**
3810
- * Read the latest stored summary by session id, regardless of the transcript
3811
- * stamp — the low-cost merge read used by the display path (a live row through
3812
- * {@link applyImmutableMemo} or a history/mirror row) to surface the
3813
- * last-computed summary without a model call. A slightly-stale summary is the
3814
- * best available until the service recomputes; the SERVICE uses the stamped
3815
- * {@link readSessionSummary} to decide whether to recompute.
3816
- */
3817
2768
  export function readSessionSummaryAny(id) {
3818
2769
  const row = getDB().prepare(`
3819
2770
  SELECT summary_json AS summaryJson
@@ -3829,7 +2780,6 @@ export function readSessionSummaryAny(id) {
3829
2780
  return undefined;
3830
2781
  }
3831
2782
  }
3832
- /** Persist a computed summary against the exact transcript bytes it was derived from. */
3833
2783
  export function writeSessionSummary(entry) {
3834
2784
  getDB().prepare(`
3835
2785
  INSERT INTO session_summaries
@@ -3851,11 +2801,6 @@ function parseTimelineProjection(json) {
3851
2801
  return undefined;
3852
2802
  }
3853
2803
  }
3854
- /**
3855
- * Read the projection AND the resume state for one session, whatever the
3856
- * transcript stamp — the daemon pass's read, which needs the state to continue
3857
- * from and re-stats the file itself.
3858
- */
3859
2804
  export function readSessionTimelineEntry(id) {
3860
2805
  const row = getDB().prepare(`
3861
2806
  SELECT projection_json AS projectionJson, state_json AS stateJson, computed_at AS computedAt
@@ -3874,11 +2819,6 @@ export function readSessionTimelineEntry(id) {
3874
2819
  return undefined;
3875
2820
  }
3876
2821
  }
3877
- /**
3878
- * Read only the bounded projection for one session — the display merge, on the
3879
- * read path. Deliberately does NOT parse the resume state: a live row needs the
3880
- * 8 steps and the request, never the fold's bookkeeping.
3881
- */
3882
2822
  export function readSessionTimelineAny(id, stamp) {
3883
2823
  const row = getDB().prepare(`
3884
2824
  SELECT projection_json AS projectionJson
@@ -3890,7 +2830,6 @@ export function readSessionTimelineAny(id, stamp) {
3890
2830
  return undefined;
3891
2831
  return parseTimelineProjection(row.projectionJson);
3892
2832
  }
3893
- /** Persist a folded timeline against the exact transcript bytes it was folded to. */
3894
2833
  export function writeSessionTimeline(entry) {
3895
2834
  const { state, ...projection } = entry.timeline;
3896
2835
  getDB().prepare(`
@@ -3906,13 +2845,6 @@ export function writeSessionTimeline(entry) {
3906
2845
  state_json = excluded.state_json
3907
2846
  `).run(entry.id, entry.fileMtimeMs, entry.fileSize, TIMELINE_EXTRACTOR_VERSION, entry.computedAtMs ?? Date.now(), JSON.stringify(projection), JSON.stringify(state));
3908
2847
  }
3909
- /**
3910
- * The most-recently-active sessions whose transcript is genuinely local to this
3911
- * box — a real `file_path`, not a peer mirror — for publishing to the fleet
3912
- * session mirror (PHNX-3792). Bounded and team-origin-excluded so the published
3913
- * payload stays the size a picker would show. Never returns a row this box is
3914
- * itself mirroring from a peer (`mirror_synced_at IS NULL`).
3915
- */
3916
2848
  export function queryLocalOriginSessionsForMirror(self, limit) {
3917
2849
  const rows = getDB().prepare(`
3918
2850
  SELECT id, short_id, agent, version, machine, cwd, topic, first_user_message,
@@ -3931,23 +2863,10 @@ export function queryLocalOriginSessionsForMirror(self, limit) {
3931
2863
  cwd: r.cwd, topic: r.topic, firstUserMessage: r.first_user_message, label: r.label,
3932
2864
  generatedTitle: r.generated_title,
3933
2865
  lastActivity: r.last_activity, timestamp: r.timestamp, ticketId: r.ticket_id, prUrl: r.pr_url,
3934
- // Ride the daemon-computed summary alongside the digest so a peer renders it
3935
- // without a transcript (PHNX-3939); only a summarized session carries one.
3936
2866
  summary: readSessionSummaryAny(r.id) ?? null,
3937
2867
  timeline: readSessionTimelineAny(r.id) ?? null,
3938
2868
  }));
3939
2869
  }
3940
- /**
3941
- * Write one peer session's digest into the local `sessions` index as a mirror
3942
- * row (PHNX-3792), so the picker/list/focus render its topic/preview inline
3943
- * with no per-row SSH. The upsert is GUARDED: it never overwrites a genuine
3944
- * local transcript row (`file_path` non-empty and not itself a mirror), so a
3945
- * mirror can only create a new peer-only row or enrich an existing empty-file
3946
- * host-dispatch stub / older mirror. Returns true when a row was written (the
3947
- * caller then persists the matching preview digest); false when a real row was
3948
- * protected. `machine` carries the publisher's recorded execution host so the
3949
- * row collapses with the live fan-out row under the same `machine:id` key.
3950
- */
3951
2870
  export function upsertMirrorSession(row, source, syncedAt) {
3952
2871
  const db = getDB();
3953
2872
  const lastActivity = row.lastActivity ?? row.timestamp;
@@ -4009,24 +2928,14 @@ export function upsertMirrorSession(row, source, syncedAt) {
4009
2928
  });
4010
2929
  if (result.changes === 0)
4011
2930
  return false;
4012
- // Keep the mirror row searchable by topic + first user turn, like a local row.
4013
2931
  db.prepare(`DELETE FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`).run(row.id);
4014
2932
  db.prepare(`
4015
2933
  INSERT INTO session_text (rowid, session_id, label, topic, project, content, assistant)
4016
2934
  VALUES (${SESSION_TEXT_ROWID}, ?, ?, ?, '', ?, '')
4017
2935
  `).run(row.id, row.id, row.label ?? '', row.topic ?? '', row.firstUser ?? '');
4018
- // Carry the peer's daemon-computed summary into the same session_summaries
4019
- // cache the local merge reads (PHNX-3939), so a peer session renders its
4020
- // goal/checkpoints/checklist inline with no transcript. Stamp is null: the
4021
- // display merge reads by id (readSessionSummaryAny), and this box's own
4022
- // summarizer service only ever recomputes LOCAL live sessions, never a peer's.
4023
2936
  if (row.summary) {
4024
2937
  writeSessionSummary({ id: row.id, fileMtimeMs: null, fileSize: null, summary: row.summary });
4025
2938
  }
4026
- // Same for the peer's folded timeline (PHNX-3939): the projection is what a row
4027
- // renders, and the resume state is deliberately EMPTY — this box never folds a
4028
- // peer's transcript (it does not have one), so storing a foreign byte offset
4029
- // would be a resume point into a file that is not here.
4030
2939
  if (row.timeline) {
4031
2940
  writeSessionTimeline({
4032
2941
  id: row.id,
@@ -4037,19 +2946,6 @@ export function upsertMirrorSession(row, source, syncedAt) {
4037
2946
  }
4038
2947
  return true;
4039
2948
  }
4040
- /**
4041
- * The most-recently-active LOCAL sessions the titler may consider (PHNX-3797).
4042
- *
4043
- * Deliberately not "every untitled row": the caller decides which of these still
4044
- * needs work by comparing each row's stored `generatedTitleKey` against the key
4045
- * of its current user text, so a first-user-message change re-titles without a
4046
- * second stored copy of that text. Rows excluded here can never be titled:
4047
- * - a peer MIRROR row — its owning box generates its title and publishes it;
4048
- * - a row with no user text at all — there is nothing user-anchored to title;
4049
- * - a row that already carries a `/rename`-style `label`, which outranks a
4050
- * generated title in the ladder, so generating one would buy nothing.
4051
- * `sinceMs` bounds the work to sessions recent enough to still be shown.
4052
- */
4053
2949
  export function querySessionTitleCandidates(limit, opts = {}) {
4054
2950
  const clauses = [
4055
2951
  `mirror_synced_at IS NULL`,
@@ -4061,8 +2957,6 @@ export function querySessionTitleCandidates(limit, opts = {}) {
4061
2957
  params.push(opts.id, opts.id);
4062
2958
  }
4063
2959
  else {
4064
- // An explicitly requested session is titled even when it carries a label or
4065
- // is older than the window; the periodic sweep honours both bounds.
4066
2960
  clauses.push(`(label IS NULL OR trim(label) = '')`);
4067
2961
  if (opts.sinceMs != null) {
4068
2962
  clauses.push(`COALESCE(last_activity, timestamp) >= ?`);
@@ -4091,13 +2985,6 @@ export function querySessionTitleCandidates(limit, opts = {}) {
4091
2985
  generatedTitleKey: r.generated_title_key,
4092
2986
  }));
4093
2987
  }
4094
- /**
4095
- * Persist one generated title against the source key it was derived from
4096
- * (PHNX-3797). `sourceKey` is what makes generation once-per-session: the next
4097
- * sweep sees a stored key equal to the row's current user text and skips it,
4098
- * and regenerates only when that text changed. Never touches a mirror row —
4099
- * the publishing box owns its own sessions' titles.
4100
- */
4101
2988
  export function setSessionGeneratedTitle(id, title, sourceKey, now = Date.now()) {
4102
2989
  const result = getDB().prepare(`
4103
2990
  UPDATE sessions
@@ -4106,12 +2993,6 @@ export function setSessionGeneratedTitle(id, title, sourceKey, now = Date.now())
4106
2993
  `).run(title, sourceKey, now, id);
4107
2994
  return result.changes > 0;
4108
2995
  }
4109
- /**
4110
- * Drop peer mirror rows (and their cached digests) whose last sync predates the
4111
- * cutoff — the size ceiling / staleness pruner for the fleet session mirror
4112
- * (PHNX-3792). Only ever touches mirror rows (`mirror_synced_at IS NOT NULL`),
4113
- * never a genuine local or host-dispatch row. Returns the number of rows pruned.
4114
- */
4115
2996
  export function pruneMirrorSessions(cutoffMs) {
4116
2997
  const db = getDB();
4117
2998
  const stale = db.prepare(`SELECT id FROM sessions WHERE mirror_synced_at IS NOT NULL AND mirror_synced_at < ?`).all(cutoffMs);
@@ -4124,7 +3005,6 @@ export function pruneMirrorSessions(cutoffMs) {
4124
3005
  const delTimeline = db.prepare(`DELETE FROM session_timelines WHERE session_id = ?`);
4125
3006
  const txn = db.transaction(() => {
4126
3007
  for (const { id } of stale) {
4127
- // Text first: its rowid lookup needs the sessions row still present.
4128
3008
  delText.run(id);
4129
3009
  delRow.run(id);
4130
3010
  delPreview.run(id);
@@ -4135,7 +3015,6 @@ export function pruneMirrorSessions(cutoffMs) {
4135
3015
  txn();
4136
3016
  return stale.length;
4137
3017
  }
4138
- /** Plugin provenance already indexed for resources used by one session. */
4139
3018
  export function getSessionPlugins(id) {
4140
3019
  const rows = getDB().prepare(`
4141
3020
  SELECT DISTINCT plugin
@@ -4150,7 +3029,6 @@ export function queryAffinityRollup(options) {
4150
3029
  const where = [];
4151
3030
  const params = [];
4152
3031
  const sinceMs = options.sinceMs ?? (Date.now() - 14 * 24 * 60 * 60 * 1000);
4153
- // ISO timestamps sort lexicographically; compare as string prefix of datetime.
4154
3032
  where.push(`timestamp >= ?`);
4155
3033
  params.push(new Date(sinceMs).toISOString());
4156
3034
  if (options.onlyCli !== false) {
@@ -4196,13 +3074,6 @@ export function queryAffinityRollup(options) {
4196
3074
  `;
4197
3075
  return db.prepare(sql).all(...params);
4198
3076
  }
4199
- /**
4200
- * Aggregate cost / duration / tokens across sessions, grouped by agent,
4201
- * project, shortened model id, account, or calendar day. Honors the same
4202
- * filter shape as querySessions (agent, since/until, team-origin) so
4203
- * `agents insights cost --since 7d --by day` lines up with what
4204
- * `agents sessions` would list. Ordered by cost desc.
4205
- */
4206
3077
  export function queryUsageRollup(options) {
4207
3078
  const db = getDB();
4208
3079
  const { clause, params } = buildSessionWhere(options);
@@ -4211,8 +3082,6 @@ export function queryUsageRollup(options) {
4211
3082
  : options.groupBy === 'project'
4212
3083
  ? `IFNULL(NULLIF(project, ''), '(no project)')`
4213
3084
  : options.groupBy === 'model'
4214
- // Match shortenModel(): remove Claude's redundant harness prefix and
4215
- // an optional eight-digit release-date suffix before grouping.
4216
3085
  ? `IFNULL(NULLIF(CASE
4217
3086
  WHEN substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -9, 1) = '-'
4218
3087
  AND length(substr(CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END, -8)) = 8
@@ -4222,19 +3091,12 @@ export function queryUsageRollup(options) {
4222
3091
  ELSE CASE WHEN model LIKE 'claude-%' THEN substr(model, 8) ELSE model END
4223
3092
  END, ''), '(unknown)')`
4224
3093
  : options.groupBy === 'account'
4225
- // A NULL account_key means this harness has no account attribution yet —
4226
- // the mechanism is Claude-only today (see lib/session/claude-accounts.ts).
4227
- // Bucket per agent so the rows are named honestly instead of being called
4228
- // "not indexed", which they are not, and instead of joining a real account.
4229
3094
  ? `IFNULL(NULLIF(account_key, ''), 'unattributed:' || agent)`
4230
- // ISO timestamps are lexicographically date-sortable; the date is the
4231
- // first 10 chars (YYYY-MM-DD).
4232
3095
  : `substr(timestamp, 1, 10)`;
4233
3096
  const sql = `
4234
3097
  SELECT
4235
3098
  ${keyExpr} AS key,
4236
3099
  ${options.groupBy === 'account'
4237
- // One label per account_key by construction, so MAX just picks it out.
4238
3100
  ? `MAX(CASE WHEN account_org IS NOT NULL AND account IS NOT NULL
4239
3101
  THEN account_org || ' <' || account || '>' END) AS label,`
4240
3102
  : ''}
@@ -4257,34 +3119,9 @@ export function queryUsageRollup(options) {
4257
3119
  `;
4258
3120
  return db.prepare(sql).all(...params);
4259
3121
  }
4260
- /**
4261
- * Roll up skill / slash-command usage from session_resource_usage, joined to
4262
- * `sessions` for attribution so the same filter shape as querySessions
4263
- * (agent / project / since / machine) narrows WHICH sessions count. Grouped by
4264
- * resource identity (kind + name + plugin + source) and ordered by invocation
4265
- * volume — the read side of "which skills/commands do I actually use, and which
4266
- * are dead weight". `order: 'bottom'` ranks least-used first (the one-time
4267
- * skills); `limit` caps the returned rows.
4268
- *
4269
- * The signal only captures EXPLICIT invocations (slash commands and `Skill`
4270
- * tool calls). An auto-triggered skill (loaded by description match) emits no
4271
- * event, so it reads as zero here — a 0 means "never explicitly invoked", not
4272
- * "never loaded". Skill invocations are recorded for Claude and Kimi (the
4273
- * `Skill`-tool harnesses); slash-commands are Claude-only.
4274
- *
4275
- * `kind` / `pluginFilter` filter the RESOURCE rows directly (r.kind / r.plugin),
4276
- * distinct from QueryOptions.skill / QueryOptions.plugin, which filter SESSIONS
4277
- * — deliberately not routed through buildSessionWhere so `--plugin rush` shows
4278
- * only rush's resources rather than every resource used by a rush-touching
4279
- * session.
4280
- */
4281
3122
  export function queryResourceUsageStats(options) {
4282
3123
  const db = getDB();
4283
3124
  const { clause, params } = buildSessionWhere(options);
4284
- // buildSessionWhere emits bare column names (agent, timestamp, machine, …) and
4285
- // `id IN (…)` subqueries; in this join they resolve unambiguously to `sessions`
4286
- // (session_resource_usage carries none of those columns, and its key is
4287
- // session_id, not id). Strip the leading WHERE so resource predicates append.
4288
3125
  const base = clause.replace(/^WHERE\s+/, '');
4289
3126
  const preds = base ? [base] : [];
4290
3127
  const allParams = [...params];
@@ -4299,15 +3136,6 @@ export function queryResourceUsageStats(options) {
4299
3136
  const whereClause = preds.length ? `WHERE ${preds.join(' AND ')}` : '';
4300
3137
  const direction = options.order === 'bottom' ? 'ASC' : 'DESC';
4301
3138
  const limitClause = options.limit ? `LIMIT ${Math.max(1, Math.floor(options.limit))}` : '';
4302
- // Group by resource IDENTITY = (kind, name) only, per SES-IF-4b. name already
4303
- // embeds the `plugin:short` prefix for a plugin resource, so (kind, name) is
4304
- // the true identity. plugin and source are PROVENANCE, not identity, and drift
4305
- // per session — the same `rush:design` resolves plugin='rush' in a session
4306
- // whose cwd discovered the plugin and plugin=NULL in one that didn't, and the
4307
- // layer a flat resource resolved to varies (user vs system). Grouping on either
4308
- // would split one resource into fractional rows. Aggregate both with MAX so the
4309
- // row carries a representative (non-NULL when any session resolved it) label
4310
- // while the counts stay whole.
4311
3139
  const sql = `
4312
3140
  SELECT
4313
3141
  r.kind AS kind,
@@ -4325,36 +3153,9 @@ export function queryResourceUsageStats(options) {
4325
3153
  `;
4326
3154
  return db.prepare(sql).all(...allParams);
4327
3155
  }
4328
- /**
4329
- * Coverage of the resource-usage signal, as three honest facts:
4330
- *
4331
- * - `scanned` — sessions the resource extractor has actually processed, i.e.
4332
- * those carrying a `resource_scan_ledger` row at the current
4333
- * `RESOURCE_INDEX_VERSION`. This is the true "has the historical backfill run"
4334
- * signal: the ledger is stamped for EVERY scanned session, including ones that
4335
- * invoked nothing (`resource_count = 0`), so `scanned/total` rises to ~1 after
4336
- * `agents sessions backfill resources` regardless of how sparse explicit
4337
- * invocations are.
4338
- * - `covered` — distinct sessions that carry AT LEAST ONE row in
4339
- * `session_resource_usage`, i.e. that actually recorded an explicit invocation.
4340
- * This is an ABSOLUTE signal count, not a coverage ratio: it stays small even
4341
- * at full scan coverage because most sessions invoke no skill/command, and a
4342
- * non-recording harness contributes none by construction.
4343
- * - `total` — sessions indexed.
4344
- *
4345
- * The two were previously conflated: `covered/total` was framed as coverage and
4346
- * read ~1.2% even after a full backfill (most sessions genuinely invoke nothing),
4347
- * so the "run the backfill" hint never cleared. Keying the hint on `scanned/total`
4348
- * fixes that — see `commands/sessions-stats.ts` (PHNX-2301).
4349
- */
4350
3156
  export function resourceUsageCoverage() {
4351
3157
  const db = getDB();
4352
3158
  const covered = db.prepare(`SELECT COUNT(DISTINCT session_id) AS n FROM session_resource_usage`).get().n;
4353
- // JOIN sessions so a ledger row for a since-vanished transcript (dropped from
4354
- // `sessions` but not yet from the ledger) can't inflate scan coverage past the
4355
- // indexed set. Only rows at the current extractor version count as scanned —
4356
- // a stale-version row is re-derived on the next backfill, so it is not yet
4357
- // "covered" for this extractor.
4358
3159
  const scanned = db.prepare(`
4359
3160
  SELECT COUNT(*) AS n
4360
3161
  FROM resource_scan_ledger l
@@ -4364,7 +3165,6 @@ export function resourceUsageCoverage() {
4364
3165
  const total = db.prepare(`SELECT COUNT(*) AS n FROM sessions`).get().n;
4365
3166
  return { covered, scanned, total };
4366
3167
  }
4367
- /** Has this session's resource usage been derived at the current extractor version for this exact file? */
4368
3168
  function needsResourceIndex(db, sessionId, stamp) {
4369
3169
  const row = db
4370
3170
  .prepare(`SELECT file_mtime_ms, file_size, extractor_version FROM resource_scan_ledger WHERE session_id = ?`)
@@ -4374,7 +3174,6 @@ function needsResourceIndex(db, sessionId, stamp) {
4374
3174
  || row.file_size !== stamp.fileSize
4375
3175
  || row.extractor_version !== RESOURCE_INDEX_VERSION;
4376
3176
  }
4377
- /** Record that a session's resource usage is current at RESOURCE_INDEX_VERSION for this file stamp. */
4378
3177
  function stampResourceLedger(db, sessionId, filePath, stamp, resourceCount) {
4379
3178
  db.prepare(`
4380
3179
  INSERT INTO resource_scan_ledger
@@ -4389,25 +3188,8 @@ function stampResourceLedger(db, sessionId, filePath, stamp, resourceCount) {
4389
3188
  resource_count = excluded.resource_count
4390
3189
  `).run(sessionId, filePath, stamp.fileMtimeMs, stamp.fileSize, RESOURCE_INDEX_VERSION, Date.now(), resourceCount);
4391
3190
  }
4392
- /**
4393
- * One-shot historical backfill of session_resource_usage (#12). The normal
4394
- * incremental scan writes resource usage only for sessions whose transcript it
4395
- * (re)parses; a session indexed before this feature shipped keeps a fresh
4396
- * scan_ledger row and is never re-derived, so its skill/slash-command tallies
4397
- * were never recorded. This walks the session index, re-parses each transcript
4398
- * FROM BYTE 0 (parseSession fully materializes — no resumable cursor, so
4399
- * claude/codex re-derive their tallies from scratch), writes the usage, and
4400
- * stamps resource_scan_ledger so reruns skip completed transcripts — the same
4401
- * independent-ledger shape `agents sessions backfill tools` uses.
4402
- *
4403
- * Harness-agnostic: parseSession + extractSkills/extractSlashCommands cover
4404
- * every agent uniformly, so there is no per-harness branch to keep in parity.
4405
- * Synthetic rows without a transcript (OpenClaw channels/cron) are skipped.
4406
- */
4407
3191
  export function backfillResourceUsage(filter = {}, onProgress) {
4408
3192
  const db = getDB();
4409
- // No LIMIT: the backfill covers the whole matching history. skipExistenceCheck
4410
- // stays off so vanished transcripts are dropped, matching querySessions.
4411
3193
  const sessions = querySessions({ ...filter, limit: undefined });
4412
3194
  const result = { scanned: 0, updated: 0, skipped: 0, failed: 0, resourceRows: 0 };
4413
3195
  let done = 0;
@@ -4420,8 +3202,6 @@ export function backfillResourceUsage(filter = {}, onProgress) {
4420
3202
  result.scanned++;
4421
3203
  let stamp;
4422
3204
  try {
4423
- // Composite rows (`<container>#<id>`) stat their container file — the
4424
- // per-session bytes/mtime stamp is derived during discovery, not here.
4425
3205
  const st = fs.statSync(sessionFilePathContainer(meta.filePath));
4426
3206
  stamp = { fileMtimeMs: st.mtimeMs, fileSize: st.size };
4427
3207
  }
@@ -4439,14 +3219,6 @@ export function backfillResourceUsage(filter = {}, onProgress) {
4439
3219
  }
4440
3220
  try {
4441
3221
  const events = parseSession(meta.filePath, meta.agent);
4442
- // Fail loud on a bad read. writeResourceUsage DELETEs the session's rows
4443
- // before it re-inserts, so a transcript that parses to ZERO events (a
4444
- // truncated / mid-sync / corrupt file — parseSession returns [] instead of
4445
- // throwing) would silently wipe real historical usage and then stamp the
4446
- // ledger "current", masking the loss. A completed historical transcript
4447
- // always has turns, so an empty parse is never a legitimate "this session
4448
- // used nothing" — it is an unreadable file. Skip it (count as failed, leave
4449
- // the ledger unstamped) so a later good read retries.
4450
3222
  if (events.length === 0) {
4451
3223
  result.failed++;
4452
3224
  done++;
@@ -4467,14 +3239,6 @@ export function backfillResourceUsage(filter = {}, onProgress) {
4467
3239
  }
4468
3240
  return result;
4469
3241
  }
4470
- /**
4471
- * Map every team name to the session that ran `agents teams create/add` for it.
4472
- *
4473
- * One scan over the rows that carry a `spawned_team`, rather than a query per
4474
- * team — `agents teams list` needs the whole map at once, and the column has no
4475
- * index. When two sessions spawned the same team name (a team re-created after a
4476
- * disband), the most recent wins, which is the one whose work the name refers to.
4477
- */
4478
3242
  export function teamSpawners() {
4479
3243
  const db = getDB();
4480
3244
  const rows = db
@@ -4488,26 +3252,13 @@ export function teamSpawners() {
4488
3252
  }
4489
3253
  return out;
4490
3254
  }
4491
- /**
4492
- * Return the N most expensive sessions (cost_usd DESC, NULLs excluded),
4493
- * honoring the same filter shape as querySessions. Drops rows whose JSONL
4494
- * vanished, mirroring querySessions' liveness filter.
4495
- */
4496
3255
  export function topSessionsByCost(n, options = {}) {
4497
3256
  const db = getDB();
4498
3257
  const { clause, params } = buildSessionWhere(options);
4499
3258
  const whereCost = clause ? `${clause} AND cost_usd IS NOT NULL` : 'WHERE cost_usd IS NOT NULL';
4500
3259
  const limit = Math.max(1, Math.floor(n));
4501
- // Over-fetch a small buffer to survive the on-disk liveness filter below.
4502
3260
  const sql = `SELECT * FROM sessions ${whereCost} ORDER BY cost_usd DESC, timestamp DESC LIMIT ${limit + 16}`;
4503
3261
  const rows = db.prepare(sql).all(...params);
4504
- // Keep a row whose file is present OR whose transcript is already archived
4505
- // (file gone but user turns still in session_text) — an expensive, real
4506
- // session must not drop out of the cost rollup just because its file was
4507
- // removed (RUSH-2436). A file-gone row with no durable content (a phantom) is
4508
- // still excluded. archived_at is the persisted signal; stamp it here too (once)
4509
- // so a session first surfaced through the cost rollup reports `archived`
4510
- // consistently with the listing path.
4511
3262
  const readContent = db.prepare(`SELECT content FROM session_text WHERE rowid = ${SESSION_TEXT_ROWID}`);
4512
3263
  const markArchived = db.prepare(`UPDATE sessions SET archived_at = ? WHERE id = ? AND archived_at IS NULL`);
4513
3264
  const now = Date.now();
@@ -4539,10 +3290,6 @@ export function topSessionsByCost(n, options = {}) {
4539
3290
  durationMs: r.duration_ms ?? 0,
4540
3291
  }));
4541
3292
  }
4542
- /**
4543
- * Read machine attribution in batches without materializing full sessions.
4544
- * Failure is best-effort so the live view can still render local attribution.
4545
- */
4546
3293
  export function findSessionMachinesByIds(ids) {
4547
3294
  const out = new Map();
4548
3295
  const uniq = [...new Set(ids.filter(Boolean))];
@@ -4550,7 +3297,7 @@ export function findSessionMachinesByIds(ids) {
4550
3297
  return out;
4551
3298
  try {
4552
3299
  const db = getDB();
4553
- const CHUNK = 500; // stay well under SQLite's default 999-variable limit
3300
+ const CHUNK = 500;
4554
3301
  for (let i = 0; i < uniq.length; i += CHUNK) {
4555
3302
  const batch = uniq.slice(i, i + CHUNK);
4556
3303
  const placeholders = batch.map(() => '?').join(',');
@@ -4563,7 +3310,6 @@ export function findSessionMachinesByIds(ids) {
4563
3310
  }
4564
3311
  }
4565
3312
  catch {
4566
- /* index read is best-effort — an unavailable DB leaves rows un-attributed */
4567
3313
  }
4568
3314
  return out;
4569
3315
  }
@@ -4572,7 +3318,6 @@ export function getSessionById(id) {
4572
3318
  const row = db.prepare(`SELECT * FROM sessions WHERE id = ?`).get(id);
4573
3319
  return row ? rowToMeta(row) : null;
4574
3320
  }
4575
- /** Exact ids win over prefixes; the normal existence check preserves archived content but excludes phantoms. */
4576
3321
  export function findSessionsById(idQuery, scope = {}) {
4577
3322
  const q = idQuery.trim();
4578
3323
  if (!q)
@@ -4582,35 +3327,25 @@ export function findSessionsById(idQuery, scope = {}) {
4582
3327
  return exact;
4583
3328
  return querySessions({ ...scope, idPrefix: q });
4584
3329
  }
4585
- /**
4586
- * Upgrade a session-id crumb to the full indexed id. The 8-char hex `short_id`
4587
- * a "Sent from …" footer shows would 404 as a `…/console/sessions/<id>` URL, so
4588
- * an owner ping resolves it through the index first (PHNX-3698). A value that is
4589
- * already a full id (or any non-crumb shape, e.g. a native `ses_…` id), or a
4590
- * crumb the index cannot resolve, is returned unchanged — the caller keeps
4591
- * exactly what it had rather than getting a fabricated id.
4592
- */
4593
3330
  export function resolveFullSessionId(idOrCrumb) {
4594
3331
  const id = idOrCrumb?.trim();
4595
3332
  if (!id)
4596
3333
  return undefined;
4597
3334
  if (!/^[0-9a-f]{8}$/i.test(id))
4598
- return id; // already a full id (or non-hex shape) — nothing to resolve
3335
+ return id;
4599
3336
  const hit = findSessionsByShortIds([id]).get(id.toLowerCase());
4600
3337
  return hit?.id ?? id;
4601
3338
  }
4602
- /** Batch-resolve pane short ids; on collision the most recently active session wins. */
4603
3339
  export function findSessionsByShortIds(shortIds) {
4604
3340
  const out = new Map();
4605
3341
  const uniq = [...new Set(shortIds.map((s) => s.trim().toLowerCase()).filter(Boolean))];
4606
3342
  if (uniq.length === 0)
4607
3343
  return out;
4608
3344
  const db = getDB();
4609
- const CHUNK = 500; // stay well under SQLite's default 999-variable limit
3345
+ const CHUNK = 500;
4610
3346
  for (let i = 0; i < uniq.length; i += CHUNK) {
4611
3347
  const batch = uniq.slice(i, i + CHUNK);
4612
3348
  const placeholders = batch.map(() => '?').join(',');
4613
- // timestamp ASC so a later (newer) row overwrites an earlier one per short_id.
4614
3349
  const rows = db
4615
3350
  .prepare(`SELECT * FROM sessions WHERE short_id IN (${placeholders}) ORDER BY timestamp ASC`)
4616
3351
  .all(...batch);
@@ -4622,11 +3357,6 @@ export function findSessionsByShortIds(shortIds) {
4622
3357
  }
4623
3358
  return out;
4624
3359
  }
4625
- /**
4626
- * Escape a raw user query into a safe FTS5 MATCH expression.
4627
- * Splits on non-word characters, keeps tokens >= 2 chars, and OR-joins
4628
- * them with a prefix wildcard so partial typing ('rush dep') matches.
4629
- */
4630
3360
  export function buildFtsQuery(input) {
4631
3361
  const terms = input.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length >= 2);
4632
3362
  if (terms.length === 0)
@@ -4634,34 +3364,12 @@ export function buildFtsQuery(input) {
4634
3364
  const expr = terms.map(t => `${t}*`).join(' OR ');
4635
3365
  return { expr, terms };
4636
3366
  }
4637
- /**
4638
- * Build a `label:(...)` FTS5 column-filter MATCH expression for the label
4639
- * tier. Unlike `buildFtsQuery` (2-char floor, tuned for full-content search),
4640
- * this allows 1-char terms: label search is the interactive type-ahead path —
4641
- * the query grows one keystroke at a time, so a single character has to be
4642
- * indexable too. Terms are filtered to `[a-z0-9]` before being embedded in the
4643
- * expression string, so there's no FTS5 syntax injection from user input.
4644
- */
4645
3367
  function buildLabelFtsQuery(input) {
4646
3368
  const terms = input.toLowerCase().split(/[^a-z0-9]+/).filter(t => t.length >= 1);
4647
3369
  if (terms.length === 0)
4648
3370
  return '';
4649
3371
  return `label:(${terms.map(t => `${t}*`).join(' OR ')})`;
4650
3372
  }
4651
- /**
4652
- * Label-first search. Sessions whose custom label substring-matches the query
4653
- * always rank ahead of FTS5 hits — this gives predictable behavior when a user
4654
- * types the exact name they gave a session via /rename.
4655
- *
4656
- * Tiers (highest → lowest):
4657
- * 1. Exact label match (case-insensitive): score 1_000_000
4658
- * 2. Label prefix match: score 900_000
4659
- * 3. Label contains query: score 800_000
4660
- * 4. FTS5 BM25 hits: score 1..1000 (scaled)
4661
- *
4662
- * Note: FTS5's bm25() returns negative numbers; we flip the sign for tier 4
4663
- * so "higher = better" is consistent across all tiers.
4664
- */
4665
3373
  export function ftsSearch(input, limit = 200) {
4666
3374
  const db = getDB();
4667
3375
  const trimmed = input.trim();
@@ -4671,28 +3379,6 @@ export function ftsSearch(input, limit = 200) {
4671
3379
  const lower = trimmed.toLowerCase();
4672
3380
  const seen = new Set();
4673
3381
  const hits = [];
4674
- // Tier 1-3: handle-based matches, ordered by exactness. A session's handle is
4675
- // its `label` — set by an agent title / `/rename`, or seeded at launch from
4676
- // `agents run --name`. Typing it resolves the session ahead of any FTS content
4677
- // hit.
4678
- //
4679
- // Candidates come from the FTS5 `label` column, not a raw `LOWER(label) LIKE
4680
- // '%q%'` scan of `sessions`: a leading wildcard can't use any index, so on a
4681
- // large session table that was a full-table scan on every keystroke of
4682
- // interactive search (RUSH-2211). `session_text.label` is kept 1:1 with
4683
- // `sessions.label` by every upsert path (storedFtsLabel), so this is the
4684
- // same data, indexed. Token-prefix matching seeks the FTS index instead of
4685
- // scanning every row, at the cost of only matching at token boundaries — a
4686
- // substring inside a single token (e.g. "ckf" inside "quickfix") no longer
4687
- // matches, since FTS5 only indexes prefixes of whole tokens, not arbitrary
4688
- // interior slices. (A slice spanning a token boundary, like "ix-b" inside
4689
- // "fix-bug", still matches: "ix-b" tokenizes to "ix" + "b", and "b" is a
4690
- // valid prefix of the "bug" token.) That's the accepted trade-off for an
4691
- // indexable interactive path; the exact/prefix/contains scoring below still
4692
- // runs in JS over the FTS candidate set, so ranking among real matches is
4693
- // unchanged. Only a query with no indexable token (rare — e.g.
4694
- // punctuation-only input) falls back to the direct scan rather than
4695
- // silently dropping the tier.
4696
3382
  const labelMatchExpr = buildLabelFtsQuery(input);
4697
3383
  const labelRows = labelMatchExpr
4698
3384
  ? db.prepare(`
@@ -4705,7 +3391,6 @@ export function ftsSearch(input, limit = 200) {
4705
3391
  `).all(`%${lower}%`);
4706
3392
  let hasExactLabelMatch = false;
4707
3393
  for (const row of labelRows) {
4708
- // Score the label by match quality (exact > prefix > contains).
4709
3394
  let score = 0;
4710
3395
  const handle = row.label;
4711
3396
  if (handle) {
@@ -4725,22 +3410,13 @@ export function ftsSearch(input, limit = 200) {
4725
3410
  }
4726
3411
  if (score === 0)
4727
3412
  continue;
4728
- // matchedTerms is empty for handle hits — the picker can render the handle
4729
- // itself as the highlight, no badge needed.
4730
3413
  hits.push({ sessionId: row.id, score, matchedTerms: [] });
4731
3414
  seen.add(row.id);
4732
3415
  }
4733
- // If the query exactly names a labeled session, don't dilute the result
4734
- // with FTS5 content hits — the user typed a specific thing, show just it.
4735
3416
  if (hasExactLabelMatch) {
4736
3417
  hits.sort((a, b) => b.score - a.score);
4737
3418
  return hits.slice(0, limit);
4738
3419
  }
4739
- // Tier 4: FTS5 content match, skipping anything already surfaced via label.
4740
- // `snippet(session_text, -1, ...)` lets FTS5 pick the best-matching column
4741
- // itself (label/topic/project/content/assistant) rather than us guessing —
4742
- // a query that only matched in `assistant` (an agent-only answer) still gets
4743
- // an excerpt from the right column instead of an empty content snippet.
4744
3420
  if (expr) {
4745
3421
  try {
4746
3422
  const rows = db
@@ -4766,24 +3442,11 @@ export function ftsSearch(input, limit = 200) {
4766
3442
  }
4767
3443
  }
4768
3444
  catch {
4769
- /* invalid MATCH expression — tier 4 just yields nothing */
4770
3445
  }
4771
3446
  }
4772
3447
  hits.sort((a, b) => b.score - a.score);
4773
3448
  return hits.slice(0, limit);
4774
3449
  }
4775
- /**
4776
- * Rewrite file_path for sessions whose path starts with any of `moves.from`,
4777
- * replacing that prefix with `moves.to` and clearing the matching scan_ledger
4778
- * entries so the next scan re-indexes from the new location.
4779
- *
4780
- * One transaction for the whole batch (PHNX-3940 T7): a migration that moves
4781
- * several homes into slots and several version dirs into trash must not leave
4782
- * the index half-rewritten. Longest `from` wins when prefixes nest, and each
4783
- * session row is rewritten at most once.
4784
- *
4785
- * Returns the number of session rows updated.
4786
- */
4787
3450
  export function reindexMovedSessionPaths(moves) {
4788
3451
  const usable = moves.filter((m) => m.from && m.from !== m.to);
4789
3452
  if (usable.length === 0)
@@ -4810,19 +3473,9 @@ export function reindexMovedSessionPaths(moves) {
4810
3473
  return n;
4811
3474
  })();
4812
3475
  }
4813
- /**
4814
- * Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
4815
- * it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
4816
- * entries so they are re-indexed from the new location on the next scan.
4817
- *
4818
- * Used by removeVersion after soft-deleting a version directory to trash, so
4819
- * that session reads (transcript view, /continue) still work from the trash path.
4820
- * Returns the number of session rows updated.
4821
- */
4822
3476
  export function updateSessionFilePaths(oldPrefix, newPrefix) {
4823
3477
  return reindexMovedSessionPaths([{ from: oldPrefix, to: newPrefix }]);
4824
3478
  }
4825
- /** Count indexed sessions whose transcript path sits under `prefix`. */
4826
3479
  export function countSessionsWithFilePrefix(prefix) {
4827
3480
  if (!prefix)
4828
3481
  return 0;
@@ -4831,24 +3484,6 @@ export function countSessionsWithFilePrefix(prefix) {
4831
3484
  .get(prefix + '%');
4832
3485
  return row.c;
4833
3486
  }
4834
- /**
4835
- * Upsert one browser task's durable metadata.
4836
- *
4837
- * Called at task START. This executes INSIDE the browser daemon (daemon.ts
4838
- * constructs BrowserService; ipc.ts dispatches `start` to it), which makes the
4839
- * shared daemon a writer of this DB — but the identity it writes is RESOLVED IN
4840
- * THE CALLING CLI PROCESS and forwarded over IPC, never resolved here. That
4841
- * distinction is the whole fix: resolving it daemon-side would attribute every
4842
- * task to the daemon's own actor (the RUSH-2020 bug).
4843
- *
4844
- * `agents browser stop` deliberately does NOT delete this row: the whole point
4845
- * is that the link outlives the task.
4846
- *
4847
- * Identity fields are only ever widened, never blanked. A later capture-count
4848
- * update carries no session id, and `COALESCE(excluded.…, browser_sessions.…)`
4849
- * keeps the one recorded at start rather than overwriting it with NULL --
4850
- * otherwise the second write would undo exactly what the first was for.
4851
- */
4852
3487
  export function recordBrowserSession(record) {
4853
3488
  const db = getDB();
4854
3489
  const now = Date.now();
@@ -4872,13 +3507,6 @@ export function recordBrowserSession(record) {
4872
3507
  captures_remote = COALESCE(excluded.captures_remote, browser_sessions.captures_remote)
4873
3508
  `).run(record.task, record.profile, record.sessionId ?? null, record.launchId ?? null, record.actor ?? null, record.machine ?? machineId(), record.startedAt ?? now, record.lastActivity ?? now, record.counts?.screenshot ?? 0, record.counts?.pdf ?? 0, record.counts?.recording ?? 0, record.counts?.download ?? 0, record.captureDir ?? null, record.capturesRemote ?? null);
4874
3509
  }
4875
- /**
4876
- * Upsert one computer-use invocation's durable metadata.
4877
- *
4878
- * `action_count` accumulates: a `computer run` loop emits many actions under one
4879
- * invocation id, and each arrives as its own call, so the count is incremented
4880
- * rather than replaced. Identity is widened-only, for the same reason as above.
4881
- */
4882
3510
  export function recordComputerSession(record) {
4883
3511
  const db = getDB();
4884
3512
  const now = Date.now();
@@ -4916,17 +3544,6 @@ function toStoredBrowserSession(row) {
4916
3544
  capturesRemote: row.captures_remote ?? undefined,
4917
3545
  };
4918
3546
  }
4919
- /**
4920
- * Stored browser tasks, newest first; optionally scoped to one profile.
4921
- *
4922
- * A PROFILE-SCOPED read is deliberately unbounded. These rows are the identity
4923
- * for capture dirs that still exist on disk, and dropping the oldest ones would
4924
- * silently regress exactly those tasks to `unlinked` — the symptom this whole
4925
- * change removes, reappearing at a higher threshold. The row count is already
4926
- * bounded by the tasks that profile has ever run, and each row is a short
4927
- * metadata record. Only the unscoped read (every profile, used for overviews)
4928
- * takes a ceiling, since that one has no natural bound.
4929
- */
4930
3547
  export function listBrowserSessionRecords(profile, opts = {}) {
4931
3548
  const db = getDB();
4932
3549
  const rows = (profile
@@ -4935,7 +3552,6 @@ export function listBrowserSessionRecords(profile, opts = {}) {
4935
3552
  .all(opts.limit ?? TOOL_SESSION_LIST_LIMIT));
4936
3553
  return rows.map(toStoredBrowserSession);
4937
3554
  }
4938
- /** One stored browser task by its (profile, task) key, or null. */
4939
3555
  export function getBrowserSessionRecord(profile, task) {
4940
3556
  const db = getDB();
4941
3557
  const row = db
@@ -4943,35 +3559,11 @@ export function getBrowserSessionRecord(profile, task) {
4943
3559
  .get(profile, task);
4944
3560
  return row ? toStoredBrowserSession(row) : null;
4945
3561
  }
4946
- /**
4947
- * Retention for the tool-session tables.
4948
- *
4949
- * These are the durable answer to a ledger that prunes at 7 days, so they are
4950
- * deliberately long-lived — but "durable" is not "unbounded". `computer_sessions`
4951
- * takes one row per `agents computer` CLI PROCESS (`COMPUTER_INVOCATION_ID` is
4952
- * minted per process), and an agent driving a desktop invokes that hundreds of
4953
- * times a day, so an unbounded table would grow without limit and be read in
4954
- * full on every listing.
4955
- */
4956
3562
  const TOOL_SESSION_MAX_AGE_DAYS = 365;
4957
- /** Default ceiling on rows one listing will read. */
4958
3563
  const TOOL_SESSION_LIST_LIMIT = 2000;
4959
- /**
4960
- * Drop tool-session rows past {@link TOOL_SESSION_MAX_AGE_DAYS}.
4961
- *
4962
- * Called from the listing path, never from the write hot path: an
4963
- * `agents computer` action must not pay for a table sweep (see the fail-soft
4964
- * note on `recordComputerSession`'s caller). Returns rows deleted.
4965
- */
4966
3564
  export function pruneToolSessions(maxAgeDays = TOOL_SESSION_MAX_AGE_DAYS) {
4967
3565
  const db = getDB();
4968
3566
  const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
4969
- // Cheap indexed guard first, so the common case is one lookup rather than a
4970
- // write transaction (the same shape as the account repair below). Every
4971
- // listing calls this, and on any table younger than the retention window
4972
- // there is nothing to delete — but a bare DELETE still opens a write
4973
- // transaction, which under `busy_timeout = 30000` can make a read-only
4974
- // listing wait on the session indexer for no reason at all.
4975
3567
  const stale = db.prepare(`
4976
3568
  SELECT 1 FROM computer_sessions WHERE started_at < ?
4977
3569
  UNION ALL
@@ -4984,22 +3576,9 @@ export function pruneToolSessions(maxAgeDays = TOOL_SESSION_MAX_AGE_DAYS) {
4984
3576
  const browser = db.prepare(`DELETE FROM browser_sessions WHERE started_at < ?`).run(cutoff);
4985
3577
  return Number(computer.changes ?? 0) + Number(browser.changes ?? 0);
4986
3578
  }
4987
- /**
4988
- * Stored computer-use invocations, newest first, bounded.
4989
- *
4990
- * The limit is a real ceiling, not a nicety: `--json` serializes whatever this
4991
- * returns, so an unbounded read would dump the entire table on every call.
4992
- */
4993
3579
  export function listComputerSessionRecords(opts = {}) {
4994
3580
  const db = getDB();
4995
3581
  const limit = opts.limit ?? TOOL_SESSION_LIST_LIMIT;
4996
- // `startedBeforeMs` selects the COMPLEMENT of the caller's other source
4997
- // rather than the newest N. The caller that recovers pruned runs already
4998
- // holds every recent invocation from the event ledger and discards any row
4999
- // it has seen — so a bare newest-N read hands it 2000 rows it is guaranteed
5000
- // to throw away, and returns nothing at all once the table passes that size.
5001
- // Bounding by "older than the ledger reaches" truncates the TAIL of
5002
- // recoverable history instead of deleting all of it.
5003
3582
  const rows = (opts.startedBeforeMs === undefined
5004
3583
  ? db
5005
3584
  .prepare(`SELECT * FROM computer_sessions ORDER BY started_at DESC LIMIT ?`)