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,182 @@
|
|
|
1
|
+
import { Format } from "../shared/Format";
|
|
2
|
+
import { ImageMime } from "../shared/ImageMime";
|
|
3
|
+
import type { ImageDetails } from "../shared/Images";
|
|
4
|
+
import type { BlockOf, KvPair, ViewBlock } from "./ViewBlock";
|
|
5
|
+
|
|
6
|
+
export type PainterMap<TOut, TArgs extends readonly unknown[] = []> = {
|
|
7
|
+
readonly [TKind in ViewBlock["kind"]]: (
|
|
8
|
+
block: BlockOf<TKind>,
|
|
9
|
+
...args: TArgs
|
|
10
|
+
) => TOut;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function dispatch<TOut, TArgs extends readonly unknown[]>(
|
|
14
|
+
painters: PainterMap<TOut, TArgs>,
|
|
15
|
+
block: ViewBlock,
|
|
16
|
+
...args: TArgs
|
|
17
|
+
): TOut {
|
|
18
|
+
const painter = painters[block.kind] as (
|
|
19
|
+
block: ViewBlock,
|
|
20
|
+
...args: TArgs
|
|
21
|
+
) => TOut;
|
|
22
|
+
return painter(block, ...args);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** How a block sits in its container: `embed` must not be restyled or re-wrapped, `tight` paints its own leading column. */
|
|
26
|
+
export type BlockFrame = "flow" | "embed" | "tight" | "heading";
|
|
27
|
+
|
|
28
|
+
const FRAMES = {
|
|
29
|
+
text: "flow",
|
|
30
|
+
markdown: "embed",
|
|
31
|
+
spans: "flow",
|
|
32
|
+
section: "heading",
|
|
33
|
+
code: "embed",
|
|
34
|
+
diff: "embed",
|
|
35
|
+
file: "flow",
|
|
36
|
+
list: "flow",
|
|
37
|
+
kv: "flow",
|
|
38
|
+
link: "flow",
|
|
39
|
+
attachment: "flow",
|
|
40
|
+
image: "flow",
|
|
41
|
+
notice: "flow",
|
|
42
|
+
} as const satisfies Record<ViewBlock["kind"], BlockFrame>;
|
|
43
|
+
|
|
44
|
+
/** What a block is to the tool that emitted it: its result, or the chrome around one. */
|
|
45
|
+
export type BlockWeight = "payload" | "chrome";
|
|
46
|
+
|
|
47
|
+
const WEIGHT = {
|
|
48
|
+
text: "chrome",
|
|
49
|
+
markdown: "payload",
|
|
50
|
+
spans: "chrome",
|
|
51
|
+
section: "chrome",
|
|
52
|
+
code: "chrome",
|
|
53
|
+
diff: "payload",
|
|
54
|
+
file: "chrome",
|
|
55
|
+
list: "chrome",
|
|
56
|
+
kv: "chrome",
|
|
57
|
+
link: "chrome",
|
|
58
|
+
attachment: "chrome",
|
|
59
|
+
image: "payload",
|
|
60
|
+
notice: "chrome",
|
|
61
|
+
} as const satisfies Record<ViewBlock["kind"], BlockWeight>;
|
|
62
|
+
|
|
63
|
+
export type FrameGroup<TFrame> = {
|
|
64
|
+
readonly frame: TFrame;
|
|
65
|
+
readonly blocks: readonly ViewBlock[];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function groupByFrame<TFrame>(
|
|
69
|
+
blocks: readonly ViewBlock[],
|
|
70
|
+
frames: Readonly<Record<ViewBlock["kind"], TFrame>>,
|
|
71
|
+
mergeable: (frame: TFrame) => boolean = () => true
|
|
72
|
+
): FrameGroup<TFrame>[] {
|
|
73
|
+
const groups: Array<{ frame: TFrame; blocks: ViewBlock[] }> = [];
|
|
74
|
+
for (const block of blocks) {
|
|
75
|
+
const frame = frames[block.kind];
|
|
76
|
+
const open = groups.at(-1);
|
|
77
|
+
if (open?.frame === frame && mergeable(frame)) {
|
|
78
|
+
open.blocks.push(block);
|
|
79
|
+
} else {
|
|
80
|
+
groups.push({ frame, blocks: [block] });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return groups;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function formatRange(range: readonly [number, number | undefined]): string {
|
|
87
|
+
const [start, end] = range;
|
|
88
|
+
return end === undefined ? `:${start}` : `:${start}-${end}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function fileSuffix(block: {
|
|
92
|
+
readonly range?: readonly [number, number | undefined];
|
|
93
|
+
readonly truncated?: boolean;
|
|
94
|
+
}): { readonly range: string; readonly truncated: string } {
|
|
95
|
+
return {
|
|
96
|
+
range: block.range ? formatRange(block.range) : "",
|
|
97
|
+
truncated: block.truncated === true ? " (truncated)" : "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function linkLabel(block: {
|
|
102
|
+
readonly label: string;
|
|
103
|
+
readonly href: string;
|
|
104
|
+
}): string {
|
|
105
|
+
return block.label === "" || block.label === block.href
|
|
106
|
+
? block.href
|
|
107
|
+
: block.label;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** What a painter that cannot draw the picture says instead: `[image 1200×800 png · 240 KB]`. */
|
|
111
|
+
function imageSummary(block: BlockOf<"image">): string {
|
|
112
|
+
return `[image ${block.width}×${block.height} ${ImageMime.extensionOf(block.mimeType)} · ${Format.bytes(block.bytes)}]`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The picture plus what it cannot show about itself; `extra` is whatever only the calling tool knows. */
|
|
116
|
+
function imageBlocks(
|
|
117
|
+
details: ImageDetails,
|
|
118
|
+
alt: string,
|
|
119
|
+
extra: readonly KvPair[] = []
|
|
120
|
+
): readonly ViewBlock[] {
|
|
121
|
+
return [
|
|
122
|
+
{
|
|
123
|
+
kind: "image",
|
|
124
|
+
sha256: details.sha256,
|
|
125
|
+
mimeType: details.mimeType,
|
|
126
|
+
width: details.width,
|
|
127
|
+
height: details.height,
|
|
128
|
+
bytes: details.bytes,
|
|
129
|
+
alt,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
kind: "kv",
|
|
133
|
+
pairs: [
|
|
134
|
+
[
|
|
135
|
+
"dimensions",
|
|
136
|
+
`${details.width}x${details.height}${details.resized ? " (downscaled)" : ""}`,
|
|
137
|
+
],
|
|
138
|
+
...(details.frames >= 2
|
|
139
|
+
? [["frames", `${details.frames} (frame 1 shown)`] as KvPair]
|
|
140
|
+
: []),
|
|
141
|
+
["size", Format.bytes(details.bytes)],
|
|
142
|
+
...extra,
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function lineNumberWidth(start: number, count: number): number {
|
|
149
|
+
return String(start + Math.max(0, count - 1)).length;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hangingList(
|
|
153
|
+
items: readonly ViewBlock[],
|
|
154
|
+
ordered: boolean,
|
|
155
|
+
paintItem: (item: ViewBlock) => readonly string[],
|
|
156
|
+
styleMarker: (marker: string) => string = (marker) => marker
|
|
157
|
+
): string[] {
|
|
158
|
+
const markers = items.map((_, index) => (ordered ? `${index + 1}.` : "•"));
|
|
159
|
+
const width = Math.max(0, ...markers.map((marker) => marker.length)) + 1;
|
|
160
|
+
const indent = " ".repeat(width);
|
|
161
|
+
|
|
162
|
+
return items.flatMap((item, index) => {
|
|
163
|
+
const marker = styleMarker((markers[index] ?? "•").padEnd(width));
|
|
164
|
+
return paintItem(item).map((line, lineIndex) =>
|
|
165
|
+
lineIndex === 0 ? marker + line : indent + line
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const Painting = {
|
|
171
|
+
FRAMES,
|
|
172
|
+
WEIGHT,
|
|
173
|
+
dispatch,
|
|
174
|
+
groupByFrame,
|
|
175
|
+
formatRange,
|
|
176
|
+
fileSuffix,
|
|
177
|
+
linkLabel,
|
|
178
|
+
imageSummary,
|
|
179
|
+
imageBlocks,
|
|
180
|
+
lineNumberWidth,
|
|
181
|
+
hangingList,
|
|
182
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ToolDiffHunk } from "../shared/DiffLines";
|
|
2
|
+
import type { ImageMimeType } from "../shared/Images";
|
|
3
|
+
|
|
4
|
+
export type DiffHunk = ToolDiffHunk;
|
|
5
|
+
|
|
6
|
+
/** Semantic inline styling roles; every painter must map the whole set. */
|
|
7
|
+
export type Tone =
|
|
8
|
+
| "default"
|
|
9
|
+
| "muted"
|
|
10
|
+
| "dim"
|
|
11
|
+
| "error"
|
|
12
|
+
| "warning"
|
|
13
|
+
| "accent"
|
|
14
|
+
| "added"
|
|
15
|
+
| "removed"
|
|
16
|
+
| "title";
|
|
17
|
+
|
|
18
|
+
export type ToolIcon =
|
|
19
|
+
| "file"
|
|
20
|
+
| "edit"
|
|
21
|
+
| "trash"
|
|
22
|
+
| "terminal"
|
|
23
|
+
| "search"
|
|
24
|
+
| "checklist"
|
|
25
|
+
| "globe"
|
|
26
|
+
| "upload"
|
|
27
|
+
| "clock"
|
|
28
|
+
| "robot";
|
|
29
|
+
|
|
30
|
+
export type Span = {
|
|
31
|
+
readonly text: string;
|
|
32
|
+
readonly tone?: Tone;
|
|
33
|
+
readonly strike?: boolean;
|
|
34
|
+
readonly strong?: boolean;
|
|
35
|
+
readonly code?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type NoticeSeverity = "info" | "warn" | "error";
|
|
39
|
+
|
|
40
|
+
export type KvPair = readonly [string, string];
|
|
41
|
+
|
|
42
|
+
export type ViewBlock =
|
|
43
|
+
| { readonly kind: "text"; readonly text: string; readonly tone?: Tone }
|
|
44
|
+
/** Markdown source; framed as an embed and never re-coloured or re-wrapped. */
|
|
45
|
+
| { readonly kind: "markdown"; readonly text: string }
|
|
46
|
+
/** One line of mixed-tone text, e.g. a `+5`/`-2` diff stat or a rename. */
|
|
47
|
+
| { readonly kind: "spans"; readonly spans: readonly Span[] }
|
|
48
|
+
/** Introduces a sub-item inside a body; painters draw it as a separated heading. */
|
|
49
|
+
| {
|
|
50
|
+
readonly kind: "section";
|
|
51
|
+
readonly label: string;
|
|
52
|
+
readonly icon?: ToolIcon;
|
|
53
|
+
readonly content: readonly ViewBlock[];
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
readonly kind: "code";
|
|
57
|
+
readonly lang: string;
|
|
58
|
+
readonly text: string;
|
|
59
|
+
readonly startLine?: number;
|
|
60
|
+
}
|
|
61
|
+
| {
|
|
62
|
+
readonly kind: "diff";
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly hunks: readonly DiffHunk[];
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
readonly kind: "file";
|
|
68
|
+
readonly path: string;
|
|
69
|
+
/** An undefined end is an open-ended range (`:40`), not a missing one. */
|
|
70
|
+
readonly range?: readonly [number, number | undefined];
|
|
71
|
+
readonly truncated?: boolean;
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
readonly kind: "list";
|
|
75
|
+
readonly items: readonly ViewBlock[];
|
|
76
|
+
readonly ordered?: boolean;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
readonly kind: "kv";
|
|
80
|
+
readonly pairs: readonly KvPair[];
|
|
81
|
+
}
|
|
82
|
+
| { readonly kind: "link"; readonly href: string; readonly label: string }
|
|
83
|
+
/** A file delivered to the reader, not a path on the agent's machine; `url` is server-relative. */
|
|
84
|
+
| {
|
|
85
|
+
readonly kind: "attachment";
|
|
86
|
+
/** What to call it on screen; never a path. */
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly url: string;
|
|
89
|
+
readonly isImage: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** A picture the model was shown, addressed by digest; the bytes never ride the wire, and the client that fetches them builds the URL. */
|
|
92
|
+
| {
|
|
93
|
+
readonly kind: "image";
|
|
94
|
+
readonly sha256: string;
|
|
95
|
+
readonly mimeType: ImageMimeType;
|
|
96
|
+
readonly width: number;
|
|
97
|
+
readonly height: number;
|
|
98
|
+
readonly bytes: number;
|
|
99
|
+
readonly alt: string;
|
|
100
|
+
}
|
|
101
|
+
| {
|
|
102
|
+
readonly kind: "notice";
|
|
103
|
+
readonly text: string;
|
|
104
|
+
readonly severity: NoticeSeverity;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type BlockOf<TKind extends ViewBlock["kind"]> = Extract<
|
|
108
|
+
ViewBlock,
|
|
109
|
+
{ kind: TKind }
|
|
110
|
+
>;
|
|
111
|
+
|
|
112
|
+
export type ToolView = {
|
|
113
|
+
/** Display label for the title row, e.g. `"Read"`; defaults to the definition's label. */
|
|
114
|
+
readonly label?: string;
|
|
115
|
+
/** Tints the label; defaults to the title colour. */
|
|
116
|
+
readonly labelTone?: Tone;
|
|
117
|
+
readonly icon?: ToolIcon;
|
|
118
|
+
/** Painted as one line; a lone `markdown` block is handed over unpainted instead. */
|
|
119
|
+
readonly title: readonly ViewBlock[];
|
|
120
|
+
/** Status content rendered in every state: streaming, collapsed and expanded. */
|
|
121
|
+
readonly summary?: readonly ViewBlock[];
|
|
122
|
+
/** Rendered only once the row is expanded, and never while streaming. */
|
|
123
|
+
readonly body?: readonly ViewBlock[];
|
|
124
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SurfaceName } from "./Surfaces";
|
|
2
|
+
|
|
3
|
+
/** A running surface, and the only thing the daemon asks of one. */
|
|
4
|
+
export type SurfaceHandle = {
|
|
5
|
+
readonly stop: () => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type Surface = {
|
|
9
|
+
readonly name: SurfaceName;
|
|
10
|
+
/** Brings the surface up, resolving once it is serving rather than when it stops. */
|
|
11
|
+
readonly start: () => Promise<SurfaceHandle>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Every surface in one process, and none of them able to take another down: a
|
|
16
|
+
* bot that cannot reach Telegram leaves the browser served, and a port already
|
|
17
|
+
* bound leaves the bot polling.
|
|
18
|
+
*/
|
|
19
|
+
export class Daemon {
|
|
20
|
+
private readonly surfaces: ReadonlyArray<Surface>;
|
|
21
|
+
private readonly live = new Map<SurfaceName, SurfaceHandle>();
|
|
22
|
+
|
|
23
|
+
public constructor(surfaces: ReadonlyArray<Surface>) {
|
|
24
|
+
this.surfaces = surfaces;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public get running(): ReadonlyArray<SurfaceName> {
|
|
28
|
+
return [...this.live.keys()];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Starts every surface; throws only when none of them came up. */
|
|
32
|
+
public async start(): Promise<ReadonlyArray<SurfaceName>> {
|
|
33
|
+
await Promise.all(
|
|
34
|
+
this.surfaces.map(async (surface) => {
|
|
35
|
+
try {
|
|
36
|
+
this.live.set(surface.name, await surface.start());
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`[daemon] the ${surface.name} surface failed:`, err);
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
if (this.live.size === 0) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`no surface started: ${this.surfaces.map((surface) => surface.name).join(", ")}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return this.running;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async stop(): Promise<void> {
|
|
51
|
+
const live = [...this.live];
|
|
52
|
+
this.live.clear();
|
|
53
|
+
await Promise.all(
|
|
54
|
+
live.map(async ([name, handle]) => {
|
|
55
|
+
try {
|
|
56
|
+
await handle.stop();
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`[daemon] the ${name} surface failed to stop:`, err);
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { DaemonUnit, SupersededUnits } from "#core/shared/DaemonUnit";
|
|
4
|
+
import { Supervisor, type Unit } from "#core/shared/Supervisor";
|
|
5
|
+
import { DEFAULT_CLIENT_DIR } from "#server/StaticClient";
|
|
6
|
+
import { WebOptions } from "#server/WebOptions";
|
|
7
|
+
import { Surfaces, type SurfaceName } from "./Surfaces";
|
|
8
|
+
|
|
9
|
+
type FrozenUnit = Unit & { readonly args: ReadonlyArray<string> };
|
|
10
|
+
|
|
11
|
+
/** What this machine serves today: the unit being replaced, or the ones it supersedes. */
|
|
12
|
+
export type Installed = {
|
|
13
|
+
readonly surfaces: ReadonlyArray<SurfaceName>;
|
|
14
|
+
readonly args: ReadonlyArray<string>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const NOTHING: Installed = { surfaces: [], args: [] };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A supervisor starts the daemon with no argv: every flag it needs must be
|
|
21
|
+
* frozen in here. What is installed already is the base the new argv overrides
|
|
22
|
+
* flag by flag, so installing one surface keeps the other one — and its
|
|
23
|
+
* settings. Naming `--surfaces` outright is how a daemon is shrunk again.
|
|
24
|
+
*/
|
|
25
|
+
function unit(
|
|
26
|
+
argv: ReadonlyArray<string>,
|
|
27
|
+
installed: Installed = NOTHING
|
|
28
|
+
): FrozenUnit {
|
|
29
|
+
const surfaces = Surfaces.named(argv)
|
|
30
|
+
? Surfaces.parse(argv)
|
|
31
|
+
: Surfaces.union(installed.surfaces, Surfaces.parse(argv));
|
|
32
|
+
const merged = [...installed.args, ...argv];
|
|
33
|
+
return {
|
|
34
|
+
...DaemonUnit,
|
|
35
|
+
description: `${DaemonUnit.description} (${surfaces.join(", ")})`,
|
|
36
|
+
args: [
|
|
37
|
+
"--surfaces",
|
|
38
|
+
Surfaces.format(surfaces),
|
|
39
|
+
...(surfaces.includes("web")
|
|
40
|
+
? WebOptions.freeze(WebOptions.parse(merged))
|
|
41
|
+
: []),
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The merged unit's own argv, or — before the cutover — whatever the per-surface
|
|
48
|
+
* units were installed with, so a tailnet hostname frozen months ago survives.
|
|
49
|
+
*/
|
|
50
|
+
async function installed(): Promise<Installed> {
|
|
51
|
+
const current = await Supervisor.installedArgs(DaemonUnit);
|
|
52
|
+
if (current.length > 0) {
|
|
53
|
+
return { surfaces: Surfaces.parse(current), args: carriedOver(current) };
|
|
54
|
+
}
|
|
55
|
+
const superseded = (
|
|
56
|
+
await Promise.all(
|
|
57
|
+
SupersededUnits.map((unit) => Supervisor.installedArgs(unit))
|
|
58
|
+
)
|
|
59
|
+
).filter((args) => args.length > 0);
|
|
60
|
+
return {
|
|
61
|
+
surfaces: Surfaces.union(...superseded.map(Surfaces.parse)),
|
|
62
|
+
args: superseded.flatMap(carriedOver),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// `--web-cwd` becomes `--cwd` on the way in, or it would outrank a new `--cwd`.
|
|
67
|
+
function carriedOver(args: ReadonlyArray<string>): ReadonlyArray<string> {
|
|
68
|
+
return args.map((arg) => (arg === "--web-cwd" ? "--cwd" : arg));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function install(argv: ReadonlyArray<string>): Promise<void> {
|
|
72
|
+
const descriptor = unit(argv, await installed());
|
|
73
|
+
const surfaces = Surfaces.parse(descriptor.args);
|
|
74
|
+
if (surfaces.includes("web")) {
|
|
75
|
+
await buildClient();
|
|
76
|
+
}
|
|
77
|
+
const kept = surfaces.filter((name) => !Surfaces.parse(argv).includes(name));
|
|
78
|
+
if (kept.length > 0) {
|
|
79
|
+
console.log(
|
|
80
|
+
`[install] keeping the ${kept.join(", ")} surface this machine already serves; pass --surfaces to change that`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
console.log(
|
|
84
|
+
`[install] unit starts: pim --mode ${descriptor.mode} ${descriptor.args.join(" ")}`
|
|
85
|
+
);
|
|
86
|
+
await Supervisor.install(descriptor, { replaces: SupersededUnits });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function uninstall(): Promise<void> {
|
|
90
|
+
await Supervisor.uninstall(DaemonUnit);
|
|
91
|
+
await Supervisor.supersede(SupersededUnits);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function buildClient(): Promise<void> {
|
|
95
|
+
const at = await Supervisor.detectInstall();
|
|
96
|
+
// Probe `index.html`, not the directory: a half-emptied `dist/client` is still a broken bundle.
|
|
97
|
+
const bundled = await Bun.file(
|
|
98
|
+
join(DEFAULT_CLIENT_DIR, "index.html")
|
|
99
|
+
).exists();
|
|
100
|
+
if (at.kind !== "dev" || bundled) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log("[install] building the web client");
|
|
104
|
+
const proc = Bun.spawn(["bun", "run", "web:build"], {
|
|
105
|
+
cwd: at.packageRoot,
|
|
106
|
+
stdout: "inherit",
|
|
107
|
+
stderr: "inherit",
|
|
108
|
+
});
|
|
109
|
+
const code = await proc.exited;
|
|
110
|
+
if (code !== 0) {
|
|
111
|
+
throw new Error(`bun run web:build exited ${code}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const DaemonInstall = { unit, installed, install, uninstall };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Cli as Argv } from "#core/shared/Cli";
|
|
2
|
+
|
|
3
|
+
export const SURFACE_NAMES = ["web", "telegram"] as const;
|
|
4
|
+
|
|
5
|
+
export type SurfaceName = (typeof SURFACE_NAMES)[number];
|
|
6
|
+
|
|
7
|
+
function isSurfaceName(value: string | undefined): value is SurfaceName {
|
|
8
|
+
return SURFACE_NAMES.includes(value as SurfaceName);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Which surfaces this process should bring up. `--mode web` and `--mode
|
|
13
|
+
* telegram` are the single-surface spellings of `--surfaces`, so a Telegram-only
|
|
14
|
+
* install needs no port and a web-only one needs no token.
|
|
15
|
+
*/
|
|
16
|
+
function parse(args: ReadonlyArray<string>): ReadonlyArray<SurfaceName> {
|
|
17
|
+
let requested: string | undefined;
|
|
18
|
+
let mode: string | undefined;
|
|
19
|
+
Argv.scan(args, (key, take) => {
|
|
20
|
+
switch (key) {
|
|
21
|
+
case "--surfaces":
|
|
22
|
+
requested = take() ?? requested;
|
|
23
|
+
break;
|
|
24
|
+
case "--mode":
|
|
25
|
+
mode = take() ?? mode;
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (requested === undefined) {
|
|
32
|
+
return isSurfaceName(mode) ? [mode] : SURFACE_NAMES;
|
|
33
|
+
}
|
|
34
|
+
const names = requested
|
|
35
|
+
.split(",")
|
|
36
|
+
.map((name) => name.trim())
|
|
37
|
+
.filter((name) => name.length > 0);
|
|
38
|
+
const unknown = names.filter((name) => !isSurfaceName(name));
|
|
39
|
+
if (names.length === 0 || unknown.length > 0) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`--surfaces takes a comma-separated list of ${SURFACE_NAMES.join(", ")}, got "${requested}"`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return SURFACE_NAMES.filter((name) => names.includes(name));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function format(names: ReadonlyArray<SurfaceName>): string {
|
|
48
|
+
return names.join(",");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether the caller named the surfaces outright, rather than implying them by mode. */
|
|
52
|
+
function named(args: ReadonlyArray<string>): boolean {
|
|
53
|
+
return args.some(
|
|
54
|
+
(arg) => arg === "--surfaces" || arg.startsWith("--surfaces=")
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function union(
|
|
59
|
+
...groups: ReadonlyArray<ReadonlyArray<SurfaceName>>
|
|
60
|
+
): ReadonlyArray<SurfaceName> {
|
|
61
|
+
return SURFACE_NAMES.filter((name) =>
|
|
62
|
+
groups.some((group) => group.includes(name))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Surfaces = { parse, format, named, union };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AgentRuntime } from "#core/session/AgentRuntime";
|
|
2
|
+
import { Bot } from "#telegram/Bot";
|
|
3
|
+
import { Config } from "#telegram/Config";
|
|
4
|
+
import type { Surface } from "./Daemon";
|
|
5
|
+
|
|
6
|
+
function create(args: ReadonlyArray<string>, runtime: AgentRuntime): Surface {
|
|
7
|
+
return {
|
|
8
|
+
name: "telegram",
|
|
9
|
+
start: async () => {
|
|
10
|
+
const config = await Config.load(Config.parseArgs(args));
|
|
11
|
+
const bot = new Bot(config, runtime);
|
|
12
|
+
await bot.start();
|
|
13
|
+
return { stop: () => bot.stop() };
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `--print-config`, which resolves the bot's config the way a run would and stops there. */
|
|
19
|
+
async function printConfig(args: ReadonlyArray<string>): Promise<void> {
|
|
20
|
+
const config = await Config.load(Config.parseArgs(args));
|
|
21
|
+
console.log(
|
|
22
|
+
JSON.stringify(
|
|
23
|
+
{ ...config, token: config.token ? "***" : undefined },
|
|
24
|
+
null,
|
|
25
|
+
2
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const TelegramSurface = { create, printConfig };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { AttachmentStore } from "#core/attachments/AttachmentStore";
|
|
4
|
+
import type { AgentRuntime } from "#core/session/AgentRuntime";
|
|
5
|
+
import { SessionRegistry } from "#core/session/SessionRegistry";
|
|
6
|
+
import { Tools } from "#core/shared/Tools";
|
|
7
|
+
import { defaultAttachmentsRoot } from "#server/AttachmentEndpoint";
|
|
8
|
+
import { SendFileTool } from "#server/SendFileTool";
|
|
9
|
+
import { WebOptions } from "#server/WebOptions";
|
|
10
|
+
import { WsGateway } from "#server/WsGateway";
|
|
11
|
+
import type { Surface } from "./Daemon";
|
|
12
|
+
|
|
13
|
+
function create(args: ReadonlyArray<string>, runtime: AgentRuntime): Surface {
|
|
14
|
+
return {
|
|
15
|
+
name: "web",
|
|
16
|
+
start: async () => {
|
|
17
|
+
const options = WebOptions.parse(args);
|
|
18
|
+
const attachmentsRoot = defaultAttachmentsRoot();
|
|
19
|
+
const store = new AttachmentStore(attachmentsRoot);
|
|
20
|
+
const registry = new SessionRegistry({
|
|
21
|
+
runtime,
|
|
22
|
+
defaults: { cwd: options.cwd },
|
|
23
|
+
customTools: ({ cwd, sessionId }) => [
|
|
24
|
+
Tools.wrap(
|
|
25
|
+
SendFileTool.build({ store, cwd, sessionId })
|
|
26
|
+
) as ToolDefinition,
|
|
27
|
+
],
|
|
28
|
+
surface: "web browser",
|
|
29
|
+
});
|
|
30
|
+
await registry.init();
|
|
31
|
+
|
|
32
|
+
const gateway = new WsGateway({
|
|
33
|
+
registry,
|
|
34
|
+
hostname: options.hostname,
|
|
35
|
+
port: WebOptions.port(options),
|
|
36
|
+
attachmentsRoot,
|
|
37
|
+
...(options.clientDir === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { clientDir: options.clientDir }),
|
|
40
|
+
});
|
|
41
|
+
gateway.start();
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
`pim-server listening on ${gateway.url} (web UI: http://${options.hostname}:${gateway.port})\n`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
stop: async () => {
|
|
48
|
+
await gateway.stop();
|
|
49
|
+
await registry.disposeAll();
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const WebSurface = { create };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AgentRuntime } from "#core/session/AgentRuntime";
|
|
2
|
+
import { Daemon, type Surface } from "./Daemon";
|
|
3
|
+
import { Surfaces } from "./Surfaces";
|
|
4
|
+
import { TelegramSurface } from "./TelegramSurface";
|
|
5
|
+
import { WebSurface } from "./WebSurface";
|
|
6
|
+
|
|
7
|
+
/** Every surface in this process shares one pi installation, and so one `auth.json`. */
|
|
8
|
+
export function build(args: ReadonlyArray<string>): ReadonlyArray<Surface> {
|
|
9
|
+
const runtime = new AgentRuntime();
|
|
10
|
+
return Surfaces.parse(args).map((name) =>
|
|
11
|
+
name === "web"
|
|
12
|
+
? WebSurface.create(args, runtime)
|
|
13
|
+
: TelegramSurface.create(args, runtime)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function start(args: ReadonlyArray<string>): Promise<void> {
|
|
18
|
+
if (
|
|
19
|
+
Surfaces.parse(args).includes("telegram") &&
|
|
20
|
+
args.includes("--print-config")
|
|
21
|
+
) {
|
|
22
|
+
await TelegramSurface.printConfig(args);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const daemon = new Daemon(build(args));
|
|
27
|
+
const running = await daemon.start();
|
|
28
|
+
console.log(`[daemon] serving ${running.join(", ")}`);
|
|
29
|
+
|
|
30
|
+
await new Promise<void>((resolve) => {
|
|
31
|
+
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
32
|
+
process.once(signal, () => {
|
|
33
|
+
void daemon.stop().then(resolve);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|