pim-agent 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -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 +3 -2
- package/packages/core/src/extensions/system-prompt/prompt.ts +13 -3
- 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-WweOU5DK.js +1 -0
- package/packages/web/dist/client/assets/arc-O2PmZXWt.js +1 -0
- package/packages/web/dist/client/assets/architecture-7GRP2DOG-COBxVilU.js +1 -0
- package/packages/web/dist/client/assets/architectureDiagram-NJMV4G6O-DwYq1YaM.js +36 -0
- package/packages/web/dist/client/assets/array-BifhSqXX.js +1 -0
- package/packages/web/dist/client/assets/blockDiagram-BEXU5L5S-CVab041n.js +140 -0
- package/packages/web/dist/client/assets/c4Diagram-YGBWAQC7-BmotHez3.js +38 -0
- package/packages/web/dist/client/assets/channel-BKV-IcTA.js +1 -0
- package/packages/web/dist/client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/packages/web/dist/client/assets/chunk-3FUC2YCW-CWNS7-n4.js +2 -0
- package/packages/web/dist/client/assets/chunk-5DYCD2WN-DflvXohu.js +1 -0
- package/packages/web/dist/client/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/packages/web/dist/client/assets/chunk-742MDFTN-B_0t6_RE.js +1 -0
- package/packages/web/dist/client/assets/chunk-7INBJB4K-wKj0Q3Kv.js +62 -0
- package/packages/web/dist/client/assets/chunk-7M6MHVWA-BMvUghc8.js +213 -0
- package/packages/web/dist/client/assets/chunk-7PRAP22T-BY41wkcT.js +1 -0
- package/packages/web/dist/client/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/packages/web/dist/client/assets/chunk-FOHPRMQF-CoA4358r.js +161 -0
- package/packages/web/dist/client/assets/chunk-GTNCS2PH-PqY_YoS0.js +88 -0
- package/packages/web/dist/client/assets/chunk-GWA4HPMP-fozSU5ty.js +1 -0
- package/packages/web/dist/client/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/packages/web/dist/client/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/packages/web/dist/client/assets/chunk-MBY4JIJT-CrHrC3jw.js +72 -0
- package/packages/web/dist/client/assets/chunk-NETBCI7D-DWdAxieG.js +1 -0
- package/packages/web/dist/client/assets/chunk-O7XYJQB3-B6uOn9iS.js +128 -0
- package/packages/web/dist/client/assets/chunk-UA2S7LBM-BxYSHqyy.js +1 -0
- package/packages/web/dist/client/assets/chunk-WEXAMYUT-CtwIaQDa.js +1 -0
- package/packages/web/dist/client/assets/chunk-XXDRQBXY-fgBFe-PX.js +1 -0
- package/packages/web/dist/client/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/packages/web/dist/client/assets/chunk-Z7XXMR3K-CPonoV3p.js +10 -0
- package/packages/web/dist/client/assets/chunk-ZIGJFQKS-Cdu87cTj.js +2 -0
- package/packages/web/dist/client/assets/classDiagram-v2-NBCMYWYE-DSWsPaa7.js +217 -0
- package/packages/web/dist/client/assets/core-CBoS7AST.js +3 -0
- package/packages/web/dist/client/assets/cose-bilkent-JH36ORCC-CTwlldtJ.js +1 -0
- package/packages/web/dist/client/assets/cynefin-OW5HDTMX-CZQ4EPNJ.js +1 -0
- package/packages/web/dist/client/assets/cynefinDiagram-VND7K2PF-C2gp6H_Q.js +62 -0
- package/packages/web/dist/client/assets/cytoscape.esm-B-1iW2ft.js +321 -0
- package/packages/web/dist/client/assets/dagre-6A5THRUB-CDxTP47t.js +4 -0
- package/packages/web/dist/client/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/packages/web/dist/client/assets/diagram-22UHCM2B-BIohE1lI.js +200 -0
- package/packages/web/dist/client/assets/diagram-3UASUU5V-TwAUir-w.js +24 -0
- package/packages/web/dist/client/assets/diagram-ATOU4E4O-BM1JQrIu.js +3 -0
- package/packages/web/dist/client/assets/diagram-CDSNMT55-BKIqhxSI.js +30 -0
- package/packages/web/dist/client/assets/diagram-MLGK6HIB-CVe3QDHr.js +24 -0
- package/packages/web/dist/client/assets/diagram-MPIPVDR6-Bj-MQIFI.js +41 -0
- package/packages/web/dist/client/assets/dist-DlZpcyc2.js +1 -0
- package/packages/web/dist/client/assets/ebnfDiagram-ZINNZB2B-C06mmvmz.js +1 -0
- package/packages/web/dist/client/assets/elk-276RUBZZ-CizJSNEj.js +27 -0
- package/packages/web/dist/client/assets/erDiagram-OPXOYQCR-M3FQkKI1.js +99 -0
- 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-CojF9Q16.js +292 -0
- package/packages/web/dist/client/assets/gitGraph-4MIJSDKK-YvKqGEPk.js +1 -0
- package/packages/web/dist/client/assets/gitGraphDiagram-X574FWY7-XqNqIFo9.js +106 -0
- package/packages/web/dist/client/assets/graphlib-DS17s2tU.js +1 -0
- package/packages/web/dist/client/assets/index--2CDtzvY.css +1 -0
- package/packages/web/dist/client/assets/index-bT6QPJZs.js +47 -0
- package/packages/web/dist/client/assets/info-A6RAGUB7-B_1BCzM2.js +1 -0
- package/packages/web/dist/client/assets/infoDiagram-VRGFBTTK-zFX8B-dP.js +2 -0
- package/packages/web/dist/client/assets/init-C-OQMol4.js +1 -0
- package/packages/web/dist/client/assets/ishikawaDiagram-OU5B5YK6-DLzQ_Uyf.js +70 -0
- package/packages/web/dist/client/assets/journeyDiagram-ZHPQQLJL-S3WRMmoa.js +139 -0
- package/packages/web/dist/client/assets/kanban-definition-PNTS6WVX-B5Z8I-kM.js +89 -0
- package/packages/web/dist/client/assets/katex-ZlcWpGUi.js +257 -0
- package/packages/web/dist/client/assets/line-BGMH-i2X.js +1 -0
- package/packages/web/dist/client/assets/linear-KsrestXG.js +1 -0
- package/packages/web/dist/client/assets/mermaid-parser.core-DNffyJJA.js +7 -0
- package/packages/web/dist/client/assets/mermaid.core-Cz3vJeQG.js +44 -0
- package/packages/web/dist/client/assets/mindmap-definition-NLK3R4M7-CzA61XUf.js +96 -0
- package/packages/web/dist/client/assets/ordinal-BDEzSJ7C.js +1 -0
- package/packages/web/dist/client/assets/packet-AYTQ26CC-BO55e7cR.js +1 -0
- package/packages/web/dist/client/assets/path-fybaL0A-.js +1 -0
- package/packages/web/dist/client/assets/pegDiagram-GJSIUBJH-DeRiXYNd.js +1 -0
- package/packages/web/dist/client/assets/pie-WAS4IAKB-D9ibrQRe.js +1 -0
- package/packages/web/dist/client/assets/pieDiagram-5QR66LMP-BaBpPP33.js +39 -0
- package/packages/web/dist/client/assets/quadrantDiagram-O4NWA36T-DIfn4GjM.js +7 -0
- package/packages/web/dist/client/assets/radar-RG4KPBEZ-Dmn5-nJq.js +1 -0
- package/packages/web/dist/client/assets/railroad-74A4TZTK-ZCWNEd2Z.js +1 -0
- 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-CDPZ39sy.js +1 -0
- package/packages/web/dist/client/assets/requirementDiagram-PLB6GJNP-CyCI8Yul.js +84 -0
- package/packages/web/dist/client/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/packages/web/dist/client/assets/sankeyDiagram-IPEJSGJF-p_XAPv72.js +40 -0
- package/packages/web/dist/client/assets/sequenceDiagram-PO4LG4MO-DmR6IP_o.js +169 -0
- package/packages/web/dist/client/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/packages/web/dist/client/assets/src-gnv7pJ1z.js +1 -0
- package/packages/web/dist/client/assets/stateDiagram-v2-GCMORJYK-7ZTHl6ih.js +291 -0
- package/packages/web/dist/client/assets/swimlanes-2SLR337P-N6bj71pw.js +1 -0
- package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-BJQ2CBsp.js +8 -0
- package/packages/web/dist/client/assets/timeline-definition-EJHVYXUP-GNez0kW8.js +120 -0
- package/packages/web/dist/client/assets/treeView-Q6P3EWNA-DyQZkboR.js +1 -0
- package/packages/web/dist/client/assets/treemap-WGGIJYW6-_J8Pw2TM.js +1 -0
- package/packages/web/dist/client/assets/usecaseDiagram-POWQR4AR-Bg-CzKjl.js +328 -0
- package/packages/web/dist/client/assets/vennDiagram-UO4OBE2U-DBQKHXhK.js +34 -0
- package/packages/web/dist/client/assets/wardley-WFR3VGLG-B5aAsQ3F.js +1 -0
- package/packages/web/dist/client/assets/wardleyDiagram-VNRHLVJA-BKUQ1LK6.js +78 -0
- package/packages/web/dist/client/assets/xychartDiagram-PMCCYNJV-BI2sVbks.js +7 -0
- package/packages/web/dist/client/index.html +2 -2
- package/packages/web/dist/client/assets/core-C5fJJN75.js +0 -3
- package/packages/web/dist/client/assets/index-C5D7B_8d.js +0 -45
- package/packages/web/dist/client/assets/index-OVLzpES-.css +0 -1
|
@@ -6,23 +6,22 @@ import { Paths } from "../shared/Paths";
|
|
|
6
6
|
|
|
7
7
|
export type SessionEntry = {
|
|
8
8
|
readonly archived?: boolean;
|
|
9
|
-
/**
|
|
9
|
+
/** Set by hand; only a read clears it. */
|
|
10
10
|
readonly unread?: boolean;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export type ProjectEntry = {
|
|
14
14
|
readonly pinned?: boolean;
|
|
15
|
-
/**
|
|
15
|
+
/** Absent means folded. */
|
|
16
16
|
readonly expanded?: boolean;
|
|
17
|
-
/**
|
|
17
|
+
/** Display name instead of the directory's base name. */
|
|
18
18
|
readonly label?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
/** The pin flags and the order they are shown in, from one read of the file. */
|
|
22
21
|
export type Pinning = {
|
|
23
22
|
/** Keyed by absolute cwd. */
|
|
24
23
|
readonly projects: ReadonlyMap<string, ProjectEntry>;
|
|
25
|
-
/**
|
|
24
|
+
/** Pinned cwds in display order. */
|
|
26
25
|
readonly order: readonly string[];
|
|
27
26
|
};
|
|
28
27
|
|
|
@@ -30,13 +29,7 @@ type Stored = {
|
|
|
30
29
|
readonly version: 1;
|
|
31
30
|
readonly sessions: Record<string, SessionEntry>;
|
|
32
31
|
readonly projects: Record<string, ProjectEntry>;
|
|
33
|
-
/**
|
|
34
|
-
* Where the pinned directories sort, and only that: `projects` stays the
|
|
35
|
-
* truth of whether one is pinned. An entry here that is not pinned is
|
|
36
|
-
* ignored and a pin it has never heard of still sorts, so a file written by
|
|
37
|
-
* a pim that predates the order — or by one that does not write it — needs
|
|
38
|
-
* no migration.
|
|
39
|
-
*/
|
|
32
|
+
/** Sort order only; `projects` decides what is pinned. */
|
|
40
33
|
readonly pins: readonly string[];
|
|
41
34
|
};
|
|
42
35
|
|
|
@@ -48,7 +41,7 @@ type Loaded = {
|
|
|
48
41
|
|
|
49
42
|
const NONE: SessionEntry = {};
|
|
50
43
|
|
|
51
|
-
/** Per-session and per-project
|
|
44
|
+
/** Per-session and per-project UI state, shared by every surface on the machine. */
|
|
52
45
|
export class SessionMeta {
|
|
53
46
|
private readonly file: string;
|
|
54
47
|
private readonly writes = Fs.serialised();
|
|
@@ -62,18 +55,17 @@ export class SessionMeta {
|
|
|
62
55
|
return (await this.sessions()).get(sessionId) ?? NONE;
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
/** One read for
|
|
58
|
+
/** One file read for the whole listing. */
|
|
66
59
|
public async sessions(): Promise<ReadonlyMap<string, SessionEntry>> {
|
|
67
60
|
return (await this.read()).sessions;
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
/** The pinned directories in display order; a listing wants both halves, and this is one read for them. */
|
|
71
63
|
public async pinning(): Promise<Pinning> {
|
|
72
64
|
const loaded = await this.read();
|
|
73
65
|
return { projects: loaded.projects, order: ordered(loaded) };
|
|
74
66
|
}
|
|
75
67
|
|
|
76
|
-
/**
|
|
68
|
+
/** Pinned cwds in display order. */
|
|
77
69
|
public async pins(): Promise<readonly string[]> {
|
|
78
70
|
return ordered(await this.read());
|
|
79
71
|
}
|
|
@@ -88,7 +80,7 @@ export class SessionMeta {
|
|
|
88
80
|
return this.mutate((loaded) => put(loaded.sessions, sessionId, { unread }));
|
|
89
81
|
}
|
|
90
82
|
|
|
91
|
-
/** A new pin goes to the top
|
|
83
|
+
/** A new pin goes to the top. */
|
|
92
84
|
public setPinned(cwd: string, pinned: boolean): Promise<void> {
|
|
93
85
|
return this.mutate((loaded) => {
|
|
94
86
|
const at = loaded.pins.indexOf(cwd);
|
|
@@ -102,22 +94,18 @@ export class SessionMeta {
|
|
|
102
94
|
});
|
|
103
95
|
}
|
|
104
96
|
|
|
105
|
-
/**
|
|
106
|
-
* Folds a project's group, or unfolds it. Kept beside the pin rather than
|
|
107
|
-
* in the browser, so the fold a phone made is the fold a desktop opens to.
|
|
108
|
-
*/
|
|
109
97
|
public setExpanded(cwd: string, expanded: boolean): Promise<void> {
|
|
110
98
|
return this.mutate((loaded) => put(loaded.projects, cwd, { expanded }));
|
|
111
99
|
}
|
|
112
100
|
|
|
113
|
-
/**
|
|
101
|
+
/** `null` restores the base name. */
|
|
114
102
|
public setLabel(cwd: string, label: string | null): Promise<void> {
|
|
115
103
|
return this.mutate((loaded) =>
|
|
116
104
|
put(loaded.projects, cwd, { label: label ?? "" })
|
|
117
105
|
);
|
|
118
106
|
}
|
|
119
107
|
|
|
120
|
-
/**
|
|
108
|
+
/** Replaces the whole order; unpinned and duplicate cwds are dropped. */
|
|
121
109
|
public setPinOrder(order: readonly string[]): Promise<void> {
|
|
122
110
|
return this.mutate((loaded) => {
|
|
123
111
|
loaded.pins = [...new Set(order)].filter(
|
|
@@ -127,7 +115,7 @@ export class SessionMeta {
|
|
|
127
115
|
});
|
|
128
116
|
}
|
|
129
117
|
|
|
130
|
-
/** Drops
|
|
118
|
+
/** Drops entries for sessions not in `alive`. Projects are kept. */
|
|
131
119
|
public prune(alive: ReadonlySet<string>): Promise<void> {
|
|
132
120
|
return this.mutate((loaded) => {
|
|
133
121
|
let dropped = false;
|
|
@@ -141,7 +129,7 @@ export class SessionMeta {
|
|
|
141
129
|
});
|
|
142
130
|
}
|
|
143
131
|
|
|
144
|
-
/**
|
|
132
|
+
/** Waits for pending writes. */
|
|
145
133
|
public async flush(): Promise<void> {
|
|
146
134
|
await this.writes.run(async () => undefined);
|
|
147
135
|
}
|
|
@@ -162,7 +150,7 @@ export class SessionMeta {
|
|
|
162
150
|
};
|
|
163
151
|
}
|
|
164
152
|
|
|
165
|
-
/** Re-reads under the lock
|
|
153
|
+
/** Re-reads under the lock so another process's write is not lost. */
|
|
166
154
|
private async mutate(update: (loaded: Loaded) => boolean): Promise<void> {
|
|
167
155
|
await this.writes.run(async () => {
|
|
168
156
|
const loaded = await this.read();
|
|
@@ -180,11 +168,7 @@ export class SessionMeta {
|
|
|
180
168
|
}
|
|
181
169
|
}
|
|
182
170
|
|
|
183
|
-
/**
|
|
184
|
-
* The pinned directories in display order: the ones the stored order names,
|
|
185
|
-
* then any pin it has not heard of, by path. A duplicate in the order is
|
|
186
|
-
* taken once, at the first place it appears.
|
|
187
|
-
*/
|
|
171
|
+
/** Stored order first, then pins missing from it sorted by path. */
|
|
188
172
|
function ordered({ projects, pins }: Loaded): readonly string[] {
|
|
189
173
|
const unplaced = new Set(
|
|
190
174
|
[...projects]
|
|
@@ -209,7 +193,7 @@ function put<T extends object>(
|
|
|
209
193
|
return true;
|
|
210
194
|
}
|
|
211
195
|
|
|
212
|
-
/**
|
|
196
|
+
/** Drops `false` and `""` fields; they mean the same as absent. */
|
|
213
197
|
function onlySet<T extends object>(entry: T): T {
|
|
214
198
|
return Object.fromEntries(
|
|
215
199
|
Object.entries(entry).filter(
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Twice the width a sidebar row can show, so a name is never silently cut at the
|
|
3
|
-
* length it happens to render at, and a pasted essay still cannot become one.
|
|
4
|
-
*/
|
|
1
|
+
/** In code points. */
|
|
5
2
|
const LIMIT = 80;
|
|
6
3
|
|
|
7
|
-
/**
|
|
4
|
+
/** Collapses whitespace and strips control characters; `""` clears the name. */
|
|
8
5
|
function normalise(name: string | null | undefined): string {
|
|
9
6
|
const clean = (name ?? "")
|
|
10
7
|
.replace(/\p{Cc}/gu, " ")
|
|
11
8
|
.replace(/\s+/gu, " ")
|
|
12
9
|
.trim();
|
|
13
|
-
//
|
|
10
|
+
// Slice by code point so a surrogate pair is never split.
|
|
14
11
|
return [...clean].slice(0, LIMIT).join("").trim();
|
|
15
12
|
}
|
|
16
13
|
|
|
17
|
-
/** The one spelling of a session name pim writes into pi's `session_info`. */
|
|
18
14
|
export const SessionName = { normalise };
|
|
@@ -20,7 +20,6 @@ import { SessionLease } from "./SessionLease";
|
|
|
20
20
|
import { SessionName } from "./SessionName";
|
|
21
21
|
import type { SessionUi } from "./SessionUi";
|
|
22
22
|
|
|
23
|
-
/** A session as pi stores it: one JSONL file under a cwd-encoded directory. */
|
|
24
23
|
export type SessionSummary = {
|
|
25
24
|
readonly sessionId: string;
|
|
26
25
|
readonly cwd: string;
|
|
@@ -31,9 +30,9 @@ export type SessionSummary = {
|
|
|
31
30
|
|
|
32
31
|
export type SessionRegistryDeps = {
|
|
33
32
|
readonly defaults: { readonly cwd: string; readonly model?: string };
|
|
34
|
-
/**
|
|
33
|
+
/** Defaults to a new runtime over `agentDir`. */
|
|
35
34
|
readonly runtime?: AgentRuntime;
|
|
36
|
-
/**
|
|
35
|
+
/** Must match pi's `getAgentDir()`. */
|
|
37
36
|
readonly agentDir?: string;
|
|
38
37
|
readonly capacity?: number;
|
|
39
38
|
readonly customTools?: (
|
|
@@ -41,20 +40,19 @@ export type SessionRegistryDeps = {
|
|
|
41
40
|
) => readonly ToolDefinition[];
|
|
42
41
|
/** Appended to every session's system prompt. */
|
|
43
42
|
readonly systemInstruction?: () => Promise<string | undefined>;
|
|
44
|
-
/** Where this frontend's human is reading. */
|
|
45
43
|
readonly surface?: Surface;
|
|
46
44
|
};
|
|
47
45
|
|
|
48
46
|
export type SessionCreateOptions = {
|
|
49
47
|
readonly cwd?: string;
|
|
50
|
-
/**
|
|
48
|
+
/** Copies model, thinking level and (if `cwd` is unset) cwd from this host. */
|
|
51
49
|
readonly like?: SessionHost;
|
|
52
50
|
};
|
|
53
51
|
|
|
54
|
-
/**
|
|
52
|
+
/** Concurrent header reads, to avoid running out of file descriptors. */
|
|
55
53
|
const HEADER_READS = 32;
|
|
56
54
|
|
|
57
|
-
/** Live sessions keyed
|
|
55
|
+
/** Live sessions keyed by pi's session id, plus listing of pi's sessions directory. */
|
|
58
56
|
export class SessionRegistry {
|
|
59
57
|
private readonly deps: SessionRegistryDeps;
|
|
60
58
|
private readonly runtime: AgentRuntime;
|
|
@@ -68,10 +66,8 @@ export class SessionRegistry {
|
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* and pi freezes a session's UI mode at that bind. Resolved per call: a
|
|
74
|
-
* host outlives the stream that speaks for it.
|
|
69
|
+
* UI sink for hosts built after this call. Lives here because `create` binds
|
|
70
|
+
* the agent eagerly and pi fixes the UI at bind time.
|
|
75
71
|
*/
|
|
76
72
|
public setUi(ui: (host: SessionHost) => SessionUi | undefined): void {
|
|
77
73
|
this.ui = ui;
|
|
@@ -89,7 +85,7 @@ export class SessionRegistry {
|
|
|
89
85
|
await this.runtime.init();
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
/**
|
|
88
|
+
/** Newest first; reads only each file's header. */
|
|
93
89
|
public async list(cwd?: string): Promise<readonly SessionSummary[]> {
|
|
94
90
|
const summaries = (
|
|
95
91
|
await Pool.mapPooled(
|
|
@@ -104,15 +100,13 @@ export class SessionRegistry {
|
|
|
104
100
|
return summaries.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
105
101
|
}
|
|
106
102
|
|
|
107
|
-
/** The live host for `sessionId`, if one is currently loaded. */
|
|
108
103
|
public peek(sessionId: string): SessionHost | undefined {
|
|
109
104
|
return this.hosts.peek(sessionId);
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* by appending to its file under the turn lease. Answers with the name pi kept.
|
|
108
|
+
* Sets pi's session name; `null` clears it. A closed session's file is
|
|
109
|
+
* appended to under the turn lease. Returns the stored name.
|
|
116
110
|
*/
|
|
117
111
|
public async setName(
|
|
118
112
|
sessionId: string,
|
|
@@ -127,8 +121,7 @@ export class SessionRegistry {
|
|
|
127
121
|
throw new Error(`unknown session: ${sessionId}`);
|
|
128
122
|
}
|
|
129
123
|
const next = SessionName.normalise(name);
|
|
130
|
-
//
|
|
131
|
-
// The lease is only ever held for a whole turn, so a rename refuses rather than queues.
|
|
124
|
+
// Fails fast instead of waiting out a whole turn.
|
|
132
125
|
return await SessionLease.hold(
|
|
133
126
|
summary.path,
|
|
134
127
|
"daemon",
|
|
@@ -141,7 +134,6 @@ export class SessionRegistry {
|
|
|
141
134
|
);
|
|
142
135
|
}
|
|
143
136
|
|
|
144
|
-
/** Every model this machine has credentials for, qualified as `SessionHost.setModel` takes them. */
|
|
145
137
|
public models(): readonly ModelChoice[] {
|
|
146
138
|
return this.runtime.models();
|
|
147
139
|
}
|
|
@@ -164,7 +156,7 @@ export class SessionRegistry {
|
|
|
164
156
|
);
|
|
165
157
|
}
|
|
166
158
|
|
|
167
|
-
/**
|
|
159
|
+
/** Builds the agent eagerly: pi assigns the session id. */
|
|
168
160
|
public async create(
|
|
169
161
|
options: SessionCreateOptions = {}
|
|
170
162
|
): Promise<SessionHost> {
|
|
@@ -189,11 +181,7 @@ export class SessionRegistry {
|
|
|
189
181
|
await this.hosts.disposeAll();
|
|
190
182
|
}
|
|
191
183
|
|
|
192
|
-
/**
|
|
193
|
-
* One session by id. Pi names a file for the id it carries, so the usual
|
|
194
|
-
* answer is one glob and one header; resolving it by listing would read the
|
|
195
|
-
* header of every session on disk to throw all but one away.
|
|
196
|
-
*/
|
|
184
|
+
/** Tries files named for the id first, then falls back to a full listing. */
|
|
197
185
|
private async find(sessionId: string): Promise<SessionSummary | undefined> {
|
|
198
186
|
for (const path of await this.paths(`*/*${sessionId}.jsonl`)) {
|
|
199
187
|
const summary = await readSummary(path);
|
|
@@ -201,7 +189,6 @@ export class SessionRegistry {
|
|
|
201
189
|
return summary;
|
|
202
190
|
}
|
|
203
191
|
}
|
|
204
|
-
// A file named something else entirely: the id is the header's, not the name's.
|
|
205
192
|
return (await this.list()).find(
|
|
206
193
|
(summary) => summary.sessionId === sessionId
|
|
207
194
|
);
|
|
@@ -224,8 +211,6 @@ export class SessionRegistry {
|
|
|
224
211
|
|
|
225
212
|
private buildHost(label: string, settings: HostSettings): SessionHost {
|
|
226
213
|
const ui = this.ui;
|
|
227
|
-
// Named so the sink can reach back for the host it belongs to; the closure
|
|
228
|
-
// only ever runs once construction has returned.
|
|
229
214
|
const host: SessionHost = new SessionHost({
|
|
230
215
|
label: `session ${label}`,
|
|
231
216
|
settings,
|
|
@@ -235,7 +220,6 @@ export class SessionRegistry {
|
|
|
235
220
|
modelRegistry: this.runtime.modelRegistry,
|
|
236
221
|
settingsManagerFor: (cwd) => this.runtime.settingsManagerFor(cwd),
|
|
237
222
|
persistSettings: async () => {},
|
|
238
|
-
// The terminal can hold the same file open, so every mutation goes through the turn lease.
|
|
239
223
|
lease: "daemon",
|
|
240
224
|
customTools: this.deps.customTools,
|
|
241
225
|
...(this.deps.systemInstruction === undefined
|
|
@@ -5,13 +5,13 @@ import type {
|
|
|
5
5
|
|
|
6
6
|
import type { NoticeSeverity } from "../view/ViewBlock";
|
|
7
7
|
|
|
8
|
-
/** The
|
|
8
|
+
/** The subset of pi's dialog options a non-terminal surface honours. */
|
|
9
9
|
export type UiAsk = {
|
|
10
10
|
readonly timeout?: number;
|
|
11
11
|
readonly signal?: AbortSignal;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/** The parts of pi's `ExtensionUIContext` a non-terminal surface implements. */
|
|
15
15
|
export type SessionUi = {
|
|
16
16
|
readonly notify: (text: string, severity: NoticeSeverity) => void;
|
|
17
17
|
readonly select: (
|
|
@@ -31,15 +31,11 @@ export type SessionUi = {
|
|
|
31
31
|
) => Promise<string | undefined>;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
/** Everything off the terminal paints with; also what pi's border getters hand back. */
|
|
35
34
|
const IDENTITY = (text: string): string => text;
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* skips the private fields the cast is for — so a method pi adds is a type
|
|
41
|
-
* error here rather than a throw inside somebody's event handler.
|
|
42
|
-
*/
|
|
36
|
+
const DEFAULT_COLOR = { kind: "indexed", index: 7 } as const;
|
|
37
|
+
|
|
38
|
+
/** `satisfies` checks every public member, so a method pi adds is a type error here. */
|
|
43
39
|
const PLAIN_THEME = {
|
|
44
40
|
fg: (_color: string, text: string) => text,
|
|
45
41
|
bg: (_color: string, text: string) => text,
|
|
@@ -53,6 +49,9 @@ const PLAIN_THEME = {
|
|
|
53
49
|
getColorMode: () => "truecolor",
|
|
54
50
|
getThinkingBorderColor: () => IDENTITY,
|
|
55
51
|
getBashModeBorderColor: () => IDENTITY,
|
|
52
|
+
appearance: "dark",
|
|
53
|
+
colors: new Proxy({}, { get: () => DEFAULT_COLOR }) as Theme["colors"],
|
|
54
|
+
style: IDENTITY,
|
|
56
55
|
} satisfies { [K in keyof Theme]: Theme[K] } as unknown as Theme;
|
|
57
56
|
|
|
58
57
|
function severityOf(
|
|
@@ -61,7 +60,7 @@ function severityOf(
|
|
|
61
60
|
return kind === "warning" ? "warn" : (kind ?? "info");
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
/** A
|
|
63
|
+
/** A failing sink answers `undefined` instead of throwing. */
|
|
65
64
|
async function settle<T>(
|
|
66
65
|
ask: () => Promise<T> | undefined
|
|
67
66
|
): Promise<T | undefined> {
|
|
@@ -74,13 +73,10 @@ async function settle<T>(
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* an agent rebuilt under a host keeps whatever its host was last handed.
|
|
76
|
+
* Adapts a sink to pi's `ExtensionUIContext`; terminal-only methods are no-ops,
|
|
77
|
+
* as in pi's RPC mode. `sink` is read per call.
|
|
80
78
|
*
|
|
81
|
-
*
|
|
82
|
-
* `{ ...ui }` (`runner.js:274`), which copies own enumerable keys only. A class
|
|
83
|
-
* instance would arrive with `notify` and the rest of the prototype missing.
|
|
79
|
+
* Must be a plain object: pi copies it with `{ ...ui }`, which drops prototype methods.
|
|
84
80
|
*/
|
|
85
81
|
export function adaptSessionUi(
|
|
86
82
|
sink: () => SessionUi | undefined
|
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
import type { EventLog } from "./EventLog";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** Lines in a session file (`head`) and entries pi holds in memory (`entries`). */
|
|
4
4
|
export type WriteMark = {
|
|
5
5
|
readonly head: number;
|
|
6
6
|
readonly entries: number;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
/** Pi's in-memory entry list: an `AgentSession`'s `sessionManager`, or one held directly. */
|
|
10
9
|
type EntrySource = {
|
|
11
10
|
getEntries(): readonly unknown[];
|
|
12
11
|
};
|
|
13
12
|
|
|
14
|
-
/** Nothing accounted for yet, which `foreignSince` reads as "the file is pi's to write". */
|
|
15
13
|
const UNREAD: WriteMark = { head: 0, entries: 0 };
|
|
16
14
|
|
|
17
|
-
/**
|
|
15
|
+
/** Reads head first so a concurrent append under-counts the file, never over-counts. */
|
|
18
16
|
async function of(log: EventLog, source: EntrySource): Promise<WriteMark> {
|
|
19
17
|
return { head: await log.head(), entries: source.getEntries().length };
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
|
-
* Whether
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* pi names a session file before it writes one, buffering entries until the first
|
|
28
|
-
* assistant reply, so a mark taken before the file exists is answered by the whole of
|
|
29
|
-
* it: header plus every entry pi was holding.
|
|
21
|
+
* Whether another process appended lines since `mark`. Our own writes move both
|
|
22
|
+
* counts together. A mark taken before pi created the file (`head` 0) counts
|
|
23
|
+
* the header plus every buffered entry as ours.
|
|
30
24
|
*/
|
|
31
25
|
function foreignSince(mark: WriteMark, next: WriteMark): boolean {
|
|
32
26
|
const base = mark.head === 0 ? { head: 1, entries: 0 } : mark;
|
|
@@ -34,10 +28,8 @@ function foreignSince(mark: WriteMark, next: WriteMark): boolean {
|
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
/**
|
|
37
|
-
* Whether every line
|
|
38
|
-
*
|
|
39
|
-
* it stays on the session it is already holding. Only ask between turns: a window
|
|
40
|
-
* that also holds our own appends is not benign, and a running turn's is.
|
|
31
|
+
* Whether every line added since `mark` is a `session_info` (a rename), so no
|
|
32
|
+
* replay is needed. Only meaningful between turns.
|
|
41
33
|
*/
|
|
42
34
|
async function benignSince(log: EventLog, mark: WriteMark): Promise<boolean> {
|
|
43
35
|
const added = await log.read(mark.head);
|
|
@@ -47,5 +39,4 @@ async function benignSince(log: EventLog, mark: WriteMark): Promise<boolean> {
|
|
|
47
39
|
);
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
/** Write accounting over one session file: whose lines are these? */
|
|
51
42
|
export const WriteMark = { UNREAD, of, foreignSince, benignSince };
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { Unit } from "./Supervisor";
|
|
2
2
|
|
|
3
|
-
/** The single unit every pim surface runs inside; `--surfaces` picks which of them come up. */
|
|
4
3
|
export const DaemonUnit = {
|
|
5
4
|
mode: "daemon",
|
|
6
5
|
description: "Pim daemon",
|
|
7
6
|
} satisfies Unit;
|
|
8
7
|
|
|
9
|
-
/**
|
|
8
|
+
/** Legacy per-surface units, removed on install. */
|
|
10
9
|
export const SupersededUnits: ReadonlyArray<Unit> = [
|
|
11
10
|
{ mode: "web", description: "Pim web daemon" },
|
|
12
11
|
{ mode: "telegram", description: "Pim Telegram daemon" },
|
|
@@ -28,7 +28,7 @@ export type ToolDiff = {
|
|
|
28
28
|
readonly hunks: readonly ToolDiffHunk[];
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// The EOF newline is a flag, not an empty last line, or diffs gain a phantom blank line.
|
|
32
32
|
export type ToolDiffSide = {
|
|
33
33
|
readonly lines: readonly string[];
|
|
34
34
|
readonly hasTrailingNewline: boolean;
|
|
@@ -48,16 +48,11 @@ function buildToolDiff(
|
|
|
48
48
|
|
|
49
49
|
const lines = build(oldSide.lines, newSide.lines);
|
|
50
50
|
|
|
51
|
-
if (
|
|
51
|
+
if (lines.every((line) => line.kind === "context")) {
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
path,
|
|
59
|
-
hunks: buildHunks(emphasized, contextSize),
|
|
60
|
-
};
|
|
55
|
+
return { path, hunks: buildHunks(attachEmphasis(lines), contextSize) };
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
function fromText(text: string): ToolDiffSide {
|
|
@@ -82,48 +77,35 @@ function attachEmphasis(
|
|
|
82
77
|
let i = 0;
|
|
83
78
|
|
|
84
79
|
while (i < result.length) {
|
|
85
|
-
if (result[i]
|
|
80
|
+
if (result[i]!.kind !== "removed") {
|
|
86
81
|
i += 1;
|
|
87
82
|
continue;
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
let removedEnd = i;
|
|
91
|
-
while (
|
|
92
|
-
removedEnd < result.length &&
|
|
93
|
-
result[removedEnd]?.kind === "removed"
|
|
94
|
-
) {
|
|
86
|
+
while (result[removedEnd]?.kind === "removed") {
|
|
95
87
|
removedEnd += 1;
|
|
96
88
|
}
|
|
97
|
-
|
|
98
89
|
let addedEnd = removedEnd;
|
|
99
|
-
while (
|
|
90
|
+
while (result[addedEnd]?.kind === "added") {
|
|
100
91
|
addedEnd += 1;
|
|
101
92
|
}
|
|
102
93
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const added = result[removedEnd + k];
|
|
110
|
-
|
|
111
|
-
if (removed === undefined || added === undefined) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
94
|
+
// Only pair lines when the removed and added runs are the same length.
|
|
95
|
+
const count = removedEnd - i;
|
|
96
|
+
if (count === addedEnd - removedEnd) {
|
|
97
|
+
for (let k = 0; k < count; k += 1) {
|
|
98
|
+
const removed = result[i + k]!;
|
|
99
|
+
const added = result[removedEnd + k]!;
|
|
115
100
|
const ranges = computeIntraLineRanges(removed.text, added.text);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
101
|
+
if (ranges !== undefined) {
|
|
102
|
+
result[i + k] = { ...removed, emphasis: ranges.removed };
|
|
103
|
+
result[removedEnd + k] = { ...added, emphasis: ranges.added };
|
|
119
104
|
}
|
|
120
|
-
|
|
121
|
-
result[i + k] = { ...removed, emphasis: ranges.removed };
|
|
122
|
-
result[removedEnd + k] = { ...added, emphasis: ranges.added };
|
|
123
105
|
}
|
|
124
106
|
}
|
|
125
107
|
|
|
126
|
-
i = addedEnd
|
|
108
|
+
i = addedEnd;
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
return result;
|
|
@@ -112,8 +112,7 @@ function backgroundsFor(theme: Theme): DiffBackgrounds {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function isLightTheme(theme: Theme): boolean {
|
|
115
|
-
|
|
116
|
-
return name === "light" || name.includes("light");
|
|
115
|
+
return theme.name?.toLowerCase().includes("light") ?? false;
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
function renderHunk(
|
|
@@ -174,10 +173,6 @@ function applyEmphasis(
|
|
|
174
173
|
lineBg: string,
|
|
175
174
|
emphBg: string
|
|
176
175
|
): string {
|
|
177
|
-
if (ranges.length === 0) {
|
|
178
|
-
return text;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
176
|
const starts = new Set<number>();
|
|
182
177
|
const ends = new Set<number>();
|
|
183
178
|
|
|
@@ -192,6 +187,9 @@ function applyEmphasis(
|
|
|
192
187
|
return text;
|
|
193
188
|
}
|
|
194
189
|
|
|
190
|
+
const marker = (pos: number): string =>
|
|
191
|
+
(ends.has(pos) ? lineBg : "") + (starts.has(pos) ? emphBg : "");
|
|
192
|
+
|
|
195
193
|
let result = "";
|
|
196
194
|
let visiblePos = 0;
|
|
197
195
|
let i = 0;
|
|
@@ -209,16 +207,9 @@ function applyEmphasis(
|
|
|
209
207
|
continue;
|
|
210
208
|
}
|
|
211
209
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
if (ends.has(visiblePos)) {
|
|
217
|
-
result += lineBg;
|
|
218
|
-
}
|
|
219
|
-
if (starts.has(visiblePos)) {
|
|
220
|
-
result += emphBg;
|
|
221
|
-
}
|
|
210
|
+
const mark = marker(visiblePos);
|
|
211
|
+
if (mark !== "") {
|
|
212
|
+
result += text.slice(segStart, i) + mark;
|
|
222
213
|
segStart = i;
|
|
223
214
|
}
|
|
224
215
|
|
|
@@ -226,17 +217,7 @@ function applyEmphasis(
|
|
|
226
217
|
i += 1;
|
|
227
218
|
}
|
|
228
219
|
|
|
229
|
-
|
|
230
|
-
result += text.slice(segStart, i);
|
|
231
|
-
}
|
|
232
|
-
if (ends.has(visiblePos)) {
|
|
233
|
-
result += lineBg;
|
|
234
|
-
}
|
|
235
|
-
if (starts.has(visiblePos)) {
|
|
236
|
-
result += emphBg;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return result;
|
|
220
|
+
return result + text.slice(segStart) + marker(visiblePos);
|
|
240
221
|
}
|
|
241
222
|
|
|
242
223
|
function applyBackground(
|
|
@@ -10,11 +10,11 @@ export type DirectoryEntry = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export type DirectoryListing = {
|
|
13
|
-
/**
|
|
13
|
+
/** Absolute, resolved, `~` expanded. */
|
|
14
14
|
readonly path: string;
|
|
15
|
-
/** Absent at the
|
|
15
|
+
/** Absent at the filesystem root. */
|
|
16
16
|
readonly parent?: string;
|
|
17
|
-
/**
|
|
17
|
+
/** Subdirectories only, name-sorted, hidden included. */
|
|
18
18
|
readonly entries: readonly DirectoryEntry[];
|
|
19
19
|
};
|
|
20
20
|
|
|
@@ -78,7 +78,7 @@ function refusal(error: unknown, path: string): string {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/**
|
|
81
|
+
/** Not recursive: a missing parent is an error. */
|
|
82
82
|
async function create(path: string): Promise<void> {
|
|
83
83
|
const name = basename(Paths.expandHome(path));
|
|
84
84
|
if (name === "" || name === "." || name === "..") {
|