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
|
@@ -22,7 +22,7 @@ export class StreamCapture {
|
|
|
22
22
|
return this.totalBytesAccum;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/** The first `n` bytes,
|
|
25
|
+
/** The first `n` bytes, across chunk boundaries. */
|
|
26
26
|
lead(n: number): Uint8Array {
|
|
27
27
|
if (this.fullBytes) {
|
|
28
28
|
return this.fullBytes.subarray(0, n);
|
|
@@ -48,36 +48,29 @@ export class StreamCapture {
|
|
|
48
48
|
return this.fullBytes;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/** Opaque
|
|
51
|
+
/** Opaque streams (images) are neither cut nor decoded. */
|
|
52
52
|
snapshot(opaque = false): CapturedStream {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
const totalBytes = this.totalBytesAccum;
|
|
54
|
+
const untruncated = {
|
|
55
|
+
totalBytes,
|
|
56
|
+
truncated: false,
|
|
57
|
+
path: null,
|
|
58
|
+
nextStart: null,
|
|
59
|
+
};
|
|
60
|
+
if (totalBytes === 0 || opaque) {
|
|
61
|
+
return { text: "", ...untruncated };
|
|
61
62
|
}
|
|
62
63
|
const dec = new TextDecoder();
|
|
63
64
|
const all = this.full();
|
|
64
|
-
if (
|
|
65
|
-
return {
|
|
66
|
-
text: dec.decode(all),
|
|
67
|
-
totalBytes: this.totalBytesAccum,
|
|
68
|
-
truncated: false,
|
|
69
|
-
path: null,
|
|
70
|
-
nextStart: null,
|
|
71
|
-
};
|
|
65
|
+
if (totalBytes <= STREAM_HEAD_BYTES + STREAM_TAIL_BYTES) {
|
|
66
|
+
return { text: dec.decode(all), ...untruncated };
|
|
72
67
|
}
|
|
73
68
|
const headText = dec.decode(all.subarray(0, STREAM_HEAD_BYTES));
|
|
74
|
-
const tailText = dec.decode(
|
|
75
|
-
|
|
76
|
-
);
|
|
77
|
-
const middle = this.totalBytesAccum - STREAM_HEAD_BYTES - STREAM_TAIL_BYTES;
|
|
69
|
+
const tailText = dec.decode(all.subarray(totalBytes - STREAM_TAIL_BYTES));
|
|
70
|
+
const middle = totalBytes - STREAM_HEAD_BYTES - STREAM_TAIL_BYTES;
|
|
78
71
|
return {
|
|
79
72
|
text: `${headText}\n... ${middle} bytes truncated ...\n${tailText}`,
|
|
80
|
-
totalBytes
|
|
73
|
+
totalBytes,
|
|
81
74
|
truncated: true,
|
|
82
75
|
path: null,
|
|
83
76
|
nextStart: Lines.continuationLine(headText),
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
const STREAMS = ["stdout", "stderr"] as const;
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
function stripTrailingNewline(s: string): string {
|
|
14
14
|
return s.endsWith("\n") ? s.slice(0, -1) : s;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -68,7 +68,7 @@ export function formatResult(
|
|
|
68
68
|
return lines.join("\n");
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/** Null when the image is shown in the content array. */
|
|
72
72
|
function unshownImageLine(
|
|
73
73
|
result: BashCommandResult,
|
|
74
74
|
sniffed: ImageMimeType,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { Format } from "../../shared/Format";
|
|
2
2
|
import { Images, type NormalisedImage } from "../../shared/Images";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* The picture stdout printed, once a provider will take it. Bytes that sniffed
|
|
6
|
-
* as an image but cannot be decoded are not an error here: the command ran, and
|
|
7
|
-
* bash reports what it printed.
|
|
8
|
-
*/
|
|
4
|
+
/** Null when the bytes cannot be decoded; that is not a tool error. */
|
|
9
5
|
export async function normaliseStdoutImage(
|
|
10
6
|
bytes: Uint8Array
|
|
11
7
|
): Promise<NormalisedImage | null> {
|
|
@@ -16,12 +12,12 @@ export async function normaliseStdoutImage(
|
|
|
16
12
|
}
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
/** `stdout is a 1200x800 png (240 KB)
|
|
20
|
-
|
|
15
|
+
/** e.g. `stdout is a 1200x800 png (240 KB)`. */
|
|
16
|
+
function imageSubject(image: NormalisedImage): string {
|
|
21
17
|
return `stdout is a ${image.width}x${image.height} ${Images.extensionOf(image.mimeType)} (${Format.bytes(image.bytes)})`;
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
/**
|
|
20
|
+
/** Text sent with the image, or instead of it for a model without vision. */
|
|
25
21
|
export function imageNote(image: NormalisedImage, vision: boolean): string {
|
|
26
22
|
if (!vision) {
|
|
27
23
|
return Images.noVisionNote("bash", imageSubject(image));
|
|
@@ -24,7 +24,7 @@ function installLifecycleHandlers(): void {
|
|
|
24
24
|
}
|
|
25
25
|
lifecycleHandlersInstalled = true;
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// Kill running commands, then re-raise so the default handler still runs.
|
|
28
28
|
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"] as const) {
|
|
29
29
|
process.once(sig, () => {
|
|
30
30
|
try {
|
|
@@ -12,7 +12,7 @@ export function bashView({ args, result }: BashViewInput): ToolView {
|
|
|
12
12
|
return {
|
|
13
13
|
label: "Bash",
|
|
14
14
|
icon: "terminal",
|
|
15
|
-
//
|
|
15
|
+
// Not `text`: that would join a multi-line command onto one line.
|
|
16
16
|
title: [
|
|
17
17
|
{
|
|
18
18
|
kind: "spans",
|
|
@@ -16,10 +16,8 @@ type Reader = ReadableStreamDefaultReader<Uint8Array>;
|
|
|
16
16
|
const activePids = new Set<number>();
|
|
17
17
|
|
|
18
18
|
const COMMAND_VAR = "PIM_BASH_COMMAND";
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// the unset keeps it out of every child's environment too.
|
|
22
|
-
// The raised oom_score_adj makes the command, not the agent or the desktop, the kernel's first victim.
|
|
19
|
+
// The command goes via env, not argv, so `pkill -f <target>` inside it can't match this shell.
|
|
20
|
+
// oom_score_adj makes the command the OOM killer's first victim, not pim.
|
|
23
21
|
const RUNNER = `{ echo 500 > /proc/self/oom_score_adj; } 2>/dev/null; __pim_command=$${COMMAND_VAR}; unset ${COMMAND_VAR}; eval "$__pim_command"`;
|
|
24
22
|
|
|
25
23
|
export function killAllActiveBashGroups(sig: NodeJS.Signals = "SIGTERM"): void {
|
|
@@ -59,11 +57,7 @@ async function spillIfTruncated(
|
|
|
59
57
|
return stream.truncated ? SpillCache.write("bash", ext, cap.full()) : null;
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
/**
|
|
63
|
-
* The picture stdout printed, or null. The verdict is the leading bytes',
|
|
64
|
-
* before anything is decoded; the cap is the whole stream's, so an image too
|
|
65
|
-
* big to send is text again and truncates like any other output.
|
|
66
|
-
*/
|
|
60
|
+
/** Sniffs the leading bytes only. Over the size cap, stdout is treated as text. */
|
|
67
61
|
export function sniffStdoutImage(cap: StreamCapture): ImageMimeType | null {
|
|
68
62
|
if (
|
|
69
63
|
cap.totalBytes < Images.SNIFF_BYTES ||
|
|
@@ -104,7 +98,7 @@ export async function runBashCommand(
|
|
|
104
98
|
const stderrCap = new StreamCapture();
|
|
105
99
|
const scope = await MemoryCap.scope();
|
|
106
100
|
|
|
107
|
-
// setsid
|
|
101
|
+
// setsid: own process group (pgid == pid), so the whole tree can be signalled.
|
|
108
102
|
const proc = Bun.spawn({
|
|
109
103
|
cmd: [...(scope?.argv ?? []), "setsid", "bash", "-lc", RUNNER],
|
|
110
104
|
cwd,
|
|
@@ -119,7 +113,7 @@ export async function runBashCommand(
|
|
|
119
113
|
let timedOut = false;
|
|
120
114
|
let aborted = false;
|
|
121
115
|
|
|
122
|
-
//
|
|
116
|
+
// Keep the readers: cancelling a locked stream directly throws.
|
|
123
117
|
const stdoutReader = getReader(
|
|
124
118
|
proc.stdout as unknown as ReadableStream<Uint8Array>
|
|
125
119
|
);
|
|
@@ -127,7 +121,7 @@ export async function runBashCommand(
|
|
|
127
121
|
proc.stderr as unknown as ReadableStream<Uint8Array>
|
|
128
122
|
);
|
|
129
123
|
|
|
130
|
-
//
|
|
124
|
+
// Not awaited: a backgrounded child can hold the pipes open after bash exits.
|
|
131
125
|
const stdoutDrain = drain(stdoutReader, stdoutCap);
|
|
132
126
|
const stderrDrain = drain(stderrReader, stderrCap);
|
|
133
127
|
|
|
@@ -181,7 +175,7 @@ export async function runBashCommand(
|
|
|
181
175
|
exitCode = proc.exitCode ?? null;
|
|
182
176
|
signalCode = (proc.signalCode as NodeJS.Signals | null | undefined) ?? null;
|
|
183
177
|
|
|
184
|
-
//
|
|
178
|
+
// Bounded: a detached grandchild may hold the pipe open forever.
|
|
185
179
|
await Promise.race([
|
|
186
180
|
Promise.all([stdoutDrain, stderrDrain]),
|
|
187
181
|
Bun.sleep(DRAIN_GRACE_MS),
|
|
@@ -206,14 +200,14 @@ export async function runBashCommand(
|
|
|
206
200
|
}
|
|
207
201
|
}
|
|
208
202
|
|
|
209
|
-
// A
|
|
203
|
+
// A failed command's image is never decoded.
|
|
210
204
|
const sniffed = sniffStdoutImage(stdoutCap);
|
|
211
205
|
const shows =
|
|
212
206
|
sniffed !== null && !isErrorResult({ exitCode, timedOut, aborted });
|
|
213
207
|
const stdout = stdoutCap.snapshot(sniffed !== null);
|
|
214
208
|
const stderr = stderrCap.snapshot();
|
|
215
209
|
|
|
216
|
-
//
|
|
210
|
+
// OOMPolicy=kill + KillSignal=SIGKILL: an OOM ends in SIGKILL whoever wins; systemd's Result is racy.
|
|
217
211
|
const memoryLimitHit =
|
|
218
212
|
scope !== null && signalCode === "SIGKILL" && !timedOut && !aborted
|
|
219
213
|
? scope.limitBytes
|
|
@@ -37,18 +37,18 @@ export type BashCommandResult = {
|
|
|
37
37
|
readonly signal: NodeJS.Signals | null;
|
|
38
38
|
readonly stdout: CapturedStream;
|
|
39
39
|
readonly stderr: CapturedStream;
|
|
40
|
-
/**
|
|
40
|
+
/** Set even when the image could not be decoded. */
|
|
41
41
|
readonly stdoutSniffed: ImageMimeType | null;
|
|
42
|
-
/** Set only when
|
|
42
|
+
/** Set only when the image decoded and will be shown. */
|
|
43
43
|
readonly stdoutImage: NormalisedImage | null;
|
|
44
44
|
readonly timedOut: boolean;
|
|
45
45
|
readonly aborted: boolean;
|
|
46
|
-
/** The limit in bytes
|
|
46
|
+
/** The limit in bytes; set when the command was SIGKILLed by something other than pim. */
|
|
47
47
|
readonly memoryLimitHit: number | null;
|
|
48
48
|
readonly durationMs: number;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
type BashStreamDetails = {
|
|
52
52
|
readonly totalBytes: number;
|
|
53
53
|
readonly truncated: boolean;
|
|
54
54
|
readonly path: string | null;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Stats } from "node:fs";
|
|
2
1
|
import { realpath, stat } from "node:fs/promises";
|
|
3
2
|
import {
|
|
4
3
|
EditMatcher,
|
|
@@ -11,12 +10,12 @@ import { FsErrors } from "../../shared/FsErrors";
|
|
|
11
10
|
import { Lines } from "../../shared/Lines";
|
|
12
11
|
import type { RawEdit } from "./schema";
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
type NoopEdit = {
|
|
15
14
|
readonly index: number;
|
|
16
15
|
readonly range: string;
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
type ResolvedEditMetadata = {
|
|
20
19
|
readonly index: number;
|
|
21
20
|
readonly ranges: readonly string[];
|
|
22
21
|
readonly strategy: EditMatchStrategy;
|
|
@@ -162,10 +161,10 @@ async function performEdit(
|
|
|
162
161
|
|
|
163
162
|
const nextContent = EditMatcher.applyAll(originalContent, effectiveMutations);
|
|
164
163
|
|
|
165
|
-
await
|
|
164
|
+
await Fs.writeKeepingLinks(
|
|
166
165
|
canonicalPath,
|
|
167
166
|
hadBom ? `${Lines.utf8Bom}${nextContent}` : nextContent,
|
|
168
|
-
metadata
|
|
167
|
+
{ mode: Number(metadata.mode), nlink: metadata.nlink }
|
|
169
168
|
);
|
|
170
169
|
|
|
171
170
|
const original = Lines.splitWithTrailingNewline(originalContent);
|
|
@@ -256,7 +255,11 @@ function assertNoDuplicateEdits(edits: readonly ParsedEdit[]): void {
|
|
|
256
255
|
const seen = new Map<string, number>();
|
|
257
256
|
|
|
258
257
|
for (const [index, edit] of edits.entries()) {
|
|
259
|
-
const key =
|
|
258
|
+
const key = JSON.stringify([
|
|
259
|
+
edit.oldString,
|
|
260
|
+
edit.newString,
|
|
261
|
+
edit.replaceAll,
|
|
262
|
+
]);
|
|
260
263
|
const previous = seen.get(key);
|
|
261
264
|
|
|
262
265
|
if (previous !== undefined) {
|
|
@@ -269,22 +272,11 @@ function assertNoDuplicateEdits(edits: readonly ParsedEdit[]): void {
|
|
|
269
272
|
}
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
function duplicateKey(edit: ParsedEdit): string {
|
|
273
|
-
return JSON.stringify({
|
|
274
|
-
oldString: edit.oldString,
|
|
275
|
-
newString: edit.newString,
|
|
276
|
-
replaceAll: edit.replaceAll,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
275
|
function sortMutations(mutations: readonly Mutation[]): readonly Mutation[] {
|
|
281
|
-
return [...mutations].sort(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return left.range[1] - right.range[1];
|
|
287
|
-
});
|
|
276
|
+
return [...mutations].sort(
|
|
277
|
+
(left, right) =>
|
|
278
|
+
left.range[0] - right.range[0] || left.range[1] - right.range[1]
|
|
279
|
+
);
|
|
288
280
|
}
|
|
289
281
|
|
|
290
282
|
function assertNoOverlaps(sorted: readonly Mutation[]): void {
|
|
@@ -320,17 +312,6 @@ function renderAllNoopError(noops: readonly NoopEdit[]): string {
|
|
|
320
312
|
].join("\n");
|
|
321
313
|
}
|
|
322
314
|
|
|
323
|
-
async function writeFileAtomic(
|
|
324
|
-
canonicalPath: string,
|
|
325
|
-
content: string,
|
|
326
|
-
metadata: Stats
|
|
327
|
-
): Promise<void> {
|
|
328
|
-
await Fs.writeKeepingLinks(canonicalPath, content, {
|
|
329
|
-
mode: Number(metadata.mode),
|
|
330
|
-
nlink: metadata.nlink,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
315
|
async function enqueue<T>(key: string, task: () => Promise<T>): Promise<T> {
|
|
335
316
|
const previous = editQueues.get(key) ?? Promise.resolve();
|
|
336
317
|
const queued = previous.then(task, task);
|
|
@@ -359,7 +340,7 @@ function joinHuman(items: readonly string[]): string {
|
|
|
359
340
|
}
|
|
360
341
|
|
|
361
342
|
if (items.length === 1) {
|
|
362
|
-
return items[0]
|
|
343
|
+
return items[0]!;
|
|
363
344
|
}
|
|
364
345
|
|
|
365
346
|
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
@@ -6,12 +6,10 @@ export type GlobMatch = {
|
|
|
6
6
|
readonly mtime: number;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export type GlobScanOptions = FileScanOptions;
|
|
10
|
-
|
|
11
9
|
export async function findFiles(
|
|
12
10
|
root: string,
|
|
13
11
|
pattern: string,
|
|
14
|
-
options:
|
|
12
|
+
options: FileScanOptions
|
|
15
13
|
): Promise<readonly GlobMatch[]> {
|
|
16
14
|
const metadata = await FsErrors.statOrThrow(root);
|
|
17
15
|
|
|
@@ -39,15 +39,15 @@ export default function (pi: ExtensionAPI): void {
|
|
|
39
39
|
throw new Error("Glob aborted before execution.");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
const resolvedPathFormat = pathFormat ?? DEFAULT_PATH_FORMAT;
|
|
44
|
-
const absolutePath = Paths.resolve(path ?? ".", ctx.cwd);
|
|
45
|
-
const matches = await findFiles(absolutePath, pattern, {
|
|
42
|
+
const scan = {
|
|
46
43
|
exclude,
|
|
47
44
|
includeDotfiles: includeDotfiles ?? false,
|
|
48
45
|
includeIgnored: includeIgnored ?? false,
|
|
49
|
-
}
|
|
50
|
-
const
|
|
46
|
+
};
|
|
47
|
+
const resolvedPathFormat = pathFormat ?? DEFAULT_PATH_FORMAT;
|
|
48
|
+
const absolutePath = Paths.resolve(path ?? ".", ctx.cwd);
|
|
49
|
+
const matches = await findFiles(absolutePath, pattern, scan);
|
|
50
|
+
const outcome = renderFiles(matches, headLimit ?? GLOB_HEAD_LIMIT_MAX, {
|
|
51
51
|
cwd: ctx.cwd,
|
|
52
52
|
pathFormat: resolvedPathFormat,
|
|
53
53
|
});
|
|
@@ -67,9 +67,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
67
67
|
details: {
|
|
68
68
|
absolutePath,
|
|
69
69
|
pattern,
|
|
70
|
-
|
|
71
|
-
includeDotfiles: includeDotfiles ?? false,
|
|
72
|
-
includeIgnored: includeIgnored ?? false,
|
|
70
|
+
...scan,
|
|
73
71
|
pathFormat: resolvedPathFormat,
|
|
74
72
|
fileCount: matches.length,
|
|
75
73
|
totalItems: outcome.totalItems,
|
|
@@ -11,9 +11,7 @@ import type {
|
|
|
11
11
|
globSchema,
|
|
12
12
|
} from "./schema";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type RenderOptions = {
|
|
14
|
+
type RenderOptions = {
|
|
17
15
|
readonly cwd: string;
|
|
18
16
|
readonly pathFormat: GlobPathFormat;
|
|
19
17
|
};
|
|
@@ -22,7 +20,7 @@ export function renderFiles(
|
|
|
22
20
|
matches: readonly GlobMatch[],
|
|
23
21
|
headLimit: number,
|
|
24
22
|
options: RenderOptions
|
|
25
|
-
):
|
|
23
|
+
): SearchList {
|
|
26
24
|
return SearchRender.capList(
|
|
27
25
|
matches.map((match) => SearchRender.formatPath(match.path, options)),
|
|
28
26
|
headLimit
|
|
@@ -3,7 +3,7 @@ import { type Static, Type } from "typebox";
|
|
|
3
3
|
|
|
4
4
|
export const GLOB_HEAD_LIMIT_MAX = 1000;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const GLOB_PATH_FORMATS = ["relative", "absolute"] as const;
|
|
7
7
|
|
|
8
8
|
export const globSchema = Type.Object({
|
|
9
9
|
pattern: Type.String({
|
|
@@ -6,7 +6,7 @@ import { Pool } from "../../shared/Pool";
|
|
|
6
6
|
|
|
7
7
|
const MATCH_CONCURRENCY = 16;
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type GrepLine = {
|
|
10
10
|
readonly lineNumber: number;
|
|
11
11
|
readonly text: string;
|
|
12
12
|
};
|
|
@@ -24,18 +24,18 @@ export type GrepMatch = {
|
|
|
24
24
|
readonly fileLines: readonly string[];
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
type GrepMatcher = {
|
|
28
28
|
readonly regex: RegExp;
|
|
29
29
|
readonly matchAcrossLines: boolean;
|
|
30
|
-
/**
|
|
30
|
+
/** Byte needle for the literal fast path. */
|
|
31
31
|
readonly literal: Buffer | undefined;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
type GrepScanOptions = FileScanOptions & {
|
|
35
35
|
readonly retainFileLines?: boolean;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
// ASCII
|
|
38
|
+
// ASCII chars that mean the same as regex source and as raw bytes.
|
|
39
39
|
const PURE_LITERAL = /^[A-Za-z0-9_ \-/]+$/;
|
|
40
40
|
|
|
41
41
|
function literalNeedle(
|
|
@@ -95,7 +95,7 @@ export async function findMatches(
|
|
|
95
95
|
return scanned.filter((match) => match !== undefined);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
//
|
|
98
|
+
// Unreadable entries are non-matches, not errors.
|
|
99
99
|
async function matchFile(
|
|
100
100
|
filePath: string,
|
|
101
101
|
matcher: GrepMatcher,
|
|
@@ -110,7 +110,7 @@ async function matchFile(
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
if (matcher.literal !== undefined) {
|
|
113
|
-
//
|
|
113
|
+
// Skip decoding files that can't contain the needle.
|
|
114
114
|
const bytes = Buffer.from(await file.arrayBuffer());
|
|
115
115
|
if (bytes.indexOf(matcher.literal) < 0) {
|
|
116
116
|
return undefined;
|
|
@@ -10,13 +10,13 @@ import type { ToolView, ViewBlock } from "../../view/ViewBlock";
|
|
|
10
10
|
import type { GrepLineRange, GrepMatch } from "./grep";
|
|
11
11
|
import type { GrepInput, GrepOutputMode, GrepPathFormat } from "./schema";
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
type RenderOutcome = SearchList & {
|
|
14
14
|
readonly fileCount: number;
|
|
15
15
|
readonly totalMatches: number;
|
|
16
16
|
readonly itemNoun: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
type RenderOptions = {
|
|
20
20
|
readonly cwd: string;
|
|
21
21
|
readonly pathFormat: GrepPathFormat;
|
|
22
22
|
readonly context: number;
|
|
@@ -60,7 +60,7 @@ export function renderMatches(
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
type TitleOptions = {
|
|
64
64
|
readonly pattern: string | undefined;
|
|
65
65
|
readonly path: string | undefined;
|
|
66
66
|
readonly glob: string | undefined;
|
|
@@ -81,10 +81,9 @@ export type GrepViewDetails = {
|
|
|
81
81
|
readonly fileCount?: number;
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
/** Partially streamed while the call is in flight; every field is optional. */
|
|
84
|
+
type GrepViewInput = {
|
|
86
85
|
readonly args: Partial<GrepInput>;
|
|
87
|
-
/**
|
|
86
|
+
/** Empty while in flight. */
|
|
88
87
|
readonly body: string;
|
|
89
88
|
readonly details?: GrepViewDetails;
|
|
90
89
|
readonly cwd: string;
|
|
@@ -2,15 +2,11 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
3
|
|
|
4
4
|
export const GREP_HEAD_LIMIT_MAX = 1000;
|
|
5
|
-
|
|
5
|
+
const GREP_CONTEXT_MAX = 20;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
"files_with_matches",
|
|
9
|
-
"content",
|
|
10
|
-
"count",
|
|
11
|
-
] as const;
|
|
7
|
+
const GREP_OUTPUT_MODES = ["files_with_matches", "content", "count"] as const;
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
const GREP_PATH_FORMATS = ["relative", "absolute"] as const;
|
|
14
10
|
|
|
15
11
|
export const grepSchema = Type.Object({
|
|
16
12
|
pattern: Type.String({
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import { PimSettings } from "../../shared/PimSettings";
|
|
2
2
|
import type { ReadImageDetails } from "./schema";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export type ImageStamp = {
|
|
4
|
+
type ImageStamp = {
|
|
6
5
|
readonly mtimeMs: number;
|
|
7
6
|
readonly size: number;
|
|
8
7
|
};
|
|
9
8
|
|
|
10
9
|
type Sent = ImageStamp & { readonly details: ReadImageDetails };
|
|
11
10
|
|
|
12
|
-
/**
|
|
13
|
-
* The pictures already in this conversation, so a screenshot loop pays the
|
|
14
|
-
* resize once. One instance per extension factory, which is one per session;
|
|
15
|
-
* a resumed session starts empty and re-sends every image once.
|
|
16
|
-
*/
|
|
11
|
+
/** Images already sent this session, keyed by path. A resumed session starts empty. */
|
|
17
12
|
export class ImageMemory {
|
|
18
13
|
private readonly sent = new Map<string, Sent>();
|
|
19
14
|
|
|
20
|
-
/** The settings flag is the escape hatch, so it is read only once a picture repeats. */
|
|
21
15
|
async recall(
|
|
22
16
|
path: string,
|
|
23
17
|
stamp: ImageStamp
|
|
@@ -11,18 +11,18 @@ export type ImageReadOutcome = {
|
|
|
11
11
|
readonly details: ReadImageDetails;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/** Already sent earlier; the model only gets a note. */
|
|
15
15
|
export type UnchangedImageReadOutcome = {
|
|
16
16
|
readonly kind: "image-unchanged";
|
|
17
17
|
readonly details: ReadImageDetails;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/** True for image bytes or an image filename; a bad image then errors rather than reading as text. */
|
|
21
21
|
export function isImageRead(head: Uint8Array, path: string): boolean {
|
|
22
22
|
return Images.sniff(head) !== null || Images.looksLikeImageName(path);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
25
|
+
/** Checks to run before loading the file into memory. */
|
|
26
26
|
export function assertImageReadable(
|
|
27
27
|
path: string,
|
|
28
28
|
sizeOnDisk: number,
|
|
@@ -68,20 +68,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
details: {
|
|
74
|
-
kind: "text",
|
|
75
|
-
absolutePath,
|
|
76
|
-
totalLines: outcome.totalLines,
|
|
77
|
-
visibleStart: outcome.visibleStart,
|
|
78
|
-
visibleEnd: outcome.visibleEnd,
|
|
79
|
-
truncatedByByteCap: outcome.truncatedByByteCap,
|
|
80
|
-
truncatedByEnd: outcome.truncatedByEnd,
|
|
81
|
-
hadBom: outcome.hadBom,
|
|
82
|
-
nextStart: outcome.nextStart,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
71
|
+
const { body: _body, ...text } = outcome;
|
|
72
|
+
return { content, details: { ...text, absolutePath } };
|
|
85
73
|
},
|
|
86
74
|
toViewModel: readView,
|
|
87
75
|
});
|
|
@@ -30,9 +30,8 @@ export type ReadOutcome =
|
|
|
30
30
|
| ImageReadOutcome
|
|
31
31
|
| UnchangedImageReadOutcome;
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
type ReadOptions = {
|
|
34
34
|
readonly model?: VisionModel;
|
|
35
|
-
/** The pictures this session has already sent; absent when the caller keeps none. */
|
|
36
35
|
readonly memory?: ImageMemory;
|
|
37
36
|
};
|
|
38
37
|
|
|
@@ -75,7 +74,7 @@ export async function readFile(
|
|
|
75
74
|
|
|
76
75
|
const file = Bun.file(path);
|
|
77
76
|
|
|
78
|
-
//
|
|
77
|
+
// Sniffs both the image signature and binary-ness.
|
|
79
78
|
const head = await bytesOf(file.slice(0, Lines.binarySniffBytes), path);
|
|
80
79
|
|
|
81
80
|
if (isImageRead(head, path)) {
|
|
@@ -113,7 +112,7 @@ async function bytesOf(file: Bun.BunFile, path: string): Promise<Uint8Array> {
|
|
|
113
112
|
try {
|
|
114
113
|
return await file.bytes();
|
|
115
114
|
} catch (error) {
|
|
116
|
-
rethrowFsError(error, path
|
|
115
|
+
rethrowFsError(error, path);
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
|
|
@@ -178,7 +177,7 @@ function renderText(
|
|
|
178
177
|
};
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
function rethrowFsError(error: unknown, path: string
|
|
180
|
+
function rethrowFsError(error: unknown, path: string): never {
|
|
182
181
|
const code = FsErrors.code(error);
|
|
183
182
|
|
|
184
183
|
if (code === "EACCES" || code === "EPERM") {
|
|
@@ -186,6 +185,6 @@ function rethrowFsError(error: unknown, path: string, action: string): never {
|
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
throw new Error(
|
|
189
|
-
`Cannot
|
|
188
|
+
`Cannot read ${path}: ${code ?? (error instanceof Error ? error.message : "unknown error")}.`
|
|
190
189
|
);
|
|
191
190
|
}
|
|
@@ -44,7 +44,7 @@ function titleBlock(
|
|
|
44
44
|
return { kind: "file", path, range: [input.start ?? 1, input.end] };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/** Legacy
|
|
47
|
+
/** Legacy details have no `kind`, so check the fields. */
|
|
48
48
|
function visibleRange(
|
|
49
49
|
details: ReadDetails | undefined
|
|
50
50
|
): readonly [number, number] | undefined {
|