pim-agent 0.13.0 → 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 (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-WweOU5DK.js} +1 -1
  163. package/packages/web/dist/client/assets/{arc-DiHGkUvc.js → arc-O2PmZXWt.js} +1 -1
  164. package/packages/web/dist/client/assets/architecture-7GRP2DOG-COBxVilU.js +1 -0
  165. package/packages/web/dist/client/assets/{architectureDiagram-NJMV4G6O-XS5DhfJB.js → architectureDiagram-NJMV4G6O-DwYq1YaM.js} +1 -1
  166. package/packages/web/dist/client/assets/{blockDiagram-BEXU5L5S-BCsgLvy1.js → blockDiagram-BEXU5L5S-CVab041n.js} +1 -1
  167. package/packages/web/dist/client/assets/{c4Diagram-YGBWAQC7-Bj9LcWNm.js → c4Diagram-YGBWAQC7-BmotHez3.js} +1 -1
  168. package/packages/web/dist/client/assets/channel-BKV-IcTA.js +1 -0
  169. package/packages/web/dist/client/assets/{chunk-3FUC2YCW-BmDurFxp.js → chunk-3FUC2YCW-CWNS7-n4.js} +1 -1
  170. package/packages/web/dist/client/assets/{chunk-5DYCD2WN-DAjG-pqn.js → chunk-5DYCD2WN-DflvXohu.js} +1 -1
  171. package/packages/web/dist/client/assets/{chunk-742MDFTN-Bmxs5puY.js → chunk-742MDFTN-B_0t6_RE.js} +1 -1
  172. package/packages/web/dist/client/assets/{chunk-7INBJB4K-Di7kIxrQ.js → chunk-7INBJB4K-wKj0Q3Kv.js} +1 -1
  173. package/packages/web/dist/client/assets/{chunk-7M6MHVWA-CSc8sR0b.js → chunk-7M6MHVWA-BMvUghc8.js} +1 -1
  174. package/packages/web/dist/client/assets/{chunk-7PRAP22T-C8kd5QvY.js → chunk-7PRAP22T-BY41wkcT.js} +1 -1
  175. package/packages/web/dist/client/assets/{chunk-GTNCS2PH-DG2b2LYE.js → chunk-GTNCS2PH-PqY_YoS0.js} +1 -1
  176. package/packages/web/dist/client/assets/{chunk-GWA4HPMP-DdLaLQCu.js → chunk-GWA4HPMP-fozSU5ty.js} +1 -1
  177. package/packages/web/dist/client/assets/{chunk-MBY4JIJT-C1j0yQlk.js → chunk-MBY4JIJT-CrHrC3jw.js} +1 -1
  178. package/packages/web/dist/client/assets/{chunk-NETBCI7D-Bfq9H74p.js → chunk-NETBCI7D-DWdAxieG.js} +1 -1
  179. package/packages/web/dist/client/assets/{chunk-O7XYJQB3-ZHvBmBO-.js → chunk-O7XYJQB3-B6uOn9iS.js} +1 -1
  180. package/packages/web/dist/client/assets/{chunk-UA2S7LBM-ku1JeHSK.js → chunk-UA2S7LBM-BxYSHqyy.js} +1 -1
  181. package/packages/web/dist/client/assets/{chunk-WEXAMYUT-BP1iga_7.js → chunk-WEXAMYUT-CtwIaQDa.js} +1 -1
  182. package/packages/web/dist/client/assets/{chunk-XXDRQBXY-BBeccPcX.js → chunk-XXDRQBXY-fgBFe-PX.js} +1 -1
  183. package/packages/web/dist/client/assets/{chunk-Z7XXMR3K-SMHZugaR.js → chunk-Z7XXMR3K-CPonoV3p.js} +1 -1
  184. package/packages/web/dist/client/assets/{chunk-ZIGJFQKS-CFU_JLG4.js → chunk-ZIGJFQKS-Cdu87cTj.js} +1 -1
  185. package/packages/web/dist/client/assets/{classDiagram-v2-NBCMYWYE-CYviuQzJ.js → classDiagram-v2-NBCMYWYE-DSWsPaa7.js} +1 -1
  186. package/packages/web/dist/client/assets/{core-DyHsgs5k.js → core-CBoS7AST.js} +1 -1
  187. package/packages/web/dist/client/assets/{cose-bilkent-JH36ORCC-9v2ZkJzo.js → cose-bilkent-JH36ORCC-CTwlldtJ.js} +1 -1
  188. package/packages/web/dist/client/assets/{cynefin-OW5HDTMX-Bx5xRadi.js → cynefin-OW5HDTMX-CZQ4EPNJ.js} +1 -1
  189. package/packages/web/dist/client/assets/{cynefinDiagram-VND7K2PF-BRfGDKqa.js → cynefinDiagram-VND7K2PF-C2gp6H_Q.js} +1 -1
  190. package/packages/web/dist/client/assets/{dagre-6A5THRUB-C4l-2L2e.js → dagre-6A5THRUB-CDxTP47t.js} +1 -1
  191. package/packages/web/dist/client/assets/{diagram-22UHCM2B-d_hQ3kyi.js → diagram-22UHCM2B-BIohE1lI.js} +1 -1
  192. package/packages/web/dist/client/assets/{diagram-3UASUU5V-DrEeguPI.js → diagram-3UASUU5V-TwAUir-w.js} +1 -1
  193. package/packages/web/dist/client/assets/{diagram-ATOU4E4O-BmfQ22pD.js → diagram-ATOU4E4O-BM1JQrIu.js} +1 -1
  194. package/packages/web/dist/client/assets/{diagram-CDSNMT55-BjVWjuPF.js → diagram-CDSNMT55-BKIqhxSI.js} +1 -1
  195. package/packages/web/dist/client/assets/{diagram-MLGK6HIB-wDjk-O8i.js → diagram-MLGK6HIB-CVe3QDHr.js} +1 -1
  196. package/packages/web/dist/client/assets/{diagram-MPIPVDR6-DqXQFE1s.js → diagram-MPIPVDR6-Bj-MQIFI.js} +1 -1
  197. package/packages/web/dist/client/assets/{dist-BddGOpie.js → dist-DlZpcyc2.js} +1 -1
  198. package/packages/web/dist/client/assets/{ebnfDiagram-ZINNZB2B-UB8kOITM.js → ebnfDiagram-ZINNZB2B-C06mmvmz.js} +1 -1
  199. package/packages/web/dist/client/assets/{elk-276RUBZZ-D4miYQnD.js → elk-276RUBZZ-CizJSNEj.js} +1 -1
  200. package/packages/web/dist/client/assets/{erDiagram-OPXOYQCR--RXKv7KH.js → erDiagram-OPXOYQCR-M3FQkKI1.js} +1 -1
  201. package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-D_UXLpIu.js +1 -0
  202. package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-BT4eEEVm.js +1 -0
  203. package/packages/web/dist/client/assets/{ganttDiagram-FUAMR5RP-C0CFpE5x.js → ganttDiagram-FUAMR5RP-CojF9Q16.js} +1 -1
  204. package/packages/web/dist/client/assets/{gitGraph-4MIJSDKK-VPL7nv2c.js → gitGraph-4MIJSDKK-YvKqGEPk.js} +1 -1
  205. package/packages/web/dist/client/assets/{gitGraphDiagram-X574FWY7-CgxJA-T8.js → gitGraphDiagram-X574FWY7-XqNqIFo9.js} +1 -1
  206. package/packages/web/dist/client/assets/index--2CDtzvY.css +1 -0
  207. package/packages/web/dist/client/assets/{index-XTlTnQmx.js → index-bT6QPJZs.js} +7 -7
  208. package/packages/web/dist/client/assets/{info-A6RAGUB7-P0-o68pj.js → info-A6RAGUB7-B_1BCzM2.js} +1 -1
  209. package/packages/web/dist/client/assets/{infoDiagram-VRGFBTTK-DFJZNRUP.js → infoDiagram-VRGFBTTK-zFX8B-dP.js} +1 -1
  210. package/packages/web/dist/client/assets/{ishikawaDiagram-OU5B5YK6-ZkyhNd1T.js → ishikawaDiagram-OU5B5YK6-DLzQ_Uyf.js} +1 -1
  211. package/packages/web/dist/client/assets/{journeyDiagram-ZHPQQLJL-DFevH8my.js → journeyDiagram-ZHPQQLJL-S3WRMmoa.js} +1 -1
  212. package/packages/web/dist/client/assets/{kanban-definition-PNTS6WVX-l7e7ZZDC.js → kanban-definition-PNTS6WVX-B5Z8I-kM.js} +1 -1
  213. package/packages/web/dist/client/assets/{line-DO_s6Ujd.js → line-BGMH-i2X.js} +1 -1
  214. package/packages/web/dist/client/assets/{linear-C4xH5PPc.js → linear-KsrestXG.js} +1 -1
  215. package/packages/web/dist/client/assets/{mermaid-parser.core-nQaTP3VY.js → mermaid-parser.core-DNffyJJA.js} +3 -3
  216. package/packages/web/dist/client/assets/{mermaid.core-Cz05yTZh.js → mermaid.core-Cz3vJeQG.js} +4 -4
  217. package/packages/web/dist/client/assets/{mindmap-definition-NLK3R4M7-CHOEuHm2.js → mindmap-definition-NLK3R4M7-CzA61XUf.js} +1 -1
  218. package/packages/web/dist/client/assets/{packet-AYTQ26CC-BH3ehPMo.js → packet-AYTQ26CC-BO55e7cR.js} +1 -1
  219. package/packages/web/dist/client/assets/{pegDiagram-GJSIUBJH-CkWBLtad.js → pegDiagram-GJSIUBJH-DeRiXYNd.js} +1 -1
  220. package/packages/web/dist/client/assets/{pie-WAS4IAKB-nNVEq3dx.js → pie-WAS4IAKB-D9ibrQRe.js} +1 -1
  221. package/packages/web/dist/client/assets/{pieDiagram-5QR66LMP-DimUBAgN.js → pieDiagram-5QR66LMP-BaBpPP33.js} +1 -1
  222. package/packages/web/dist/client/assets/{quadrantDiagram-O4NWA36T-BR0XpRGQ.js → quadrantDiagram-O4NWA36T-DIfn4GjM.js} +1 -1
  223. package/packages/web/dist/client/assets/{radar-RG4KPBEZ-paA-H3mb.js → radar-RG4KPBEZ-Dmn5-nJq.js} +1 -1
  224. package/packages/web/dist/client/assets/{railroad-74A4TZTK-yEiKB3rz.js → railroad-74A4TZTK-ZCWNEd2Z.js} +1 -1
  225. package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-_swqhy0y.js +1 -0
  226. package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CytbazPx.js +1 -0
  227. package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-Bg8QW17T.js +1 -0
  228. package/packages/web/dist/client/assets/{railroadDiagram-XR7U4H2S--TXe6rdU.js → railroadDiagram-XR7U4H2S-CDPZ39sy.js} +1 -1
  229. package/packages/web/dist/client/assets/{requirementDiagram-PLB6GJNP-DyqH36Za.js → requirementDiagram-PLB6GJNP-CyCI8Yul.js} +1 -1
  230. package/packages/web/dist/client/assets/{sankeyDiagram-IPEJSGJF-Bg6e3BlM.js → sankeyDiagram-IPEJSGJF-p_XAPv72.js} +1 -1
  231. package/packages/web/dist/client/assets/{sequenceDiagram-PO4LG4MO-ClGf85My.js → sequenceDiagram-PO4LG4MO-DmR6IP_o.js} +1 -1
  232. package/packages/web/dist/client/assets/{src-DF7hQfMh.js → src-gnv7pJ1z.js} +1 -1
  233. package/packages/web/dist/client/assets/{stateDiagram-v2-GCMORJYK-BK8TpxNo.js → stateDiagram-v2-GCMORJYK-7ZTHl6ih.js} +1 -1
  234. package/packages/web/dist/client/assets/{swimlanes-2SLR337P-DDnTIT9M.js → swimlanes-2SLR337P-N6bj71pw.js} +1 -1
  235. package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-BJQ2CBsp.js +8 -0
  236. package/packages/web/dist/client/assets/{timeline-definition-EJHVYXUP-mBBbH3bk.js → timeline-definition-EJHVYXUP-GNez0kW8.js} +1 -1
  237. package/packages/web/dist/client/assets/{treeView-Q6P3EWNA-CTK7tRwR.js → treeView-Q6P3EWNA-DyQZkboR.js} +1 -1
  238. package/packages/web/dist/client/assets/{treemap-WGGIJYW6-vOI-Ef_m.js → treemap-WGGIJYW6-_J8Pw2TM.js} +1 -1
  239. package/packages/web/dist/client/assets/{usecaseDiagram-POWQR4AR-COcii8x-.js → usecaseDiagram-POWQR4AR-Bg-CzKjl.js} +1 -1
  240. package/packages/web/dist/client/assets/{vennDiagram-UO4OBE2U-D6kmDGpM.js → vennDiagram-UO4OBE2U-DBQKHXhK.js} +1 -1
  241. package/packages/web/dist/client/assets/{wardley-WFR3VGLG-CtE2KI9r.js → wardley-WFR3VGLG-B5aAsQ3F.js} +1 -1
  242. package/packages/web/dist/client/assets/{wardleyDiagram-VNRHLVJA-ncxNGWnW.js → wardleyDiagram-VNRHLVJA-BKUQ1LK6.js} +1 -1
  243. package/packages/web/dist/client/assets/{xychartDiagram-PMCCYNJV-DUvd2Qze.js → xychartDiagram-PMCCYNJV-BI2sVbks.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
@@ -6,11 +6,11 @@ import { ImageRoute } from "#protocol/ImageRoute";
6
6
  import { IMMUTABLE, serveFile } from "./StaticClient";
7
7
 
8
8
  export type ImageEndpointDeps = {
9
- /** Defaults to `~/.pim/cache`, where `read` spills the picture it showed the model. */
9
+ /** Defaults to `~/.pim/cache`. */
10
10
  readonly root?: string;
11
11
  };
12
12
 
13
- /** `GET /image/<sha256>.<ext>`: the cached copy of a picture a tool view addresses, or 404 once the TTL sweep took it. */
13
+ /** Serves images cached by `read`; 404 once the TTL sweep removed them. */
14
14
  export class ImageEndpoint {
15
15
  private readonly root: string;
16
16
 
@@ -18,7 +18,6 @@ export class ImageEndpoint {
18
18
  this.root = deps.root ?? SpillCache.dir();
19
19
  }
20
20
 
21
- /** Whether this endpoint owns the path, so the gateway can route to it. */
22
21
  public static owns(pathname: string): boolean {
23
22
  return ImageRoute.owns(pathname);
24
23
  }
@@ -27,7 +26,7 @@ export class ImageEndpoint {
27
26
  if (request.method !== "GET" && request.method !== "HEAD") {
28
27
  return new Response("expected GET", { status: 405 });
29
28
  }
30
- // The name is a digest and a format we wrote, which admits no separator to escape the cache with.
29
+ // `nameOf` only admits `<hex>.<ext>`, so no path traversal.
31
30
  const name = ImageRoute.nameOf(new URL(request.url).pathname);
32
31
  return name === null
33
32
  ? new Response("not found", { status: 404 })
@@ -20,23 +20,21 @@ import {
20
20
 
21
21
  export type ProbeOptions = {
22
22
  readonly url: string;
23
- /** Omit to have the server create a session; the id comes back on attach. */
23
+ /** Omit to have the server create one. */
24
24
  readonly sessionId?: string;
25
25
  readonly cwd?: string;
26
26
  readonly fromSeq?: number;
27
- /** Sent on the attach frame; false is a client that is connected but not looking. */
28
27
  readonly attentive?: boolean;
29
- /** Called for every frame, in arrival order. */
30
28
  readonly onEvent?: (event: ServerEvent) => void;
31
29
  /** Keystroke debounce for `files`; 0 makes tests deterministic. */
32
30
  readonly debounceMs?: number;
33
31
  };
34
32
 
35
- /** What `POST /upload` answers with, all of it server-side. */
33
+ /** The `POST /upload` response. */
36
34
  export type UploadedFile = {
37
35
  readonly id: string;
38
36
  readonly path: string;
39
- /** Where the bytes can be read back, relative to the server's origin. */
37
+ /** Server-relative. */
40
38
  readonly url: string;
41
39
  readonly mimeType: string;
42
40
  readonly isImage: boolean;
@@ -47,12 +45,11 @@ type Pending = {
47
45
  readonly reject: (err: Error) => void;
48
46
  };
49
47
 
50
- /** The reference client: speaks the whole protocol, renders nothing. */
48
+ /** Headless protocol client for tests and the probe CLI. */
51
49
  export class ProbeClient {
52
50
  public sessionId: string | undefined;
53
51
  public seq: number;
54
52
  public readonly events: ServerEvent[] = [];
55
- /** The `@` picker, answered by the server one query at a time. */
56
53
  public readonly files: RemoteFilePickerSuggestionEngine;
57
54
  private readonly options: ProbeOptions;
58
55
  private readonly pending = new Map<string, Pending>();
@@ -105,7 +102,6 @@ export class ProbeClient {
105
102
  });
106
103
  }
107
104
 
108
- /** Says whether this client's reader is present; regaining it reads the session. */
109
105
  public attention(value: boolean): Promise<ResponseEvent> {
110
106
  return this.send({ type: "attention", value });
111
107
  }
@@ -141,7 +137,7 @@ export class ProbeClient {
141
137
  query,
142
138
  limit,
143
139
  });
144
- return itemsOf(response);
140
+ return ok(response).items ?? [];
145
141
  }
146
142
 
147
143
  public async pickCommands(
@@ -154,10 +150,10 @@ export class ProbeClient {
154
150
  query,
155
151
  ...(limit === undefined ? {} : { limit }),
156
152
  });
157
- return itemsOf(response);
153
+ return ok(response).items ?? [];
158
154
  }
159
155
 
160
- /** Reads one subagent's transcript; its events stay enveloped in `events`. */
156
+ /** Child events stay enveloped in `events`. */
161
157
  public watchSubagent(callId: string, fromSeq = 0): Promise<ResponseEvent> {
162
158
  return this.send({
163
159
  type: "watch_subagent",
@@ -171,38 +167,27 @@ export class ProbeClient {
171
167
  return this.send({ type: "unwatch_subagent", callId });
172
168
  }
173
169
 
174
- /** Pi's session catalogue; answers whether or not this probe is attached. */
175
170
  public async listSessions(
176
171
  scope: SessionScope = {}
177
172
  ): Promise<SessionListing["sessions"]> {
178
173
  return (await this.catalogue(scope)).sessions;
179
174
  }
180
175
 
181
- /** The listing whole: the rows the page kept and the projects they came from. */
182
176
  public async catalogue(scope: SessionScope = {}): Promise<SessionListing> {
183
- const response = await this.send({ type: "list_sessions", ...scope });
184
- if (!response.success) {
185
- throw new Error(response.error ?? "list_sessions failed");
186
- }
177
+ const response = ok(await this.send({ type: "list_sessions", ...scope }));
187
178
  return {
188
179
  sessions: response.sessions ?? [],
189
180
  projects: response.projects ?? [],
190
181
  };
191
182
  }
192
183
 
193
- /** Searches every session on disk; an empty query warms the index and answers with no hits. */
194
184
  public async search(
195
185
  query: string,
196
186
  scope: SearchScope = {}
197
187
  ): Promise<SessionSearch> {
198
- const response = await this.send({
199
- type: "search_sessions",
200
- query,
201
- ...scope,
202
- });
203
- if (!response.success) {
204
- throw new Error(response.error ?? "search_sessions failed");
205
- }
188
+ const response = ok(
189
+ await this.send({ type: "search_sessions", query, ...scope })
190
+ );
206
191
  return {
207
192
  hits: response.hits ?? [],
208
193
  dropped: response.dropped ?? [],
@@ -210,7 +195,6 @@ export class ProbeClient {
210
195
  };
211
196
  }
212
197
 
213
- /** Names a session through pi's own name; `null` clears it back to its opening message. */
214
198
  public rename(
215
199
  sessionId: string,
216
200
  value: string | null
@@ -218,7 +202,6 @@ export class ProbeClient {
218
202
  return this.send({ type: "set_session_name", sessionId, value });
219
203
  }
220
204
 
221
- /** Puts a session out of the live listing, or brings it back. */
222
205
  public setArchived(
223
206
  sessionId: string,
224
207
  value: boolean
@@ -226,67 +209,45 @@ export class ProbeClient {
226
209
  return this.send({ type: "set_session_archived", sessionId, value });
227
210
  }
228
211
 
229
- /** Holds a session unread until something reads it; survives a listing and a re-attach. */
230
212
  public markUnread(sessionId: string, value: boolean): Promise<ResponseEvent> {
231
213
  return this.send({ type: "set_session_unread", sessionId, value });
232
214
  }
233
215
 
234
- /** Pins a working directory, not a session. */
235
216
  public setPinned(cwd: string, value: boolean): Promise<ResponseEvent> {
236
217
  return this.send({ type: "set_project_pinned", cwd, value });
237
218
  }
238
219
 
239
- /** Re-orders the pinned directories; the whole order, pinned ones only. */
240
220
  public setPinOrder(order: readonly string[]): Promise<ResponseEvent> {
241
221
  return this.send({ type: "set_pin_order", order });
242
222
  }
243
223
 
244
- /** Unfolds a working directory's sidebar group, or folds it. */
245
224
  public setExpanded(cwd: string, value: boolean): Promise<ResponseEvent> {
246
225
  return this.send({ type: "set_project_expanded", cwd, value });
247
226
  }
248
227
 
249
- /** Names a working directory for the listings; `null` puts it back to its base name. */
250
228
  public setLabel(cwd: string, value: string | null): Promise<ResponseEvent> {
251
229
  return this.send({ type: "set_project_label", cwd, value });
252
230
  }
253
231
 
254
- /** The model catalogue, plus this session's thinking levels; empty when unattached. */
255
232
  public async listModels(): Promise<{
256
233
  readonly models: readonly ModelView[];
257
234
  readonly thinkingLevels: readonly string[];
258
235
  }> {
259
- const response = await this.send({ type: "list_models" });
260
- if (!response.success) {
261
- throw new Error(response.error ?? "list_models failed");
262
- }
236
+ const response = ok(await this.send({ type: "list_models" }));
263
237
  return {
264
238
  models: response.models ?? [],
265
239
  thinkingLevels: response.thinkingLevels ?? [],
266
240
  };
267
241
  }
268
242
 
269
- /** Every extension this server can switch, scoped to the attached session's cwd. */
270
243
  public async listExtensions(): Promise<readonly ExtensionEntry[]> {
271
- const response = await this.send({ type: "list_extensions" });
272
- if (!response.success) {
273
- throw new Error(response.error ?? "list_extensions failed");
274
- }
275
- return response.extensions ?? [];
244
+ return ok(await this.send({ type: "list_extensions" })).extensions ?? [];
276
245
  }
277
246
 
278
247
  public async setExtension(id: string, value: boolean): Promise<void> {
279
- const response = await this.send({
280
- type: "set_extension",
281
- extensionId: id,
282
- value,
283
- });
284
- if (!response.success) {
285
- throw new Error(response.error ?? "set_extension failed");
286
- }
248
+ ok(await this.send({ type: "set_extension", extensionId: id, value }));
287
249
  }
288
250
 
289
- /** Transfers a client-local file into the server's world; only the bytes and bare filename are sent. */
290
251
  public async upload(localPath: string): Promise<UploadedFile> {
291
252
  const file = Bun.file(localPath);
292
253
  const form = new FormData();
@@ -331,8 +292,6 @@ export class ProbeClient {
331
292
  return Promise.resolve(hit);
332
293
  }
333
294
  return new Promise<ServerEvent>((resolve, reject) => {
334
- const waiter = { test, resolve };
335
- this.waiters.add(waiter);
336
295
  const timer = setTimeout(() => {
337
296
  this.waiters.delete(waiter);
338
297
  reject(
@@ -340,10 +299,18 @@ export class ProbeClient {
340
299
  );
341
300
  }, timeoutMs);
342
301
  timer.unref?.();
302
+ const waiter = {
303
+ test,
304
+ resolve: (event: ServerEvent) => {
305
+ clearTimeout(timer);
306
+ resolve(event);
307
+ },
308
+ };
309
+ this.waiters.add(waiter);
343
310
  });
344
311
  }
345
312
 
346
- /** Drops the socket without a close frame, the way a killed client would. */
313
+ /** Closes with an abnormal code, like a killed client. */
347
314
  public kill(): void {
348
315
  this.socket?.close(4000, "probe killed");
349
316
  this.socket = undefined;
@@ -387,9 +354,9 @@ export class ProbeClient {
387
354
  }
388
355
  }
389
356
 
390
- function itemsOf(response: ResponseEvent): readonly PickerItem[] {
357
+ function ok(response: ResponseEvent): ResponseEvent {
391
358
  if (!response.success) {
392
- throw new Error(response.error ?? "picker query failed");
359
+ throw new Error(response.error ?? "command failed");
393
360
  }
394
- return response.items ?? [];
361
+ return response;
395
362
  }
@@ -4,21 +4,19 @@ import { Updater, type UpdateOutcome } from "#core/shared/Updater";
4
4
  import type { ServerEvent } from "#protocol/ServerEvent";
5
5
 
6
6
  export type ReloaderDeps = {
7
- /** To every connection: the restart takes all of them down together. */
8
7
  readonly announce: (event: ServerEvent) => void;
9
- /** Runs the update, reporting each step as it starts. */
10
8
  readonly update?: (onStep: (label: string) => void) => Promise<UpdateOutcome>;
11
- /** Takes this process down so the supervisor puts the new code up in its place. */
9
+ /** Exits so the supervisor restarts on the new code. */
12
10
  readonly shutdown?: () => Promise<void>;
13
11
  };
14
12
 
15
- // Re-raise SIGTERM rather than exiting: the handler stops the gateway and flushes the read cursors.
13
+ // SIGTERM rather than exit, so the handler stops the gateway and flushes read cursors.
16
14
  async function restartAndExit(): Promise<void> {
17
15
  await Supervisor.restartSiblings(DaemonUnit);
18
16
  process.kill(process.pid, "SIGTERM");
19
17
  }
20
18
 
21
- /** Runs the update-restart-return an operator asked for, announcing each phase. */
19
+ /** Runs update then restart, announcing each phase. */
22
20
  export class Reloader {
23
21
  private readonly announce: (event: ServerEvent) => void;
24
22
  private readonly update: (
@@ -34,7 +32,7 @@ export class Reloader {
34
32
  this.running = undefined;
35
33
  }
36
34
 
37
- /** The run already in flight, or a new one; two callers share one install. */
35
+ /** Concurrent callers share one run. */
38
36
  public start(): Promise<void> {
39
37
  this.running ??= this.run().finally(() => {
40
38
  this.running = undefined;
@@ -78,7 +76,6 @@ export class Reloader {
78
76
  try {
79
77
  await this.shutdown();
80
78
  } catch (err) {
81
- // Must follow the `restarting` frame: it corrects a client already told to expect the drop.
82
79
  this.failed(messageOf(err));
83
80
  }
84
81
  }
@@ -54,7 +54,7 @@ function build(
54
54
  isImage: stored.mimeType.startsWith("image/"),
55
55
  };
56
56
  return {
57
- // Never the URL: an address the model can repeat is a second, dead copy of this delivery.
57
+ // Omit the URL so the model can't repeat it.
58
58
  content: [{ type: "text", text: `Sent ${details.name} (${size} B)` }],
59
59
  details,
60
60
  };
@@ -27,9 +27,7 @@ import type {
27
27
 
28
28
  export type SessionCatalogueDeps = {
29
29
  readonly registry: SessionRegistry;
30
- /** Which sessions have been read, shared by every client. */
31
30
  readonly cursors: ReadCursors;
32
- /** Archived, held-unread and pinned, shared by every client. */
33
31
  readonly meta: SessionMeta;
34
32
  readonly liveStatus: (sessionId: string) => SessionStatus | undefined;
35
33
  readonly liveSessionIds: () => Iterable<string>;
@@ -39,10 +37,10 @@ export type SessionCatalogueDeps = {
39
37
 
40
38
  const DEFAULT_SESSION_LIMIT = 50;
41
39
 
42
- /** A digest is a whole-file read; a page of them at once is a page of files in memory at once. */
40
+ /** Concurrent digest reads; each reads a whole file into memory. */
43
41
  const DIGEST_READS = 16;
44
42
 
45
- /** Long enough that a turn's entries are one announcement, short enough that a new session appears while the user is still looking. */
43
+ /** Debounce for `sessions_changed`. */
46
44
  const ANNOUNCE_MS = 500;
47
45
 
48
46
  type CachedDigest = SessionDigest & { readonly modifiedAt: number };
@@ -51,7 +49,6 @@ type Overrides = ReadonlyMap<string, SessionEntry>;
51
49
 
52
50
  type PageScope = {
53
51
  readonly overrides: Overrides;
54
- /** True while listing the archived sessions rather than the live ones. */
55
52
  readonly archived: boolean;
56
53
  readonly limit: number;
57
54
  readonly perProject: number;
@@ -72,12 +69,7 @@ export class SessionCatalogue {
72
69
  this.index = this.newIndex();
73
70
  }
74
71
 
75
- /**
76
- * Follow the sessions tree while anyone is connected. A session the terminal
77
- * or a vanilla `pi` starts is a file this process never hears about
78
- * otherwise, and a list nobody asked to re-fetch is a list that never learns
79
- * about it.
80
- */
72
+ /** Watches the sessions tree, so sessions started by other processes are announced. */
81
73
  public watch(active: boolean): void {
82
74
  if (active === this.watching) {
83
75
  return;
@@ -103,7 +95,7 @@ export class SessionCatalogue {
103
95
  this.deps.meta.sessions(),
104
96
  this.deps.meta.pinning(),
105
97
  ]);
106
- // Prune only on an unfiltered listing: a cwd-filtered one would forget every other directory.
98
+ // Prune only when unfiltered, or other directories would be forgotten.
107
99
  if (command.cwd === undefined) {
108
100
  const alive = new Set([
109
101
  ...summaries.map((summary) => summary.sessionId),
@@ -113,9 +105,10 @@ export class SessionCatalogue {
113
105
  this.deps.cursors.prune(alive),
114
106
  this.deps.meta.prune(alive),
115
107
  ]);
108
+ this.forget(alive, new Set(summaries.map((summary) => summary.path)));
116
109
  }
117
110
  const scope = command.archived === true;
118
- // Before the cut: an archived row that ate the page budget would push a live one off the end.
111
+ // Filter before paging so out-of-scope rows don't use the page budget.
119
112
  const inScope = summaries.filter(
120
113
  (summary) =>
121
114
  (overrides.get(summary.sessionId)?.archived === true) === scope
@@ -126,19 +119,13 @@ export class SessionCatalogue {
126
119
  overrides,
127
120
  archived: scope,
128
121
  limit,
129
- // Absent, a project may fill the page; it is the page that bounds it either way.
130
122
  perProject: command.perProject ?? limit,
131
123
  }),
132
- // Counted before the cut, and over the whole scope: a collapsed group says how many it holds, and a row the cut dropped is one a client can still ask for.
124
+ // Counted before paging.
133
125
  projects: projectsOf(inScope, pinning),
134
126
  };
135
127
  }
136
128
 
137
- /**
138
- * The index cannot see the sidecar, so the archived scope is a predicate it
139
- * calls before its own limit, and the badge is a `Map.get` over the one read
140
- * of `sessions.json` this query makes.
141
- */
142
129
  public async search(
143
130
  command: Command & { readonly type: "search_sessions" }
144
131
  ): Promise<SessionSearch> {
@@ -162,7 +149,7 @@ export class SessionCatalogue {
162
149
  };
163
150
  }
164
151
 
165
- /** Announces a status edge to every client; the announcement must precede the read mark. */
152
+ /** Announces a status change; must precede the read mark. */
166
153
  public onStatus(sessionId: string, status: SessionStatus): void {
167
154
  if (this.activity.get(sessionId) === status) {
168
155
  return;
@@ -178,10 +165,10 @@ export class SessionCatalogue {
178
165
  this.activity.set(sessionId, status);
179
166
  }
180
167
 
181
- /** Moves a session's read cursor to now, drops the mark a reader left on it by hand, and says so to every client. */
168
+ /** Marks read now, clears the sticky unread flag, and announces it. */
182
169
  public async markRead(sessionId: string): Promise<void> {
183
170
  await this.deps.cursors.mark(sessionId);
184
- // The only place the sticky flag is cleared; a listing must never clear it.
171
+ // The only place the sticky flag is cleared.
185
172
  if ((await this.deps.meta.of(sessionId)).unread === true) {
186
173
  await this.deps.meta.setUnread(sessionId, false);
187
174
  }
@@ -199,17 +186,28 @@ export class SessionCatalogue {
199
186
  this.index = this.newIndex();
200
187
  }
201
188
 
202
- /** One index per catalogue, and so one per session root; the registry's listing is the tree it follows. */
189
+ private forget(alive: ReadonlySet<string>, paths: ReadonlySet<string>): void {
190
+ for (const map of [this.activity, this.settled]) {
191
+ for (const sessionId of map.keys()) {
192
+ if (!alive.has(sessionId)) {
193
+ map.delete(sessionId);
194
+ }
195
+ }
196
+ }
197
+ for (const path of this.digests.keys()) {
198
+ if (!paths.has(path)) {
199
+ this.digests.delete(path);
200
+ }
201
+ }
202
+ }
203
+
203
204
  private newIndex(): SearchIndex {
204
205
  return new SearchIndex({ list: () => this.deps.registry.list() });
205
206
  }
206
207
 
207
208
  /**
208
- * Selects by modified time under a per-project budget and digests only what
209
- * it selects: ordering by settle time would read every session on disk. A
210
- * session with nothing to call itself is not a row, and the budget it spent
211
- * goes back to its project, so a directory of abandoned files still shows
212
- * the sessions underneath them.
209
+ * Selects by mtime under a per-project budget and digests only the selection.
210
+ * A session without a title yields no row and refunds its project's budget.
213
211
  */
214
212
  private async page(
215
213
  candidates: readonly SessionSummary[],
@@ -241,7 +239,7 @@ export class SessionCatalogue {
241
239
  return rows.sort((a, b) => b.settledAt - a.settledAt);
242
240
  }
243
241
 
244
- /** Nothing for a session that never asked anything and was never named: the row it would draw is a truncated UUID. */
242
+ /** Undefined for a session with no title (never prompted or named). */
245
243
  private async row(
246
244
  { sessionId, cwd, path, createdAt, modifiedAt }: SessionSummary,
247
245
  { overrides, archived }: PageScope
@@ -273,7 +271,7 @@ export class SessionCatalogue {
273
271
  };
274
272
  }
275
273
 
276
- // One watch per directory, never a recursive one: `fs.watch` recursion is unsupported on some platforms and silent on others.
274
+ // One watch per directory: recursive `fs.watch` is unreliable across platforms.
277
275
  private follow(): void {
278
276
  const root = this.deps.registry.sessionsRoot;
279
277
  const wanted = new Set([root, ...FileWatch.subdirectories(root)]);
@@ -295,7 +293,6 @@ export class SessionCatalogue {
295
293
  }
296
294
  }
297
295
 
298
- // Debounced: a turn writes a dozen entries, and re-listing is a read of every session header.
299
296
  private onTreeChange(): void {
300
297
  if (this.announcing !== undefined || !this.watching) {
301
298
  return;
@@ -308,7 +305,7 @@ export class SessionCatalogue {
308
305
  this.announcing.unref?.();
309
306
  }
310
307
 
311
- // A running session must be answered for by its last idle reading: mid-turn the file's time is the line just written.
308
+ // Mid-turn, use the last idle time: the file's time is just the latest write.
312
309
  private answerTime(
313
310
  sessionId: string,
314
311
  status: SessionStatus | undefined,
@@ -339,13 +336,7 @@ export class SessionCatalogue {
339
336
  return renamed(digest, name);
340
337
  }
341
338
 
342
- /**
343
- * Mid-turn the file is appended to between listings, so its digest never hits
344
- * the cache and the whole-file read lands on the busiest session there is.
345
- * Nothing it answers can have moved: the opening message was written long
346
- * ago, the settle time is deliberately not read from the file while a turn
347
- * runs, and the name is the live session's own to say.
348
- */
339
+ /** Mid-turn the cached digest is reused despite a changed mtime: nothing it holds can have moved. */
349
340
  private reusable(
350
341
  path: string,
351
342
  modifiedAt: number,
@@ -356,8 +347,7 @@ export class SessionCatalogue {
356
347
  if (cached === undefined) {
357
348
  return undefined;
358
349
  }
359
- // Except a name the live session has since cleared: the opening message a
360
- // nameless row falls back to is in the file and nowhere else.
350
+ // A cleared name needs the opening message, which only the file has.
361
351
  if (name === null && cached.named === true) {
362
352
  return undefined;
363
353
  }
@@ -374,7 +364,7 @@ export class SessionCatalogue {
374
364
  return digest;
375
365
  }
376
366
 
377
- /** What a live session calls itself, without reading its file; `null` is live and unnamed. */
367
+ /** `null` is live and unnamed; undefined is not live. */
378
368
  private liveName(sessionId: string): string | null | undefined {
379
369
  const agent = this.deps.registry.peek(sessionId)?.agentSession;
380
370
  return agent === undefined
@@ -383,7 +373,6 @@ export class SessionCatalogue {
383
373
  }
384
374
  }
385
375
 
386
- /** The row the wire carries: the index's hit, plus the one thing about it only the sidecar knows. */
387
376
  function hitOf(hit: SearchHit, archived: boolean): SearchHitView {
388
377
  return {
389
378
  sessionId: hit.sessionId,
@@ -398,7 +387,7 @@ function hitOf(hit: SearchHit, archived: boolean): SearchHitView {
398
387
  };
399
388
  }
400
389
 
401
- /** A live session's own name wins over the file's: a rename lands on the row before the digest behind it expires. */
390
+ /** A live session's name wins over the file's. */
402
391
  function renamed(
403
392
  digest: SessionDigest,
404
393
  name: string | null | undefined
@@ -408,7 +397,7 @@ function renamed(
408
397
  : digest;
409
398
  }
410
399
 
411
- /** The next sessions to digest, newest first, skipping the projects that have spent their budget and the candidates an earlier round already took. */
400
+ /** The next unconsumed sessions to digest, skipping projects over budget. */
412
401
  function choose(
413
402
  candidates: readonly SessionSummary[],
414
403
  consumed: Set<SessionSummary>,
@@ -431,7 +420,6 @@ function choose(
431
420
  return batch;
432
421
  }
433
422
 
434
- /** Every working directory the scope holds sessions in, newest first; the count is the header scan's, so it owes nothing to the page. */
435
423
  function projectsOf(
436
424
  summaries: readonly SessionSummary[],
437
425
  { projects, order }: Pinning
@@ -15,17 +15,13 @@ import type {
15
15
  } from "#protocol/ServerEvent";
16
16
  import { attachmentUrl } from "./AttachmentEndpoint";
17
17
 
18
- type PendingCall = {
19
- readonly name: string;
20
- readonly args: unknown;
21
- };
22
-
23
- /** Projects pi's session file into durable events: one line in, at most one `DurableEvent` out, tagged with that line's ordinal. */
18
+ /** Projects pi's session file into durable events: at most one per line, tagged with the line ordinal. */
24
19
  export class SessionProjection {
25
20
  private readonly log: EventLog;
26
21
  private readonly cwd: () => string;
27
22
  private readonly events: DurableEvent[] = [];
28
- private readonly calls = new Map<string, PendingCall>();
23
+ /** Tool call args by call id. */
24
+ private readonly callArgs = new Map<string, unknown>();
29
25
  private draining: Promise<void> = Promise.resolve();
30
26
  private highest = 0;
31
27
 
@@ -53,7 +49,7 @@ export class SessionProjection {
53
49
  return this.events.slice(low);
54
50
  }
55
51
 
56
- /** Read whatever pi has appended since the last drain; serialized, as overlapping reads double-project lines. */
52
+ /** Reads what pi appended since the last drain. Serialized: overlapping reads would double-project lines. */
57
53
  public async drain(): Promise<readonly DurableEvent[]> {
58
54
  const before = this.events.length;
59
55
  const done = this.draining.then(async () => {
@@ -111,7 +107,7 @@ export class SessionProjection {
111
107
  if (part.type !== "toolCall") {
112
108
  continue;
113
109
  }
114
- this.calls.set(part.id, { name: part.name, args: part.arguments });
110
+ this.callArgs.set(part.id, part.arguments);
115
111
  toolCalls.push({
116
112
  callId: part.id,
117
113
  name: part.name,
@@ -141,7 +137,6 @@ export class SessionProjection {
141
137
  };
142
138
  }
143
139
  case "toolResult": {
144
- const call = this.calls.get(message.toolCallId);
145
140
  return {
146
141
  seq,
147
142
  type: "tool_result",
@@ -150,7 +145,7 @@ export class SessionProjection {
150
145
  isError: message.isError,
151
146
  view: Tools.viewOf({
152
147
  name: message.toolName,
153
- args: call?.args ?? {},
148
+ args: this.callArgs.get(message.toolCallId) ?? {},
154
149
  isError: message.isError,
155
150
  result: {
156
151
  content: withoutImages(message.content),
@@ -178,7 +173,7 @@ type ToolResultContent = Extract<
178
173
  { role: "toolResult" }
179
174
  >["content"];
180
175
 
181
- /** No base64 crosses the websocket: a view reaches its picture through `details.sha256` instead. */
176
+ /** Keeps base64 off the websocket; views load images via `details.sha256`. */
182
177
  function withoutImages(content: ToolResultContent): ToolResultContent {
183
178
  return content.some((part) => part.type === "image")
184
179
  ? content.map((part) =>