pim-agent 0.13.0 → 0.14.1

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 (253) hide show
  1. package/package.json +3 -3
  2. package/packages/core/src/attachments/AttachmentStore.ts +8 -13
  3. package/packages/core/src/attachments/Attachments.ts +2 -4
  4. package/packages/core/src/extensions/CoreExtensions.ts +1 -1
  5. package/packages/core/src/extensions/apply-patch/coordinator.ts +1 -1
  6. package/packages/core/src/extensions/apply-patch/executor.ts +21 -25
  7. package/packages/core/src/extensions/apply-patch/matcher.ts +1 -1
  8. package/packages/core/src/extensions/apply-patch/model.ts +2 -6
  9. package/packages/core/src/extensions/apply-patch/parser.ts +60 -162
  10. package/packages/core/src/extensions/apply-patch/schema.ts +0 -1
  11. package/packages/core/src/extensions/bash/MemoryCap.ts +5 -4
  12. package/packages/core/src/extensions/bash/capture.ts +16 -23
  13. package/packages/core/src/extensions/bash/format.ts +2 -2
  14. package/packages/core/src/extensions/bash/image.ts +4 -8
  15. package/packages/core/src/extensions/bash/index.ts +1 -1
  16. package/packages/core/src/extensions/bash/render.ts +1 -1
  17. package/packages/core/src/extensions/bash/run.ts +9 -15
  18. package/packages/core/src/extensions/bash/schema.ts +4 -4
  19. package/packages/core/src/extensions/edit/edit.ts +14 -33
  20. package/packages/core/src/extensions/glob/glob.ts +1 -3
  21. package/packages/core/src/extensions/glob/index.ts +7 -9
  22. package/packages/core/src/extensions/glob/render.ts +2 -4
  23. package/packages/core/src/extensions/glob/schema.ts +1 -1
  24. package/packages/core/src/extensions/grep/grep.ts +7 -7
  25. package/packages/core/src/extensions/grep/render.ts +5 -6
  26. package/packages/core/src/extensions/grep/schema.ts +3 -7
  27. package/packages/core/src/extensions/read/ImageMemory.ts +2 -8
  28. package/packages/core/src/extensions/read/image.ts +3 -3
  29. package/packages/core/src/extensions/read/index.ts +2 -14
  30. package/packages/core/src/extensions/read/read.ts +5 -6
  31. package/packages/core/src/extensions/read/render.ts +1 -1
  32. package/packages/core/src/extensions/read/schema.ts +2 -2
  33. package/packages/core/src/extensions/subagent/render.ts +2 -2
  34. package/packages/core/src/extensions/subagent/subagent.ts +10 -21
  35. package/packages/core/src/extensions/system-prompt/index.ts +1 -2
  36. package/packages/core/src/extensions/system-prompt/prompt.ts +2 -4
  37. package/packages/core/src/extensions/todo/render.ts +1 -4
  38. package/packages/core/src/extensions/todo/todo.ts +16 -23
  39. package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +3 -11
  40. package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +1 -3
  41. package/packages/core/src/extensions/web-fetch/fetch.ts +4 -12
  42. package/packages/core/src/extensions/web-fetch/image.ts +3 -4
  43. package/packages/core/src/extensions/web-fetch/index.ts +2 -13
  44. package/packages/core/src/extensions/web-fetch/render.ts +3 -4
  45. package/packages/core/src/extensions/web-fetch/schema.ts +3 -4
  46. package/packages/core/src/extensions/web-search/ExaMcpClient.ts +23 -31
  47. package/packages/core/src/extensions/web-search/SearchBreaker.ts +1 -1
  48. package/packages/core/src/extensions/web-search/SearchChain.ts +6 -5
  49. package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +2 -2
  50. package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +1 -2
  51. package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +3 -3
  52. package/packages/core/src/extensions/write/write.ts +1 -1
  53. package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +1 -3
  54. package/packages/core/src/picker/PickerItem.ts +1 -1
  55. package/packages/core/src/picker/PickerService.ts +3 -7
  56. package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +1 -2
  57. package/packages/core/src/picker/catalog.ts +7 -15
  58. package/packages/core/src/picker/filePickerWorker.ts +1 -3
  59. package/packages/core/src/picker/ranker.ts +1 -1
  60. package/packages/core/src/picker/token.ts +3 -4
  61. package/packages/core/src/session/AgentRuntime.ts +5 -8
  62. package/packages/core/src/session/EventLog.ts +6 -12
  63. package/packages/core/src/session/ReadCursors.ts +3 -4
  64. package/packages/core/src/session/SearchIndex.ts +49 -86
  65. package/packages/core/src/session/SearchTokens.ts +4 -7
  66. package/packages/core/src/session/SessionCache.ts +8 -6
  67. package/packages/core/src/session/SessionDigest.ts +10 -19
  68. package/packages/core/src/session/SessionHost.ts +53 -83
  69. package/packages/core/src/session/SessionLease.ts +12 -15
  70. package/packages/core/src/session/SessionMeta.ts +16 -32
  71. package/packages/core/src/session/SessionName.ts +3 -7
  72. package/packages/core/src/session/SessionRegistry.ts +13 -29
  73. package/packages/core/src/session/SessionUi.ts +12 -16
  74. package/packages/core/src/session/WriteMark.ts +7 -16
  75. package/packages/core/src/shared/Cli.ts +1 -1
  76. package/packages/core/src/shared/DaemonUnit.ts +1 -2
  77. package/packages/core/src/shared/DiffLines.ts +16 -34
  78. package/packages/core/src/shared/DiffRenderer.ts +8 -27
  79. package/packages/core/src/shared/Directories.ts +4 -4
  80. package/packages/core/src/shared/EditMatcher.ts +18 -50
  81. package/packages/core/src/shared/ExtensionToggles.ts +3 -6
  82. package/packages/core/src/shared/FileEnumerator.ts +73 -57
  83. package/packages/core/src/shared/FileScanner.ts +5 -13
  84. package/packages/core/src/shared/FileWatch.ts +13 -17
  85. package/packages/core/src/shared/Format.ts +2 -3
  86. package/packages/core/src/shared/FuzzyMatcher.ts +18 -19
  87. package/packages/core/src/shared/Git.ts +22 -47
  88. package/packages/core/src/shared/GitMonitor.ts +12 -23
  89. package/packages/core/src/shared/GlobExclusions.ts +0 -4
  90. package/packages/core/src/shared/Http.ts +1 -1
  91. package/packages/core/src/shared/ImageMime.ts +3 -5
  92. package/packages/core/src/shared/Images.ts +17 -27
  93. package/packages/core/src/shared/Languages.ts +2 -2
  94. package/packages/core/src/shared/Levenshtein.ts +1 -6
  95. package/packages/core/src/shared/Lines.ts +1 -1
  96. package/packages/core/src/shared/McpClient.ts +2 -2
  97. package/packages/core/src/shared/PatchSummary.ts +1 -1
  98. package/packages/core/src/shared/Paths.ts +4 -1
  99. package/packages/core/src/shared/PiExtensions.ts +5 -12
  100. package/packages/core/src/shared/PimSettings.ts +14 -15
  101. package/packages/core/src/shared/Proc.ts +2 -3
  102. package/packages/core/src/shared/RateLimiter.ts +1 -1
  103. package/packages/core/src/shared/Renderer.ts +1 -3
  104. package/packages/core/src/shared/RepoDiff.ts +24 -46
  105. package/packages/core/src/shared/SearchRender.ts +3 -4
  106. package/packages/core/src/shared/SpillCache.ts +2 -3
  107. package/packages/core/src/shared/SubagentLogs.ts +4 -6
  108. package/packages/core/src/shared/Supervisor.ts +19 -32
  109. package/packages/core/src/shared/Surface.ts +1 -1
  110. package/packages/core/src/shared/Sweeper.ts +1 -1
  111. package/packages/core/src/shared/Tools.ts +17 -30
  112. package/packages/core/src/shared/Updater.ts +15 -15
  113. package/packages/core/src/view/BodyRenderer.ts +1 -1
  114. package/packages/core/src/view/DiffBlocks.ts +0 -1
  115. package/packages/core/src/view/DiffExpand.ts +10 -25
  116. package/packages/core/src/view/DiffLayout.ts +2 -2
  117. package/packages/core/src/view/DiffPairs.ts +2 -2
  118. package/packages/core/src/view/MarkdownPainter.ts +2 -19
  119. package/packages/core/src/view/MovePath.ts +4 -17
  120. package/packages/core/src/view/Painting.ts +4 -4
  121. package/packages/core/src/view/ViewBlock.ts +11 -12
  122. package/packages/daemon/src/Daemon.ts +2 -7
  123. package/packages/daemon/src/DaemonInstall.ts +5 -12
  124. package/packages/daemon/src/Surfaces.ts +3 -12
  125. package/packages/daemon/src/TelegramSurface.ts +0 -1
  126. package/packages/daemon/src/index.ts +0 -1
  127. package/packages/protocol/src/Command.ts +33 -41
  128. package/packages/protocol/src/Diff.ts +0 -5
  129. package/packages/protocol/src/ImageRoute.ts +2 -5
  130. package/packages/protocol/src/ServerEvent.ts +68 -105
  131. package/packages/server/src/AttachmentEndpoint.ts +45 -19
  132. package/packages/server/src/ClientConnection.ts +11 -12
  133. package/packages/server/src/ImageEndpoint.ts +3 -4
  134. package/packages/server/src/ProbeClient.ts +26 -59
  135. package/packages/server/src/Reloader.ts +4 -7
  136. package/packages/server/src/SendFileTool.ts +1 -1
  137. package/packages/server/src/SessionCatalogue.ts +35 -47
  138. package/packages/server/src/SessionProjection.ts +7 -12
  139. package/packages/server/src/SessionStream.ts +33 -80
  140. package/packages/server/src/StaticClient.ts +7 -8
  141. package/packages/server/src/WebOptions.ts +2 -6
  142. package/packages/server/src/WsGateway.ts +35 -96
  143. package/packages/server/src/probe.ts +6 -18
  144. package/packages/telegram/src/Bot.ts +2 -2
  145. package/packages/telegram/src/Commands.ts +108 -65
  146. package/packages/telegram/src/Config.ts +1 -6
  147. package/packages/telegram/src/Markdown.ts +4 -13
  148. package/packages/telegram/src/Renderer.ts +1 -1
  149. package/packages/telegram/src/Session.ts +3 -10
  150. package/packages/telegram/src/TaskScheduler.ts +31 -10
  151. package/packages/telegram/src/TaskStore.ts +11 -1
  152. package/packages/telegram/src/TaskTool.ts +1 -1
  153. package/packages/telegram/src/UpdateConfirm.ts +2 -2
  154. package/packages/tui/src/extensions/_init/index.ts +2 -5
  155. package/packages/tui/src/extensions/file-picker/index.ts +2 -6
  156. package/packages/tui/src/extensions/footer/index.ts +0 -1
  157. package/packages/tui/src/extensions/footer/powerline.ts +16 -28
  158. package/packages/tui/src/extensions/pim/index.ts +1 -1
  159. package/packages/tui/src/extensions/session-lease/index.ts +5 -6
  160. package/packages/tui/src/extensions/working-indicator/index.ts +1 -2
  161. package/packages/tui/src/themes/themeCliArgs.ts +1 -1
  162. package/packages/web/dist/client/assets/{abnfDiagram-O67JEVCF-CZ7V84ot.js → abnfDiagram-O67JEVCF-w0u0UKcC.js} +1 -1
  163. package/packages/web/dist/client/assets/{arc-DiHGkUvc.js → arc-C8AeY-3C.js} +1 -1
  164. package/packages/web/dist/client/assets/architecture-7GRP2DOG-D-GDy9ga.js +1 -0
  165. package/packages/web/dist/client/assets/{architectureDiagram-NJMV4G6O-XS5DhfJB.js → architectureDiagram-NJMV4G6O-CLdCZfsP.js} +1 -1
  166. package/packages/web/dist/client/assets/{blockDiagram-BEXU5L5S-BCsgLvy1.js → blockDiagram-BEXU5L5S-BgAbOzdH.js} +1 -1
  167. package/packages/web/dist/client/assets/{c4Diagram-YGBWAQC7-Bj9LcWNm.js → c4Diagram-YGBWAQC7-DGRXXzDl.js} +1 -1
  168. package/packages/web/dist/client/assets/channel-BJtpIKaC.js +1 -0
  169. package/packages/web/dist/client/assets/{chunk-3FUC2YCW-BmDurFxp.js → chunk-3FUC2YCW-Dmz4_CY0.js} +1 -1
  170. package/packages/web/dist/client/assets/{chunk-5DYCD2WN-DAjG-pqn.js → chunk-5DYCD2WN-D6q0r_NW.js} +1 -1
  171. package/packages/web/dist/client/assets/{chunk-742MDFTN-Bmxs5puY.js → chunk-742MDFTN-biXvhzMn.js} +1 -1
  172. package/packages/web/dist/client/assets/{chunk-7INBJB4K-Di7kIxrQ.js → chunk-7INBJB4K-CtfPEr8P.js} +1 -1
  173. package/packages/web/dist/client/assets/{chunk-7M6MHVWA-CSc8sR0b.js → chunk-7M6MHVWA-CTixWKLk.js} +1 -1
  174. package/packages/web/dist/client/assets/{chunk-7PRAP22T-C8kd5QvY.js → chunk-7PRAP22T-DijOiKl2.js} +1 -1
  175. package/packages/web/dist/client/assets/{chunk-GTNCS2PH-DG2b2LYE.js → chunk-GTNCS2PH-DLId7dTl.js} +1 -1
  176. package/packages/web/dist/client/assets/{chunk-GWA4HPMP-DdLaLQCu.js → chunk-GWA4HPMP-YUQxYmiu.js} +1 -1
  177. package/packages/web/dist/client/assets/{chunk-MBY4JIJT-C1j0yQlk.js → chunk-MBY4JIJT-VWR3HSRr.js} +1 -1
  178. package/packages/web/dist/client/assets/{chunk-NETBCI7D-Bfq9H74p.js → chunk-NETBCI7D-B8SeJoMY.js} +1 -1
  179. package/packages/web/dist/client/assets/{chunk-O7XYJQB3-ZHvBmBO-.js → chunk-O7XYJQB3-CeLpEz0Q.js} +1 -1
  180. package/packages/web/dist/client/assets/{chunk-UA2S7LBM-ku1JeHSK.js → chunk-UA2S7LBM-cBGKkXV_.js} +1 -1
  181. package/packages/web/dist/client/assets/{chunk-WEXAMYUT-BP1iga_7.js → chunk-WEXAMYUT-BesXAOy9.js} +1 -1
  182. package/packages/web/dist/client/assets/{chunk-XXDRQBXY-BBeccPcX.js → chunk-XXDRQBXY-DiuGUc8m.js} +1 -1
  183. package/packages/web/dist/client/assets/{chunk-Z7XXMR3K-SMHZugaR.js → chunk-Z7XXMR3K-Dr2W5_t1.js} +1 -1
  184. package/packages/web/dist/client/assets/{chunk-ZIGJFQKS-CFU_JLG4.js → chunk-ZIGJFQKS-CwoMYXLJ.js} +1 -1
  185. package/packages/web/dist/client/assets/{classDiagram-v2-NBCMYWYE-CYviuQzJ.js → classDiagram-v2-NBCMYWYE-Gc3jAsDf.js} +1 -1
  186. package/packages/web/dist/client/assets/{core-DyHsgs5k.js → core-C27Q2ZSN.js} +1 -1
  187. package/packages/web/dist/client/assets/{cose-bilkent-JH36ORCC-9v2ZkJzo.js → cose-bilkent-JH36ORCC-ox5QohTH.js} +1 -1
  188. package/packages/web/dist/client/assets/{cynefin-OW5HDTMX-Bx5xRadi.js → cynefin-OW5HDTMX-CjQ-AjuJ.js} +1 -1
  189. package/packages/web/dist/client/assets/{cynefinDiagram-VND7K2PF-BRfGDKqa.js → cynefinDiagram-VND7K2PF-BVajmbbO.js} +1 -1
  190. package/packages/web/dist/client/assets/{dagre-6A5THRUB-C4l-2L2e.js → dagre-6A5THRUB-BtXhDxGN.js} +1 -1
  191. package/packages/web/dist/client/assets/{diagram-22UHCM2B-d_hQ3kyi.js → diagram-22UHCM2B-DwjrV0H_.js} +1 -1
  192. package/packages/web/dist/client/assets/{diagram-3UASUU5V-DrEeguPI.js → diagram-3UASUU5V-DQ1E_6hO.js} +1 -1
  193. package/packages/web/dist/client/assets/{diagram-ATOU4E4O-BmfQ22pD.js → diagram-ATOU4E4O-wVYWlHRV.js} +1 -1
  194. package/packages/web/dist/client/assets/{diagram-CDSNMT55-BjVWjuPF.js → diagram-CDSNMT55-CMlxHWMz.js} +1 -1
  195. package/packages/web/dist/client/assets/{diagram-MLGK6HIB-wDjk-O8i.js → diagram-MLGK6HIB-Cbiwo7t9.js} +1 -1
  196. package/packages/web/dist/client/assets/{diagram-MPIPVDR6-DqXQFE1s.js → diagram-MPIPVDR6-Cv4zIyW1.js} +1 -1
  197. package/packages/web/dist/client/assets/{dist-BddGOpie.js → dist-Dl4GbwtH.js} +1 -1
  198. package/packages/web/dist/client/assets/{ebnfDiagram-ZINNZB2B-UB8kOITM.js → ebnfDiagram-ZINNZB2B-Bd8xl8W2.js} +1 -1
  199. package/packages/web/dist/client/assets/{elk-276RUBZZ-D4miYQnD.js → elk-276RUBZZ-DPRtUxgw.js} +1 -1
  200. package/packages/web/dist/client/assets/{erDiagram-OPXOYQCR--RXKv7KH.js → erDiagram-OPXOYQCR-BNoG4SxS.js} +1 -1
  201. package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-Dh9l6A3t.js +1 -0
  202. package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-DoJ97uSW.js +1 -0
  203. package/packages/web/dist/client/assets/{ganttDiagram-FUAMR5RP-C0CFpE5x.js → ganttDiagram-FUAMR5RP-BtFYI7uw.js} +1 -1
  204. package/packages/web/dist/client/assets/{gitGraph-4MIJSDKK-VPL7nv2c.js → gitGraph-4MIJSDKK-Di6RG5Kg.js} +1 -1
  205. package/packages/web/dist/client/assets/{gitGraphDiagram-X574FWY7-CgxJA-T8.js → gitGraphDiagram-X574FWY7-J4_JKGBH.js} +1 -1
  206. package/packages/web/dist/client/assets/index-BD7ptjuS.css +1 -0
  207. package/packages/web/dist/client/assets/{index-XTlTnQmx.js → index-Cn66jcLF.js} +7 -7
  208. package/packages/web/dist/client/assets/{info-A6RAGUB7-P0-o68pj.js → info-A6RAGUB7-wk6vNDP4.js} +1 -1
  209. package/packages/web/dist/client/assets/{infoDiagram-VRGFBTTK-DFJZNRUP.js → infoDiagram-VRGFBTTK-Zl7Uhlcj.js} +1 -1
  210. package/packages/web/dist/client/assets/{ishikawaDiagram-OU5B5YK6-ZkyhNd1T.js → ishikawaDiagram-OU5B5YK6-I0VVed8w.js} +1 -1
  211. package/packages/web/dist/client/assets/{journeyDiagram-ZHPQQLJL-DFevH8my.js → journeyDiagram-ZHPQQLJL-CLGZAy0Q.js} +1 -1
  212. package/packages/web/dist/client/assets/{kanban-definition-PNTS6WVX-l7e7ZZDC.js → kanban-definition-PNTS6WVX-D3Zv63YL.js} +1 -1
  213. package/packages/web/dist/client/assets/{line-DO_s6Ujd.js → line-C4_qzTJY.js} +1 -1
  214. package/packages/web/dist/client/assets/{linear-C4xH5PPc.js → linear-Dbp9FWGm.js} +1 -1
  215. package/packages/web/dist/client/assets/{mermaid-parser.core-nQaTP3VY.js → mermaid-parser.core-Cbd2Flfk.js} +3 -3
  216. package/packages/web/dist/client/assets/{mermaid.core-Cz05yTZh.js → mermaid.core-CPXk02t0.js} +4 -4
  217. package/packages/web/dist/client/assets/{mindmap-definition-NLK3R4M7-CHOEuHm2.js → mindmap-definition-NLK3R4M7-hcjd2vg0.js} +1 -1
  218. package/packages/web/dist/client/assets/{packet-AYTQ26CC-BH3ehPMo.js → packet-AYTQ26CC-CLiY7MdL.js} +1 -1
  219. package/packages/web/dist/client/assets/{pegDiagram-GJSIUBJH-CkWBLtad.js → pegDiagram-GJSIUBJH-Or6L-npJ.js} +1 -1
  220. package/packages/web/dist/client/assets/{pie-WAS4IAKB-nNVEq3dx.js → pie-WAS4IAKB-DTF5qxhB.js} +1 -1
  221. package/packages/web/dist/client/assets/{pieDiagram-5QR66LMP-DimUBAgN.js → pieDiagram-5QR66LMP-wDNyDsTs.js} +1 -1
  222. package/packages/web/dist/client/assets/{quadrantDiagram-O4NWA36T-BR0XpRGQ.js → quadrantDiagram-O4NWA36T-BL_0xWWi.js} +1 -1
  223. package/packages/web/dist/client/assets/{radar-RG4KPBEZ-paA-H3mb.js → radar-RG4KPBEZ-hK8uEd3E.js} +1 -1
  224. package/packages/web/dist/client/assets/{railroad-74A4TZTK-yEiKB3rz.js → railroad-74A4TZTK-Cj1mtGfH.js} +1 -1
  225. package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-t8ibqa0P.js +1 -0
  226. package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-B4RYlcUM.js +1 -0
  227. package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-DFqfvrfB.js +1 -0
  228. package/packages/web/dist/client/assets/{railroadDiagram-XR7U4H2S--TXe6rdU.js → railroadDiagram-XR7U4H2S-BUXe7pvJ.js} +1 -1
  229. package/packages/web/dist/client/assets/{requirementDiagram-PLB6GJNP-DyqH36Za.js → requirementDiagram-PLB6GJNP-DBnKurFY.js} +1 -1
  230. package/packages/web/dist/client/assets/{sankeyDiagram-IPEJSGJF-Bg6e3BlM.js → sankeyDiagram-IPEJSGJF-BDlSxPY7.js} +1 -1
  231. package/packages/web/dist/client/assets/{sequenceDiagram-PO4LG4MO-ClGf85My.js → sequenceDiagram-PO4LG4MO-BcKSq_cS.js} +1 -1
  232. package/packages/web/dist/client/assets/{src-DF7hQfMh.js → src-kcIgRrGC.js} +1 -1
  233. package/packages/web/dist/client/assets/{stateDiagram-v2-GCMORJYK-BK8TpxNo.js → stateDiagram-v2-GCMORJYK-D-1tpyK1.js} +1 -1
  234. package/packages/web/dist/client/assets/{swimlanes-2SLR337P-DDnTIT9M.js → swimlanes-2SLR337P-edQaMeEJ.js} +1 -1
  235. package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-vI6XtOyR.js +8 -0
  236. package/packages/web/dist/client/assets/{timeline-definition-EJHVYXUP-mBBbH3bk.js → timeline-definition-EJHVYXUP-BHspcgaG.js} +1 -1
  237. package/packages/web/dist/client/assets/{treeView-Q6P3EWNA-CTK7tRwR.js → treeView-Q6P3EWNA-Bvc71ryx.js} +1 -1
  238. package/packages/web/dist/client/assets/{treemap-WGGIJYW6-vOI-Ef_m.js → treemap-WGGIJYW6-CoZvoF_t.js} +1 -1
  239. package/packages/web/dist/client/assets/{usecaseDiagram-POWQR4AR-COcii8x-.js → usecaseDiagram-POWQR4AR-B203AiCD.js} +1 -1
  240. package/packages/web/dist/client/assets/{vennDiagram-UO4OBE2U-D6kmDGpM.js → vennDiagram-UO4OBE2U-Cia3spa9.js} +1 -1
  241. package/packages/web/dist/client/assets/{wardley-WFR3VGLG-CtE2KI9r.js → wardley-WFR3VGLG-C7qHTXAu.js} +1 -1
  242. package/packages/web/dist/client/assets/{wardleyDiagram-VNRHLVJA-ncxNGWnW.js → wardleyDiagram-VNRHLVJA-CK2R_SgC.js} +1 -1
  243. package/packages/web/dist/client/assets/{xychartDiagram-PMCCYNJV-DUvd2Qze.js → xychartDiagram-PMCCYNJV-BBLqtYJ_.js} +1 -1
  244. package/packages/web/dist/client/index.html +2 -2
  245. package/packages/web/dist/client/assets/architecture-7GRP2DOG-HIzEIkwQ.js +0 -1
  246. package/packages/web/dist/client/assets/channel-BCA6qe9I.js +0 -1
  247. package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-l6SYFw1s.js +0 -1
  248. package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-CWW9qnqm.js +0 -1
  249. package/packages/web/dist/client/assets/index-D9_XJLSc.css +0 -1
  250. package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-mVQx9aVM.js +0 -1
  251. package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CllXx1OJ.js +0 -1
  252. package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-BBD2Q5W_.js +0 -1
  253. package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-CM8UnP5t.js +0 -8
@@ -18,7 +18,7 @@ export type SearchProvider = {
18
18
  search(input: ProviderSearchInput): Promise<readonly SearchResult[]>;
19
19
  };
20
20
 
21
- /** Quota or rate limit exhausted; only this error opens the breaker and sidelines a provider. */
21
+ /** The only error that trips the breaker. */
22
22
  export class ProviderQuotaError extends Error {
23
23
  public readonly provider: string;
24
24
  public readonly retryAfterMs: number | undefined;
@@ -43,7 +43,7 @@ export class ProviderSearchError extends Error {
43
43
 
44
44
  const MAX_SNIPPET_LENGTH = 500;
45
45
 
46
- /** Caps a provider snippet; Firecrawl returns whole scraped pages where others return two lines. */
46
+ /** Collapses whitespace and caps length; Firecrawl can return whole pages. */
47
47
  export function normalizeSnippet(value: string | undefined): string {
48
48
  const collapsed = (value ?? "").replaceAll(/\s+/gu, " ").trim();
49
49
 
@@ -52,7 +52,7 @@ export function normalizeSnippet(value: string | undefined): string {
52
52
  : collapsed;
53
53
  }
54
54
 
55
- export function parseRetryAfterMs(value: string | null): number | undefined {
55
+ function parseRetryAfterMs(value: string | null): number | undefined {
56
56
  if (value === null) {
57
57
  return undefined;
58
58
  }
@@ -4,7 +4,7 @@ import { FsErrors } from "../../shared/FsErrors";
4
4
 
5
5
  const CONTEXT_LINES = 3;
6
6
 
7
- // Diff.diffLines is O(n*d): above this only the diff is skipped, never the write.
7
+ // Diffing is O(n*d); above this the diff is skipped, not the write.
8
8
  const MAX_DIFF_BYTES = 2 * 1024 * 1024;
9
9
 
10
10
  export type WriteOutcome = {
@@ -25,9 +25,7 @@ export class InProcessFilePickerSuggestionEngine implements FilePickerSuggestion
25
25
  this.cachedRelative = catalog;
26
26
  })
27
27
  .catch(() => {
28
- if (this.cachedRelative === undefined) {
29
- this.cachedRelative = [];
30
- }
28
+ this.cachedRelative ??= [];
31
29
  })
32
30
  .finally(() => {
33
31
  this.refresh = undefined;
@@ -1,4 +1,4 @@
1
- /** One completion row, structurally identical to pi-tui's `AutocompleteItem`. */
1
+ /** Structurally identical to pi-tui's `AutocompleteItem`. */
2
2
  export type PickerItem = {
3
3
  readonly value: string;
4
4
  readonly label: string;
@@ -9,19 +9,16 @@ import { InProcessFilePickerSuggestionEngine } from "./InProcessFilePickerSugges
9
9
  import type { PickerItem } from "./PickerItem";
10
10
 
11
11
  export type PickerServiceDeps = {
12
- /** Read per query: a session's cwd can move between turns. */
12
+ /** Read per query; the cwd can change between turns. */
13
13
  readonly cwd: () => string;
14
14
  readonly agentDir: string;
15
- /** Extension-registered commands, available only while an agent is built. */
15
+ /** Source of extension commands; undefined until an agent is built. */
16
16
  readonly agent?: () => AgentSession | undefined;
17
17
  };
18
18
 
19
19
  const DEFAULT_LIMIT = 50;
20
20
 
21
- /**
22
- * The `.agents/skills` roots pi reads and `loadSkills` does not: every one from
23
- * the cwd up to the repository root, plus the user's own.
24
- */
21
+ /** `.agents/skills` dirs from cwd up to the repo root, plus `~/.agents/skills`; `loadSkills` skips these. */
25
22
  function agentsSkillDirs(cwd: string): string[] {
26
23
  const dirs: string[] = [];
27
24
  let dir = resolve(cwd);
@@ -37,7 +34,6 @@ function agentsSkillDirs(cwd: string): string[] {
37
34
  return dirs;
38
35
  }
39
36
 
40
- /** Answers one session's picker queries against the machine the agent runs on. */
41
37
  export class PickerService {
42
38
  private readonly deps: PickerServiceDeps;
43
39
  private fileCache:
@@ -13,7 +13,7 @@ const DEBOUNCE_MS = 30;
13
13
 
14
14
  const CACHE_LIMIT = 100;
15
15
 
16
- /** The client half of the `@` picker: the TUI's engine contract, answered by the server. Browser-safe APIs only. */
16
+ /** Browser-safe: asks the server for `@` picker rows. */
17
17
  export class RemoteFilePickerSuggestionEngine implements FilePickerSuggestionEngine {
18
18
  private readonly cache = new Map<string, readonly PickerItem[]>();
19
19
  private generation = 0;
@@ -23,7 +23,6 @@ export class RemoteFilePickerSuggestionEngine implements FilePickerSuggestionEng
23
23
  private readonly debounceMs: number = DEBOUNCE_MS
24
24
  ) {}
25
25
 
26
- /** The invalidation hook: `picker_invalidate` from the server lands here. */
27
26
  public refreshRelative(): Promise<void> {
28
27
  this.cache.clear();
29
28
  return Promise.resolve();
@@ -122,14 +122,13 @@ async function runLoadRelative(
122
122
  options: LoadRelativeOptions
123
123
  ): Promise<readonly FileCandidate[]> {
124
124
  const spawner = options.gitSpawner ?? defaultGitSpawner;
125
-
126
- const fastPath = await tryGitListFiles(root, spawner);
127
- if (fastPath !== undefined) {
128
- return finalizeRelative(fastPath);
129
- }
130
-
131
- const fallback = await scanWithGlob(root);
132
- return finalizeRelative(fallback);
125
+ const paths =
126
+ (await tryGitListFiles(root, spawner)) ??
127
+ (await FileEnumerator.enumerate(root, {
128
+ includeDotfiles: false,
129
+ includeIgnored: false,
130
+ }));
131
+ return finalizeRelative(paths);
133
132
  }
134
133
 
135
134
  async function tryGitListFiles(
@@ -148,13 +147,6 @@ async function tryGitListFiles(
148
147
  return result.stdout.split("\n").filter((line) => line.length > 0);
149
148
  }
150
149
 
151
- async function scanWithGlob(root: string): Promise<readonly string[]> {
152
- return FileEnumerator.enumerate(root, {
153
- includeDotfiles: false,
154
- includeIgnored: false,
155
- });
156
- }
157
-
158
150
  function finalizeRelative(paths: readonly string[]): readonly FileCandidate[] {
159
151
  const normalized = paths.map(Paths.toForwardSlashes);
160
152
 
@@ -18,9 +18,7 @@ const refreshRelative = async (id: number, root: string): Promise<void> => {
18
18
  ok: true,
19
19
  } satisfies FilePickerWorkerResponse);
20
20
  } catch (error) {
21
- if (cachedRelative === undefined) {
22
- cachedRelative = [];
23
- }
21
+ cachedRelative ??= [];
24
22
  self.postMessage({
25
23
  id,
26
24
  type: "refreshRelative",
@@ -164,7 +164,7 @@ class RelativeRankingIndex {
164
164
  substringHits.push(candidate);
165
165
  }
166
166
 
167
- // Only prefix hits may end the scan early: a late prefix must not lose to an earlier substring.
167
+ // Only prefix hits end the scan, so a late prefix hit still beats substrings.
168
168
  if (prefixHits.length >= limitSize) {
169
169
  break;
170
170
  }
@@ -1,15 +1,14 @@
1
- /** Which picker the caret is sitting in, if any. */
2
1
  export type PickerToken = {
3
2
  readonly kind: "file" | "command";
4
3
  readonly query: string;
5
- /** Offset of the sigil itself, so a completion replaces it too. */
4
+ /** Offset of the sigil. */
6
5
  readonly start: number;
7
6
  };
8
7
 
9
8
  export const AT_PREFIX = /(?:^|\s)@(\S*)$/;
10
9
  export const SLASH_PREFIX = /(?:^|\s)\/(\S*)$/;
11
10
 
12
- /** Offset of the sigil in a match that also swallows the whitespace before it. */
11
+ /** Skips the leading whitespace the prefix regexes may match. */
13
12
  export function sigilOffset(match: RegExpMatchArray): number {
14
13
  const matched = match[0] ?? "";
15
14
  return (match.index ?? 0) + (/^\s/.test(matched) ? 1 : 0);
@@ -53,7 +52,7 @@ export function tokenKey(token: PickerToken | undefined): string {
53
52
  export type Completion = {
54
53
  readonly text: string;
55
54
  readonly caret: number;
56
- /** A directory keeps the picker open so the next segment can be drilled into. */
55
+ /** True for a directory, so the next segment can be picked. */
57
56
  readonly keepOpen: boolean;
58
57
  };
59
58
 
@@ -7,19 +7,16 @@ import {
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
  import { join } from "node:path";
9
9
 
10
- /** One model a session can be switched to, named the way `setModel` takes it. */
11
10
  export type ModelChoice = {
11
+ /** `provider/id`, as `setModel` takes it. */
12
12
  readonly id: string;
13
13
  readonly label: string;
14
- /** Who serves it, for a client that groups or tags the catalogue. */
15
14
  readonly provider: string;
16
15
  };
17
16
 
18
17
  /**
19
- * Pi's installation as one process sees it: a single `ModelRuntime` over a
20
- * single `auth.json`, and one `SettingsManager` per cwd. Every surface in a
21
- * process shares one of these — two runtimes over one auth file race each
22
- * other's token refresh.
18
+ * One `ModelRuntime` per process, shared by every surface: two runtimes over
19
+ * one `auth.json` race each other's token refresh.
23
20
  */
24
21
  export class AgentRuntime {
25
22
  public readonly agentDir: string;
@@ -32,7 +29,7 @@ export class AgentRuntime {
32
29
  this.agentDir = agentDir ?? getAgentDir();
33
30
  }
34
31
 
35
- /** Idempotent, and shared by every caller: the second surface to ask waits on the first. */
32
+ /** Idempotent; concurrent callers share one boot. A failed boot can be retried. */
36
33
  public init(): Promise<void> {
37
34
  this.booting ??= this.bootstrap().catch((err: unknown) => {
38
35
  this.booting = undefined;
@@ -49,7 +46,7 @@ export class AgentRuntime {
49
46
  return this.required(this.registry);
50
47
  }
51
48
 
52
- /** Every model this machine has credentials for, qualified as `setModel` takes them. */
49
+ /** Models with credentials available. */
53
50
  public models(): readonly ModelChoice[] {
54
51
  return this.modelRegistry.getAvailable().map((model: Model<ModelApi>) => ({
55
52
  id: `${model.provider}/${model.id}`,
@@ -18,7 +18,7 @@ async function ifPresent<T>(read: () => Promise<T>, absent: T): Promise<T> {
18
18
  }
19
19
  }
20
20
 
21
- /** One durable line of a session file; the header is line 1, so entries start at `seq` 2. */
21
+ /** `seq` is the 1-based line number; the header is line 1. */
22
22
  export type LoggedEntry = {
23
23
  readonly seq: number;
24
24
  readonly entry: FileEntry;
@@ -40,7 +40,7 @@ function isHeader(entry: FileEntry): entry is SessionHeader {
40
40
  return entry.type === "session";
41
41
  }
42
42
 
43
- /** A reader over one pi session file: append-only JSONL, so a line's `seq` never changes. */
43
+ /** Reader over one append-only pi session file. */
44
44
  export class EventLog {
45
45
  public readonly path: string;
46
46
  private cursor: Cursor = { offset: 0, seq: 0 };
@@ -56,7 +56,7 @@ export class EventLog {
56
56
  const file = Bun.file(this.path);
57
57
  const text = await ifPresent(() => file.slice(start.offset).text(), "");
58
58
 
59
- // A trailing fragment is a write in progress: stop short of it or reads lose entries.
59
+ // Skip a trailing partial line; it is still being written.
60
60
  const lastBreak = text.lastIndexOf("\n");
61
61
  if (lastBreak === -1) {
62
62
  return [];
@@ -92,24 +92,18 @@ export class EventLog {
92
92
  return undefined;
93
93
  }
94
94
 
95
- /** The `seq` of the last complete line, without parsing any of them. */
95
+ /** The `seq` of the last complete line. */
96
96
  public async head(): Promise<number> {
97
97
  await this.read(Number.MAX_SAFE_INTEGER);
98
98
  return this.cursor.seq;
99
99
  }
100
100
 
101
- /**
102
- * The name pi's own `/name` writes: the last `session_info` entry wins, and an
103
- * empty one clears it. Appended rather than replaced, so it is found by reading
104
- * the whole file — the one unbounded read the catalogue makes, and the only
105
- * reason `SessionDigest` takes a whole file rather than a bounded head and tail.
106
- */
101
+ /** The last `session_info` name; an empty one clears it. Reads the whole file. */
107
102
  public async name(): Promise<string | undefined> {
108
103
  const body = await durableBytes(Bun.file(this.path));
109
104
  return body === undefined ? undefined : SessionDigest.nameOf(body);
110
105
  }
111
106
 
112
- /** Title and settle time for the catalogue, without opening a session. */
113
107
  public async digest(): Promise<SessionDigest> {
114
108
  const body = await durableBytes(Bun.file(this.path));
115
109
  return body === undefined
@@ -122,7 +116,7 @@ async function* headLines(
122
116
  file: BunFile,
123
117
  count: number
124
118
  ): AsyncGenerator<string> {
125
- // Decode with `stream: true`: a chunk boundary can split a character.
119
+ // `stream: true`: a chunk boundary can split a character.
126
120
  const decoder = new TextDecoder();
127
121
  let pending = "";
128
122
  let yielded = 0;
@@ -22,7 +22,6 @@ export class ReadCursors {
22
22
  this.ready = this.load();
23
23
  }
24
24
 
25
- /** Whether a session has been answered since anything last read it. */
26
25
  public async isUnread(
27
26
  sessionId: string,
28
27
  answeredAt: number | undefined
@@ -34,7 +33,7 @@ export class ReadCursors {
34
33
  );
35
34
  }
36
35
 
37
- /** Moves a session's cursor forward; the write behind it is not waited on. */
36
+ /** Only moves forward. Does not wait for the write. */
38
37
  public async mark(sessionId: string, at: number = Date.now()): Promise<void> {
39
38
  await this.ready;
40
39
  if ((this.cursors.get(sessionId) ?? this.baseline) >= at) {
@@ -58,7 +57,7 @@ export class ReadCursors {
58
57
  }
59
58
  }
60
59
 
61
- /** Settles the writes behind the marks taken so far, for a clean stop. */
60
+ /** Waits for pending writes. */
62
61
  public async flush(): Promise<void> {
63
62
  await this.ready;
64
63
  await this.writes.run(async () => undefined);
@@ -69,7 +68,7 @@ export class ReadCursors {
69
68
  .json()
70
69
  .catch(() => undefined)) as Partial<Stored> | undefined;
71
70
  if (typeof stored?.baseline !== "number") {
72
- // Persist the baseline now: recomputing it on each start clears every unread session.
71
+ // Persist it, or every restart would mark everything read.
73
72
  this.baseline = Date.now();
74
73
  this.persist();
75
74
  return;
@@ -13,59 +13,56 @@ import {
13
13
  } from "./SessionDigest";
14
14
  import type { SessionSummary } from "./SessionRegistry";
15
15
 
16
- /** Half-open character offsets into the string they mark, non-overlapping, ascending. */
16
+ /** Half-open character offsets; non-overlapping and ascending. */
17
17
  export type SearchRange = readonly [start: number, end: number];
18
18
 
19
19
  export type SearchSnippet = {
20
20
  readonly seq: number;
21
21
  readonly role: "user" | "assistant";
22
- /** The windowed snippet, verbatim from the message. */
23
22
  readonly text: string;
24
23
  /** Offsets into `text`, not into the message. */
25
24
  readonly ranges: readonly SearchRange[];
26
- /** The window opened past the message's first word. */
25
+ /** The snippet does not start at the message start. */
27
26
  readonly cutHead?: true;
28
27
  };
29
28
 
30
29
  export type SearchHit = {
31
30
  readonly sessionId: string;
32
31
  readonly cwd: string;
33
- /** This machine's path to the session file: local to whoever reads it, and never fit for a wire. */
32
+ /** Local file path; do not send over the wire. */
34
33
  readonly path: string;
35
34
  readonly title?: string;
36
35
  readonly named?: true;
37
36
  /** Offsets into the clamped `title`. */
38
37
  readonly titleRanges: readonly SearchRange[];
39
- /** The session's opening ask, for a row whose name is all that matched and so has no snippet to show. */
38
+ /** The first user message; set only for named sessions. */
40
39
  readonly opening?: string;
41
- /** End of the last completed turn, falling back to when the session started: a row always has a clock to print. */
40
+ /** End of the last completed turn, else the session's creation time. */
42
41
  readonly settledAt: number;
43
42
  readonly snippets: readonly SearchSnippet[];
44
43
  /** Matching messages in this session, before the snippet cut. */
45
44
  readonly total: number;
46
- /** This hit was reached through typo expansion, so it ranks below every exact hit. */
45
+ /** Matched only via typo expansion; ranks below exact hits. */
47
46
  readonly typos: boolean;
48
47
  };
49
48
 
50
49
  export type SearchAnswer = {
51
50
  readonly hits: readonly SearchHit[];
52
- /** Query words dropped because they were too rare; the UI says these out loud. */
51
+ /** Query words dropped to get any results, rarest first. */
53
52
  readonly dropped: readonly string[];
54
- /** Sessions searched. */
55
53
  readonly scanned: number;
56
54
  };
57
55
 
58
56
  export type SearchOptions = {
59
57
  readonly limit?: number;
60
58
  readonly cwd?: string;
61
- /** Applied before the limit, so the server can exclude sessions without core knowing why. */
59
+ /** Applied before `limit`. */
62
60
  readonly accept?: (sessionId: string) => boolean;
63
61
  };
64
62
 
65
63
  export type SearchIndexDeps = {
66
- /** `SessionRegistry.list` in production; a fake in tests. */
67
64
  readonly list: () => Promise<readonly SessionSummary[]>;
68
- /** Only the refresh throttle reads it, and only a test replaces it. */
65
+ /** Clock for the refresh throttle. */
69
66
  readonly now?: () => number;
70
67
  };
71
68
 
@@ -83,9 +80,9 @@ type Entry = {
83
80
  readonly path: string;
84
81
  readonly createdAt: number;
85
82
  modifiedAt: number;
86
- /** Durable bytes taken from the file, and the resume point of the next tail read. */
83
+ /** Bytes read so far; the next tail read starts here. */
87
84
  offset: number;
88
- /** Durable lines taken, so an appended one keeps counting from the right ordinal. */
85
+ /** Lines read so far. */
89
86
  seq: number;
90
87
  parts: DigestParts;
91
88
  digest: SessionDigest;
@@ -97,23 +94,22 @@ type Posting = {
97
94
  readonly turn: Turn;
98
95
  };
99
96
 
100
- /** What one file's read yielded: `full` replaces the entry, otherwise it extends it. */
97
+ /** `full` replaces the entry; otherwise `body` is appended to it. */
101
98
  type Read = {
102
99
  readonly full: boolean;
103
100
  readonly body?: Durable;
104
101
  };
105
102
 
106
- /** One file's read, folded into its entry and with the bytes already let go. */
107
103
  type Taken = {
108
104
  readonly entry: Entry;
109
- /** Turns the entry held before this read, so only the appended ones are posted. */
105
+ /** Turn count before this read; later turns are new. */
110
106
  readonly from: number;
111
- /** A whole file read over an entry we already had, so every ordinal it owned is stale. */
107
+ /** An existing entry was re-read in full, so postings must be rebuilt. */
112
108
  readonly replaced: boolean;
113
109
  };
114
110
 
115
111
  type Plan = {
116
- /** Terms reached with no typos: the word itself, and prefixes of it when it is last. */
112
+ /** The word itself, plus completions when it is the last word. */
117
113
  readonly exact: readonly string[];
118
114
  readonly typo: readonly string[];
119
115
  };
@@ -124,11 +120,7 @@ type Group = {
124
120
  exact: boolean;
125
121
  };
126
122
 
127
- /**
128
- * Everything `byRank` weighs, taken from the group alone: no message is
129
- * tokenised until the sort has cut the losers, and four in five matched
130
- * sessions never reach a snippet.
131
- */
123
+ /** Rank inputs, computed without tokenising any message. */
132
124
  type Ranked = {
133
125
  readonly entry: Entry;
134
126
  readonly group: Group;
@@ -140,45 +132,34 @@ type Ranked = {
140
132
 
141
133
  const FILE_READS = 16;
142
134
 
143
- /** The granularity at which the catalogue already decided file changes become visible. */
144
135
  const REFRESH_MS = 500;
145
136
 
146
- /** Typesense's `typo_tokens_threshold`: below this many results, and only then, typos are allowed in. */
137
+ /** Typesense's `typo_tokens_threshold`: typos are tried only below this many results. */
147
138
  const ENOUGH = 1;
148
139
 
149
- /** Typesense's `max_candidates`; unbounded expansion is where precision dies. */
140
+ /** Typesense's `max_candidates`. */
150
141
  const MAX_CANDIDATES = 4;
151
142
 
152
143
  const MAX_TYPOS = 2;
153
144
 
154
- /** Typesense's `highlight_affix_num_tokens`, for the run-up only: how far back of the match a window opens. */
145
+ /** Typesense's `highlight_affix_num_tokens`: tokens shown before the first match. */
155
146
  const AFFIX = 4;
156
147
 
157
- /**
158
- * How far past the window's start a snippet runs. A row is cut to pixels, and
159
- * only the row knows how many it has, so the tail is not a window at all: it is
160
- * more text than the widest row can draw — the modal is capped at 30rem, which
161
- * is about 59 of these characters — and the client's own ellipsis does the
162
- * cutting. A word count here cut short rows shorter still, on the wide screens
163
- * that had the most room to spare.
164
- */
148
+ /** Snippet length in characters; more than the widest row shows, so the client's ellipsis cuts it. */
165
149
  const TAIL = 100;
166
150
 
167
151
  const SNIPPETS = 2;
168
152
 
169
- /** What a row draws of an opening ask before its own box cuts it anyway. */
170
153
  const PREVIEW = 200;
171
154
 
172
155
  const LIMIT = 20;
173
156
 
174
- /** Cheap reject before decoding a line: no text part, nothing to index. */
157
+ /** Lines without this are skipped before decoding. */
175
158
  const TEXT = Buffer.from('"text"');
176
159
 
177
160
  /**
178
- * An inverted index over what was said in every session on disk: `user:text`
179
- * and `assistant:text` only, which is a sixtieth of the bytes a session tree
180
- * weighs. Built lazily on the first query and refreshed by later ones, never
181
- * by a watcher, so the cold cost belongs to whoever searches.
161
+ * Inverted index over user and assistant text in every session, plus titles.
162
+ * Built on the first query and refreshed incrementally by later ones.
182
163
  */
183
164
  export class SearchIndex {
184
165
  private readonly list: () => Promise<readonly SessionSummary[]>;
@@ -229,13 +210,13 @@ export class SearchIndex {
229
210
  hits: ranked
230
211
  .sort(byRank)
231
212
  .slice(0, options.limit ?? LIMIT)
232
- .map((one) => hitOf(one)),
213
+ .map(hitOf),
233
214
  dropped,
234
215
  scanned: scope.size,
235
216
  };
236
217
  }
237
218
 
238
- /** Exact and prefix first; typos only widen a result set that came back thin. */
219
+ /** Exact and prefix first; adds typos only while results are too few. */
239
220
  private run(words: readonly string[], scope: ReadonlySet<Entry>): Ranked[] {
240
221
  const reach = Math.min(MAX_TYPOS, Math.max(...words.map(gateOf)));
241
222
  const last = words.length - 1;
@@ -286,15 +267,11 @@ export class SearchIndex {
286
267
  }
287
268
 
288
269
  private collect(plans: readonly Plan[], scope: ReadonlySet<Entry>): Ranked[] {
289
- const turnsExact = plans.map((plan) =>
290
- this.union(this.postings, plan.exact)
291
- );
292
- const titlesExact = plans.map((plan) =>
293
- this.union(this.titles, plan.exact)
294
- );
270
+ const turnsExact = plans.map((plan) => union(this.postings, plan.exact));
271
+ const titlesExact = plans.map((plan) => union(this.titles, plan.exact));
295
272
  const everything = plans.map((plan) => [...plan.exact, ...plan.typo]);
296
- const turns = everything.map((terms) => this.union(this.postings, terms));
297
- const titles = everything.map((terms) => this.union(this.titles, terms));
273
+ const turns = everything.map((terms) => union(this.postings, terms));
274
+ const titles = everything.map((terms) => union(this.titles, terms));
298
275
  const terms = new Set(everything.flat());
299
276
 
300
277
  const groups = new Map<Entry, Group>();
@@ -363,19 +340,6 @@ export class SearchIndex {
363
340
  .slice(0, MAX_CANDIDATES);
364
341
  }
365
342
 
366
- private union<T>(
367
- index: ReadonlyMap<string, T[]>,
368
- terms: readonly string[]
369
- ): ReadonlySet<T> {
370
- const found = new Set<T>();
371
- for (const term of terms) {
372
- for (const value of index.get(term) ?? []) {
373
- found.add(value);
374
- }
375
- }
376
- return found;
377
- }
378
-
379
343
  private vocabularyOf(): readonly string[] {
380
344
  if (this.vocabularyStale) {
381
345
  this.vocabulary = [
@@ -421,9 +385,7 @@ export class SearchIndex {
421
385
  }
422
386
  }
423
387
 
424
- // A posting is an ordinal into one flat array of turns, which only holds
425
- // while every entry keeps the turns it had; a replaced or departed session
426
- // invalidates the lot, and an appended one does not.
388
+ // Postings are indices into `turns`, so a replaced or removed session forces a full reindex.
427
389
  if (replaced) {
428
390
  this.reindex();
429
391
  } else {
@@ -436,11 +398,7 @@ export class SearchIndex {
436
398
  this.syncedAt = this.now();
437
399
  }
438
400
 
439
- /**
440
- * One file's bytes end here, with the turns and parts taken out of them:
441
- * held until the pool drained instead, a whole session tree would be
442
- * resident at once. The entry lands in `entries` in the caller's order.
443
- */
401
+ /** Absorbs the file's bytes immediately so they are not all held at once. */
444
402
  private async take(summary: SessionSummary): Promise<Taken | undefined> {
445
403
  const read = await this.read(summary);
446
404
  if (read === undefined) {
@@ -456,11 +414,7 @@ export class SearchIndex {
456
414
  return { entry, from, replaced: read.full && known !== undefined };
457
415
  }
458
416
 
459
- /**
460
- * A session file is append-only, so the usual read is the bytes past the
461
- * stored offset. Only a file that shrank or whose mtime went backwards has
462
- * been rewritten under us, and only that forces the whole file again.
463
- */
417
+ /** Reads only the appended tail, unless the file shrank or its mtime went backwards. */
464
418
  private async read(summary: SessionSummary): Promise<Read | undefined> {
465
419
  const known = this.entries.get(summary.sessionId);
466
420
  const file = Bun.file(summary.path);
@@ -643,7 +597,7 @@ function rangesOf(
643
597
  return rangesIn(SearchTokens.scan(text), terms, 0, text.length);
644
598
  }
645
599
 
646
- /** Offsets into the cut the caller is about to make, not into the string the tokens came from. */
600
+ /** Ranges within `[from, to)`, relative to `from`. */
647
601
  function rangesIn(
648
602
  tokens: readonly SearchToken[],
649
603
  terms: ReadonlySet<string>,
@@ -675,12 +629,12 @@ function merge(ranges: readonly SearchRange[]): readonly SearchRange[] {
675
629
  return marked;
676
630
  }
677
631
 
678
- /** What you asked is more memorable than what the agent answered. */
632
+ /** User turns first, then by seq. */
679
633
  function byRecognition(a: Turn, b: Turn): number {
680
634
  return a.role === b.role ? a.seq - b.seq : a.role === "user" ? -1 : 1;
681
635
  }
682
636
 
683
- /** The turn a hit will lead with, without sorting the ones it will never show. */
637
+ /** The first turn by `byRecognition`, without sorting. */
684
638
  function leadOf(turns: readonly Turn[]): Turn | undefined {
685
639
  let lead: Turn | undefined;
686
640
  for (const turn of turns) {
@@ -698,11 +652,7 @@ function byRank(a: Ranked, b: Ranked): number {
698
652
  return a.field - b.field || b.settledAt - a.settledAt;
699
653
  }
700
654
 
701
- /**
702
- * A title match outranks a spoken one. The title index is built from the very
703
- * tokens `rangesOf` marks, so a titled group always has a range to show, and
704
- * the field is settled without cutting a single snippet.
705
- */
655
+ /** Title match, then user turn, then assistant turn. */
706
656
  function fieldOf(group: Group, title: string | undefined): number {
707
657
  if (group.title && title !== undefined) {
708
658
  return 0;
@@ -737,6 +687,19 @@ function groupIn(groups: Map<Entry, Group>, entry: Entry): Group {
737
687
  return found;
738
688
  }
739
689
 
690
+ function union<T>(
691
+ index: ReadonlyMap<string, T[]>,
692
+ terms: readonly string[]
693
+ ): ReadonlySet<T> {
694
+ const found = new Set<T>();
695
+ for (const term of terms) {
696
+ for (const value of index.get(term) ?? []) {
697
+ found.add(value);
698
+ }
699
+ }
700
+ return found;
701
+ }
702
+
740
703
  function intersect<T>(sets: readonly ReadonlySet<T>[]): ReadonlySet<T> {
741
704
  const [first, ...rest] = [...sets].sort((a, b) => a.size - b.size);
742
705
  if (first === undefined) {