pim-agent 0.0.1 → 0.9.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.
Files changed (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -7
  3. package/bin/pim.ts +151 -0
  4. package/package.json +82 -4
  5. package/packages/core/src/attachments/AttachmentStore.ts +99 -0
  6. package/packages/core/src/attachments/Attachments.ts +69 -0
  7. package/packages/core/src/extensions/CoreExtensions.ts +51 -0
  8. package/packages/core/src/extensions/apply-patch/coordinator.ts +53 -0
  9. package/packages/core/src/extensions/apply-patch/executor.ts +550 -0
  10. package/packages/core/src/extensions/apply-patch/index.ts +69 -0
  11. package/packages/core/src/extensions/apply-patch/matcher.ts +47 -0
  12. package/packages/core/src/extensions/apply-patch/model.ts +39 -0
  13. package/packages/core/src/extensions/apply-patch/parser.ts +379 -0
  14. package/packages/core/src/extensions/apply-patch/render.ts +216 -0
  15. package/packages/core/src/extensions/apply-patch/schema.ts +37 -0
  16. package/packages/core/src/extensions/apply-patch/types.ts +30 -0
  17. package/packages/core/src/extensions/bash/capture.ts +86 -0
  18. package/packages/core/src/extensions/bash/format.ts +110 -0
  19. package/packages/core/src/extensions/bash/image.ts +31 -0
  20. package/packages/core/src/extensions/bash/index.ts +88 -0
  21. package/packages/core/src/extensions/bash/render.ts +31 -0
  22. package/packages/core/src/extensions/bash/run.ts +221 -0
  23. package/packages/core/src/extensions/bash/schema.ts +64 -0
  24. package/packages/core/src/extensions/edit/edit.ts +366 -0
  25. package/packages/core/src/extensions/edit/index.ts +36 -0
  26. package/packages/core/src/extensions/edit/render.ts +16 -0
  27. package/packages/core/src/extensions/edit/schema.ts +37 -0
  28. package/packages/core/src/extensions/glob/glob.ts +34 -0
  29. package/packages/core/src/extensions/glob/index.ts +83 -0
  30. package/packages/core/src/extensions/glob/render.ts +72 -0
  31. package/packages/core/src/extensions/glob/schema.ts +66 -0
  32. package/packages/core/src/extensions/grep/grep.ts +256 -0
  33. package/packages/core/src/extensions/grep/index.ts +114 -0
  34. package/packages/core/src/extensions/grep/render.ts +271 -0
  35. package/packages/core/src/extensions/grep/schema.ts +92 -0
  36. package/packages/core/src/extensions/read/ImageMemory.ts +42 -0
  37. package/packages/core/src/extensions/read/image.ts +57 -0
  38. package/packages/core/src/extensions/read/index.ts +88 -0
  39. package/packages/core/src/extensions/read/read.ts +191 -0
  40. package/packages/core/src/extensions/read/render.ts +58 -0
  41. package/packages/core/src/extensions/read/schema.ts +49 -0
  42. package/packages/core/src/extensions/subagent/index.ts +33 -0
  43. package/packages/core/src/extensions/subagent/render.ts +127 -0
  44. package/packages/core/src/extensions/subagent/schema.ts +9 -0
  45. package/packages/core/src/extensions/subagent/subagent.ts +419 -0
  46. package/packages/core/src/extensions/system-prompt/index.ts +28 -0
  47. package/packages/core/src/extensions/system-prompt/prompt.ts +228 -0
  48. package/packages/core/src/extensions/todo/index.ts +123 -0
  49. package/packages/core/src/extensions/todo/render.ts +156 -0
  50. package/packages/core/src/extensions/todo/schema.ts +24 -0
  51. package/packages/core/src/extensions/todo/todo.ts +185 -0
  52. package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +187 -0
  53. package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +183 -0
  54. package/packages/core/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +512 -0
  55. package/packages/core/src/extensions/web-fetch/fetch.ts +265 -0
  56. package/packages/core/src/extensions/web-fetch/image.ts +165 -0
  57. package/packages/core/src/extensions/web-fetch/index.ts +83 -0
  58. package/packages/core/src/extensions/web-fetch/render.ts +100 -0
  59. package/packages/core/src/extensions/web-fetch/schema.ts +46 -0
  60. package/packages/core/src/extensions/web-search/ExaMcpClient.ts +251 -0
  61. package/packages/core/src/extensions/web-search/SearchBreaker.ts +147 -0
  62. package/packages/core/src/extensions/web-search/SearchChain.ts +77 -0
  63. package/packages/core/src/extensions/web-search/index.ts +90 -0
  64. package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +195 -0
  65. package/packages/core/src/extensions/web-search/providers/ExaProvider.ts +54 -0
  66. package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +144 -0
  67. package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +118 -0
  68. package/packages/core/src/extensions/web-search/render.ts +49 -0
  69. package/packages/core/src/extensions/web-search/schema.ts +29 -0
  70. package/packages/core/src/extensions/web-search/search.ts +23 -0
  71. package/packages/core/src/extensions/write/index.ts +58 -0
  72. package/packages/core/src/extensions/write/render.ts +16 -0
  73. package/packages/core/src/extensions/write/schema.ts +14 -0
  74. package/packages/core/src/extensions/write/write.ts +87 -0
  75. package/packages/core/src/picker/FilePickerSuggestionEngine.ts +14 -0
  76. package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +52 -0
  77. package/packages/core/src/picker/PickerItem.ts +6 -0
  78. package/packages/core/src/picker/PickerService.ts +110 -0
  79. package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +56 -0
  80. package/packages/core/src/picker/WorkerFilePickerSuggestionEngine.ts +268 -0
  81. package/packages/core/src/picker/catalog.ts +220 -0
  82. package/packages/core/src/picker/commandRanker.ts +17 -0
  83. package/packages/core/src/picker/filePickerWorker.ts +66 -0
  84. package/packages/core/src/picker/filePickerWorkerMessages.ts +39 -0
  85. package/packages/core/src/picker/ranker.ts +245 -0
  86. package/packages/core/src/picker/token.ts +70 -0
  87. package/packages/core/src/session/AgentRuntime.ts +85 -0
  88. package/packages/core/src/session/EventLog.ts +213 -0
  89. package/packages/core/src/session/MessageText.ts +21 -0
  90. package/packages/core/src/session/ReadCursors.ts +94 -0
  91. package/packages/core/src/session/SessionCache.ts +68 -0
  92. package/packages/core/src/session/SessionHost.ts +876 -0
  93. package/packages/core/src/session/SessionLease.ts +279 -0
  94. package/packages/core/src/session/SessionRegistry.ts +169 -0
  95. package/packages/core/src/session/WriteMark.ts +37 -0
  96. package/packages/core/src/shared/Cli.ts +25 -0
  97. package/packages/core/src/shared/DaemonUnit.ts +13 -0
  98. package/packages/core/src/shared/DiffLines.ts +304 -0
  99. package/packages/core/src/shared/DiffRenderer.ts +283 -0
  100. package/packages/core/src/shared/DiffView.ts +29 -0
  101. package/packages/core/src/shared/Directories.ts +61 -0
  102. package/packages/core/src/shared/EditMatcher.ts +767 -0
  103. package/packages/core/src/shared/Errors.ts +9 -0
  104. package/packages/core/src/shared/ExtensionToggles.ts +130 -0
  105. package/packages/core/src/shared/FileEnumerator.ts +361 -0
  106. package/packages/core/src/shared/FileScanner.ts +57 -0
  107. package/packages/core/src/shared/FileWatch.ts +107 -0
  108. package/packages/core/src/shared/Format.ts +74 -0
  109. package/packages/core/src/shared/Fs.ts +91 -0
  110. package/packages/core/src/shared/FsErrors.ts +67 -0
  111. package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
  112. package/packages/core/src/shared/Git.ts +89 -0
  113. package/packages/core/src/shared/GlobExclusions.ts +13 -0
  114. package/packages/core/src/shared/Http.ts +14 -0
  115. package/packages/core/src/shared/ImageMime.ts +36 -0
  116. package/packages/core/src/shared/Images.ts +357 -0
  117. package/packages/core/src/shared/Json.ts +19 -0
  118. package/packages/core/src/shared/Languages.ts +95 -0
  119. package/packages/core/src/shared/Levenshtein.ts +30 -0
  120. package/packages/core/src/shared/Lines.ts +79 -0
  121. package/packages/core/src/shared/McpClient.ts +444 -0
  122. package/packages/core/src/shared/OutputBudget.ts +84 -0
  123. package/packages/core/src/shared/PatchSummary.ts +40 -0
  124. package/packages/core/src/shared/Paths.ts +80 -0
  125. package/packages/core/src/shared/PimSettings.ts +128 -0
  126. package/packages/core/src/shared/PimVersion.ts +93 -0
  127. package/packages/core/src/shared/Pool.ts +26 -0
  128. package/packages/core/src/shared/Proc.ts +30 -0
  129. package/packages/core/src/shared/RateLimiter.ts +50 -0
  130. package/packages/core/src/shared/Renderer.ts +371 -0
  131. package/packages/core/src/shared/SafePath.ts +17 -0
  132. package/packages/core/src/shared/SearchRender.ts +82 -0
  133. package/packages/core/src/shared/SendFile.ts +23 -0
  134. package/packages/core/src/shared/SpillCache.ts +78 -0
  135. package/packages/core/src/shared/SubagentLogs.ts +102 -0
  136. package/packages/core/src/shared/Supervisor.ts +460 -0
  137. package/packages/core/src/shared/Sweeper.ts +32 -0
  138. package/packages/core/src/shared/Tools.ts +801 -0
  139. package/packages/core/src/shared/Updater.ts +193 -0
  140. package/packages/core/src/view/AnsiPainter.ts +227 -0
  141. package/packages/core/src/view/BodyRenderer.ts +102 -0
  142. package/packages/core/src/view/DiffBlocks.ts +52 -0
  143. package/packages/core/src/view/DiffLayout.ts +69 -0
  144. package/packages/core/src/view/MarkdownPainter.ts +314 -0
  145. package/packages/core/src/view/Painting.ts +182 -0
  146. package/packages/core/src/view/ViewBlock.ts +124 -0
  147. package/packages/daemon/src/Daemon.ts +63 -0
  148. package/packages/daemon/src/DaemonInstall.ts +115 -0
  149. package/packages/daemon/src/Surfaces.ts +66 -0
  150. package/packages/daemon/src/TelegramSurface.ts +30 -0
  151. package/packages/daemon/src/WebSurface.ts +57 -0
  152. package/packages/daemon/src/index.ts +37 -0
  153. package/packages/protocol/src/Command.ts +92 -0
  154. package/packages/protocol/src/ImageRoute.ts +23 -0
  155. package/packages/protocol/src/Protocol.ts +6 -0
  156. package/packages/protocol/src/ServerEvent.ts +251 -0
  157. package/packages/server/src/AttachmentEndpoint.ts +184 -0
  158. package/packages/server/src/ClientConnection.ts +265 -0
  159. package/packages/server/src/ImageEndpoint.ts +36 -0
  160. package/packages/server/src/ProbeClient.ts +312 -0
  161. package/packages/server/src/Reloader.ts +93 -0
  162. package/packages/server/src/SendFileTool.ts +88 -0
  163. package/packages/server/src/SessionCatalogue.ts +202 -0
  164. package/packages/server/src/SessionProjection.ts +194 -0
  165. package/packages/server/src/SessionStream.ts +601 -0
  166. package/packages/server/src/StaticClient.ts +95 -0
  167. package/packages/server/src/WebOptions.ts +72 -0
  168. package/packages/server/src/WsGateway.ts +563 -0
  169. package/packages/server/src/probe.ts +146 -0
  170. package/packages/telegram/src/Bot.ts +229 -0
  171. package/packages/telegram/src/Commands.ts +768 -0
  172. package/packages/telegram/src/Config.ts +148 -0
  173. package/packages/telegram/src/Markdown.ts +235 -0
  174. package/packages/telegram/src/Message.ts +152 -0
  175. package/packages/telegram/src/Renderer.ts +456 -0
  176. package/packages/telegram/src/SendFileSchema.ts +18 -0
  177. package/packages/telegram/src/SendFileTool.ts +65 -0
  178. package/packages/telegram/src/Session.ts +252 -0
  179. package/packages/telegram/src/SessionRegistry.ts +126 -0
  180. package/packages/telegram/src/TaskScheduler.ts +296 -0
  181. package/packages/telegram/src/TaskSchema.ts +88 -0
  182. package/packages/telegram/src/TaskStore.ts +67 -0
  183. package/packages/telegram/src/TaskTool.ts +208 -0
  184. package/packages/telegram/src/TelegramHtml.ts +288 -0
  185. package/packages/telegram/src/TypingIndicator.ts +43 -0
  186. package/packages/telegram/src/UpdateConfirm.ts +61 -0
  187. package/packages/tui/src/extensions/_init/index.ts +144 -0
  188. package/packages/tui/src/extensions/command-picker/index.ts +43 -0
  189. package/packages/tui/src/extensions/file-picker/index.ts +156 -0
  190. package/packages/tui/src/extensions/footer/index.ts +117 -0
  191. package/packages/tui/src/extensions/footer/powerline.ts +87 -0
  192. package/packages/tui/src/extensions/footer/segments.ts +202 -0
  193. package/packages/tui/src/extensions/pim/index.ts +89 -0
  194. package/packages/tui/src/extensions/session-lease/index.ts +248 -0
  195. package/packages/tui/src/extensions/tps/index.ts +118 -0
  196. package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
  197. package/packages/tui/src/extensions/wrapProvider.ts +18 -0
  198. package/packages/tui/src/themes/pim-dark.json +84 -0
  199. package/packages/tui/src/themes/pim-light.json +84 -0
  200. package/packages/tui/src/themes/themeCliArgs.ts +20 -0
  201. package/packages/web/dist/client/apple-touch-icon.png +0 -0
  202. package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
  203. package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
  204. package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
  205. package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
  206. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
  207. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
  208. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
  209. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
  210. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
  211. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
  212. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
  213. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
  214. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
  215. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
  216. package/packages/web/dist/client/assets/core-CGdQx9la.js +3 -0
  217. package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
  218. package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
  219. package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
  220. package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
  221. package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
  222. package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
  223. package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
  224. package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
  225. package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
  226. package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
  227. package/packages/web/dist/client/assets/index-BMNwleWO.js +40 -0
  228. package/packages/web/dist/client/assets/index-CqjB2RPr.css +1 -0
  229. package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
  230. package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
  231. package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
  232. package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
  233. package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
  234. package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
  235. package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
  236. package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
  237. package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
  238. package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
  239. package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
  240. package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
  241. package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
  242. package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
  243. package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
  244. package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
  245. package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
  246. package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
  247. package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
  248. package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
  249. package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
  250. package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
  251. package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
  252. package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
  253. package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
  254. package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
  255. package/packages/web/dist/client/favicon.svg +1 -0
  256. package/packages/web/dist/client/icon-192.png +0 -0
  257. package/packages/web/dist/client/icon-512.png +0 -0
  258. package/packages/web/dist/client/icon-maskable.png +0 -0
  259. package/packages/web/dist/client/index.html +24 -0
  260. package/packages/web/dist/client/manifest.webmanifest +21 -0
  261. package/packages/web/dist/client/wordmark.svg +1 -0
@@ -0,0 +1,146 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ import type { AttachmentRef } from "#protocol/Command";
4
+ import { isDurableEvent, type ServerEvent } from "#protocol/ServerEvent";
5
+ import { ProbeClient } from "./ProbeClient";
6
+
7
+ const USAGE = `pim probe — CLI client for pim-server, dumps every frame as JSONL
8
+
9
+ bun run probe [options]
10
+
11
+ --url <ws://host:port> server to connect to (default ws://127.0.0.1:4319)
12
+ --session <uuid> attach to an existing session (default: create one)
13
+ --cwd <path> cwd for a session this probe creates
14
+ --from-seq <n> resume from this durable seq (default 0)
15
+ --prompt <text> send a user message once attached; sent into a turn
16
+ already running it steers that turn
17
+ --pick-files <query> ask the server to complete an @ path, print the rows
18
+ --pick-commands <query> ask the server for matching skills and commands
19
+ --list-sessions print pi's session catalogue and exit
20
+ --list-models print the models this server can switch to
21
+ --upload <path> transfer a local file to the server, attach it to
22
+ --prompt (repeatable)
23
+ --cancel cancel the current turn
24
+ --dequeue take back what the turn in flight is holding,
25
+ without stopping it
26
+ --wait keep streaming after the turn ends (Ctrl-C to stop)
27
+ --quiet print only durable events
28
+ --protocol-version <n> override the handshake version (to test rejection)
29
+ `;
30
+
31
+ const { values } = parseArgs({
32
+ options: {
33
+ url: { type: "string", default: "ws://127.0.0.1:4319" },
34
+ session: { type: "string" },
35
+ cwd: { type: "string" },
36
+ "from-seq": { type: "string", default: "0" },
37
+ prompt: { type: "string" },
38
+ "pick-files": { type: "string" },
39
+ "pick-commands": { type: "string" },
40
+ "list-sessions": { type: "boolean", default: false },
41
+ "list-models": { type: "boolean", default: false },
42
+ upload: { type: "string", multiple: true },
43
+ cancel: { type: "boolean", default: false },
44
+ dequeue: { type: "boolean", default: false },
45
+ wait: { type: "boolean", default: false },
46
+ quiet: { type: "boolean", default: false },
47
+ "protocol-version": { type: "string" },
48
+ help: { type: "boolean", default: false },
49
+ },
50
+ allowPositionals: false,
51
+ });
52
+
53
+ if (values.help) {
54
+ process.stdout.write(USAGE);
55
+ process.exit(0);
56
+ }
57
+
58
+ function dump(event: ServerEvent): void {
59
+ if (values.quiet && !isDurableEvent(event)) {
60
+ return;
61
+ }
62
+ process.stdout.write(`${JSON.stringify(event)}\n`);
63
+ }
64
+
65
+ const probe = new ProbeClient({
66
+ url: values.url,
67
+ ...(values.session === undefined ? {} : { sessionId: values.session }),
68
+ ...(values.cwd === undefined ? {} : { cwd: values.cwd }),
69
+ fromSeq: Number(values["from-seq"]),
70
+ ...(values["protocol-version"] === undefined
71
+ ? {}
72
+ : { protocolVersion: Number(values["protocol-version"]) }),
73
+ onEvent: dump,
74
+ });
75
+
76
+ const attached = await probe.connect();
77
+ if (!attached.success) {
78
+ process.stderr.write(`attach failed: ${attached.error ?? "unknown error"}\n`);
79
+ process.exit(1);
80
+ }
81
+
82
+ const mark = probe.events.length;
83
+ if (values["pick-files"] !== undefined) {
84
+ const started = Bun.nanoseconds();
85
+ const items = await probe.files.rank(values["pick-files"], { limit: 50 });
86
+ const ms = (Bun.nanoseconds() - started) / 1e6;
87
+ process.stderr.write(
88
+ `pick-files: ${items?.length ?? 0} rows in ${ms.toFixed(1)}ms\n`
89
+ );
90
+ for (const item of items ?? []) {
91
+ process.stdout.write(`${JSON.stringify(item)}\n`);
92
+ }
93
+ }
94
+ if (values["list-sessions"]) {
95
+ for (const summary of await probe.listSessions(values.cwd)) {
96
+ process.stdout.write(`${JSON.stringify(summary)}\n`);
97
+ }
98
+ }
99
+ if (values["list-models"]) {
100
+ process.stdout.write(`${JSON.stringify(await probe.listModels())}\n`);
101
+ }
102
+ if (values["pick-commands"] !== undefined) {
103
+ const items = await probe.pickCommands(values["pick-commands"]);
104
+ process.stderr.write(`pick-commands: ${items.length} rows\n`);
105
+ for (const item of items) {
106
+ process.stdout.write(`${JSON.stringify(item)}\n`);
107
+ }
108
+ }
109
+
110
+ const uploaded: AttachmentRef[] = [];
111
+ for (const path of values.upload ?? []) {
112
+ const file = await probe.upload(path);
113
+ uploaded.push({ id: file.id });
114
+ process.stderr.write(`uploaded: ${JSON.stringify(file)}\n`);
115
+ }
116
+
117
+ if (values.cancel) {
118
+ const response = await probe.send({
119
+ type: "cancel",
120
+ sessionId: probe.sessionId ?? "",
121
+ });
122
+ process.stderr.write(`cancel: ${JSON.stringify(response)}\n`);
123
+ }
124
+ if (values.dequeue) {
125
+ const response = await probe.send({
126
+ type: "dequeue",
127
+ sessionId: probe.sessionId ?? "",
128
+ });
129
+ process.stderr.write(`dequeue: ${JSON.stringify(response)}\n`);
130
+ }
131
+ if (values.prompt !== undefined) {
132
+ await probe.promptWith(values.prompt, uploaded);
133
+ }
134
+
135
+ if (values.prompt !== undefined) {
136
+ await probe.waitFor(
137
+ (event) => event.type === "session_state" && event.status === "idle",
138
+ { timeoutMs: 10 * 60_000, from: mark }
139
+ );
140
+ }
141
+
142
+ if (values.wait) {
143
+ await new Promise<never>(() => {});
144
+ }
145
+ probe.close();
146
+ process.stderr.write(`session ${probe.sessionId} @ seq ${probe.seq}\n`);
@@ -0,0 +1,229 @@
1
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
+ import { Bot as Grammy } from "grammy";
3
+
4
+ import type { AgentRuntime } from "#core/session/AgentRuntime";
5
+ import { PimVersion } from "#core/shared/PimVersion";
6
+ import { Commands } from "./Commands";
7
+ import type { TelegramConfig } from "./Config";
8
+ import { Message, type Prompt } from "./Message";
9
+ import { Renderer, type TurnEndState } from "./Renderer";
10
+ import { Session, type SessionId } from "./Session";
11
+ import { SessionRegistry } from "./SessionRegistry";
12
+ import type { ScheduledTask } from "./TaskSchema";
13
+ import { TaskScheduler } from "./TaskScheduler";
14
+ import { UpdateConfirm } from "./UpdateConfirm";
15
+
16
+ export class Bot {
17
+ private readonly grammy: Grammy;
18
+ private readonly allowSet: ReadonlySet<number>;
19
+ private readonly registry: SessionRegistry;
20
+ private readonly scheduler: TaskScheduler;
21
+ private readonly config: TelegramConfig;
22
+ private readonly commands: Commands;
23
+ /** Grammy's long poll, which only settles once the bot is stopped. */
24
+ private polling: Promise<void> | undefined;
25
+
26
+ public constructor(config: TelegramConfig, runtime: AgentRuntime) {
27
+ this.config = config;
28
+ this.grammy = new Grammy(config.token);
29
+ this.allowSet = new Set(config.allow);
30
+ this.scheduler = new TaskScheduler({
31
+ configDir: config.configDir,
32
+ runTask: (task) => this.runScheduledTask(task),
33
+ });
34
+ this.registry = new SessionRegistry(
35
+ config,
36
+ this.grammy.api,
37
+ this.scheduler,
38
+ runtime
39
+ );
40
+ this.commands = new Commands(config, this.grammy.api, this.registry);
41
+
42
+ this.grammy.on("callback_query:data", async (ctx) => {
43
+ if (!this.allowSet.has(ctx.chat?.id ?? -1)) {
44
+ return;
45
+ }
46
+ await this.commands.handleCallback(ctx);
47
+ });
48
+
49
+ this.grammy.on("message", async (ctx) => {
50
+ const chatId = ctx.chat.id;
51
+ if (!this.allowSet.has(chatId)) {
52
+ console.log(`[recv] reject chatId=${chatId} (not in allow-list)`);
53
+ return;
54
+ }
55
+ const sessionId: SessionId = {
56
+ chatId,
57
+ threadId: ctx.message.message_thread_id,
58
+ };
59
+ let prompt: Prompt | undefined;
60
+ try {
61
+ prompt = await Message.toPrompt(
62
+ ctx,
63
+ this.config.token,
64
+ this.config.configDir,
65
+ sessionId
66
+ );
67
+ } catch (err) {
68
+ await this.reportFailure(sessionId, "attachment download failed", err);
69
+ return;
70
+ }
71
+ if (!prompt) {
72
+ return;
73
+ }
74
+ const preview = prompt.text.slice(0, 120).replace(/\s+/g, " ");
75
+ console.log(
76
+ `[recv] chatId=${chatId} threadId=${sessionId.threadId ?? "main"} ${preview}`
77
+ );
78
+
79
+ const session = this.registry.get(sessionId);
80
+ if (prompt.text.startsWith("/") && !prompt.options.images?.length) {
81
+ await this.commands.handleCommand(ctx, session, prompt.text);
82
+ return;
83
+ }
84
+
85
+ void session
86
+ .run(
87
+ (agent) => this.handleTurn(session, agent, prompt),
88
+ session.temporary ? { isolated: true } : undefined
89
+ )
90
+ .catch((err: unknown) =>
91
+ this.reportFailure(sessionId, "turn setup failed", err)
92
+ );
93
+ });
94
+
95
+ this.grammy.catch((err) => {
96
+ console.error("[bot] handler error:", err.error);
97
+ });
98
+ }
99
+
100
+ public async start(): Promise<void> {
101
+ await this.registry.init();
102
+ await this.grammy.init();
103
+ await this.processBootUpdateConfirm();
104
+ await this.grammy.api.deleteWebhook({ drop_pending_updates: true });
105
+ const username = this.grammy.botInfo.username;
106
+ this.registry.setBotUsername(username);
107
+ console.log(`bot @${username} ready`);
108
+ await this.scheduler.start();
109
+ // Polling outlives this call: awaiting it would hold every other surface down with it.
110
+ this.polling = this.grammy.start().catch((err: unknown) => {
111
+ console.error("[bot] polling stopped:", err);
112
+ });
113
+ }
114
+
115
+ public async stop(): Promise<void> {
116
+ await this.scheduler.stop();
117
+ await this.grammy.stop();
118
+ await this.polling;
119
+ await this.registry.disposeAll();
120
+ }
121
+
122
+ private async runScheduledTask(task: ScheduledTask): Promise<void> {
123
+ const sessionId: SessionId = {
124
+ chatId: task.chatId,
125
+ threadId: task.threadId,
126
+ };
127
+ const prompt: Prompt = { text: task.prompt, options: {} };
128
+ const session = this.registry.get(sessionId);
129
+ await session
130
+ .run(
131
+ (agent) => this.handleTurn(session, agent, prompt),
132
+ task.isolatedSession ? { isolated: true } : undefined
133
+ )
134
+ .catch((err: unknown) =>
135
+ this.reportFailure(sessionId, `scheduled task ${task.id} failed`, err)
136
+ );
137
+ }
138
+
139
+ private async reportFailure(
140
+ sessionId: SessionId,
141
+ label: string,
142
+ err: unknown
143
+ ): Promise<void> {
144
+ console.error(`[bot] ${label}:`, err);
145
+ await this.grammy.api
146
+ .sendMessage(
147
+ sessionId.chatId,
148
+ `⚠️ ${(err as Error).message ?? String(err)}`,
149
+ {
150
+ message_thread_id: sessionId.threadId,
151
+ link_preview_options: { is_disabled: true },
152
+ }
153
+ )
154
+ .catch((e: unknown) => console.error(`[send] plain failed:`, e));
155
+ }
156
+
157
+ private async processBootUpdateConfirm(): Promise<void> {
158
+ const entries = await UpdateConfirm.read(this.config.configDir);
159
+ if (entries.length === 0) {
160
+ return;
161
+ }
162
+ const [version, piVersion] = await Promise.all([
163
+ PimVersion.current(),
164
+ PimVersion.pi(),
165
+ ]);
166
+ const text = `✅ Pim Agent updated to v${version} (pi v${piVersion})!`;
167
+ await Promise.all(
168
+ entries.map((e) =>
169
+ this.grammy.api
170
+ .editMessageText(e.chatId, e.messageId, text, {
171
+ link_preview_options: { is_disabled: true },
172
+ })
173
+ .catch((err) => console.warn(`[update-confirm] edit failed:`, err))
174
+ )
175
+ );
176
+ await UpdateConfirm.clear(this.config.configDir);
177
+ }
178
+
179
+ private async handleTurn(
180
+ session: Session,
181
+ agent: AgentSession,
182
+ prompt: Prompt
183
+ ): Promise<void> {
184
+ const renderer = new Renderer(session, this.grammy.api);
185
+ const unsubscribe = agent.subscribe((event) => renderer.handleEvent(event));
186
+ await renderer.start();
187
+ try {
188
+ await agent.prompt(prompt.text, {
189
+ ...prompt.options,
190
+ source: "rpc",
191
+ });
192
+ const final = extractFinalResult(agent);
193
+ await renderer.finish(final.text, final.state);
194
+ } catch (err) {
195
+ const msg = (err as Error).message ?? String(err);
196
+ console.error(`[bot] turn failed:`, err);
197
+ await renderer.finish(`⚠️ ${msg}`, "error");
198
+ } finally {
199
+ unsubscribe();
200
+ }
201
+ }
202
+ }
203
+
204
+ function extractFinalResult(agent: AgentSession): {
205
+ readonly text: string;
206
+ readonly state: TurnEndState;
207
+ } {
208
+ const messages = agent.messages;
209
+ for (let i = messages.length - 1; i >= 0; i--) {
210
+ const msg = messages[i]!;
211
+ if (msg.role !== "assistant") {
212
+ continue;
213
+ }
214
+ if (msg.stopReason === "error") {
215
+ return { text: msg.errorMessage ?? "", state: "error" };
216
+ }
217
+ if (msg.stopReason === "aborted") {
218
+ return { text: msg.errorMessage ?? "", state: "cancelled" };
219
+ }
220
+ const parts: string[] = [];
221
+ for (const block of msg.content) {
222
+ if (block.type === "text") {
223
+ parts.push(block.text);
224
+ }
225
+ }
226
+ return { text: parts.join("").trim(), state: "ok" };
227
+ }
228
+ return { text: "", state: "ok" };
229
+ }