pim-agent 0.0.1 → 0.10.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/LICENSE +21 -0
- package/README.md +293 -7
- package/bin/pim.ts +151 -0
- package/package.json +82 -4
- package/packages/core/src/attachments/AttachmentStore.ts +99 -0
- package/packages/core/src/attachments/Attachments.ts +69 -0
- package/packages/core/src/extensions/CoreExtensions.ts +54 -0
- package/packages/core/src/extensions/apply-patch/coordinator.ts +53 -0
- package/packages/core/src/extensions/apply-patch/executor.ts +550 -0
- package/packages/core/src/extensions/apply-patch/index.ts +69 -0
- package/packages/core/src/extensions/apply-patch/matcher.ts +47 -0
- package/packages/core/src/extensions/apply-patch/model.ts +39 -0
- package/packages/core/src/extensions/apply-patch/parser.ts +379 -0
- package/packages/core/src/extensions/apply-patch/render.ts +177 -0
- package/packages/core/src/extensions/apply-patch/schema.ts +37 -0
- package/packages/core/src/extensions/apply-patch/types.ts +30 -0
- package/packages/core/src/extensions/bash/capture.ts +86 -0
- package/packages/core/src/extensions/bash/format.ts +110 -0
- package/packages/core/src/extensions/bash/image.ts +31 -0
- package/packages/core/src/extensions/bash/index.ts +88 -0
- package/packages/core/src/extensions/bash/render.ts +31 -0
- package/packages/core/src/extensions/bash/run.ts +221 -0
- package/packages/core/src/extensions/bash/schema.ts +64 -0
- package/packages/core/src/extensions/edit/edit.ts +366 -0
- package/packages/core/src/extensions/edit/index.ts +36 -0
- package/packages/core/src/extensions/edit/render.ts +16 -0
- package/packages/core/src/extensions/edit/schema.ts +37 -0
- package/packages/core/src/extensions/glob/glob.ts +34 -0
- package/packages/core/src/extensions/glob/index.ts +83 -0
- package/packages/core/src/extensions/glob/render.ts +72 -0
- package/packages/core/src/extensions/glob/schema.ts +66 -0
- package/packages/core/src/extensions/grep/grep.ts +256 -0
- package/packages/core/src/extensions/grep/index.ts +114 -0
- package/packages/core/src/extensions/grep/render.ts +271 -0
- package/packages/core/src/extensions/grep/schema.ts +92 -0
- package/packages/core/src/extensions/read/ImageMemory.ts +42 -0
- package/packages/core/src/extensions/read/image.ts +57 -0
- package/packages/core/src/extensions/read/index.ts +88 -0
- package/packages/core/src/extensions/read/read.ts +191 -0
- package/packages/core/src/extensions/read/render.ts +58 -0
- package/packages/core/src/extensions/read/schema.ts +49 -0
- package/packages/core/src/extensions/subagent/index.ts +33 -0
- package/packages/core/src/extensions/subagent/render.ts +127 -0
- package/packages/core/src/extensions/subagent/schema.ts +9 -0
- package/packages/core/src/extensions/subagent/subagent.ts +419 -0
- package/packages/core/src/extensions/system-prompt/index.ts +36 -0
- package/packages/core/src/extensions/system-prompt/prompt.ts +232 -0
- package/packages/core/src/extensions/todo/index.ts +123 -0
- package/packages/core/src/extensions/todo/render.ts +156 -0
- package/packages/core/src/extensions/todo/schema.ts +24 -0
- package/packages/core/src/extensions/todo/todo.ts +185 -0
- package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +187 -0
- package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +183 -0
- package/packages/core/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +512 -0
- package/packages/core/src/extensions/web-fetch/fetch.ts +265 -0
- package/packages/core/src/extensions/web-fetch/image.ts +165 -0
- package/packages/core/src/extensions/web-fetch/index.ts +83 -0
- package/packages/core/src/extensions/web-fetch/render.ts +100 -0
- package/packages/core/src/extensions/web-fetch/schema.ts +46 -0
- package/packages/core/src/extensions/web-search/ExaMcpClient.ts +251 -0
- package/packages/core/src/extensions/web-search/SearchBreaker.ts +147 -0
- package/packages/core/src/extensions/web-search/SearchChain.ts +77 -0
- package/packages/core/src/extensions/web-search/index.ts +90 -0
- package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +195 -0
- package/packages/core/src/extensions/web-search/providers/ExaProvider.ts +54 -0
- package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +144 -0
- package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +118 -0
- package/packages/core/src/extensions/web-search/render.ts +49 -0
- package/packages/core/src/extensions/web-search/schema.ts +29 -0
- package/packages/core/src/extensions/web-search/search.ts +23 -0
- package/packages/core/src/extensions/write/index.ts +58 -0
- package/packages/core/src/extensions/write/render.ts +16 -0
- package/packages/core/src/extensions/write/schema.ts +14 -0
- package/packages/core/src/extensions/write/write.ts +87 -0
- package/packages/core/src/picker/FilePickerSuggestionEngine.ts +14 -0
- package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +52 -0
- package/packages/core/src/picker/PickerItem.ts +6 -0
- package/packages/core/src/picker/PickerService.ts +132 -0
- package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +56 -0
- package/packages/core/src/picker/WorkerFilePickerSuggestionEngine.ts +268 -0
- package/packages/core/src/picker/catalog.ts +220 -0
- package/packages/core/src/picker/commandRanker.ts +17 -0
- package/packages/core/src/picker/filePickerWorker.ts +66 -0
- package/packages/core/src/picker/filePickerWorkerMessages.ts +39 -0
- package/packages/core/src/picker/ranker.ts +245 -0
- package/packages/core/src/picker/token.ts +77 -0
- package/packages/core/src/session/AgentRuntime.ts +85 -0
- package/packages/core/src/session/EventLog.ts +156 -0
- package/packages/core/src/session/MessageText.ts +21 -0
- package/packages/core/src/session/ReadCursors.ts +94 -0
- package/packages/core/src/session/SearchIndex.ts +752 -0
- package/packages/core/src/session/SearchTokens.ts +97 -0
- package/packages/core/src/session/SessionCache.ts +68 -0
- package/packages/core/src/session/SessionDigest.ts +161 -0
- package/packages/core/src/session/SessionHost.ts +892 -0
- package/packages/core/src/session/SessionLease.ts +279 -0
- package/packages/core/src/session/SessionMeta.ts +267 -0
- package/packages/core/src/session/SessionName.ts +18 -0
- package/packages/core/src/session/SessionRegistry.ts +244 -0
- package/packages/core/src/session/WriteMark.ts +51 -0
- package/packages/core/src/shared/Cli.ts +25 -0
- package/packages/core/src/shared/DaemonUnit.ts +13 -0
- package/packages/core/src/shared/DiffLines.ts +304 -0
- package/packages/core/src/shared/DiffPatch.ts +70 -0
- package/packages/core/src/shared/DiffRenderer.ts +283 -0
- package/packages/core/src/shared/DiffView.ts +29 -0
- package/packages/core/src/shared/Directories.ts +61 -0
- package/packages/core/src/shared/EditMatcher.ts +767 -0
- package/packages/core/src/shared/Errors.ts +9 -0
- package/packages/core/src/shared/ExtensionToggles.ts +130 -0
- package/packages/core/src/shared/FileEnumerator.ts +361 -0
- package/packages/core/src/shared/FileScanner.ts +57 -0
- package/packages/core/src/shared/FileWatch.ts +121 -0
- package/packages/core/src/shared/Format.ts +74 -0
- package/packages/core/src/shared/Fs.ts +91 -0
- package/packages/core/src/shared/FsErrors.ts +67 -0
- package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
- package/packages/core/src/shared/Git.ts +530 -0
- package/packages/core/src/shared/GitMonitor.ts +221 -0
- package/packages/core/src/shared/GlobExclusions.ts +13 -0
- package/packages/core/src/shared/Http.ts +14 -0
- package/packages/core/src/shared/ImageMime.ts +36 -0
- package/packages/core/src/shared/Images.ts +357 -0
- package/packages/core/src/shared/Json.ts +19 -0
- package/packages/core/src/shared/Languages.ts +95 -0
- package/packages/core/src/shared/Levenshtein.ts +83 -0
- package/packages/core/src/shared/Lines.ts +79 -0
- package/packages/core/src/shared/McpClient.ts +444 -0
- package/packages/core/src/shared/OutputBudget.ts +84 -0
- package/packages/core/src/shared/PatchSummary.ts +40 -0
- package/packages/core/src/shared/Paths.ts +80 -0
- package/packages/core/src/shared/PimSettings.ts +128 -0
- package/packages/core/src/shared/PimVersion.ts +93 -0
- package/packages/core/src/shared/Pool.ts +26 -0
- package/packages/core/src/shared/Proc.ts +50 -0
- package/packages/core/src/shared/RateLimiter.ts +50 -0
- package/packages/core/src/shared/Renderer.ts +371 -0
- package/packages/core/src/shared/RepoDiff.ts +741 -0
- package/packages/core/src/shared/SafePath.ts +17 -0
- package/packages/core/src/shared/SearchRender.ts +82 -0
- package/packages/core/src/shared/SendFile.ts +23 -0
- package/packages/core/src/shared/SpillCache.ts +78 -0
- package/packages/core/src/shared/SubagentLogs.ts +102 -0
- package/packages/core/src/shared/Supervisor.ts +460 -0
- package/packages/core/src/shared/Surface.ts +2 -0
- package/packages/core/src/shared/Sweeper.ts +32 -0
- package/packages/core/src/shared/Tools.ts +801 -0
- package/packages/core/src/shared/Updater.ts +209 -0
- package/packages/core/src/view/AnsiPainter.ts +227 -0
- package/packages/core/src/view/BodyRenderer.ts +102 -0
- package/packages/core/src/view/DiffBlocks.ts +52 -0
- package/packages/core/src/view/DiffExpand.ts +214 -0
- package/packages/core/src/view/DiffLayout.ts +69 -0
- package/packages/core/src/view/DiffPairs.ts +47 -0
- package/packages/core/src/view/MarkdownPainter.ts +314 -0
- package/packages/core/src/view/MovePath.ts +70 -0
- package/packages/core/src/view/Painting.ts +182 -0
- package/packages/core/src/view/ViewBlock.ts +124 -0
- package/packages/daemon/src/Daemon.ts +63 -0
- package/packages/daemon/src/DaemonInstall.ts +115 -0
- package/packages/daemon/src/Surfaces.ts +66 -0
- package/packages/daemon/src/TelegramSurface.ts +30 -0
- package/packages/daemon/src/WebSurface.ts +56 -0
- package/packages/daemon/src/index.ts +37 -0
- package/packages/protocol/src/Command.ts +219 -0
- package/packages/protocol/src/Diff.ts +15 -0
- package/packages/protocol/src/ImageRoute.ts +23 -0
- package/packages/protocol/src/ServerEvent.ts +406 -0
- package/packages/server/src/AttachmentEndpoint.ts +184 -0
- package/packages/server/src/ClientConnection.ts +277 -0
- package/packages/server/src/ImageEndpoint.ts +36 -0
- package/packages/server/src/ProbeClient.ts +374 -0
- package/packages/server/src/Reloader.ts +93 -0
- package/packages/server/src/SendFileTool.ts +88 -0
- package/packages/server/src/SessionCatalogue.ts +456 -0
- package/packages/server/src/SessionProjection.ts +194 -0
- package/packages/server/src/SessionStream.ts +627 -0
- package/packages/server/src/StaticClient.ts +178 -0
- package/packages/server/src/WebOptions.ts +72 -0
- package/packages/server/src/WsGateway.ts +798 -0
- package/packages/server/src/probe.ts +200 -0
- package/packages/telegram/src/Bot.ts +229 -0
- package/packages/telegram/src/Commands.ts +768 -0
- package/packages/telegram/src/Config.ts +148 -0
- package/packages/telegram/src/Markdown.ts +235 -0
- package/packages/telegram/src/Message.ts +152 -0
- package/packages/telegram/src/Renderer.ts +456 -0
- package/packages/telegram/src/SendFileSchema.ts +18 -0
- package/packages/telegram/src/SendFileTool.ts +65 -0
- package/packages/telegram/src/Session.ts +253 -0
- package/packages/telegram/src/SessionRegistry.ts +126 -0
- package/packages/telegram/src/TaskScheduler.ts +296 -0
- package/packages/telegram/src/TaskSchema.ts +88 -0
- package/packages/telegram/src/TaskStore.ts +67 -0
- package/packages/telegram/src/TaskTool.ts +208 -0
- package/packages/telegram/src/TelegramHtml.ts +288 -0
- package/packages/telegram/src/TypingIndicator.ts +43 -0
- package/packages/telegram/src/UpdateConfirm.ts +61 -0
- package/packages/tui/src/extensions/_init/index.ts +144 -0
- package/packages/tui/src/extensions/command-picker/index.ts +75 -0
- package/packages/tui/src/extensions/file-picker/index.ts +156 -0
- package/packages/tui/src/extensions/footer/index.ts +93 -0
- package/packages/tui/src/extensions/footer/powerline.ts +87 -0
- package/packages/tui/src/extensions/footer/segments.ts +202 -0
- package/packages/tui/src/extensions/pim/index.ts +89 -0
- package/packages/tui/src/extensions/session-lease/index.ts +253 -0
- package/packages/tui/src/extensions/tps/index.ts +118 -0
- package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
- package/packages/tui/src/extensions/wrapProvider.ts +18 -0
- package/packages/tui/src/themes/pim-dark.json +84 -0
- package/packages/tui/src/themes/pim-light.json +84 -0
- package/packages/tui/src/themes/themeCliArgs.ts +20 -0
- package/packages/web/dist/client/apple-touch-icon.png +0 -0
- package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
- package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
- package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
- package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-normal-B-iV2FbL.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-normal-kM0OTZCv.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
- package/packages/web/dist/client/assets/core-CA0aSzPu.js +3 -0
- package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
- package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
- package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
- package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
- package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
- package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
- package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
- package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
- package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
- package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
- package/packages/web/dist/client/assets/index-B9be-i9d.css +1 -0
- package/packages/web/dist/client/assets/index-CpinM5NK.js +45 -0
- package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
- package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
- package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
- package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
- package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
- package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
- package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
- package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
- package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
- package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
- package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
- package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
- package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
- package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
- package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
- package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
- package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
- package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
- package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
- package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
- package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
- package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
- package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
- package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
- package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
- package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
- package/packages/web/dist/client/favicon.svg +1 -0
- package/packages/web/dist/client/icon-192.png +0 -0
- package/packages/web/dist/client/icon-512.png +0 -0
- package/packages/web/dist/client/icon-maskable.png +0 -0
- package/packages/web/dist/client/index.html +24 -0
- package/packages/web/dist/client/manifest.webmanifest +21 -0
- package/packages/web/dist/client/wordmark.svg +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
|
+
|
|
4
|
+
export const GREP_HEAD_LIMIT_MAX = 1000;
|
|
5
|
+
export const GREP_CONTEXT_MAX = 20;
|
|
6
|
+
|
|
7
|
+
export const GREP_OUTPUT_MODES = [
|
|
8
|
+
"files_with_matches",
|
|
9
|
+
"content",
|
|
10
|
+
"count",
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export const GREP_PATH_FORMATS = ["relative", "absolute"] as const;
|
|
14
|
+
|
|
15
|
+
export const grepSchema = Type.Object({
|
|
16
|
+
pattern: Type.String({
|
|
17
|
+
description: "JavaScript regex source, without /.../ delimiters.",
|
|
18
|
+
}),
|
|
19
|
+
path: Type.Optional(
|
|
20
|
+
Type.String({
|
|
21
|
+
description:
|
|
22
|
+
"Absolute or relative path to file/directory (resolved against cwd). Defaults to cwd.",
|
|
23
|
+
})
|
|
24
|
+
),
|
|
25
|
+
glob: Type.Optional(
|
|
26
|
+
Type.String({
|
|
27
|
+
description:
|
|
28
|
+
"Relative glob filter under path when path is a directory. Brace expansion spans sibling dirs (eg. {src,docs}/**/*.ts); a bare directory (eg. src/tools) expands to everything under it. Gitignored files and dotfiles are skipped during directory scans unless includeIgnored/includeDotfiles is true.",
|
|
29
|
+
})
|
|
30
|
+
),
|
|
31
|
+
exclude: Type.Optional(
|
|
32
|
+
Type.Array(Type.String(), {
|
|
33
|
+
description:
|
|
34
|
+
"Glob patterns to exclude from directory searches, relative to path (eg. **/*.test.ts or dist/**).",
|
|
35
|
+
})
|
|
36
|
+
),
|
|
37
|
+
outputMode: Type.Optional(
|
|
38
|
+
StringEnum(GREP_OUTPUT_MODES, {
|
|
39
|
+
description:
|
|
40
|
+
"`files_with_matches` (default): returns file paths. `content`: returns path:line:text. `count`: returns path:count.",
|
|
41
|
+
})
|
|
42
|
+
),
|
|
43
|
+
matchAcrossLines: Type.Optional(
|
|
44
|
+
Type.Boolean({
|
|
45
|
+
description:
|
|
46
|
+
"Search whole files so one match can span line breaks. Defaults to false.",
|
|
47
|
+
})
|
|
48
|
+
),
|
|
49
|
+
context: Type.Optional(
|
|
50
|
+
Type.Integer({
|
|
51
|
+
minimum: 0,
|
|
52
|
+
maximum: GREP_CONTEXT_MAX,
|
|
53
|
+
description:
|
|
54
|
+
"For outputMode='content', include this many lines before and after each match. Defaults to 0.",
|
|
55
|
+
})
|
|
56
|
+
),
|
|
57
|
+
includeDotfiles: Type.Optional(
|
|
58
|
+
Type.Boolean({
|
|
59
|
+
description:
|
|
60
|
+
"Include dot-prefixed files and directories such as .env or .github during directory scans. Defaults to false.",
|
|
61
|
+
})
|
|
62
|
+
),
|
|
63
|
+
includeIgnored: Type.Optional(
|
|
64
|
+
Type.Boolean({
|
|
65
|
+
description:
|
|
66
|
+
"Include gitignored and normally ignored paths such as node_modules during directory scans. Defaults to false.",
|
|
67
|
+
})
|
|
68
|
+
),
|
|
69
|
+
pathFormat: Type.Optional(
|
|
70
|
+
StringEnum(GREP_PATH_FORMATS, {
|
|
71
|
+
description:
|
|
72
|
+
"`relative` (default): output paths relative to cwd when possible. `absolute`: output absolute paths.",
|
|
73
|
+
})
|
|
74
|
+
),
|
|
75
|
+
caseInsensitive: Type.Optional(
|
|
76
|
+
Type.Boolean({
|
|
77
|
+
description: "Search case-insensitively. Defaults to false.",
|
|
78
|
+
})
|
|
79
|
+
),
|
|
80
|
+
headLimit: Type.Optional(
|
|
81
|
+
Type.Integer({
|
|
82
|
+
minimum: 1,
|
|
83
|
+
maximum: GREP_HEAD_LIMIT_MAX,
|
|
84
|
+
description: `Maximum returned items. Defaults to ${GREP_HEAD_LIMIT_MAX}.`,
|
|
85
|
+
})
|
|
86
|
+
),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export type GrepInput = Static<typeof grepSchema>;
|
|
90
|
+
|
|
91
|
+
export type GrepOutputMode = (typeof GREP_OUTPUT_MODES)[number];
|
|
92
|
+
export type GrepPathFormat = (typeof GREP_PATH_FORMATS)[number];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PimSettings } from "../../shared/PimSettings";
|
|
2
|
+
import type { ReadImageDetails } from "./schema";
|
|
3
|
+
|
|
4
|
+
/** What the file looked like when its picture was last sent to the model. */
|
|
5
|
+
export type ImageStamp = {
|
|
6
|
+
readonly mtimeMs: number;
|
|
7
|
+
readonly size: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type Sent = ImageStamp & { readonly details: ReadImageDetails };
|
|
11
|
+
|
|
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
|
+
*/
|
|
17
|
+
export class ImageMemory {
|
|
18
|
+
private readonly sent = new Map<string, Sent>();
|
|
19
|
+
|
|
20
|
+
/** The settings flag is the escape hatch, so it is read only once a picture repeats. */
|
|
21
|
+
async recall(
|
|
22
|
+
path: string,
|
|
23
|
+
stamp: ImageStamp
|
|
24
|
+
): Promise<ReadImageDetails | undefined> {
|
|
25
|
+
const sent = this.sent.get(path);
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
sent === undefined ||
|
|
29
|
+
sent.mtimeMs !== stamp.mtimeMs ||
|
|
30
|
+
sent.size !== stamp.size
|
|
31
|
+
) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { dedupImages } = await PimSettings.get("read");
|
|
36
|
+
return dedupImages ? sent.details : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
remember(path: string, stamp: ImageStamp, details: ReadImageDetails): void {
|
|
40
|
+
this.sent.set(path, { ...stamp, details });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Images,
|
|
3
|
+
type NormalisedImage,
|
|
4
|
+
type VisionModel,
|
|
5
|
+
} from "../../shared/Images";
|
|
6
|
+
import type { ReadImageDetails } from "./schema";
|
|
7
|
+
|
|
8
|
+
export type ImageReadOutcome = {
|
|
9
|
+
readonly kind: "image";
|
|
10
|
+
readonly image: NormalisedImage;
|
|
11
|
+
readonly details: ReadImageDetails;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** The picture is already in the transcript: the row still draws it, the model is only told so. */
|
|
15
|
+
export type UnchangedImageReadOutcome = {
|
|
16
|
+
readonly kind: "image-unchanged";
|
|
17
|
+
readonly details: ReadImageDetails;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Bytes that are a picture, or a name that claims one — a claim that fails is a diagnostic, not text. */
|
|
21
|
+
export function isImageRead(head: Uint8Array, path: string): boolean {
|
|
22
|
+
return Images.sniff(head) !== null || Images.looksLikeImageName(path);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Both refusals that must land before the file is pulled into memory. */
|
|
26
|
+
export function assertImageReadable(
|
|
27
|
+
path: string,
|
|
28
|
+
sizeOnDisk: number,
|
|
29
|
+
model: VisionModel | undefined
|
|
30
|
+
): void {
|
|
31
|
+
if (model !== undefined && !model.input.includes("image")) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Cannot read images: the current model (${model.id}) has no vision input. Switch models with /model, or inspect it with bash: file ${path}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
Images.assertWithinSourceCap(sizeOnDisk, path);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function readImage(
|
|
40
|
+
bytes: Uint8Array,
|
|
41
|
+
path: string
|
|
42
|
+
): Promise<ImageReadOutcome> {
|
|
43
|
+
const image = await Images.normalise(bytes, path);
|
|
44
|
+
return {
|
|
45
|
+
kind: "image",
|
|
46
|
+
image,
|
|
47
|
+
details: {
|
|
48
|
+
kind: "image",
|
|
49
|
+
absolutePath: path,
|
|
50
|
+
...Images.detailsOf(image),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function unchangedImageNote(path: string): string {
|
|
56
|
+
return `[read tool: ${path} is unchanged since it was read earlier in this conversation; use the image already above. Touch the file or read a different path to force a re-send.]`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Images } from "../../shared/Images";
|
|
3
|
+
import { Paths } from "../../shared/Paths";
|
|
4
|
+
import { Tools } from "../../shared/Tools";
|
|
5
|
+
import { unchangedImageNote } from "./image";
|
|
6
|
+
import { ImageMemory } from "./ImageMemory";
|
|
7
|
+
import { buildReadRange, readFile } from "./read";
|
|
8
|
+
import { readView } from "./render";
|
|
9
|
+
import { type ReadDetails, type ReadInput, readSchema } from "./schema";
|
|
10
|
+
|
|
11
|
+
export default function (pi: ExtensionAPI): void {
|
|
12
|
+
const memory = new ImageMemory();
|
|
13
|
+
|
|
14
|
+
Tools.register<typeof readSchema, ReadDetails>(pi, {
|
|
15
|
+
name: "read",
|
|
16
|
+
label: "read",
|
|
17
|
+
description:
|
|
18
|
+
"Read a local UTF-8 text file or image (png, jpeg, gif, webp). " +
|
|
19
|
+
"Text output is `LINE:CONTENT` with no space after the colon.",
|
|
20
|
+
parameters: readSchema,
|
|
21
|
+
renderShell: "self",
|
|
22
|
+
effect: { kind: "readOnly" },
|
|
23
|
+
executionMode: "parallel",
|
|
24
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
25
|
+
const { path, start, end } = params as ReadInput;
|
|
26
|
+
|
|
27
|
+
if (signal?.aborted) {
|
|
28
|
+
throw new Error("Read aborted before execution.");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const range = buildReadRange(start, end);
|
|
32
|
+
const absolutePath = Paths.resolve(path, ctx.cwd);
|
|
33
|
+
const outcome = await readFile(absolutePath, range, {
|
|
34
|
+
model: ctx.model,
|
|
35
|
+
memory,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (outcome.kind === "image") {
|
|
39
|
+
return {
|
|
40
|
+
content: Images.contentOf(
|
|
41
|
+
outcome.image,
|
|
42
|
+
Images.noteOf(outcome.image)
|
|
43
|
+
),
|
|
44
|
+
details: outcome.details,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (outcome.kind === "image-unchanged") {
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: unchangedImageNote(outcome.details.absolutePath),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
details: outcome.details,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const content: Array<{ type: "text"; text: string }> = [
|
|
61
|
+
{ type: "text", text: outcome.body },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
if (outcome.truncatedByEnd && outcome.nextStart !== undefined) {
|
|
65
|
+
content.push({
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `[read tool: showing lines ${outcome.visibleStart}-${outcome.visibleEnd} of ${outcome.totalLines}; call read again with start=${outcome.nextStart} to continue.]`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
content,
|
|
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
|
+
};
|
|
85
|
+
},
|
|
86
|
+
toViewModel: readView,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Format } from "../../shared/Format";
|
|
2
|
+
import { FsErrors } from "../../shared/FsErrors";
|
|
3
|
+
import type { VisionModel } from "../../shared/Images";
|
|
4
|
+
import { Lines } from "../../shared/Lines";
|
|
5
|
+
import { OutputBudget } from "../../shared/OutputBudget";
|
|
6
|
+
import {
|
|
7
|
+
assertImageReadable,
|
|
8
|
+
type ImageReadOutcome,
|
|
9
|
+
isImageRead,
|
|
10
|
+
readImage,
|
|
11
|
+
type UnchangedImageReadOutcome,
|
|
12
|
+
} from "./image";
|
|
13
|
+
import type { ImageMemory } from "./ImageMemory";
|
|
14
|
+
import type { ReadRange } from "./schema";
|
|
15
|
+
|
|
16
|
+
export type TextReadOutcome = {
|
|
17
|
+
readonly kind: "text";
|
|
18
|
+
readonly body: string;
|
|
19
|
+
readonly totalLines: number;
|
|
20
|
+
readonly visibleStart: number;
|
|
21
|
+
readonly visibleEnd: number;
|
|
22
|
+
readonly truncatedByByteCap: boolean;
|
|
23
|
+
readonly truncatedByEnd: boolean;
|
|
24
|
+
readonly hadBom: boolean;
|
|
25
|
+
readonly nextStart?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ReadOutcome =
|
|
29
|
+
| TextReadOutcome
|
|
30
|
+
| ImageReadOutcome
|
|
31
|
+
| UnchangedImageReadOutcome;
|
|
32
|
+
|
|
33
|
+
export type ReadOptions = {
|
|
34
|
+
readonly model?: VisionModel;
|
|
35
|
+
/** The pictures this session has already sent; absent when the caller keeps none. */
|
|
36
|
+
readonly memory?: ImageMemory;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function buildReadRange(
|
|
40
|
+
start: number | undefined,
|
|
41
|
+
end: number | undefined
|
|
42
|
+
): ReadRange {
|
|
43
|
+
const startLine = start ?? 1;
|
|
44
|
+
|
|
45
|
+
if (start !== undefined && (!Number.isInteger(start) || start <= 0)) {
|
|
46
|
+
throw new Error(`Read start ${start} must be a positive integer.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (end !== undefined && (!Number.isInteger(end) || end <= 0)) {
|
|
50
|
+
throw new Error(`Read end ${end} must be a positive integer.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (end !== undefined && end < startLine) {
|
|
54
|
+
throw new Error(`Read end line ${end} must be >= start line ${startLine}.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
start: startLine,
|
|
59
|
+
...(end === undefined ? {} : { end }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function readFile(
|
|
64
|
+
path: string,
|
|
65
|
+
range: ReadRange,
|
|
66
|
+
options: ReadOptions = {}
|
|
67
|
+
): Promise<ReadOutcome> {
|
|
68
|
+
const metadata = await FsErrors.statOrThrow(path);
|
|
69
|
+
|
|
70
|
+
if (metadata.isDirectory()) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Path is a directory: ${path}. Use grep or glob to inspect directories.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const file = Bun.file(path);
|
|
77
|
+
|
|
78
|
+
// One head serves both verdicts: the signature is in its first bytes, a NUL anywhere in it.
|
|
79
|
+
const head = await bytesOf(file.slice(0, Lines.binarySniffBytes), path);
|
|
80
|
+
|
|
81
|
+
if (isImageRead(head, path)) {
|
|
82
|
+
assertImageReadable(path, metadata.size, options.model);
|
|
83
|
+
|
|
84
|
+
const stamp = { mtimeMs: metadata.mtimeMs, size: metadata.size };
|
|
85
|
+
const remembered = await options.memory?.recall(path, stamp);
|
|
86
|
+
|
|
87
|
+
if (remembered !== undefined) {
|
|
88
|
+
return {
|
|
89
|
+
kind: "image-unchanged",
|
|
90
|
+
details: { ...remembered, deduped: true },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const outcome = await readImage(await bytesOf(file, path), path);
|
|
95
|
+
options.memory?.remember(path, stamp, outcome.details);
|
|
96
|
+
return outcome;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Lines.isBinaryBytes(head)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Read only supports UTF-8 text files but given path is a binary file. Use bash with 'file' or 'xxd' to inspect binary contents.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const bytes = await bytesOf(file, path);
|
|
106
|
+
const hadBom = Lines.hasUtf8Bom(bytes);
|
|
107
|
+
const text = new TextDecoder("utf-8").decode(bytes);
|
|
108
|
+
|
|
109
|
+
return renderText(Lines.stripUtf8Bom(text), range, path, hadBom);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function bytesOf(file: Bun.BunFile, path: string): Promise<Uint8Array> {
|
|
113
|
+
try {
|
|
114
|
+
return await file.bytes();
|
|
115
|
+
} catch (error) {
|
|
116
|
+
rethrowFsError(error, path, "read");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderText(
|
|
121
|
+
content: string,
|
|
122
|
+
range: ReadRange,
|
|
123
|
+
path: string,
|
|
124
|
+
hadBom: boolean
|
|
125
|
+
): TextReadOutcome {
|
|
126
|
+
const lines = Lines.split(content);
|
|
127
|
+
const totalLines = lines.length;
|
|
128
|
+
|
|
129
|
+
if (totalLines === 0) {
|
|
130
|
+
throw new Error("File is empty. Use the write tool to create content.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (range.start > totalLines) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Start ${range.start} is beyond end of file (${totalLines} lines total). Use start=1 to read from the beginning, or start=${totalLines} to read the last line.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lastLine = Math.min(range.end ?? totalLines, totalLines);
|
|
140
|
+
const visible: string[] = [];
|
|
141
|
+
let bytes = 0;
|
|
142
|
+
let lastVisibleLine = range.start;
|
|
143
|
+
|
|
144
|
+
for (let lineNumber = range.start; lineNumber <= lastLine; lineNumber += 1) {
|
|
145
|
+
const text = `${lineNumber}:${OutputBudget.truncateLine(lines[lineNumber - 1] ?? "")}`;
|
|
146
|
+
const separatorBytes = visible.length === 0 ? 0 : 1;
|
|
147
|
+
const lineBytes = Buffer.byteLength(text, "utf8");
|
|
148
|
+
|
|
149
|
+
if (visible.length === 0) {
|
|
150
|
+
if (lineBytes > OutputBudget.maxBytes) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Line ${lineNumber} is ${Format.bytes(lineBytes)}, exceeds the ${Format.bytes(OutputBudget.maxBytes)} read cap. Use bash: sed -n '${lineNumber}p' ${path} | head -c ${OutputBudget.maxBytes}${range.start < totalLines ? `, or call read again with start=${range.start + 1} to skip this line.` : "."}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
} else if (bytes + separatorBytes + lineBytes > OutputBudget.maxBytes) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
visible.push(text);
|
|
160
|
+
bytes += separatorBytes + lineBytes;
|
|
161
|
+
lastVisibleLine = lineNumber;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const body = visible.join("\n");
|
|
165
|
+
const truncatedByByteCap = lastVisibleLine < lastLine;
|
|
166
|
+
const truncatedByEnd = lastVisibleLine < totalLines;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
kind: "text",
|
|
170
|
+
body,
|
|
171
|
+
totalLines,
|
|
172
|
+
visibleStart: range.start,
|
|
173
|
+
visibleEnd: lastVisibleLine,
|
|
174
|
+
truncatedByByteCap,
|
|
175
|
+
truncatedByEnd,
|
|
176
|
+
hadBom,
|
|
177
|
+
...(truncatedByEnd ? { nextStart: lastVisibleLine + 1 } : {}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function rethrowFsError(error: unknown, path: string, action: string): never {
|
|
182
|
+
const code = FsErrors.code(error);
|
|
183
|
+
|
|
184
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
185
|
+
throw new Error(`Permission denied reading ${path}.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Cannot ${action} ${path}: ${code ?? (error instanceof Error ? error.message : "unknown error")}.`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Paths } from "../../shared/Paths";
|
|
2
|
+
import { Renderer } from "../../shared/Renderer";
|
|
3
|
+
import type { ToolViewInput } from "../../shared/Tools";
|
|
4
|
+
import { Painting } from "../../view/Painting";
|
|
5
|
+
import type { ToolView, ViewBlock } from "../../view/ViewBlock";
|
|
6
|
+
import type { ReadDetails, ReadInput, readSchema } from "./schema";
|
|
7
|
+
|
|
8
|
+
export type ReadViewInput = ToolViewInput<typeof readSchema, ReadDetails>;
|
|
9
|
+
|
|
10
|
+
export function readView({ args, result, cwd }: ReadViewInput): ToolView {
|
|
11
|
+
const input: Partial<ReadInput> = args ?? {};
|
|
12
|
+
const details = result?.details;
|
|
13
|
+
const image = details?.kind === "image" ? details : undefined;
|
|
14
|
+
const path = Paths.titleOr(input.path, cwd);
|
|
15
|
+
return {
|
|
16
|
+
label: "Read",
|
|
17
|
+
icon: "file",
|
|
18
|
+
title: [image ? { kind: "file", path } : titleBlock(input, details, path)],
|
|
19
|
+
body: image
|
|
20
|
+
? Painting.imageBlocks(
|
|
21
|
+
image,
|
|
22
|
+
path,
|
|
23
|
+
image.deduped ? [["reused", "unchanged since the earlier read"]] : []
|
|
24
|
+
)
|
|
25
|
+
: [{ kind: "text", text: Renderer.firstText(result) }],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function titleBlock(
|
|
30
|
+
input: Partial<ReadInput>,
|
|
31
|
+
details: ReadDetails | undefined,
|
|
32
|
+
path: string
|
|
33
|
+
): ViewBlock {
|
|
34
|
+
const visible = visibleRange(details);
|
|
35
|
+
|
|
36
|
+
if (visible) {
|
|
37
|
+
return { kind: "file", path, range: visible };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (input.start === undefined && input.end === undefined) {
|
|
41
|
+
return { kind: "file", path };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { kind: "file", path, range: [input.start ?? 1, input.end] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Legacy sessions predate the tag, so an untagged result is read for the range it may still carry. */
|
|
48
|
+
function visibleRange(
|
|
49
|
+
details: ReadDetails | undefined
|
|
50
|
+
): readonly [number, number] | undefined {
|
|
51
|
+
if (details === undefined || details.kind === "image") {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const { visibleStart, visibleEnd } = details;
|
|
55
|
+
return typeof visibleStart === "number" && typeof visibleEnd === "number"
|
|
56
|
+
? [visibleStart, visibleEnd]
|
|
57
|
+
: undefined;
|
|
58
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
import type { ImageDetails } from "../../shared/Images";
|
|
3
|
+
|
|
4
|
+
export const readSchema = Type.Object({
|
|
5
|
+
path: Type.String({
|
|
6
|
+
description: "Absolute or relative path to file (resolved against cwd).",
|
|
7
|
+
}),
|
|
8
|
+
start: Type.Optional(
|
|
9
|
+
Type.Integer({
|
|
10
|
+
minimum: 1,
|
|
11
|
+
description: "First line to return, 1-indexed. Defaults to 1.",
|
|
12
|
+
})
|
|
13
|
+
),
|
|
14
|
+
end: Type.Optional(
|
|
15
|
+
Type.Integer({
|
|
16
|
+
minimum: 1,
|
|
17
|
+
description:
|
|
18
|
+
"Last line to return, 1-indexed and inclusive. Defaults to EOF or the byte cap.",
|
|
19
|
+
})
|
|
20
|
+
),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type ReadInput = Static<typeof readSchema>;
|
|
24
|
+
|
|
25
|
+
export type ReadRange = {
|
|
26
|
+
readonly start: number;
|
|
27
|
+
readonly end?: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ReadTextDetails = {
|
|
31
|
+
readonly kind: "text";
|
|
32
|
+
readonly absolutePath: string;
|
|
33
|
+
readonly totalLines: number;
|
|
34
|
+
readonly visibleStart: number;
|
|
35
|
+
readonly visibleEnd: number;
|
|
36
|
+
readonly truncatedByByteCap: boolean;
|
|
37
|
+
readonly truncatedByEnd: boolean;
|
|
38
|
+
readonly hadBom: boolean;
|
|
39
|
+
readonly nextStart?: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ReadImageDetails = ImageDetails & {
|
|
43
|
+
readonly kind: "image";
|
|
44
|
+
readonly absolutePath: string;
|
|
45
|
+
/** Sent earlier in this conversation and unchanged since, so only the note went to the model. */
|
|
46
|
+
readonly deduped?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type ReadDetails = ReadTextDetails | ReadImageDetails;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { SubagentLogs } from "../../shared/SubagentLogs";
|
|
3
|
+
import { Tools } from "../../shared/Tools";
|
|
4
|
+
import { subagentView } from "./render";
|
|
5
|
+
import { subagentSchema, type SubagentInput } from "./schema";
|
|
6
|
+
import { runSubagent, type SubagentDetails } from "./subagent";
|
|
7
|
+
|
|
8
|
+
export default function (pi: ExtensionAPI): void {
|
|
9
|
+
SubagentLogs.installSweeper();
|
|
10
|
+
Tools.register<typeof subagentSchema, SubagentDetails>(pi, {
|
|
11
|
+
name: "subagent",
|
|
12
|
+
label: "subagent",
|
|
13
|
+
description:
|
|
14
|
+
"Run a task in an isolated subagent with a fresh context. " +
|
|
15
|
+
"The subagent inherits the currently active tools, except subagent itself. " +
|
|
16
|
+
"Multiple subagent calls in one turn run in parallel. " +
|
|
17
|
+
"Subagent output returned to the main agent is capped at 32KB.",
|
|
18
|
+
parameters: subagentSchema,
|
|
19
|
+
renderShell: "self",
|
|
20
|
+
effect: { kind: "unbounded" },
|
|
21
|
+
executionMode: "parallel",
|
|
22
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
23
|
+
const input = params as SubagentInput;
|
|
24
|
+
return runSubagent(input.prompt, ctx, {
|
|
25
|
+
callId: toolCallId,
|
|
26
|
+
signal,
|
|
27
|
+
onUpdate,
|
|
28
|
+
activeToolNames: pi.getActiveTools(),
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
toViewModel: subagentView,
|
|
32
|
+
});
|
|
33
|
+
}
|