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.
- package/package.json +3 -3
- package/packages/core/src/attachments/AttachmentStore.ts +8 -13
- package/packages/core/src/attachments/Attachments.ts +2 -4
- package/packages/core/src/extensions/CoreExtensions.ts +1 -1
- package/packages/core/src/extensions/apply-patch/coordinator.ts +1 -1
- package/packages/core/src/extensions/apply-patch/executor.ts +21 -25
- package/packages/core/src/extensions/apply-patch/matcher.ts +1 -1
- package/packages/core/src/extensions/apply-patch/model.ts +2 -6
- package/packages/core/src/extensions/apply-patch/parser.ts +60 -162
- package/packages/core/src/extensions/apply-patch/schema.ts +0 -1
- package/packages/core/src/extensions/bash/MemoryCap.ts +5 -4
- package/packages/core/src/extensions/bash/capture.ts +16 -23
- package/packages/core/src/extensions/bash/format.ts +2 -2
- package/packages/core/src/extensions/bash/image.ts +4 -8
- package/packages/core/src/extensions/bash/index.ts +1 -1
- package/packages/core/src/extensions/bash/render.ts +1 -1
- package/packages/core/src/extensions/bash/run.ts +9 -15
- package/packages/core/src/extensions/bash/schema.ts +4 -4
- package/packages/core/src/extensions/edit/edit.ts +14 -33
- package/packages/core/src/extensions/glob/glob.ts +1 -3
- package/packages/core/src/extensions/glob/index.ts +7 -9
- package/packages/core/src/extensions/glob/render.ts +2 -4
- package/packages/core/src/extensions/glob/schema.ts +1 -1
- package/packages/core/src/extensions/grep/grep.ts +7 -7
- package/packages/core/src/extensions/grep/render.ts +5 -6
- package/packages/core/src/extensions/grep/schema.ts +3 -7
- package/packages/core/src/extensions/read/ImageMemory.ts +2 -8
- package/packages/core/src/extensions/read/image.ts +3 -3
- package/packages/core/src/extensions/read/index.ts +2 -14
- package/packages/core/src/extensions/read/read.ts +5 -6
- package/packages/core/src/extensions/read/render.ts +1 -1
- package/packages/core/src/extensions/read/schema.ts +2 -2
- package/packages/core/src/extensions/subagent/render.ts +2 -2
- package/packages/core/src/extensions/subagent/subagent.ts +10 -21
- package/packages/core/src/extensions/system-prompt/index.ts +1 -2
- package/packages/core/src/extensions/system-prompt/prompt.ts +2 -4
- package/packages/core/src/extensions/todo/render.ts +1 -4
- package/packages/core/src/extensions/todo/todo.ts +16 -23
- package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +3 -11
- package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +1 -3
- package/packages/core/src/extensions/web-fetch/fetch.ts +4 -12
- package/packages/core/src/extensions/web-fetch/image.ts +3 -4
- package/packages/core/src/extensions/web-fetch/index.ts +2 -13
- package/packages/core/src/extensions/web-fetch/render.ts +3 -4
- package/packages/core/src/extensions/web-fetch/schema.ts +3 -4
- package/packages/core/src/extensions/web-search/ExaMcpClient.ts +23 -31
- package/packages/core/src/extensions/web-search/SearchBreaker.ts +1 -1
- package/packages/core/src/extensions/web-search/SearchChain.ts +6 -5
- package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +2 -2
- package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +1 -2
- package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +3 -3
- package/packages/core/src/extensions/write/write.ts +1 -1
- package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +1 -3
- package/packages/core/src/picker/PickerItem.ts +1 -1
- package/packages/core/src/picker/PickerService.ts +3 -7
- package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +1 -2
- package/packages/core/src/picker/catalog.ts +7 -15
- package/packages/core/src/picker/filePickerWorker.ts +1 -3
- package/packages/core/src/picker/ranker.ts +1 -1
- package/packages/core/src/picker/token.ts +3 -4
- package/packages/core/src/session/AgentRuntime.ts +5 -8
- package/packages/core/src/session/EventLog.ts +6 -12
- package/packages/core/src/session/ReadCursors.ts +3 -4
- package/packages/core/src/session/SearchIndex.ts +49 -86
- package/packages/core/src/session/SearchTokens.ts +4 -7
- package/packages/core/src/session/SessionCache.ts +8 -6
- package/packages/core/src/session/SessionDigest.ts +10 -19
- package/packages/core/src/session/SessionHost.ts +53 -83
- package/packages/core/src/session/SessionLease.ts +12 -15
- package/packages/core/src/session/SessionMeta.ts +16 -32
- package/packages/core/src/session/SessionName.ts +3 -7
- package/packages/core/src/session/SessionRegistry.ts +13 -29
- package/packages/core/src/session/SessionUi.ts +12 -16
- package/packages/core/src/session/WriteMark.ts +7 -16
- package/packages/core/src/shared/Cli.ts +1 -1
- package/packages/core/src/shared/DaemonUnit.ts +1 -2
- package/packages/core/src/shared/DiffLines.ts +16 -34
- package/packages/core/src/shared/DiffRenderer.ts +8 -27
- package/packages/core/src/shared/Directories.ts +4 -4
- package/packages/core/src/shared/EditMatcher.ts +18 -50
- package/packages/core/src/shared/ExtensionToggles.ts +3 -6
- package/packages/core/src/shared/FileEnumerator.ts +73 -57
- package/packages/core/src/shared/FileScanner.ts +5 -13
- package/packages/core/src/shared/FileWatch.ts +13 -17
- package/packages/core/src/shared/Format.ts +2 -3
- package/packages/core/src/shared/FuzzyMatcher.ts +18 -19
- package/packages/core/src/shared/Git.ts +22 -47
- package/packages/core/src/shared/GitMonitor.ts +12 -23
- package/packages/core/src/shared/GlobExclusions.ts +0 -4
- package/packages/core/src/shared/Http.ts +1 -1
- package/packages/core/src/shared/ImageMime.ts +3 -5
- package/packages/core/src/shared/Images.ts +17 -27
- package/packages/core/src/shared/Languages.ts +2 -2
- package/packages/core/src/shared/Levenshtein.ts +1 -6
- package/packages/core/src/shared/Lines.ts +1 -1
- package/packages/core/src/shared/McpClient.ts +2 -2
- package/packages/core/src/shared/PatchSummary.ts +1 -1
- package/packages/core/src/shared/Paths.ts +4 -1
- package/packages/core/src/shared/PiExtensions.ts +5 -12
- package/packages/core/src/shared/PimSettings.ts +14 -15
- package/packages/core/src/shared/Proc.ts +2 -3
- package/packages/core/src/shared/RateLimiter.ts +1 -1
- package/packages/core/src/shared/Renderer.ts +1 -3
- package/packages/core/src/shared/RepoDiff.ts +24 -46
- package/packages/core/src/shared/SearchRender.ts +3 -4
- package/packages/core/src/shared/SpillCache.ts +2 -3
- package/packages/core/src/shared/SubagentLogs.ts +4 -6
- package/packages/core/src/shared/Supervisor.ts +19 -32
- package/packages/core/src/shared/Surface.ts +1 -1
- package/packages/core/src/shared/Sweeper.ts +1 -1
- package/packages/core/src/shared/Tools.ts +17 -30
- package/packages/core/src/shared/Updater.ts +15 -15
- package/packages/core/src/view/BodyRenderer.ts +1 -1
- package/packages/core/src/view/DiffBlocks.ts +0 -1
- package/packages/core/src/view/DiffExpand.ts +10 -25
- package/packages/core/src/view/DiffLayout.ts +2 -2
- package/packages/core/src/view/DiffPairs.ts +2 -2
- package/packages/core/src/view/MarkdownPainter.ts +2 -19
- package/packages/core/src/view/MovePath.ts +4 -17
- package/packages/core/src/view/Painting.ts +4 -4
- package/packages/core/src/view/ViewBlock.ts +11 -12
- package/packages/daemon/src/Daemon.ts +2 -7
- package/packages/daemon/src/DaemonInstall.ts +5 -12
- package/packages/daemon/src/Surfaces.ts +3 -12
- package/packages/daemon/src/TelegramSurface.ts +0 -1
- package/packages/daemon/src/index.ts +0 -1
- package/packages/protocol/src/Command.ts +33 -41
- package/packages/protocol/src/Diff.ts +0 -5
- package/packages/protocol/src/ImageRoute.ts +2 -5
- package/packages/protocol/src/ServerEvent.ts +68 -105
- package/packages/server/src/AttachmentEndpoint.ts +45 -19
- package/packages/server/src/ClientConnection.ts +11 -12
- package/packages/server/src/ImageEndpoint.ts +3 -4
- package/packages/server/src/ProbeClient.ts +26 -59
- package/packages/server/src/Reloader.ts +4 -7
- package/packages/server/src/SendFileTool.ts +1 -1
- package/packages/server/src/SessionCatalogue.ts +35 -47
- package/packages/server/src/SessionProjection.ts +7 -12
- package/packages/server/src/SessionStream.ts +33 -80
- package/packages/server/src/StaticClient.ts +7 -8
- package/packages/server/src/WebOptions.ts +2 -6
- package/packages/server/src/WsGateway.ts +35 -96
- package/packages/server/src/probe.ts +6 -18
- package/packages/telegram/src/Bot.ts +2 -2
- package/packages/telegram/src/Commands.ts +108 -65
- package/packages/telegram/src/Config.ts +1 -6
- package/packages/telegram/src/Markdown.ts +4 -13
- package/packages/telegram/src/Renderer.ts +1 -1
- package/packages/telegram/src/Session.ts +3 -10
- package/packages/telegram/src/TaskScheduler.ts +31 -10
- package/packages/telegram/src/TaskStore.ts +11 -1
- package/packages/telegram/src/TaskTool.ts +1 -1
- package/packages/telegram/src/UpdateConfirm.ts +2 -2
- package/packages/tui/src/extensions/_init/index.ts +2 -5
- package/packages/tui/src/extensions/file-picker/index.ts +2 -6
- package/packages/tui/src/extensions/footer/index.ts +0 -1
- package/packages/tui/src/extensions/footer/powerline.ts +16 -28
- package/packages/tui/src/extensions/pim/index.ts +1 -1
- package/packages/tui/src/extensions/session-lease/index.ts +5 -6
- package/packages/tui/src/extensions/working-indicator/index.ts +1 -2
- package/packages/tui/src/themes/themeCliArgs.ts +1 -1
- package/packages/web/dist/client/assets/{abnfDiagram-O67JEVCF-CZ7V84ot.js → abnfDiagram-O67JEVCF-w0u0UKcC.js} +1 -1
- package/packages/web/dist/client/assets/{arc-DiHGkUvc.js → arc-C8AeY-3C.js} +1 -1
- package/packages/web/dist/client/assets/architecture-7GRP2DOG-D-GDy9ga.js +1 -0
- package/packages/web/dist/client/assets/{architectureDiagram-NJMV4G6O-XS5DhfJB.js → architectureDiagram-NJMV4G6O-CLdCZfsP.js} +1 -1
- package/packages/web/dist/client/assets/{blockDiagram-BEXU5L5S-BCsgLvy1.js → blockDiagram-BEXU5L5S-BgAbOzdH.js} +1 -1
- package/packages/web/dist/client/assets/{c4Diagram-YGBWAQC7-Bj9LcWNm.js → c4Diagram-YGBWAQC7-DGRXXzDl.js} +1 -1
- package/packages/web/dist/client/assets/channel-BJtpIKaC.js +1 -0
- package/packages/web/dist/client/assets/{chunk-3FUC2YCW-BmDurFxp.js → chunk-3FUC2YCW-Dmz4_CY0.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-5DYCD2WN-DAjG-pqn.js → chunk-5DYCD2WN-D6q0r_NW.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-742MDFTN-Bmxs5puY.js → chunk-742MDFTN-biXvhzMn.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7INBJB4K-Di7kIxrQ.js → chunk-7INBJB4K-CtfPEr8P.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7M6MHVWA-CSc8sR0b.js → chunk-7M6MHVWA-CTixWKLk.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7PRAP22T-C8kd5QvY.js → chunk-7PRAP22T-DijOiKl2.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-GTNCS2PH-DG2b2LYE.js → chunk-GTNCS2PH-DLId7dTl.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-GWA4HPMP-DdLaLQCu.js → chunk-GWA4HPMP-YUQxYmiu.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-MBY4JIJT-C1j0yQlk.js → chunk-MBY4JIJT-VWR3HSRr.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-NETBCI7D-Bfq9H74p.js → chunk-NETBCI7D-B8SeJoMY.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-O7XYJQB3-ZHvBmBO-.js → chunk-O7XYJQB3-CeLpEz0Q.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-UA2S7LBM-ku1JeHSK.js → chunk-UA2S7LBM-cBGKkXV_.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-WEXAMYUT-BP1iga_7.js → chunk-WEXAMYUT-BesXAOy9.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-XXDRQBXY-BBeccPcX.js → chunk-XXDRQBXY-DiuGUc8m.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-Z7XXMR3K-SMHZugaR.js → chunk-Z7XXMR3K-Dr2W5_t1.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-ZIGJFQKS-CFU_JLG4.js → chunk-ZIGJFQKS-CwoMYXLJ.js} +1 -1
- package/packages/web/dist/client/assets/{classDiagram-v2-NBCMYWYE-CYviuQzJ.js → classDiagram-v2-NBCMYWYE-Gc3jAsDf.js} +1 -1
- package/packages/web/dist/client/assets/{core-DyHsgs5k.js → core-C27Q2ZSN.js} +1 -1
- package/packages/web/dist/client/assets/{cose-bilkent-JH36ORCC-9v2ZkJzo.js → cose-bilkent-JH36ORCC-ox5QohTH.js} +1 -1
- package/packages/web/dist/client/assets/{cynefin-OW5HDTMX-Bx5xRadi.js → cynefin-OW5HDTMX-CjQ-AjuJ.js} +1 -1
- package/packages/web/dist/client/assets/{cynefinDiagram-VND7K2PF-BRfGDKqa.js → cynefinDiagram-VND7K2PF-BVajmbbO.js} +1 -1
- package/packages/web/dist/client/assets/{dagre-6A5THRUB-C4l-2L2e.js → dagre-6A5THRUB-BtXhDxGN.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-22UHCM2B-d_hQ3kyi.js → diagram-22UHCM2B-DwjrV0H_.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-3UASUU5V-DrEeguPI.js → diagram-3UASUU5V-DQ1E_6hO.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-ATOU4E4O-BmfQ22pD.js → diagram-ATOU4E4O-wVYWlHRV.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-CDSNMT55-BjVWjuPF.js → diagram-CDSNMT55-CMlxHWMz.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-MLGK6HIB-wDjk-O8i.js → diagram-MLGK6HIB-Cbiwo7t9.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-MPIPVDR6-DqXQFE1s.js → diagram-MPIPVDR6-Cv4zIyW1.js} +1 -1
- package/packages/web/dist/client/assets/{dist-BddGOpie.js → dist-Dl4GbwtH.js} +1 -1
- package/packages/web/dist/client/assets/{ebnfDiagram-ZINNZB2B-UB8kOITM.js → ebnfDiagram-ZINNZB2B-Bd8xl8W2.js} +1 -1
- package/packages/web/dist/client/assets/{elk-276RUBZZ-D4miYQnD.js → elk-276RUBZZ-DPRtUxgw.js} +1 -1
- package/packages/web/dist/client/assets/{erDiagram-OPXOYQCR--RXKv7KH.js → erDiagram-OPXOYQCR-BNoG4SxS.js} +1 -1
- package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-Dh9l6A3t.js +1 -0
- package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-DoJ97uSW.js +1 -0
- package/packages/web/dist/client/assets/{ganttDiagram-FUAMR5RP-C0CFpE5x.js → ganttDiagram-FUAMR5RP-BtFYI7uw.js} +1 -1
- package/packages/web/dist/client/assets/{gitGraph-4MIJSDKK-VPL7nv2c.js → gitGraph-4MIJSDKK-Di6RG5Kg.js} +1 -1
- package/packages/web/dist/client/assets/{gitGraphDiagram-X574FWY7-CgxJA-T8.js → gitGraphDiagram-X574FWY7-J4_JKGBH.js} +1 -1
- package/packages/web/dist/client/assets/index-BD7ptjuS.css +1 -0
- package/packages/web/dist/client/assets/{index-XTlTnQmx.js → index-Cn66jcLF.js} +7 -7
- package/packages/web/dist/client/assets/{info-A6RAGUB7-P0-o68pj.js → info-A6RAGUB7-wk6vNDP4.js} +1 -1
- package/packages/web/dist/client/assets/{infoDiagram-VRGFBTTK-DFJZNRUP.js → infoDiagram-VRGFBTTK-Zl7Uhlcj.js} +1 -1
- package/packages/web/dist/client/assets/{ishikawaDiagram-OU5B5YK6-ZkyhNd1T.js → ishikawaDiagram-OU5B5YK6-I0VVed8w.js} +1 -1
- package/packages/web/dist/client/assets/{journeyDiagram-ZHPQQLJL-DFevH8my.js → journeyDiagram-ZHPQQLJL-CLGZAy0Q.js} +1 -1
- package/packages/web/dist/client/assets/{kanban-definition-PNTS6WVX-l7e7ZZDC.js → kanban-definition-PNTS6WVX-D3Zv63YL.js} +1 -1
- package/packages/web/dist/client/assets/{line-DO_s6Ujd.js → line-C4_qzTJY.js} +1 -1
- package/packages/web/dist/client/assets/{linear-C4xH5PPc.js → linear-Dbp9FWGm.js} +1 -1
- package/packages/web/dist/client/assets/{mermaid-parser.core-nQaTP3VY.js → mermaid-parser.core-Cbd2Flfk.js} +3 -3
- package/packages/web/dist/client/assets/{mermaid.core-Cz05yTZh.js → mermaid.core-CPXk02t0.js} +4 -4
- package/packages/web/dist/client/assets/{mindmap-definition-NLK3R4M7-CHOEuHm2.js → mindmap-definition-NLK3R4M7-hcjd2vg0.js} +1 -1
- package/packages/web/dist/client/assets/{packet-AYTQ26CC-BH3ehPMo.js → packet-AYTQ26CC-CLiY7MdL.js} +1 -1
- package/packages/web/dist/client/assets/{pegDiagram-GJSIUBJH-CkWBLtad.js → pegDiagram-GJSIUBJH-Or6L-npJ.js} +1 -1
- package/packages/web/dist/client/assets/{pie-WAS4IAKB-nNVEq3dx.js → pie-WAS4IAKB-DTF5qxhB.js} +1 -1
- package/packages/web/dist/client/assets/{pieDiagram-5QR66LMP-DimUBAgN.js → pieDiagram-5QR66LMP-wDNyDsTs.js} +1 -1
- package/packages/web/dist/client/assets/{quadrantDiagram-O4NWA36T-BR0XpRGQ.js → quadrantDiagram-O4NWA36T-BL_0xWWi.js} +1 -1
- package/packages/web/dist/client/assets/{radar-RG4KPBEZ-paA-H3mb.js → radar-RG4KPBEZ-hK8uEd3E.js} +1 -1
- package/packages/web/dist/client/assets/{railroad-74A4TZTK-yEiKB3rz.js → railroad-74A4TZTK-Cj1mtGfH.js} +1 -1
- package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-t8ibqa0P.js +1 -0
- package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-B4RYlcUM.js +1 -0
- package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-DFqfvrfB.js +1 -0
- package/packages/web/dist/client/assets/{railroadDiagram-XR7U4H2S--TXe6rdU.js → railroadDiagram-XR7U4H2S-BUXe7pvJ.js} +1 -1
- package/packages/web/dist/client/assets/{requirementDiagram-PLB6GJNP-DyqH36Za.js → requirementDiagram-PLB6GJNP-DBnKurFY.js} +1 -1
- package/packages/web/dist/client/assets/{sankeyDiagram-IPEJSGJF-Bg6e3BlM.js → sankeyDiagram-IPEJSGJF-BDlSxPY7.js} +1 -1
- package/packages/web/dist/client/assets/{sequenceDiagram-PO4LG4MO-ClGf85My.js → sequenceDiagram-PO4LG4MO-BcKSq_cS.js} +1 -1
- package/packages/web/dist/client/assets/{src-DF7hQfMh.js → src-kcIgRrGC.js} +1 -1
- package/packages/web/dist/client/assets/{stateDiagram-v2-GCMORJYK-BK8TpxNo.js → stateDiagram-v2-GCMORJYK-D-1tpyK1.js} +1 -1
- package/packages/web/dist/client/assets/{swimlanes-2SLR337P-DDnTIT9M.js → swimlanes-2SLR337P-edQaMeEJ.js} +1 -1
- package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-vI6XtOyR.js +8 -0
- package/packages/web/dist/client/assets/{timeline-definition-EJHVYXUP-mBBbH3bk.js → timeline-definition-EJHVYXUP-BHspcgaG.js} +1 -1
- package/packages/web/dist/client/assets/{treeView-Q6P3EWNA-CTK7tRwR.js → treeView-Q6P3EWNA-Bvc71ryx.js} +1 -1
- package/packages/web/dist/client/assets/{treemap-WGGIJYW6-vOI-Ef_m.js → treemap-WGGIJYW6-CoZvoF_t.js} +1 -1
- package/packages/web/dist/client/assets/{usecaseDiagram-POWQR4AR-COcii8x-.js → usecaseDiagram-POWQR4AR-B203AiCD.js} +1 -1
- package/packages/web/dist/client/assets/{vennDiagram-UO4OBE2U-D6kmDGpM.js → vennDiagram-UO4OBE2U-Cia3spa9.js} +1 -1
- package/packages/web/dist/client/assets/{wardley-WFR3VGLG-CtE2KI9r.js → wardley-WFR3VGLG-C7qHTXAu.js} +1 -1
- package/packages/web/dist/client/assets/{wardleyDiagram-VNRHLVJA-ncxNGWnW.js → wardleyDiagram-VNRHLVJA-CK2R_SgC.js} +1 -1
- package/packages/web/dist/client/assets/{xychartDiagram-PMCCYNJV-DUvd2Qze.js → xychartDiagram-PMCCYNJV-BBLqtYJ_.js} +1 -1
- package/packages/web/dist/client/index.html +2 -2
- package/packages/web/dist/client/assets/architecture-7GRP2DOG-HIzEIkwQ.js +0 -1
- package/packages/web/dist/client/assets/channel-BCA6qe9I.js +0 -1
- package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-l6SYFw1s.js +0 -1
- package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-CWW9qnqm.js +0 -1
- package/packages/web/dist/client/assets/index-D9_XJLSc.css +0 -1
- package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-mVQx9aVM.js +0 -1
- package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CllXx1OJ.js +0 -1
- package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-BBD2Q5W_.js +0 -1
- 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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
29
|
-
this.cachedRelative = [];
|
|
30
|
-
}
|
|
28
|
+
this.cachedRelative ??= [];
|
|
31
29
|
})
|
|
32
30
|
.finally(() => {
|
|
33
31
|
this.refresh = undefined;
|
|
@@ -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
|
|
12
|
+
/** Read per query; the cwd can change between turns. */
|
|
13
13
|
readonly cwd: () => string;
|
|
14
14
|
readonly agentDir: string;
|
|
15
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
20
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
38
|
+
/** The first user message; set only for named sessions. */
|
|
40
39
|
readonly opening?: string;
|
|
41
|
-
/** End of the last completed turn,
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
83
|
+
/** Bytes read so far; the next tail read starts here. */
|
|
87
84
|
offset: number;
|
|
88
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
105
|
+
/** Turn count before this read; later turns are new. */
|
|
110
106
|
readonly from: number;
|
|
111
|
-
/**
|
|
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
|
-
/**
|
|
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`:
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
157
|
+
/** Lines without this are skipped before decoding. */
|
|
175
158
|
const TEXT = Buffer.from('"text"');
|
|
176
159
|
|
|
177
160
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
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(
|
|
213
|
+
.map(hitOf),
|
|
233
214
|
dropped,
|
|
234
215
|
scanned: scope.size,
|
|
235
216
|
};
|
|
236
217
|
}
|
|
237
218
|
|
|
238
|
-
/** Exact and prefix first; typos only
|
|
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
|
-
|
|
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) =>
|
|
297
|
-
const titles = everything.map((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
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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) {
|