pim-agent 0.12.1 → 0.14.0

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 (264) hide show
  1. package/package.json +4 -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 +3 -2
  36. package/packages/core/src/extensions/system-prompt/prompt.ts +13 -3
  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-WweOU5DK.js +1 -0
  163. package/packages/web/dist/client/assets/arc-O2PmZXWt.js +1 -0
  164. package/packages/web/dist/client/assets/architecture-7GRP2DOG-COBxVilU.js +1 -0
  165. package/packages/web/dist/client/assets/architectureDiagram-NJMV4G6O-DwYq1YaM.js +36 -0
  166. package/packages/web/dist/client/assets/array-BifhSqXX.js +1 -0
  167. package/packages/web/dist/client/assets/blockDiagram-BEXU5L5S-CVab041n.js +140 -0
  168. package/packages/web/dist/client/assets/c4Diagram-YGBWAQC7-BmotHez3.js +38 -0
  169. package/packages/web/dist/client/assets/channel-BKV-IcTA.js +1 -0
  170. package/packages/web/dist/client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  171. package/packages/web/dist/client/assets/chunk-3FUC2YCW-CWNS7-n4.js +2 -0
  172. package/packages/web/dist/client/assets/chunk-5DYCD2WN-DflvXohu.js +1 -0
  173. package/packages/web/dist/client/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  174. package/packages/web/dist/client/assets/chunk-742MDFTN-B_0t6_RE.js +1 -0
  175. package/packages/web/dist/client/assets/chunk-7INBJB4K-wKj0Q3Kv.js +62 -0
  176. package/packages/web/dist/client/assets/chunk-7M6MHVWA-BMvUghc8.js +213 -0
  177. package/packages/web/dist/client/assets/chunk-7PRAP22T-BY41wkcT.js +1 -0
  178. package/packages/web/dist/client/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  179. package/packages/web/dist/client/assets/chunk-FOHPRMQF-CoA4358r.js +161 -0
  180. package/packages/web/dist/client/assets/chunk-GTNCS2PH-PqY_YoS0.js +88 -0
  181. package/packages/web/dist/client/assets/chunk-GWA4HPMP-fozSU5ty.js +1 -0
  182. package/packages/web/dist/client/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  183. package/packages/web/dist/client/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  184. package/packages/web/dist/client/assets/chunk-MBY4JIJT-CrHrC3jw.js +72 -0
  185. package/packages/web/dist/client/assets/chunk-NETBCI7D-DWdAxieG.js +1 -0
  186. package/packages/web/dist/client/assets/chunk-O7XYJQB3-B6uOn9iS.js +128 -0
  187. package/packages/web/dist/client/assets/chunk-UA2S7LBM-BxYSHqyy.js +1 -0
  188. package/packages/web/dist/client/assets/chunk-WEXAMYUT-CtwIaQDa.js +1 -0
  189. package/packages/web/dist/client/assets/chunk-XXDRQBXY-fgBFe-PX.js +1 -0
  190. package/packages/web/dist/client/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  191. package/packages/web/dist/client/assets/chunk-Z7XXMR3K-CPonoV3p.js +10 -0
  192. package/packages/web/dist/client/assets/chunk-ZIGJFQKS-Cdu87cTj.js +2 -0
  193. package/packages/web/dist/client/assets/classDiagram-v2-NBCMYWYE-DSWsPaa7.js +217 -0
  194. package/packages/web/dist/client/assets/core-CBoS7AST.js +3 -0
  195. package/packages/web/dist/client/assets/cose-bilkent-JH36ORCC-CTwlldtJ.js +1 -0
  196. package/packages/web/dist/client/assets/cynefin-OW5HDTMX-CZQ4EPNJ.js +1 -0
  197. package/packages/web/dist/client/assets/cynefinDiagram-VND7K2PF-C2gp6H_Q.js +62 -0
  198. package/packages/web/dist/client/assets/cytoscape.esm-B-1iW2ft.js +321 -0
  199. package/packages/web/dist/client/assets/dagre-6A5THRUB-CDxTP47t.js +4 -0
  200. package/packages/web/dist/client/assets/defaultLocale-BFoDCU3G.js +1 -0
  201. package/packages/web/dist/client/assets/diagram-22UHCM2B-BIohE1lI.js +200 -0
  202. package/packages/web/dist/client/assets/diagram-3UASUU5V-TwAUir-w.js +24 -0
  203. package/packages/web/dist/client/assets/diagram-ATOU4E4O-BM1JQrIu.js +3 -0
  204. package/packages/web/dist/client/assets/diagram-CDSNMT55-BKIqhxSI.js +30 -0
  205. package/packages/web/dist/client/assets/diagram-MLGK6HIB-CVe3QDHr.js +24 -0
  206. package/packages/web/dist/client/assets/diagram-MPIPVDR6-Bj-MQIFI.js +41 -0
  207. package/packages/web/dist/client/assets/dist-DlZpcyc2.js +1 -0
  208. package/packages/web/dist/client/assets/ebnfDiagram-ZINNZB2B-C06mmvmz.js +1 -0
  209. package/packages/web/dist/client/assets/elk-276RUBZZ-CizJSNEj.js +27 -0
  210. package/packages/web/dist/client/assets/erDiagram-OPXOYQCR-M3FQkKI1.js +99 -0
  211. package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-D_UXLpIu.js +1 -0
  212. package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-BT4eEEVm.js +1 -0
  213. package/packages/web/dist/client/assets/ganttDiagram-FUAMR5RP-CojF9Q16.js +292 -0
  214. package/packages/web/dist/client/assets/gitGraph-4MIJSDKK-YvKqGEPk.js +1 -0
  215. package/packages/web/dist/client/assets/gitGraphDiagram-X574FWY7-XqNqIFo9.js +106 -0
  216. package/packages/web/dist/client/assets/graphlib-DS17s2tU.js +1 -0
  217. package/packages/web/dist/client/assets/index--2CDtzvY.css +1 -0
  218. package/packages/web/dist/client/assets/index-bT6QPJZs.js +47 -0
  219. package/packages/web/dist/client/assets/info-A6RAGUB7-B_1BCzM2.js +1 -0
  220. package/packages/web/dist/client/assets/infoDiagram-VRGFBTTK-zFX8B-dP.js +2 -0
  221. package/packages/web/dist/client/assets/init-C-OQMol4.js +1 -0
  222. package/packages/web/dist/client/assets/ishikawaDiagram-OU5B5YK6-DLzQ_Uyf.js +70 -0
  223. package/packages/web/dist/client/assets/journeyDiagram-ZHPQQLJL-S3WRMmoa.js +139 -0
  224. package/packages/web/dist/client/assets/kanban-definition-PNTS6WVX-B5Z8I-kM.js +89 -0
  225. package/packages/web/dist/client/assets/katex-ZlcWpGUi.js +257 -0
  226. package/packages/web/dist/client/assets/line-BGMH-i2X.js +1 -0
  227. package/packages/web/dist/client/assets/linear-KsrestXG.js +1 -0
  228. package/packages/web/dist/client/assets/mermaid-parser.core-DNffyJJA.js +7 -0
  229. package/packages/web/dist/client/assets/mermaid.core-Cz3vJeQG.js +44 -0
  230. package/packages/web/dist/client/assets/mindmap-definition-NLK3R4M7-CzA61XUf.js +96 -0
  231. package/packages/web/dist/client/assets/ordinal-BDEzSJ7C.js +1 -0
  232. package/packages/web/dist/client/assets/packet-AYTQ26CC-BO55e7cR.js +1 -0
  233. package/packages/web/dist/client/assets/path-fybaL0A-.js +1 -0
  234. package/packages/web/dist/client/assets/pegDiagram-GJSIUBJH-DeRiXYNd.js +1 -0
  235. package/packages/web/dist/client/assets/pie-WAS4IAKB-D9ibrQRe.js +1 -0
  236. package/packages/web/dist/client/assets/pieDiagram-5QR66LMP-BaBpPP33.js +39 -0
  237. package/packages/web/dist/client/assets/quadrantDiagram-O4NWA36T-DIfn4GjM.js +7 -0
  238. package/packages/web/dist/client/assets/radar-RG4KPBEZ-Dmn5-nJq.js +1 -0
  239. package/packages/web/dist/client/assets/railroad-74A4TZTK-ZCWNEd2Z.js +1 -0
  240. package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-_swqhy0y.js +1 -0
  241. package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CytbazPx.js +1 -0
  242. package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-Bg8QW17T.js +1 -0
  243. package/packages/web/dist/client/assets/railroadDiagram-XR7U4H2S-CDPZ39sy.js +1 -0
  244. package/packages/web/dist/client/assets/requirementDiagram-PLB6GJNP-CyCI8Yul.js +84 -0
  245. package/packages/web/dist/client/assets/rough.esm-Dy-Kn_BL.js +1 -0
  246. package/packages/web/dist/client/assets/sankeyDiagram-IPEJSGJF-p_XAPv72.js +40 -0
  247. package/packages/web/dist/client/assets/sequenceDiagram-PO4LG4MO-DmR6IP_o.js +169 -0
  248. package/packages/web/dist/client/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
  249. package/packages/web/dist/client/assets/src-gnv7pJ1z.js +1 -0
  250. package/packages/web/dist/client/assets/stateDiagram-v2-GCMORJYK-7ZTHl6ih.js +291 -0
  251. package/packages/web/dist/client/assets/swimlanes-2SLR337P-N6bj71pw.js +1 -0
  252. package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-BJQ2CBsp.js +8 -0
  253. package/packages/web/dist/client/assets/timeline-definition-EJHVYXUP-GNez0kW8.js +120 -0
  254. package/packages/web/dist/client/assets/treeView-Q6P3EWNA-DyQZkboR.js +1 -0
  255. package/packages/web/dist/client/assets/treemap-WGGIJYW6-_J8Pw2TM.js +1 -0
  256. package/packages/web/dist/client/assets/usecaseDiagram-POWQR4AR-Bg-CzKjl.js +328 -0
  257. package/packages/web/dist/client/assets/vennDiagram-UO4OBE2U-DBQKHXhK.js +34 -0
  258. package/packages/web/dist/client/assets/wardley-WFR3VGLG-B5aAsQ3F.js +1 -0
  259. package/packages/web/dist/client/assets/wardleyDiagram-VNRHLVJA-BKUQ1LK6.js +78 -0
  260. package/packages/web/dist/client/assets/xychartDiagram-PMCCYNJV-BI2sVbks.js +7 -0
  261. package/packages/web/dist/client/index.html +2 -2
  262. package/packages/web/dist/client/assets/core-C5fJJN75.js +0 -3
  263. package/packages/web/dist/client/assets/index-C5D7B_8d.js +0 -45
  264. package/packages/web/dist/client/assets/index-OVLzpES-.css +0 -1
@@ -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) {
@@ -1,6 +1,5 @@
1
- /** One indexable word and where it sits in the string it came from. */
2
1
  export type SearchToken = {
3
- /** Lowercased: the corpus itself is never lowered, only its tokens. */
2
+ /** Lowercased. */
4
3
  readonly text: string;
5
4
  readonly start: number;
6
5
  readonly end: number;
@@ -33,10 +32,8 @@ function scan(text: string): readonly SearchToken[] {
33
32
  }
34
33
 
35
34
  /**
36
- * The words a query asks for: its pieces alone, never the identifier they
37
- * spell. `SessionLease` asks for `session` and `lease`, which the index holds
38
- * for `SessionLease` and for `session lease` alike; asking for `sessionlease`
39
- * too would AND away the second.
35
+ * Query pieces only, never the whole identifier: `SessionLease` asks for
36
+ * `session` and `lease`, so it also matches "session lease".
40
37
  */
41
38
  function words(query: string): readonly string[] {
42
39
  const found: string[] = [];
@@ -51,7 +48,7 @@ function words(query: string): readonly string[] {
51
48
  return found;
52
49
  }
53
50
 
54
- /** Half-open slices of one run: `WsGateway` is `ws` and `gateway`, `read_file` is `read` and `file`. */
51
+ /** Half-open slices: `WsGateway` → `ws`, `gateway`; `read_file` → `read`, `file`. */
55
52
  function piecesOf(run: string): readonly (readonly [number, number])[] {
56
53
  const pieces: (readonly [number, number])[] = [];
57
54
  let start = -1;
@@ -1,4 +1,3 @@
1
- /** What a cache entry has to offer: a recency stamp and a way to let go. */
2
1
  export type CachedSession = {
3
2
  lastUsed: number;
4
3
  dispose(): Promise<void>;
@@ -6,7 +5,7 @@ export type CachedSession = {
6
5
 
7
6
  const CAPACITY = 16;
8
7
 
9
- /** Live sessions, keyed however the frontend keys them, capped by least-recently-used. */
8
+ /** LRU cache of live sessions; evicted entries are disposed. */
10
9
  export class SessionCache<T extends CachedSession> {
11
10
  private readonly capacity: number;
12
11
  private readonly entries = new Map<string, T>();
@@ -19,12 +18,11 @@ export class SessionCache<T extends CachedSession> {
19
18
  return this.entries.size;
20
19
  }
21
20
 
22
- /** The live entry, if there is one, without counting as a use. */
21
+ /** Does not count as a use. */
23
22
  public peek(key: string): T | undefined {
24
23
  return this.entries.get(key);
25
24
  }
26
25
 
27
- /** The live entry, marked used now so eviction passes over it. */
28
26
  public touch(key: string): T | undefined {
29
27
  const entry = this.entries.get(key);
30
28
  if (entry) {
@@ -33,7 +31,6 @@ export class SessionCache<T extends CachedSession> {
33
31
  return entry;
34
32
  }
35
33
 
36
- /** Store `value`, disposing the least recently used entry first when full. */
37
34
  public adopt(key: string, value: T): T {
38
35
  this.evictIfNeeded();
39
36
  this.entries.set(key, value);
@@ -63,6 +60,11 @@ export class SessionCache<T extends CachedSession> {
63
60
  }
64
61
  const evicted = this.entries.get(oldestKey)!;
65
62
  this.entries.delete(oldestKey);
66
- void evicted.dispose();
63
+ evicted.dispose().catch((err: unknown) => {
64
+ console.warn(
65
+ `[pim] failed to dispose evicted session ${oldestKey}:`,
66
+ err
67
+ );
68
+ });
67
69
  }
68
70
  }
@@ -3,35 +3,27 @@ import { parseSessionEntries } from "@earendil-works/pi-coding-agent";
3
3
  import { Attachments } from "../attachments/Attachments";
4
4
  import { MessageText } from "./MessageText";
5
5
 
6
- /** What the session catalogue shows for one file without opening a session. */
6
+ /** Catalogue summary of a session file. */
7
7
  export type SessionDigest = {
8
- /** The session's name if it has one, else its first user message; absent when it has neither. */
8
+ /** The session name, else the first user message. */
9
9
  readonly title?: string;
10
- /** True when `title` is a name somebody wrote rather than the opening message. */
10
+ /** Set when `title` is a session name. */
11
11
  readonly named?: true;
12
- /** When the agent last wrote; it moves with an in-flight turn, so read it before starting one. */
12
+ /** When the agent last wrote. Moves during a turn, so read it before starting one. */
13
13
  readonly settledAt?: number;
14
14
  };
15
15
 
16
- /**
17
- * The digest before it is clamped, so a reader that tails an appended region
18
- * can fold that region into what an earlier read of the same file found.
19
- */
16
+ /** The unclamped digest, so a tail read can be merged into an earlier one. */
20
17
  export type DigestParts = {
21
18
  readonly name?: string;
22
- /** The opening ask, unclamped. */
23
19
  readonly opening?: string;
24
20
  readonly settledAt?: number;
25
21
  };
26
22
 
27
- /**
28
- * A session file's durable bytes, undecoded: a whole session is megabytes of
29
- * tool output and the digest wants a handful of lines of it, so the search
30
- * runs over the bytes and only the lines it keeps become strings.
31
- */
23
+ /** A session file's bytes, kept undecoded so only the lines used become strings. */
32
24
  export type Durable = {
33
25
  readonly bytes: Buffer;
34
- /** Past the last newline; a trailing fragment is a write in progress and is not a line. */
26
+ /** Just past the last newline; a trailing partial line is excluded. */
35
27
  readonly end: number;
36
28
  };
37
29
 
@@ -41,7 +33,7 @@ const TAIL_LINES = 10;
41
33
 
42
34
  const TITLE_LIMIT = 120;
43
35
 
44
- /** Cheap reject before decoding a line: pi writes the discriminant verbatim. */
36
+ /** Searched in raw bytes before decoding a line. */
45
37
  const NAMED = Buffer.from('"session_info"');
46
38
 
47
39
  export const NEWLINE = 0x0a;
@@ -60,7 +52,7 @@ function partsOf(body: Durable): DigestParts {
60
52
  };
61
53
  }
62
54
 
63
- /** An append only adds, so the opening ask stands and a later name or agent line wins. */
55
+ /** Keeps the earlier opening; a later name or settle time wins. */
64
56
  function merge(before: DigestParts, appended: DigestParts): DigestParts {
65
57
  return {
66
58
  name: appended.name ?? before.name,
@@ -78,7 +70,7 @@ function of({ name, opening, settledAt }: DigestParts): SessionDigest {
78
70
  };
79
71
  }
80
72
 
81
- /** The last `session_info` line, found by walking the marker's occurrences backwards; a line that merely quotes the marker parses as something else, and the walk carries on past it. */
73
+ /** The last `session_info` entry's name. Skips lines that merely quote the marker. */
82
74
  function nameOf({ bytes, end }: Durable): string | undefined {
83
75
  let at = end - 1;
84
76
  while (at >= 0) {
@@ -140,7 +132,6 @@ function clamp(text: string): string {
140
132
  : text;
141
133
  }
142
134
 
143
- /** Over the head window the caller has already cut, not the whole file. */
144
135
  function firstUserMessage(lines: readonly string[]): string | undefined {
145
136
  for (const line of lines) {
146
137
  const entry = parseSessionEntries(line)[0];