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,127 @@
|
|
|
1
|
+
import { Format } from "../../shared/Format";
|
|
2
|
+
import { Renderer } from "../../shared/Renderer";
|
|
3
|
+
import type { ToolViewInput } from "../../shared/Tools";
|
|
4
|
+
import type { Span, Tone, ToolView, ViewBlock } from "../../view/ViewBlock";
|
|
5
|
+
import type { subagentSchema } from "./schema";
|
|
6
|
+
import type { SubagentDetails } from "./subagent";
|
|
7
|
+
|
|
8
|
+
const SEPARATOR: Span = { text: " ⬝ ", tone: "muted" };
|
|
9
|
+
|
|
10
|
+
type SubagentViewInput = ToolViewInput<typeof subagentSchema, SubagentDetails>;
|
|
11
|
+
|
|
12
|
+
export function subagentView({
|
|
13
|
+
args,
|
|
14
|
+
result,
|
|
15
|
+
isPartial,
|
|
16
|
+
}: SubagentViewInput): ToolView {
|
|
17
|
+
return {
|
|
18
|
+
label: "Subagent",
|
|
19
|
+
icon: "robot",
|
|
20
|
+
labelTone: labelTone(result, isPartial),
|
|
21
|
+
title: [{ kind: "markdown", text: formatCallTitle(args?.prompt) }],
|
|
22
|
+
summary: summaryBlocks(result?.details, isPartial),
|
|
23
|
+
body: bodyBlocks(args?.prompt, result),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function formatCallTitle(prompt: string | undefined): string {
|
|
28
|
+
return (prompt ?? "...").split(/\r?\n/u)[0]?.trim() || "...";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The status line the parent model reads while the call streams: the summary flattened. */
|
|
32
|
+
export function formatTopLine(details: SubagentDetails): string {
|
|
33
|
+
return summarySpans(details, details.stopReason === undefined)
|
|
34
|
+
.map((span) => span.text)
|
|
35
|
+
.join("");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function labelTone(
|
|
39
|
+
result: SubagentViewInput["result"],
|
|
40
|
+
isPartial: boolean
|
|
41
|
+
): ToolView["labelTone"] {
|
|
42
|
+
if (isPartial || result === undefined) {
|
|
43
|
+
return "warning";
|
|
44
|
+
}
|
|
45
|
+
return result.details === undefined ? "error" : "accent";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function summaryBlocks(
|
|
49
|
+
details: SubagentDetails | undefined,
|
|
50
|
+
isPartial: boolean
|
|
51
|
+
): readonly ViewBlock[] {
|
|
52
|
+
const spans = details === undefined ? [] : summarySpans(details, isPartial);
|
|
53
|
+
return spans.length === 0 ? [] : [{ kind: "spans", spans }];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function summarySpans(
|
|
57
|
+
details: SubagentDetails,
|
|
58
|
+
isPartial: boolean
|
|
59
|
+
): readonly Span[] {
|
|
60
|
+
// `details` is typed whole but may not be: pi empties it on failure, and old sessions lack usage.
|
|
61
|
+
if (details.usage === undefined) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const tone: Tone = isPartial ? "warning" : "muted";
|
|
66
|
+
const parts = [
|
|
67
|
+
Format.count(details.usage.turns, "turn"),
|
|
68
|
+
formatCost(details.usage.cost),
|
|
69
|
+
formatContext(details.usage.contextTokens, details.contextWindow),
|
|
70
|
+
].filter((part) => part !== undefined);
|
|
71
|
+
|
|
72
|
+
return parts.flatMap((text, index): readonly Span[] =>
|
|
73
|
+
index === 0 ? [{ text, tone }] : [SEPARATOR, { text, tone }]
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function bodyBlocks(
|
|
78
|
+
prompt: string | undefined,
|
|
79
|
+
result: SubagentViewInput["result"]
|
|
80
|
+
): readonly ViewBlock[] {
|
|
81
|
+
if (result === undefined) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const details = result.details;
|
|
86
|
+
const blocks: ViewBlock[] = [];
|
|
87
|
+
|
|
88
|
+
if (prompt !== undefined && prompt.trim().includes("\n")) {
|
|
89
|
+
blocks.push({
|
|
90
|
+
kind: "section",
|
|
91
|
+
label: "Prompt",
|
|
92
|
+
content: [{ kind: "text", text: prompt.trim() }],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const text = details?.fullOutput ?? Renderer.firstText(result);
|
|
97
|
+
if (text !== "") {
|
|
98
|
+
blocks.push({ kind: "markdown", text });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const pairs = footPairs(details);
|
|
102
|
+
if (pairs.length > 0) {
|
|
103
|
+
blocks.push({ kind: "kv", pairs });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return blocks;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function footPairs(
|
|
110
|
+
details: SubagentDetails | undefined
|
|
111
|
+
): ReadonlyArray<readonly [string, string]> {
|
|
112
|
+
return details?.model === undefined ? [] : [["model", details.model]];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function formatContext(
|
|
116
|
+
tokens: number | undefined,
|
|
117
|
+
window: number | undefined
|
|
118
|
+
): string | undefined {
|
|
119
|
+
if (window === undefined || window <= 0 || tokens === undefined) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return `${((tokens / window) * 100).toFixed(1)}%/${Format.formatTokens(window)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatCost(cost: number): string {
|
|
126
|
+
return `$${cost.toFixed(2)}`;
|
|
127
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type {
|
|
3
|
+
AgentSessionEvent,
|
|
4
|
+
AgentToolResult,
|
|
5
|
+
AgentToolUpdateCallback,
|
|
6
|
+
ExtensionContext,
|
|
7
|
+
InlineExtension,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import {
|
|
10
|
+
createAgentSession,
|
|
11
|
+
DefaultResourceLoader,
|
|
12
|
+
getAgentDir,
|
|
13
|
+
SessionManager,
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
16
|
+
import { Errors } from "../../shared/Errors";
|
|
17
|
+
import { SubagentLogs } from "../../shared/SubagentLogs";
|
|
18
|
+
import { MessageText } from "../../session/MessageText";
|
|
19
|
+
import { CoreExtensions } from "../CoreExtensions";
|
|
20
|
+
import { formatTopLine } from "./render";
|
|
21
|
+
|
|
22
|
+
export const PER_TASK_OUTPUT_CAP = 32 * 1024;
|
|
23
|
+
export const SUBAGENT_TOOL_NAME = "subagent";
|
|
24
|
+
|
|
25
|
+
export const UPDATE_INTERVAL_MS = 100;
|
|
26
|
+
|
|
27
|
+
const inSubagent = new AsyncLocalStorage<true>();
|
|
28
|
+
|
|
29
|
+
export type SubagentUsage = {
|
|
30
|
+
readonly input: number;
|
|
31
|
+
readonly output: number;
|
|
32
|
+
readonly cacheRead: number;
|
|
33
|
+
readonly cacheWrite: number;
|
|
34
|
+
readonly cost: number;
|
|
35
|
+
readonly turns: number;
|
|
36
|
+
readonly contextTokens: number | undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SubagentSnapshot = {
|
|
40
|
+
/** The child's session id, for a log reader; a transcript is found from the call id, not this. */
|
|
41
|
+
readonly sessionId: string | undefined;
|
|
42
|
+
readonly usage: SubagentUsage;
|
|
43
|
+
readonly stopReason: string | undefined;
|
|
44
|
+
readonly errorMessage: string | undefined;
|
|
45
|
+
readonly model: string | undefined;
|
|
46
|
+
readonly contextWindow: number | undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** `returnedOutput` is the capped answer for the parent model; `fullOutput` is every word written. */
|
|
50
|
+
export type SubagentDetails = SubagentSnapshot & {
|
|
51
|
+
readonly returnedOutput: string;
|
|
52
|
+
readonly fullOutput: string;
|
|
53
|
+
readonly outputTruncated: boolean;
|
|
54
|
+
readonly omittedBytes: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type SubagentSession = {
|
|
58
|
+
readonly sessionId?: string;
|
|
59
|
+
readonly subscribe: (
|
|
60
|
+
listener: (event: AgentSessionEvent) => void
|
|
61
|
+
) => () => void;
|
|
62
|
+
readonly prompt: (prompt: string) => Promise<void>;
|
|
63
|
+
readonly abort: () => Promise<void>;
|
|
64
|
+
readonly dispose: () => void;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type SubagentSessionSpec = {
|
|
68
|
+
readonly activeToolNames?: readonly string[];
|
|
69
|
+
/** The parent's tool call id, which names the child's log on disk. */
|
|
70
|
+
readonly callId?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type CreateSubagentSession = (
|
|
74
|
+
parentCtx: ExtensionContext,
|
|
75
|
+
spec: SubagentSessionSpec
|
|
76
|
+
) => Promise<SubagentSession>;
|
|
77
|
+
|
|
78
|
+
export type SubagentRun = SubagentSessionSpec & {
|
|
79
|
+
readonly signal?: AbortSignal;
|
|
80
|
+
readonly onUpdate?: AgentToolUpdateCallback<SubagentDetails>;
|
|
81
|
+
readonly createSession?: CreateSubagentSession;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function childToolNames(
|
|
85
|
+
activeToolNames: readonly string[]
|
|
86
|
+
): readonly string[] {
|
|
87
|
+
return activeToolNames.filter((name) => name !== SUBAGENT_TOOL_NAME);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The child inherits no registrations: without this roster its `tools` allowlist loses every pim tool.
|
|
91
|
+
export function childLoaderOptions(cwd: string): {
|
|
92
|
+
readonly cwd: string;
|
|
93
|
+
readonly agentDir: string;
|
|
94
|
+
readonly extensionFactories: InlineExtension[];
|
|
95
|
+
} {
|
|
96
|
+
return {
|
|
97
|
+
cwd,
|
|
98
|
+
agentDir: getAgentDir(),
|
|
99
|
+
extensionFactories: CoreExtensions.gated(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function createSdkSubagentSession(
|
|
104
|
+
parentCtx: ExtensionContext,
|
|
105
|
+
spec: SubagentSessionSpec = {}
|
|
106
|
+
): Promise<SubagentSession> {
|
|
107
|
+
const loader = new DefaultResourceLoader(childLoaderOptions(parentCtx.cwd));
|
|
108
|
+
await loader.reload();
|
|
109
|
+
|
|
110
|
+
const { session } = await createAgentSession({
|
|
111
|
+
cwd: parentCtx.cwd,
|
|
112
|
+
agentDir: getAgentDir(),
|
|
113
|
+
model: parentCtx.model,
|
|
114
|
+
sessionManager: await childSessionManager(parentCtx, spec.callId),
|
|
115
|
+
resourceLoader: loader,
|
|
116
|
+
tools: spec.activeToolNames
|
|
117
|
+
? [...childToolNames(spec.activeToolNames)]
|
|
118
|
+
: undefined,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return session;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// `open`, not `create`: the log path must be derivable from the parent session id and call id.
|
|
125
|
+
async function childSessionManager(
|
|
126
|
+
parentCtx: ExtensionContext,
|
|
127
|
+
callId: string | undefined
|
|
128
|
+
): Promise<SessionManager> {
|
|
129
|
+
const path = callId
|
|
130
|
+
? await SubagentLogs.create(parentCtx.sessionManager.getSessionId(), callId)
|
|
131
|
+
: null;
|
|
132
|
+
return path === null
|
|
133
|
+
? SessionManager.inMemory(parentCtx.cwd)
|
|
134
|
+
: SessionManager.open(path, undefined, parentCtx.cwd);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function runSubagent(
|
|
138
|
+
prompt: string,
|
|
139
|
+
parentCtx: ExtensionContext,
|
|
140
|
+
run: SubagentRun = {}
|
|
141
|
+
): Promise<AgentToolResult<SubagentDetails>> {
|
|
142
|
+
const {
|
|
143
|
+
signal,
|
|
144
|
+
onUpdate,
|
|
145
|
+
createSession = createSdkSubagentSession,
|
|
146
|
+
activeToolNames,
|
|
147
|
+
callId,
|
|
148
|
+
} = run;
|
|
149
|
+
|
|
150
|
+
if (inSubagent.getStore()) {
|
|
151
|
+
throw new Error("subagents cannot call subagent tool");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return inSubagent.run(true, async () => {
|
|
155
|
+
const capture = new SubagentEventCapture(onUpdate, {
|
|
156
|
+
contextWindow: parentCtx.model?.contextWindow,
|
|
157
|
+
model: parentCtx.model?.id,
|
|
158
|
+
});
|
|
159
|
+
let session: SubagentSession | undefined;
|
|
160
|
+
let thrown: unknown;
|
|
161
|
+
let abortRequested = false;
|
|
162
|
+
let abortPromise: Promise<void> | undefined;
|
|
163
|
+
|
|
164
|
+
const ensureAbort = (): Promise<void> => {
|
|
165
|
+
if (!session) {
|
|
166
|
+
return Promise.resolve();
|
|
167
|
+
}
|
|
168
|
+
abortPromise ??= session.abort().catch(() => {});
|
|
169
|
+
return abortPromise;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const onAbort = () => {
|
|
173
|
+
abortRequested = true;
|
|
174
|
+
void ensureAbort();
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
session = await createSession(parentCtx, { activeToolNames, callId });
|
|
179
|
+
capture.noteSessionId(session.sessionId);
|
|
180
|
+
session.subscribe((event) => capture.handle(event));
|
|
181
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
182
|
+
if (signal?.aborted) {
|
|
183
|
+
abortRequested = true;
|
|
184
|
+
throw new Error("subagent aborted before start");
|
|
185
|
+
}
|
|
186
|
+
await session.prompt(prompt);
|
|
187
|
+
if (abortRequested && capture.snapshot().stopReason !== "aborted") {
|
|
188
|
+
capture.markAborted();
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
thrown = err;
|
|
192
|
+
} finally {
|
|
193
|
+
signal?.removeEventListener("abort", onAbort);
|
|
194
|
+
await ensureAbort();
|
|
195
|
+
session?.dispose();
|
|
196
|
+
capture.dispose();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const snapshot = capture.snapshot();
|
|
200
|
+
if (thrown !== undefined) {
|
|
201
|
+
throw makeFailureError(
|
|
202
|
+
Errors.describe(thrown),
|
|
203
|
+
undefined,
|
|
204
|
+
capture.narration()
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (snapshot.stopReason === "error" || snapshot.stopReason === "aborted") {
|
|
208
|
+
throw makeFailureError(
|
|
209
|
+
snapshot.stopReason,
|
|
210
|
+
snapshot.errorMessage,
|
|
211
|
+
capture.narration()
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const details = capture.details();
|
|
216
|
+
const text =
|
|
217
|
+
details.returnedOutput ||
|
|
218
|
+
"[subagent tool: completed with no text output.]";
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: "text", text }],
|
|
221
|
+
details,
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export class SubagentEventCapture {
|
|
227
|
+
private readonly entries: string[] = [];
|
|
228
|
+
private pendingText = "";
|
|
229
|
+
private updateTimer: ReturnType<typeof setTimeout> | undefined;
|
|
230
|
+
private readonly usage: MutableUsage = emptyUsage();
|
|
231
|
+
private stopReason: string | undefined;
|
|
232
|
+
private errorMessage: string | undefined;
|
|
233
|
+
private model: string | undefined;
|
|
234
|
+
private sessionId: string | undefined;
|
|
235
|
+
|
|
236
|
+
public constructor(
|
|
237
|
+
private readonly onUpdate?: AgentToolUpdateCallback<SubagentDetails>,
|
|
238
|
+
private readonly options: {
|
|
239
|
+
readonly contextWindow?: number;
|
|
240
|
+
readonly model?: string;
|
|
241
|
+
} = {}
|
|
242
|
+
) {
|
|
243
|
+
this.model = options.model;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public handle(event: AgentSessionEvent): void {
|
|
247
|
+
if (event.type === "message_update" && event.message.role === "assistant") {
|
|
248
|
+
this.pendingText = MessageText.textOf(event.message.content);
|
|
249
|
+
this.scheduleUpdate();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
254
|
+
this.commitText(MessageText.textOf(event.message.content));
|
|
255
|
+
addUsage(this.usage, event.message.usage);
|
|
256
|
+
this.usage.turns += 1;
|
|
257
|
+
this.stopReason = event.message.stopReason;
|
|
258
|
+
this.errorMessage = event.message.errorMessage;
|
|
259
|
+
this.model = event.message.model;
|
|
260
|
+
this.emitUpdate();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public noteSessionId(sessionId: string | undefined): void {
|
|
265
|
+
this.sessionId = sessionId;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public markAborted(): void {
|
|
269
|
+
this.stopReason = "aborted";
|
|
270
|
+
this.emitUpdate();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Drops a throttled update still in flight once the run is over. */
|
|
274
|
+
public dispose(): void {
|
|
275
|
+
this.cancelPending();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public snapshot(): SubagentSnapshot {
|
|
279
|
+
return {
|
|
280
|
+
sessionId: this.sessionId,
|
|
281
|
+
usage: { ...this.usage },
|
|
282
|
+
stopReason: this.stopReason,
|
|
283
|
+
errorMessage: this.errorMessage,
|
|
284
|
+
model: this.model,
|
|
285
|
+
contextWindow: this.options.contextWindow,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Everything the child wrote, one paragraph per assistant message, including a streaming one. */
|
|
290
|
+
public narration(): string {
|
|
291
|
+
const said =
|
|
292
|
+
this.pendingText === ""
|
|
293
|
+
? this.entries
|
|
294
|
+
: [...this.entries, this.pendingText];
|
|
295
|
+
return said.join("\n\n");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
public details(): SubagentDetails {
|
|
299
|
+
const cap = applyOutputCap(this.answer());
|
|
300
|
+
return {
|
|
301
|
+
...this.snapshot(),
|
|
302
|
+
returnedOutput: cap.text,
|
|
303
|
+
fullOutput: this.narration(),
|
|
304
|
+
outputTruncated: cap.truncated,
|
|
305
|
+
omittedBytes: cap.omittedBytes,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private answer(): string {
|
|
310
|
+
return this.pendingText === ""
|
|
311
|
+
? (this.entries.at(-1) ?? "")
|
|
312
|
+
: this.pendingText;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private commitText(text: string): void {
|
|
316
|
+
this.pendingText = "";
|
|
317
|
+
if (text !== "") {
|
|
318
|
+
this.entries.push(text);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private scheduleUpdate(): void {
|
|
323
|
+
if (this.onUpdate === undefined || this.updateTimer !== undefined) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this.updateTimer = setTimeout(() => {
|
|
327
|
+
this.updateTimer = undefined;
|
|
328
|
+
this.emitUpdate();
|
|
329
|
+
}, UPDATE_INTERVAL_MS);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private cancelPending(): void {
|
|
333
|
+
if (this.updateTimer !== undefined) {
|
|
334
|
+
clearTimeout(this.updateTimer);
|
|
335
|
+
this.updateTimer = undefined;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private emitUpdate(): void {
|
|
340
|
+
this.cancelPending();
|
|
341
|
+
if (!this.onUpdate) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const details = this.details();
|
|
345
|
+
this.onUpdate({
|
|
346
|
+
content: [{ type: "text", text: formatTopLine(details) }],
|
|
347
|
+
details,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
type MutableUsage = {
|
|
353
|
+
input: number;
|
|
354
|
+
output: number;
|
|
355
|
+
cacheRead: number;
|
|
356
|
+
cacheWrite: number;
|
|
357
|
+
cost: number;
|
|
358
|
+
turns: number;
|
|
359
|
+
contextTokens: number | undefined;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export type OutputCapResult = {
|
|
363
|
+
readonly text: string;
|
|
364
|
+
readonly truncated: boolean;
|
|
365
|
+
readonly omittedBytes: number;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export function applyOutputCap(
|
|
369
|
+
text: string,
|
|
370
|
+
capBytes = PER_TASK_OUTPUT_CAP
|
|
371
|
+
): OutputCapResult {
|
|
372
|
+
const totalBytes = Buffer.byteLength(text, "utf8");
|
|
373
|
+
if (totalBytes <= capBytes) {
|
|
374
|
+
return { text, truncated: false, omittedBytes: 0 };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const buffer = new Uint8Array(capBytes);
|
|
378
|
+
const { read, written } = new TextEncoder().encodeInto(text, buffer);
|
|
379
|
+
const out = text.slice(0, read);
|
|
380
|
+
const omittedBytes = totalBytes - written;
|
|
381
|
+
return {
|
|
382
|
+
text: `${out}\n[subagent: output truncated, ${omittedBytes} bytes omitted; full output preserved in tool details.]`,
|
|
383
|
+
truncated: true,
|
|
384
|
+
omittedBytes,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function makeFailureError(
|
|
389
|
+
reason: string,
|
|
390
|
+
errorMessage: string | undefined,
|
|
391
|
+
partialOutput: string
|
|
392
|
+
): Error {
|
|
393
|
+
const capped = applyOutputCap(partialOutput);
|
|
394
|
+
return new Error(
|
|
395
|
+
`Subagent failed: ${reason}. Error: ${errorMessage ?? "none"}.\n` +
|
|
396
|
+
`Partial output before failure:\n${capped.text}`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function emptyUsage(): MutableUsage {
|
|
401
|
+
return {
|
|
402
|
+
input: 0,
|
|
403
|
+
output: 0,
|
|
404
|
+
cacheRead: 0,
|
|
405
|
+
cacheWrite: 0,
|
|
406
|
+
cost: 0,
|
|
407
|
+
turns: 0,
|
|
408
|
+
contextTokens: undefined,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function addUsage(target: MutableUsage, usage: Usage): void {
|
|
413
|
+
target.input += usage.input;
|
|
414
|
+
target.output += usage.output;
|
|
415
|
+
target.cacheRead += usage.cacheRead;
|
|
416
|
+
target.cacheWrite += usage.cacheWrite;
|
|
417
|
+
target.cost += usage.cost.total;
|
|
418
|
+
target.contextTokens = usage.totalTokens || target.contextTokens;
|
|
419
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionFactory,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { formatSkillsForPrompt } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
|
|
7
|
+
import type { Surface } from "../../shared/Surface";
|
|
8
|
+
import { buildSystemPrompt } from "./prompt";
|
|
9
|
+
|
|
10
|
+
export default function (surface?: Surface): ExtensionFactory {
|
|
11
|
+
return (pi: ExtensionAPI): void => {
|
|
12
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
13
|
+
const {
|
|
14
|
+
cwd,
|
|
15
|
+
contextFiles,
|
|
16
|
+
skills,
|
|
17
|
+
promptGuidelines,
|
|
18
|
+
appendSystemPrompt,
|
|
19
|
+
customPrompt,
|
|
20
|
+
} = event.systemPromptOptions;
|
|
21
|
+
return {
|
|
22
|
+
systemPrompt: buildSystemPrompt({
|
|
23
|
+
model: ctx.model,
|
|
24
|
+
cwd,
|
|
25
|
+
contextFiles: contextFiles ?? [],
|
|
26
|
+
skillsBlock:
|
|
27
|
+
skills && skills.length > 0 ? formatSkillsForPrompt(skills) : "",
|
|
28
|
+
toolGuidelines: promptGuidelines ?? [],
|
|
29
|
+
appendSystemPrompt,
|
|
30
|
+
customPrompt,
|
|
31
|
+
...(surface === undefined ? {} : { surface }),
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}
|