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.
- 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-WweOU5DK.js} +1 -1
- package/packages/web/dist/client/assets/{arc-DiHGkUvc.js → arc-O2PmZXWt.js} +1 -1
- package/packages/web/dist/client/assets/architecture-7GRP2DOG-COBxVilU.js +1 -0
- package/packages/web/dist/client/assets/{architectureDiagram-NJMV4G6O-XS5DhfJB.js → architectureDiagram-NJMV4G6O-DwYq1YaM.js} +1 -1
- package/packages/web/dist/client/assets/{blockDiagram-BEXU5L5S-BCsgLvy1.js → blockDiagram-BEXU5L5S-CVab041n.js} +1 -1
- package/packages/web/dist/client/assets/{c4Diagram-YGBWAQC7-Bj9LcWNm.js → c4Diagram-YGBWAQC7-BmotHez3.js} +1 -1
- package/packages/web/dist/client/assets/channel-BKV-IcTA.js +1 -0
- package/packages/web/dist/client/assets/{chunk-3FUC2YCW-BmDurFxp.js → chunk-3FUC2YCW-CWNS7-n4.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-5DYCD2WN-DAjG-pqn.js → chunk-5DYCD2WN-DflvXohu.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-742MDFTN-Bmxs5puY.js → chunk-742MDFTN-B_0t6_RE.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7INBJB4K-Di7kIxrQ.js → chunk-7INBJB4K-wKj0Q3Kv.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7M6MHVWA-CSc8sR0b.js → chunk-7M6MHVWA-BMvUghc8.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-7PRAP22T-C8kd5QvY.js → chunk-7PRAP22T-BY41wkcT.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-GTNCS2PH-DG2b2LYE.js → chunk-GTNCS2PH-PqY_YoS0.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-GWA4HPMP-DdLaLQCu.js → chunk-GWA4HPMP-fozSU5ty.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-MBY4JIJT-C1j0yQlk.js → chunk-MBY4JIJT-CrHrC3jw.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-NETBCI7D-Bfq9H74p.js → chunk-NETBCI7D-DWdAxieG.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-O7XYJQB3-ZHvBmBO-.js → chunk-O7XYJQB3-B6uOn9iS.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-UA2S7LBM-ku1JeHSK.js → chunk-UA2S7LBM-BxYSHqyy.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-WEXAMYUT-BP1iga_7.js → chunk-WEXAMYUT-CtwIaQDa.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-XXDRQBXY-BBeccPcX.js → chunk-XXDRQBXY-fgBFe-PX.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-Z7XXMR3K-SMHZugaR.js → chunk-Z7XXMR3K-CPonoV3p.js} +1 -1
- package/packages/web/dist/client/assets/{chunk-ZIGJFQKS-CFU_JLG4.js → chunk-ZIGJFQKS-Cdu87cTj.js} +1 -1
- package/packages/web/dist/client/assets/{classDiagram-v2-NBCMYWYE-CYviuQzJ.js → classDiagram-v2-NBCMYWYE-DSWsPaa7.js} +1 -1
- package/packages/web/dist/client/assets/{core-DyHsgs5k.js → core-CBoS7AST.js} +1 -1
- package/packages/web/dist/client/assets/{cose-bilkent-JH36ORCC-9v2ZkJzo.js → cose-bilkent-JH36ORCC-CTwlldtJ.js} +1 -1
- package/packages/web/dist/client/assets/{cynefin-OW5HDTMX-Bx5xRadi.js → cynefin-OW5HDTMX-CZQ4EPNJ.js} +1 -1
- package/packages/web/dist/client/assets/{cynefinDiagram-VND7K2PF-BRfGDKqa.js → cynefinDiagram-VND7K2PF-C2gp6H_Q.js} +1 -1
- package/packages/web/dist/client/assets/{dagre-6A5THRUB-C4l-2L2e.js → dagre-6A5THRUB-CDxTP47t.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-22UHCM2B-d_hQ3kyi.js → diagram-22UHCM2B-BIohE1lI.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-3UASUU5V-DrEeguPI.js → diagram-3UASUU5V-TwAUir-w.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-ATOU4E4O-BmfQ22pD.js → diagram-ATOU4E4O-BM1JQrIu.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-CDSNMT55-BjVWjuPF.js → diagram-CDSNMT55-BKIqhxSI.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-MLGK6HIB-wDjk-O8i.js → diagram-MLGK6HIB-CVe3QDHr.js} +1 -1
- package/packages/web/dist/client/assets/{diagram-MPIPVDR6-DqXQFE1s.js → diagram-MPIPVDR6-Bj-MQIFI.js} +1 -1
- package/packages/web/dist/client/assets/{dist-BddGOpie.js → dist-DlZpcyc2.js} +1 -1
- package/packages/web/dist/client/assets/{ebnfDiagram-ZINNZB2B-UB8kOITM.js → ebnfDiagram-ZINNZB2B-C06mmvmz.js} +1 -1
- package/packages/web/dist/client/assets/{elk-276RUBZZ-D4miYQnD.js → elk-276RUBZZ-CizJSNEj.js} +1 -1
- package/packages/web/dist/client/assets/{erDiagram-OPXOYQCR--RXKv7KH.js → erDiagram-OPXOYQCR-M3FQkKI1.js} +1 -1
- package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-D_UXLpIu.js +1 -0
- package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-BT4eEEVm.js +1 -0
- package/packages/web/dist/client/assets/{ganttDiagram-FUAMR5RP-C0CFpE5x.js → ganttDiagram-FUAMR5RP-CojF9Q16.js} +1 -1
- package/packages/web/dist/client/assets/{gitGraph-4MIJSDKK-VPL7nv2c.js → gitGraph-4MIJSDKK-YvKqGEPk.js} +1 -1
- package/packages/web/dist/client/assets/{gitGraphDiagram-X574FWY7-CgxJA-T8.js → gitGraphDiagram-X574FWY7-XqNqIFo9.js} +1 -1
- package/packages/web/dist/client/assets/index--2CDtzvY.css +1 -0
- package/packages/web/dist/client/assets/{index-XTlTnQmx.js → index-bT6QPJZs.js} +7 -7
- package/packages/web/dist/client/assets/{info-A6RAGUB7-P0-o68pj.js → info-A6RAGUB7-B_1BCzM2.js} +1 -1
- package/packages/web/dist/client/assets/{infoDiagram-VRGFBTTK-DFJZNRUP.js → infoDiagram-VRGFBTTK-zFX8B-dP.js} +1 -1
- package/packages/web/dist/client/assets/{ishikawaDiagram-OU5B5YK6-ZkyhNd1T.js → ishikawaDiagram-OU5B5YK6-DLzQ_Uyf.js} +1 -1
- package/packages/web/dist/client/assets/{journeyDiagram-ZHPQQLJL-DFevH8my.js → journeyDiagram-ZHPQQLJL-S3WRMmoa.js} +1 -1
- package/packages/web/dist/client/assets/{kanban-definition-PNTS6WVX-l7e7ZZDC.js → kanban-definition-PNTS6WVX-B5Z8I-kM.js} +1 -1
- package/packages/web/dist/client/assets/{line-DO_s6Ujd.js → line-BGMH-i2X.js} +1 -1
- package/packages/web/dist/client/assets/{linear-C4xH5PPc.js → linear-KsrestXG.js} +1 -1
- package/packages/web/dist/client/assets/{mermaid-parser.core-nQaTP3VY.js → mermaid-parser.core-DNffyJJA.js} +3 -3
- package/packages/web/dist/client/assets/{mermaid.core-Cz05yTZh.js → mermaid.core-Cz3vJeQG.js} +4 -4
- package/packages/web/dist/client/assets/{mindmap-definition-NLK3R4M7-CHOEuHm2.js → mindmap-definition-NLK3R4M7-CzA61XUf.js} +1 -1
- package/packages/web/dist/client/assets/{packet-AYTQ26CC-BH3ehPMo.js → packet-AYTQ26CC-BO55e7cR.js} +1 -1
- package/packages/web/dist/client/assets/{pegDiagram-GJSIUBJH-CkWBLtad.js → pegDiagram-GJSIUBJH-DeRiXYNd.js} +1 -1
- package/packages/web/dist/client/assets/{pie-WAS4IAKB-nNVEq3dx.js → pie-WAS4IAKB-D9ibrQRe.js} +1 -1
- package/packages/web/dist/client/assets/{pieDiagram-5QR66LMP-DimUBAgN.js → pieDiagram-5QR66LMP-BaBpPP33.js} +1 -1
- package/packages/web/dist/client/assets/{quadrantDiagram-O4NWA36T-BR0XpRGQ.js → quadrantDiagram-O4NWA36T-DIfn4GjM.js} +1 -1
- package/packages/web/dist/client/assets/{radar-RG4KPBEZ-paA-H3mb.js → radar-RG4KPBEZ-Dmn5-nJq.js} +1 -1
- package/packages/web/dist/client/assets/{railroad-74A4TZTK-yEiKB3rz.js → railroad-74A4TZTK-ZCWNEd2Z.js} +1 -1
- package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-_swqhy0y.js +1 -0
- package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CytbazPx.js +1 -0
- package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-Bg8QW17T.js +1 -0
- package/packages/web/dist/client/assets/{railroadDiagram-XR7U4H2S--TXe6rdU.js → railroadDiagram-XR7U4H2S-CDPZ39sy.js} +1 -1
- package/packages/web/dist/client/assets/{requirementDiagram-PLB6GJNP-DyqH36Za.js → requirementDiagram-PLB6GJNP-CyCI8Yul.js} +1 -1
- package/packages/web/dist/client/assets/{sankeyDiagram-IPEJSGJF-Bg6e3BlM.js → sankeyDiagram-IPEJSGJF-p_XAPv72.js} +1 -1
- package/packages/web/dist/client/assets/{sequenceDiagram-PO4LG4MO-ClGf85My.js → sequenceDiagram-PO4LG4MO-DmR6IP_o.js} +1 -1
- package/packages/web/dist/client/assets/{src-DF7hQfMh.js → src-gnv7pJ1z.js} +1 -1
- package/packages/web/dist/client/assets/{stateDiagram-v2-GCMORJYK-BK8TpxNo.js → stateDiagram-v2-GCMORJYK-7ZTHl6ih.js} +1 -1
- package/packages/web/dist/client/assets/{swimlanes-2SLR337P-DDnTIT9M.js → swimlanes-2SLR337P-N6bj71pw.js} +1 -1
- package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-BJQ2CBsp.js +8 -0
- package/packages/web/dist/client/assets/{timeline-definition-EJHVYXUP-mBBbH3bk.js → timeline-definition-EJHVYXUP-GNez0kW8.js} +1 -1
- package/packages/web/dist/client/assets/{treeView-Q6P3EWNA-CTK7tRwR.js → treeView-Q6P3EWNA-DyQZkboR.js} +1 -1
- package/packages/web/dist/client/assets/{treemap-WGGIJYW6-vOI-Ef_m.js → treemap-WGGIJYW6-_J8Pw2TM.js} +1 -1
- package/packages/web/dist/client/assets/{usecaseDiagram-POWQR4AR-COcii8x-.js → usecaseDiagram-POWQR4AR-Bg-CzKjl.js} +1 -1
- package/packages/web/dist/client/assets/{vennDiagram-UO4OBE2U-D6kmDGpM.js → vennDiagram-UO4OBE2U-DBQKHXhK.js} +1 -1
- package/packages/web/dist/client/assets/{wardley-WFR3VGLG-CtE2KI9r.js → wardley-WFR3VGLG-B5aAsQ3F.js} +1 -1
- package/packages/web/dist/client/assets/{wardleyDiagram-VNRHLVJA-ncxNGWnW.js → wardleyDiagram-VNRHLVJA-BKUQ1LK6.js} +1 -1
- package/packages/web/dist/client/assets/{xychartDiagram-PMCCYNJV-DUvd2Qze.js → xychartDiagram-PMCCYNJV-BI2sVbks.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
|
@@ -25,13 +25,13 @@ export type ToolCallView = {
|
|
|
25
25
|
|
|
26
26
|
/** A file a user message carried; `url` is server-relative. */
|
|
27
27
|
export type AttachmentView = {
|
|
28
|
-
/**
|
|
28
|
+
/** Display name; never a path. */
|
|
29
29
|
readonly name: string;
|
|
30
30
|
readonly url: string;
|
|
31
31
|
readonly isImage: boolean;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
/** Projected from pi's session JSONL
|
|
34
|
+
/** Projected from pi's session JSONL. `seq` is the line ordinal, so each line emits at most one durable event. */
|
|
35
35
|
export type DurableEvent =
|
|
36
36
|
| {
|
|
37
37
|
readonly seq: number;
|
|
@@ -39,13 +39,13 @@ export type DurableEvent =
|
|
|
39
39
|
readonly messageId: string;
|
|
40
40
|
readonly role: "user" | "assistant";
|
|
41
41
|
readonly text: string;
|
|
42
|
-
/**
|
|
42
|
+
/** Epoch ms. */
|
|
43
43
|
readonly timestamp: number;
|
|
44
44
|
readonly thinking?: string;
|
|
45
|
-
/**
|
|
45
|
+
/** User messages only. */
|
|
46
46
|
readonly attachments?: readonly AttachmentView[];
|
|
47
47
|
readonly toolCalls?: readonly ToolCallView[];
|
|
48
|
-
/**
|
|
48
|
+
/** Model call error; assistant messages only. */
|
|
49
49
|
readonly error?: string;
|
|
50
50
|
}
|
|
51
51
|
| {
|
|
@@ -63,7 +63,7 @@ export type DurableEvent =
|
|
|
63
63
|
readonly text: string;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
/** Progress of
|
|
66
|
+
/** Progress of a `reload`. */
|
|
67
67
|
export type UpdateStateEvent =
|
|
68
68
|
| {
|
|
69
69
|
readonly type: "update_state";
|
|
@@ -77,7 +77,7 @@ export type UpdateStateEvent =
|
|
|
77
77
|
readonly to: string;
|
|
78
78
|
readonly skipped: readonly UpdateSkip[];
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
80
|
+
/** Updated on disk, but no supervisor will restart this process. */
|
|
81
81
|
| {
|
|
82
82
|
readonly type: "update_state";
|
|
83
83
|
readonly phase: "stranded";
|
|
@@ -91,26 +91,26 @@ export type UpdateStateEvent =
|
|
|
91
91
|
readonly error: string;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
/** Unsequenced live state
|
|
94
|
+
/** Unsequenced live state. Live `tool_call`s reappear in a durable message's `toolCalls`, so dedupe on `callId`. */
|
|
95
95
|
export type EphemeralEvent =
|
|
96
96
|
| {
|
|
97
97
|
readonly type: "attached";
|
|
98
98
|
readonly sessionId: string;
|
|
99
99
|
readonly cwd: string;
|
|
100
|
-
/** Highest durable `seq` at attach time
|
|
100
|
+
/** Highest durable `seq` at attach time. */
|
|
101
101
|
readonly head: number;
|
|
102
|
-
/**
|
|
102
|
+
/** A client built from another version should reload. */
|
|
103
103
|
readonly pimVersion: string;
|
|
104
104
|
readonly piVersion: string;
|
|
105
105
|
}
|
|
106
|
-
/**
|
|
106
|
+
/** Apply in order, as if each arrived alone. */
|
|
107
107
|
| { readonly type: "replay"; readonly events: readonly StreamEvent[] }
|
|
108
108
|
| {
|
|
109
109
|
readonly type: "message_start";
|
|
110
110
|
readonly role: "assistant";
|
|
111
111
|
readonly messageId: string;
|
|
112
112
|
}
|
|
113
|
-
/** Drop
|
|
113
|
+
/** Drop this live message; its durable `message` was sent just before. */
|
|
114
114
|
| { readonly type: "message_retire"; readonly messageId: string }
|
|
115
115
|
| {
|
|
116
116
|
readonly type: "text_delta";
|
|
@@ -134,37 +134,36 @@ export type EphemeralEvent =
|
|
|
134
134
|
readonly callId: string;
|
|
135
135
|
readonly view: ToolView;
|
|
136
136
|
}
|
|
137
|
-
/**
|
|
137
|
+
/** Superseded by the `tool_result` with the same `callId`. */
|
|
138
138
|
| {
|
|
139
139
|
readonly type: "tool_end";
|
|
140
140
|
readonly callId: string;
|
|
141
141
|
readonly view: ToolView;
|
|
142
142
|
readonly isError: boolean;
|
|
143
143
|
}
|
|
144
|
-
/**
|
|
144
|
+
/** Cached picker answers for this cwd are stale. */
|
|
145
145
|
| {
|
|
146
146
|
readonly type: "picker_invalidate";
|
|
147
147
|
readonly scope: "files" | "commands" | "all";
|
|
148
148
|
readonly cwd: string;
|
|
149
149
|
}
|
|
150
|
-
/**
|
|
150
|
+
/** Sent only to the watching connection; never resumed. */
|
|
151
151
|
| {
|
|
152
152
|
readonly type: "subagent_events";
|
|
153
|
-
/** The parent tool call the watch was opened on. */
|
|
154
153
|
readonly callId: string;
|
|
155
154
|
readonly events: readonly StreamEvent[];
|
|
156
155
|
}
|
|
157
156
|
| { readonly type: "turn_end"; readonly stats: TurnStats }
|
|
158
|
-
/**
|
|
157
|
+
/** Markdown from an extension; not written to the session. */
|
|
159
158
|
| {
|
|
160
159
|
readonly type: "ui_notice";
|
|
161
160
|
readonly id: string;
|
|
162
161
|
readonly severity: NoticeSeverity;
|
|
163
162
|
readonly text: string;
|
|
164
|
-
/** The command being dispatched
|
|
163
|
+
/** The command being dispatched, like `/login`. */
|
|
165
164
|
readonly command?: string;
|
|
166
165
|
}
|
|
167
|
-
/**
|
|
166
|
+
/** The first `ui_response` wins. */
|
|
168
167
|
| {
|
|
169
168
|
readonly type: "ui_request";
|
|
170
169
|
readonly requestId: string;
|
|
@@ -173,48 +172,43 @@ export type EphemeralEvent =
|
|
|
173
172
|
readonly message?: string;
|
|
174
173
|
readonly options?: readonly string[];
|
|
175
174
|
readonly placeholder?: string;
|
|
176
|
-
/** The command that asked, like `/login
|
|
175
|
+
/** The command that asked, like `/login`. */
|
|
177
176
|
readonly command?: string;
|
|
178
177
|
}
|
|
179
|
-
/** The request is settled, by whoever answered it or by the server answering for them; drop its control. */
|
|
180
178
|
| { readonly type: "ui_request_done"; readonly requestId: string }
|
|
181
|
-
/**
|
|
179
|
+
/** Broadcast; only for sessions this server holds open. */
|
|
182
180
|
| {
|
|
183
181
|
readonly type: "session_activity";
|
|
184
182
|
readonly sessionId: string;
|
|
185
183
|
readonly status: SessionStatus;
|
|
186
184
|
}
|
|
187
|
-
/**
|
|
185
|
+
/** Broadcast; the read cursor is per session, not per client. */
|
|
188
186
|
| { readonly type: "session_read"; readonly sessionId: string }
|
|
189
|
-
/**
|
|
187
|
+
/** Broadcast patch: only changed fields are set. */
|
|
190
188
|
| {
|
|
191
189
|
readonly type: "session_meta";
|
|
192
190
|
readonly sessionId: string;
|
|
193
|
-
/**
|
|
191
|
+
/** `null` once cleared. */
|
|
194
192
|
readonly name?: string | null;
|
|
195
193
|
readonly archived?: boolean;
|
|
196
194
|
readonly unread?: boolean;
|
|
197
195
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Sent to every connection; keyed by absolute working directory, not by
|
|
200
|
-
* session. A patch like `session_meta`: only what changed is said, so a
|
|
201
|
-
* fold carries no claim about the pin beside it.
|
|
202
|
-
*/
|
|
196
|
+
/** Broadcast patch: only changed fields are set. */
|
|
203
197
|
| {
|
|
204
198
|
readonly type: "project_meta";
|
|
205
199
|
readonly cwd: string;
|
|
206
200
|
readonly pinned?: boolean;
|
|
207
201
|
readonly expanded?: boolean;
|
|
208
|
-
/**
|
|
202
|
+
/** `null` once cleared. */
|
|
209
203
|
readonly label?: string | null;
|
|
210
204
|
}
|
|
211
|
-
/**
|
|
205
|
+
/** Broadcast. */
|
|
212
206
|
| { readonly type: "pins_changed"; readonly order: readonly string[] }
|
|
213
|
-
/**
|
|
207
|
+
/** Broadcast. */
|
|
214
208
|
| { readonly type: "sessions_changed" }
|
|
215
|
-
/**
|
|
209
|
+
/** Broadcast. */
|
|
216
210
|
| { readonly type: "extensions_changed" }
|
|
217
|
-
/**
|
|
211
|
+
/** Broadcast. */
|
|
218
212
|
| UpdateStateEvent
|
|
219
213
|
| {
|
|
220
214
|
readonly type: "session_state";
|
|
@@ -227,155 +221,138 @@ export type EphemeralEvent =
|
|
|
227
221
|
readonly status: SessionStatus;
|
|
228
222
|
/** False while another process holds this session's turn lease. */
|
|
229
223
|
readonly writable: boolean;
|
|
230
|
-
/** A session in the same
|
|
224
|
+
/** A session in the same cwd is mid-turn, so git actions are refused. */
|
|
231
225
|
readonly repoBusy?: boolean;
|
|
232
|
-
/**
|
|
226
|
+
/** Absent when unheld or the lease record is corrupt. */
|
|
233
227
|
readonly heldBy?: {
|
|
234
228
|
readonly frontend: LeaseFrontend;
|
|
235
229
|
readonly pid: number;
|
|
236
230
|
};
|
|
237
231
|
readonly tps?: number;
|
|
238
|
-
/**
|
|
232
|
+
/** Absent when idle. */
|
|
239
233
|
readonly turnElapsedMs?: number;
|
|
240
234
|
/** Context filled, 0–100. Absent until a turn has reported usage. */
|
|
241
235
|
readonly contextPercent?: number;
|
|
242
236
|
readonly contextWindow?: number;
|
|
243
|
-
/**
|
|
237
|
+
/** Absent outside a git repository. */
|
|
244
238
|
readonly branch?: string;
|
|
245
|
-
/** Paths git reports as changed; zero is a clean tree. */
|
|
246
239
|
readonly dirtyCount?: number;
|
|
247
240
|
readonly ahead?: number;
|
|
248
241
|
readonly behind?: number;
|
|
249
|
-
/** Changes whenever the working copy does,
|
|
242
|
+
/** Changes whenever the working copy does, including dirty file content. */
|
|
250
243
|
readonly repoRevision?: string;
|
|
251
244
|
}
|
|
252
245
|
/** A frame the server could not attribute to any command. */
|
|
253
246
|
| { readonly type: "error"; readonly message: string };
|
|
254
247
|
|
|
255
|
-
/** One row of the session catalogue. */
|
|
256
248
|
export type SessionSummaryView = {
|
|
257
249
|
readonly sessionId: string;
|
|
258
250
|
readonly cwd: string;
|
|
259
251
|
readonly createdAt: number;
|
|
260
|
-
/** End of the last completed turn
|
|
252
|
+
/** End of the last completed turn (not file mtime); falls back to `createdAt`. Sort key. */
|
|
261
253
|
readonly settledAt: number;
|
|
262
|
-
/** The
|
|
254
|
+
/** The first user message, trimmed. */
|
|
263
255
|
readonly title?: string;
|
|
264
|
-
/**
|
|
256
|
+
/** `title` is a user-set name, not the first message. */
|
|
265
257
|
readonly named?: true;
|
|
266
258
|
readonly archived?: true;
|
|
267
|
-
/** Has answered since anything last read it; absent means it has not. */
|
|
268
259
|
readonly unread?: boolean;
|
|
269
|
-
/** Absent when idle
|
|
260
|
+
/** Absent when idle or not held open by this server. */
|
|
270
261
|
readonly status?: SessionStatus;
|
|
271
262
|
};
|
|
272
263
|
|
|
273
|
-
/** One working directory the catalogue holds sessions for, counted before any per-project cut. */
|
|
274
264
|
export type ProjectView = {
|
|
275
265
|
readonly cwd: string;
|
|
276
|
-
/**
|
|
266
|
+
/** Counted before the per-project cut. */
|
|
277
267
|
readonly count: number;
|
|
278
268
|
readonly pinned?: true;
|
|
279
|
-
/**
|
|
269
|
+
/** 0 first; only set when pinned. */
|
|
280
270
|
readonly pinRank?: number;
|
|
281
|
-
/** The sidebar group stands unfolded; absent is folded. */
|
|
282
271
|
readonly expanded?: true;
|
|
283
|
-
/**
|
|
272
|
+
/** Overrides the directory's base name. */
|
|
284
273
|
readonly label?: string;
|
|
285
274
|
};
|
|
286
275
|
|
|
287
|
-
/**
|
|
288
|
-
* One session a search matched. Deliberately not a `SessionSummaryView`: what
|
|
289
|
-
* a ranked list needs is why the row matched, and `unread` is the sidebar's
|
|
290
|
-
* triage while `status` resolves itself the moment the session opens.
|
|
291
|
-
*/
|
|
292
276
|
export type SearchHitView = {
|
|
293
277
|
readonly sessionId: string;
|
|
294
278
|
readonly cwd: string;
|
|
295
|
-
/**
|
|
279
|
+
/** The session name, else the clamped first message. */
|
|
296
280
|
readonly title?: string;
|
|
297
|
-
/** Offsets into
|
|
281
|
+
/** Offsets into `title`. */
|
|
298
282
|
readonly titleRanges: readonly SearchRange[];
|
|
299
|
-
/** The
|
|
283
|
+
/** The first message, for rows where only the name matched. */
|
|
300
284
|
readonly opening?: string;
|
|
301
|
-
/**
|
|
285
|
+
/** As in `SessionSummaryView`. */
|
|
302
286
|
readonly settledAt: number;
|
|
303
|
-
/** Searched and found anyway: the badge that makes "archived are in scope" honest. */
|
|
304
287
|
readonly archived?: true;
|
|
305
|
-
/** What matched, in the words it was said in, each with its own ranges. */
|
|
306
288
|
readonly snippets: readonly SearchSnippet[];
|
|
307
|
-
/** Matching messages
|
|
289
|
+
/** Matching messages, before the snippet cut. */
|
|
308
290
|
readonly total: number;
|
|
309
291
|
};
|
|
310
292
|
|
|
311
|
-
/** One answer to `list_sessions`: the page of rows, and every directory that had one, counted whole. */
|
|
312
293
|
export type SessionListing = {
|
|
313
294
|
readonly sessions: readonly SessionSummaryView[];
|
|
314
|
-
/** Counted before the per-project cut, so a group can say what a page of it leaves out. */
|
|
315
295
|
readonly projects: readonly ProjectView[];
|
|
316
296
|
};
|
|
317
297
|
|
|
318
|
-
/** One answer to `search_sessions`: the ranked rows, the words no session held, and the scope it read. */
|
|
319
298
|
export type SessionSearch = {
|
|
320
299
|
readonly hits: readonly SearchHitView[];
|
|
321
300
|
readonly dropped: readonly string[];
|
|
322
|
-
/** Sessions searched
|
|
301
|
+
/** Sessions searched. */
|
|
323
302
|
readonly scanned: number;
|
|
324
303
|
};
|
|
325
304
|
|
|
326
|
-
/** One model the server can be switched to, for the composer's model menu. */
|
|
327
305
|
export type ModelView = {
|
|
328
306
|
readonly id: string;
|
|
329
307
|
readonly label: string;
|
|
330
|
-
/** The provider half of `id
|
|
308
|
+
/** The provider half of `id`. */
|
|
331
309
|
readonly provider: string;
|
|
332
310
|
};
|
|
333
311
|
|
|
334
|
-
/** Answer to one `Command`, correlated by
|
|
312
|
+
/** Answer to one `Command`, correlated by `id`. */
|
|
335
313
|
export type ResponseEvent = {
|
|
336
314
|
readonly type: "response";
|
|
337
315
|
readonly id: string;
|
|
338
316
|
readonly success: boolean;
|
|
339
317
|
readonly error?: string;
|
|
340
|
-
/**
|
|
318
|
+
/** For `pick_*`. */
|
|
341
319
|
readonly items?: readonly PickerItem[];
|
|
342
|
-
/**
|
|
320
|
+
/** For `list_sessions`. */
|
|
343
321
|
readonly sessions?: readonly SessionSummaryView[];
|
|
344
|
-
/**
|
|
322
|
+
/** For `list_sessions`. */
|
|
345
323
|
readonly projects?: readonly ProjectView[];
|
|
346
|
-
/**
|
|
324
|
+
/** For `search_sessions`. */
|
|
347
325
|
readonly hits?: readonly SearchHitView[];
|
|
348
|
-
/** Query words no session held,
|
|
326
|
+
/** Query words no session held, for `search_sessions`. */
|
|
349
327
|
readonly dropped?: readonly string[];
|
|
350
|
-
/**
|
|
328
|
+
/** For `search_sessions`. */
|
|
351
329
|
readonly scanned?: number;
|
|
352
|
-
/**
|
|
330
|
+
/** For `list_models`. */
|
|
353
331
|
readonly models?: readonly ModelView[];
|
|
354
|
-
/**
|
|
332
|
+
/** The current model's levels, for `list_models`. */
|
|
355
333
|
readonly thinkingLevels?: readonly string[];
|
|
356
|
-
/**
|
|
334
|
+
/** For `list_extensions`. */
|
|
357
335
|
readonly extensions?: readonly ExtensionEntry[];
|
|
358
|
-
/**
|
|
336
|
+
/** For `list_dirs`. */
|
|
359
337
|
readonly directory?: DirectoryListing;
|
|
360
|
-
/**
|
|
338
|
+
/** For `list_branches`. */
|
|
361
339
|
readonly branches?: readonly GitBranch[];
|
|
362
|
-
/**
|
|
340
|
+
/** For `commit`. */
|
|
363
341
|
readonly commit?: Pick<Extract<CommitResult, { readonly ok: true }>, "sha">;
|
|
364
|
-
/**
|
|
342
|
+
/** For `list_changes`. */
|
|
365
343
|
readonly changes?: ChangeList;
|
|
366
|
-
/**
|
|
344
|
+
/** For `file_diff`. */
|
|
367
345
|
readonly fileDiff?: FileDiff;
|
|
368
|
-
/**
|
|
346
|
+
/** For `read_lines`. */
|
|
369
347
|
readonly fileLines?: FileLines;
|
|
370
|
-
/** For `cancel` and `dequeue`: queued messages
|
|
348
|
+
/** For `cancel` and `dequeue`: queued messages handed back to the client. */
|
|
371
349
|
readonly restored?: readonly string[];
|
|
372
|
-
/** For `user_message`:
|
|
350
|
+
/** For `user_message`: an extension command ran, so no turn started. */
|
|
373
351
|
readonly dispatched?: boolean;
|
|
374
352
|
};
|
|
375
353
|
|
|
376
354
|
export type ServerEvent = DurableEvent | EphemeralEvent | ResponseEvent;
|
|
377
355
|
|
|
378
|
-
/** Anything a session emits: everything on the wire but an answer to a command. */
|
|
379
356
|
export type StreamEvent = DurableEvent | EphemeralEvent;
|
|
380
357
|
|
|
381
358
|
export type ServerEventType = ServerEvent["type"];
|
|
@@ -384,17 +361,7 @@ export function isDurableEvent(event: ServerEvent): event is DurableEvent {
|
|
|
384
361
|
return "seq" in event;
|
|
385
362
|
}
|
|
386
363
|
|
|
387
|
-
/**
|
|
388
|
-
* Whether the frame speaks for the one session a connection is attached to,
|
|
389
|
-
* and so says nothing to a client whose attach is still in flight. The rest
|
|
390
|
-
* name their own subject — another session, a directory, or nothing at all —
|
|
391
|
-
* and gating those on the attach window drops a broadcast every time a client
|
|
392
|
-
* switches session.
|
|
393
|
-
*
|
|
394
|
-
* Exhaustive by construction: a new event is a type error here until it is
|
|
395
|
-
* classified, which is the only reason this lives beside the wire types
|
|
396
|
-
* rather than in the client that gates on it.
|
|
397
|
-
*/
|
|
364
|
+
/** Whether the event belongs to the attached session, so a client mid-attach should drop it. */
|
|
398
365
|
export function isAttachScoped(event: ServerEvent): boolean {
|
|
399
366
|
switch (event.type) {
|
|
400
367
|
case "attached":
|
|
@@ -415,9 +382,7 @@ export function isAttachScoped(event: ServerEvent): boolean {
|
|
|
415
382
|
case "ui_notice":
|
|
416
383
|
case "ui_request":
|
|
417
384
|
case "ui_request_done":
|
|
418
|
-
//
|
|
419
|
-
// is attached to, so the old session's is the only one that can arrive
|
|
420
|
-
// mid-attach — and re-querying for it would warm the wrong cache.
|
|
385
|
+
// Only sent down the attached session's stream.
|
|
421
386
|
case "picker_invalidate":
|
|
422
387
|
return true;
|
|
423
388
|
case "session_activity":
|
|
@@ -428,8 +393,6 @@ export function isAttachScoped(event: ServerEvent): boolean {
|
|
|
428
393
|
case "sessions_changed":
|
|
429
394
|
case "extensions_changed":
|
|
430
395
|
case "update_state":
|
|
431
|
-
// Sent for a frame the server could not read at all, which is likeliest
|
|
432
|
-
// before an attach has settled: gating it swallows the diagnostic.
|
|
433
396
|
case "error":
|
|
434
397
|
case "response":
|
|
435
398
|
return false;
|
|
@@ -11,42 +11,51 @@ export type AttachmentEndpointDeps = {
|
|
|
11
11
|
/** Defaults to `~/.pim/attachments`. */
|
|
12
12
|
readonly root?: string;
|
|
13
13
|
readonly maxBytes?: number;
|
|
14
|
+
/** How long an upload no prompt has taken is kept. */
|
|
15
|
+
readonly ttlMs?: number;
|
|
16
|
+
readonly now?: () => number;
|
|
14
17
|
};
|
|
15
18
|
|
|
19
|
+
type Pending = StoredAttachment & { readonly arrivedAt: number };
|
|
20
|
+
|
|
16
21
|
const DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
|
|
17
22
|
|
|
23
|
+
const DEFAULT_TTL_MS = 60 * 60 * 1000;
|
|
24
|
+
|
|
18
25
|
const PREFIX = "/attachment/";
|
|
19
26
|
|
|
20
|
-
/** Where stored bytes live when nothing says otherwise. */
|
|
21
27
|
export function defaultAttachmentsRoot(): string {
|
|
22
28
|
return join(Paths.pimHomeDir(), "attachments");
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
//
|
|
31
|
+
// Any origin: in dev the client is served from vite's port.
|
|
26
32
|
function allow(response: Response): Response {
|
|
27
33
|
response.headers.set("access-control-allow-origin", "*");
|
|
28
34
|
response.headers.set("access-control-allow-headers", "content-type");
|
|
29
35
|
return response;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
/**
|
|
38
|
+
/** Uses only the last two path segments, never the server's layout. */
|
|
33
39
|
export function attachmentUrl(path: string): string {
|
|
34
40
|
const scope = encodeURIComponent(basename(dirname(path)));
|
|
35
41
|
return `${PREFIX}${scope}/${encodeURIComponent(basename(path))}`;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
/** `POST /upload?session=<id>` with a multipart `file` field
|
|
44
|
+
/** `POST /upload?session=<id>` with a multipart `file` field; `GET /attachment/<session>/<file>` reads it back. */
|
|
39
45
|
export class AttachmentEndpoint {
|
|
40
46
|
private readonly store: AttachmentStore;
|
|
41
47
|
private readonly maxBytes: number;
|
|
42
|
-
private readonly
|
|
48
|
+
private readonly ttlMs: number;
|
|
49
|
+
private readonly now: () => number;
|
|
50
|
+
private readonly bySession = new Map<string, Map<string, Pending>>();
|
|
43
51
|
|
|
44
52
|
public constructor(deps: AttachmentEndpointDeps = {}) {
|
|
45
53
|
this.store = new AttachmentStore(deps.root ?? defaultAttachmentsRoot());
|
|
46
54
|
this.maxBytes = deps.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
55
|
+
this.ttlMs = deps.ttlMs ?? DEFAULT_TTL_MS;
|
|
56
|
+
this.now = deps.now ?? Date.now;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
/** Whether this endpoint owns the path, so the gateway can route to it. */
|
|
50
59
|
public static owns(pathname: string): boolean {
|
|
51
60
|
return pathname === "/upload" || pathname.startsWith(PREFIX);
|
|
52
61
|
}
|
|
@@ -63,7 +72,7 @@ export class AttachmentEndpoint {
|
|
|
63
72
|
);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
//
|
|
75
|
+
// `locate` re-sanitises each segment, so a crafted name cannot escape its scope.
|
|
67
76
|
private async serve(pathname: string): Promise<Response> {
|
|
68
77
|
const [scope, name, ...rest] = pathname
|
|
69
78
|
.slice(PREFIX.length)
|
|
@@ -91,10 +100,7 @@ export class AttachmentEndpoint {
|
|
|
91
100
|
}
|
|
92
101
|
const declared = Number(request.headers.get("content-length") ?? 0);
|
|
93
102
|
if (declared > this.maxBytes) {
|
|
94
|
-
return
|
|
95
|
-
{ error: `upload exceeds ${this.maxBytes} bytes` },
|
|
96
|
-
{ status: 413 }
|
|
97
|
-
);
|
|
103
|
+
return this.tooLarge();
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
let file: File | undefined;
|
|
@@ -112,10 +118,7 @@ export class AttachmentEndpoint {
|
|
|
112
118
|
return Response.json({ error: "missing `file` field" }, { status: 400 });
|
|
113
119
|
}
|
|
114
120
|
if (file.size > this.maxBytes) {
|
|
115
|
-
return
|
|
116
|
-
{ error: `upload exceeds ${this.maxBytes} bytes` },
|
|
117
|
-
{ status: 413 }
|
|
118
|
-
);
|
|
121
|
+
return this.tooLarge();
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
let stored: StoredAttachment;
|
|
@@ -140,7 +143,7 @@ export class AttachmentEndpoint {
|
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
/**
|
|
146
|
+
/** Consumes the uploads a prompt referenced; unknown ids are ignored, so a replay cannot re-attach. */
|
|
144
147
|
public take(
|
|
145
148
|
sessionId: string,
|
|
146
149
|
ids: readonly string[]
|
|
@@ -154,7 +157,8 @@ export class AttachmentEndpoint {
|
|
|
154
157
|
const stored = pending.get(id);
|
|
155
158
|
if (stored) {
|
|
156
159
|
pending.delete(id);
|
|
157
|
-
|
|
160
|
+
const { arrivedAt: _, ...attachment } = stored;
|
|
161
|
+
taken.push(attachment);
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
if (pending.size === 0) {
|
|
@@ -163,11 +167,33 @@ export class AttachmentEndpoint {
|
|
|
163
167
|
return taken;
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
private tooLarge(): Response {
|
|
171
|
+
return Response.json(
|
|
172
|
+
{ error: `upload exceeds ${this.maxBytes} bytes` },
|
|
173
|
+
{ status: 413 }
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
private remember(sessionId: string, stored: StoredAttachment): void {
|
|
167
|
-
const
|
|
168
|
-
|
|
178
|
+
const now = this.now();
|
|
179
|
+
this.sweep(now);
|
|
180
|
+
const pending = this.bySession.get(sessionId) ?? new Map<string, Pending>();
|
|
181
|
+
pending.set(stored.id, { ...stored, arrivedAt: now });
|
|
169
182
|
this.bySession.set(sessionId, pending);
|
|
170
183
|
}
|
|
184
|
+
|
|
185
|
+
private sweep(now: number): void {
|
|
186
|
+
for (const [sessionId, pending] of this.bySession) {
|
|
187
|
+
for (const [id, stored] of pending) {
|
|
188
|
+
if (now - stored.arrivedAt >= this.ttlMs) {
|
|
189
|
+
pending.delete(id);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (pending.size === 0) {
|
|
193
|
+
this.bySession.delete(sessionId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
171
197
|
}
|
|
172
198
|
|
|
173
199
|
function stemOf(name: string): string | undefined {
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "#protocol/ServerEvent";
|
|
8
8
|
import type { SessionProjection } from "./SessionProjection";
|
|
9
9
|
|
|
10
|
-
/** The half of `SessionStream` a connection needs: subscribe, and ask for everything missed. */
|
|
11
10
|
export type AttachableStream = {
|
|
12
11
|
readonly subscribe: (listener: (event: ServerEvent) => void) => () => void;
|
|
13
12
|
readonly replay: (fromSeq: number) => Promise<readonly StreamEvent[]>;
|
|
@@ -20,11 +19,13 @@ type SubagentWatch = {
|
|
|
20
19
|
cursor: number;
|
|
21
20
|
};
|
|
22
21
|
|
|
22
|
+
/** Bytes buffered before the connection pauses. */
|
|
23
23
|
const HIGH_WATER_MARK = 1 << 20;
|
|
24
24
|
|
|
25
|
+
/** Serialized once per event, shared across connections. */
|
|
25
26
|
const FRAMES = new WeakMap<object, string>();
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
function frame(event: object): string {
|
|
28
29
|
const cached = FRAMES.get(event);
|
|
29
30
|
if (cached !== undefined) {
|
|
30
31
|
return cached;
|
|
@@ -34,7 +35,7 @@ export function frame(event: object): string {
|
|
|
34
35
|
return text;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
/** One WebSocket client, attached to at most one session
|
|
38
|
+
/** One WebSocket client, attached to at most one session. A lagging client is paused and resynced on `drain`. */
|
|
38
39
|
export class ClientConnection {
|
|
39
40
|
private readonly ws: ServerWebSocket<undefined>;
|
|
40
41
|
private stream: AttachableStream | undefined;
|
|
@@ -55,7 +56,6 @@ export class ClientConnection {
|
|
|
55
56
|
return this.stream?.sessionId;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
/** Whether the reader behind this socket is looking at the session right now. */
|
|
59
59
|
public get attentive(): boolean {
|
|
60
60
|
return this.reading;
|
|
61
61
|
}
|
|
@@ -65,7 +65,6 @@ export class ClientConnection {
|
|
|
65
65
|
return this.cursor;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/** The subagent this client is reading, if it is reading one. */
|
|
69
68
|
public get watchedCallId(): string | undefined {
|
|
70
69
|
return this.watch?.callId;
|
|
71
70
|
}
|
|
@@ -89,7 +88,7 @@ export class ClientConnection {
|
|
|
89
88
|
this.reading = attentive;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
/**
|
|
91
|
+
/** At most one watch at a time. */
|
|
93
92
|
public async watchSubagent(
|
|
94
93
|
callId: string,
|
|
95
94
|
projection: SessionProjection,
|
|
@@ -99,14 +98,14 @@ export class ClientConnection {
|
|
|
99
98
|
await this.pumpWatch();
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
/**
|
|
101
|
+
/** Only closes the named watch, never one that replaced it. */
|
|
103
102
|
public unwatchSubagent(callId: string): void {
|
|
104
103
|
if (this.watch?.callId === callId) {
|
|
105
104
|
this.watch = undefined;
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
/**
|
|
108
|
+
/** For events outside the stream, e.g. command responses. */
|
|
110
109
|
public send(event: ServerEvent): void {
|
|
111
110
|
if (this.closed) {
|
|
112
111
|
return;
|
|
@@ -135,7 +134,7 @@ export class ClientConnection {
|
|
|
135
134
|
this.detach();
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
//
|
|
137
|
+
// Queue live events during replay, then keep only durable ones (ephemeral state is in the snapshot).
|
|
139
138
|
private async sync(): Promise<void> {
|
|
140
139
|
const stream = this.stream;
|
|
141
140
|
if (!stream || this.closed) {
|
|
@@ -156,7 +155,7 @@ export class ClientConnection {
|
|
|
156
155
|
if (this.touchesWatch(event)) {
|
|
157
156
|
void this.pumpWatch();
|
|
158
157
|
}
|
|
159
|
-
//
|
|
158
|
+
// Unwrap so the durable filter can see inside.
|
|
160
159
|
if (event.type === "replay") {
|
|
161
160
|
if (this.gated) {
|
|
162
161
|
this.gate.push(...event.events);
|
|
@@ -172,12 +171,12 @@ export class ClientConnection {
|
|
|
172
171
|
this.write(event);
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
//
|
|
174
|
+
// The cursor only moves once the frame is sent.
|
|
176
175
|
private writeBatch(events: readonly StreamEvent[]): void {
|
|
177
176
|
if (this.closed || this.paused) {
|
|
178
177
|
return;
|
|
179
178
|
}
|
|
180
|
-
//
|
|
179
|
+
// Replay and queue can overlap, so dedupe against a moving cursor.
|
|
181
180
|
let cursor = this.cursor;
|
|
182
181
|
const fresh: StreamEvent[] = [];
|
|
183
182
|
for (const event of events) {
|