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.
Files changed (275) 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 +54 -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 +177 -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 +36 -0
  47. package/packages/core/src/extensions/system-prompt/prompt.ts +232 -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 +132 -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 +77 -0
  87. package/packages/core/src/session/AgentRuntime.ts +85 -0
  88. package/packages/core/src/session/EventLog.ts +156 -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/SearchIndex.ts +752 -0
  92. package/packages/core/src/session/SearchTokens.ts +97 -0
  93. package/packages/core/src/session/SessionCache.ts +68 -0
  94. package/packages/core/src/session/SessionDigest.ts +161 -0
  95. package/packages/core/src/session/SessionHost.ts +892 -0
  96. package/packages/core/src/session/SessionLease.ts +279 -0
  97. package/packages/core/src/session/SessionMeta.ts +267 -0
  98. package/packages/core/src/session/SessionName.ts +18 -0
  99. package/packages/core/src/session/SessionRegistry.ts +244 -0
  100. package/packages/core/src/session/WriteMark.ts +51 -0
  101. package/packages/core/src/shared/Cli.ts +25 -0
  102. package/packages/core/src/shared/DaemonUnit.ts +13 -0
  103. package/packages/core/src/shared/DiffLines.ts +304 -0
  104. package/packages/core/src/shared/DiffPatch.ts +70 -0
  105. package/packages/core/src/shared/DiffRenderer.ts +283 -0
  106. package/packages/core/src/shared/DiffView.ts +29 -0
  107. package/packages/core/src/shared/Directories.ts +61 -0
  108. package/packages/core/src/shared/EditMatcher.ts +767 -0
  109. package/packages/core/src/shared/Errors.ts +9 -0
  110. package/packages/core/src/shared/ExtensionToggles.ts +130 -0
  111. package/packages/core/src/shared/FileEnumerator.ts +361 -0
  112. package/packages/core/src/shared/FileScanner.ts +57 -0
  113. package/packages/core/src/shared/FileWatch.ts +121 -0
  114. package/packages/core/src/shared/Format.ts +74 -0
  115. package/packages/core/src/shared/Fs.ts +91 -0
  116. package/packages/core/src/shared/FsErrors.ts +67 -0
  117. package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
  118. package/packages/core/src/shared/Git.ts +530 -0
  119. package/packages/core/src/shared/GitMonitor.ts +221 -0
  120. package/packages/core/src/shared/GlobExclusions.ts +13 -0
  121. package/packages/core/src/shared/Http.ts +14 -0
  122. package/packages/core/src/shared/ImageMime.ts +36 -0
  123. package/packages/core/src/shared/Images.ts +357 -0
  124. package/packages/core/src/shared/Json.ts +19 -0
  125. package/packages/core/src/shared/Languages.ts +95 -0
  126. package/packages/core/src/shared/Levenshtein.ts +83 -0
  127. package/packages/core/src/shared/Lines.ts +79 -0
  128. package/packages/core/src/shared/McpClient.ts +444 -0
  129. package/packages/core/src/shared/OutputBudget.ts +84 -0
  130. package/packages/core/src/shared/PatchSummary.ts +40 -0
  131. package/packages/core/src/shared/Paths.ts +80 -0
  132. package/packages/core/src/shared/PimSettings.ts +128 -0
  133. package/packages/core/src/shared/PimVersion.ts +93 -0
  134. package/packages/core/src/shared/Pool.ts +26 -0
  135. package/packages/core/src/shared/Proc.ts +50 -0
  136. package/packages/core/src/shared/RateLimiter.ts +50 -0
  137. package/packages/core/src/shared/Renderer.ts +371 -0
  138. package/packages/core/src/shared/RepoDiff.ts +741 -0
  139. package/packages/core/src/shared/SafePath.ts +17 -0
  140. package/packages/core/src/shared/SearchRender.ts +82 -0
  141. package/packages/core/src/shared/SendFile.ts +23 -0
  142. package/packages/core/src/shared/SpillCache.ts +78 -0
  143. package/packages/core/src/shared/SubagentLogs.ts +102 -0
  144. package/packages/core/src/shared/Supervisor.ts +460 -0
  145. package/packages/core/src/shared/Surface.ts +2 -0
  146. package/packages/core/src/shared/Sweeper.ts +32 -0
  147. package/packages/core/src/shared/Tools.ts +801 -0
  148. package/packages/core/src/shared/Updater.ts +209 -0
  149. package/packages/core/src/view/AnsiPainter.ts +227 -0
  150. package/packages/core/src/view/BodyRenderer.ts +102 -0
  151. package/packages/core/src/view/DiffBlocks.ts +52 -0
  152. package/packages/core/src/view/DiffExpand.ts +214 -0
  153. package/packages/core/src/view/DiffLayout.ts +69 -0
  154. package/packages/core/src/view/DiffPairs.ts +47 -0
  155. package/packages/core/src/view/MarkdownPainter.ts +314 -0
  156. package/packages/core/src/view/MovePath.ts +70 -0
  157. package/packages/core/src/view/Painting.ts +182 -0
  158. package/packages/core/src/view/ViewBlock.ts +124 -0
  159. package/packages/daemon/src/Daemon.ts +63 -0
  160. package/packages/daemon/src/DaemonInstall.ts +115 -0
  161. package/packages/daemon/src/Surfaces.ts +66 -0
  162. package/packages/daemon/src/TelegramSurface.ts +30 -0
  163. package/packages/daemon/src/WebSurface.ts +56 -0
  164. package/packages/daemon/src/index.ts +37 -0
  165. package/packages/protocol/src/Command.ts +219 -0
  166. package/packages/protocol/src/Diff.ts +15 -0
  167. package/packages/protocol/src/ImageRoute.ts +23 -0
  168. package/packages/protocol/src/ServerEvent.ts +406 -0
  169. package/packages/server/src/AttachmentEndpoint.ts +184 -0
  170. package/packages/server/src/ClientConnection.ts +277 -0
  171. package/packages/server/src/ImageEndpoint.ts +36 -0
  172. package/packages/server/src/ProbeClient.ts +374 -0
  173. package/packages/server/src/Reloader.ts +93 -0
  174. package/packages/server/src/SendFileTool.ts +88 -0
  175. package/packages/server/src/SessionCatalogue.ts +456 -0
  176. package/packages/server/src/SessionProjection.ts +194 -0
  177. package/packages/server/src/SessionStream.ts +627 -0
  178. package/packages/server/src/StaticClient.ts +178 -0
  179. package/packages/server/src/WebOptions.ts +72 -0
  180. package/packages/server/src/WsGateway.ts +798 -0
  181. package/packages/server/src/probe.ts +200 -0
  182. package/packages/telegram/src/Bot.ts +229 -0
  183. package/packages/telegram/src/Commands.ts +768 -0
  184. package/packages/telegram/src/Config.ts +148 -0
  185. package/packages/telegram/src/Markdown.ts +235 -0
  186. package/packages/telegram/src/Message.ts +152 -0
  187. package/packages/telegram/src/Renderer.ts +456 -0
  188. package/packages/telegram/src/SendFileSchema.ts +18 -0
  189. package/packages/telegram/src/SendFileTool.ts +65 -0
  190. package/packages/telegram/src/Session.ts +253 -0
  191. package/packages/telegram/src/SessionRegistry.ts +126 -0
  192. package/packages/telegram/src/TaskScheduler.ts +296 -0
  193. package/packages/telegram/src/TaskSchema.ts +88 -0
  194. package/packages/telegram/src/TaskStore.ts +67 -0
  195. package/packages/telegram/src/TaskTool.ts +208 -0
  196. package/packages/telegram/src/TelegramHtml.ts +288 -0
  197. package/packages/telegram/src/TypingIndicator.ts +43 -0
  198. package/packages/telegram/src/UpdateConfirm.ts +61 -0
  199. package/packages/tui/src/extensions/_init/index.ts +144 -0
  200. package/packages/tui/src/extensions/command-picker/index.ts +75 -0
  201. package/packages/tui/src/extensions/file-picker/index.ts +156 -0
  202. package/packages/tui/src/extensions/footer/index.ts +93 -0
  203. package/packages/tui/src/extensions/footer/powerline.ts +87 -0
  204. package/packages/tui/src/extensions/footer/segments.ts +202 -0
  205. package/packages/tui/src/extensions/pim/index.ts +89 -0
  206. package/packages/tui/src/extensions/session-lease/index.ts +253 -0
  207. package/packages/tui/src/extensions/tps/index.ts +118 -0
  208. package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
  209. package/packages/tui/src/extensions/wrapProvider.ts +18 -0
  210. package/packages/tui/src/themes/pim-dark.json +84 -0
  211. package/packages/tui/src/themes/pim-light.json +84 -0
  212. package/packages/tui/src/themes/themeCliArgs.ts +20 -0
  213. package/packages/web/dist/client/apple-touch-icon.png +0 -0
  214. package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
  215. package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
  216. package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
  217. package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
  218. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
  219. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
  220. package/packages/web/dist/client/assets/commit-mono-latin-300-normal-B-iV2FbL.woff2 +0 -0
  221. package/packages/web/dist/client/assets/commit-mono-latin-300-normal-kM0OTZCv.woff +0 -0
  222. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
  223. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
  224. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
  225. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
  226. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
  227. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
  228. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
  229. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
  230. package/packages/web/dist/client/assets/core-CA0aSzPu.js +3 -0
  231. package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
  232. package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
  233. package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
  234. package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
  235. package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
  236. package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
  237. package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
  238. package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
  239. package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
  240. package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
  241. package/packages/web/dist/client/assets/index-B9be-i9d.css +1 -0
  242. package/packages/web/dist/client/assets/index-CpinM5NK.js +45 -0
  243. package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
  244. package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
  245. package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
  246. package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
  247. package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
  248. package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
  249. package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
  250. package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
  251. package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
  252. package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
  253. package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
  254. package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
  255. package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
  256. package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
  257. package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
  258. package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
  259. package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
  260. package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
  261. package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
  262. package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
  263. package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
  264. package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
  265. package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
  266. package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
  267. package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
  268. package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
  269. package/packages/web/dist/client/favicon.svg +1 -0
  270. package/packages/web/dist/client/icon-192.png +0 -0
  271. package/packages/web/dist/client/icon-512.png +0 -0
  272. package/packages/web/dist/client/icon-maskable.png +0 -0
  273. package/packages/web/dist/client/index.html +24 -0
  274. package/packages/web/dist/client/manifest.webmanifest +21 -0
  275. package/packages/web/dist/client/wordmark.svg +1 -0
@@ -0,0 +1,200 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ import type { AttachmentRef } from "#protocol/Command";
4
+ import {
5
+ isDurableEvent,
6
+ type ResponseEvent,
7
+ type ServerEvent,
8
+ } from "#protocol/ServerEvent";
9
+ import { ProbeClient } from "./ProbeClient";
10
+
11
+ const USAGE = `pim probe — CLI client for pim-server, dumps every frame as JSONL
12
+
13
+ bun run probe [options]
14
+
15
+ --url <ws://host:port> server to connect to (default ws://127.0.0.1:4319)
16
+ --session <uuid> attach to an existing session (default: create one)
17
+ --cwd <path> cwd for a session this probe creates
18
+ --from-seq <n> resume from this durable seq (default 0)
19
+ --prompt <text> send a user message once attached; sent into a turn
20
+ already running it steers that turn
21
+ --pick-files <query> ask the server to complete an @ path, print the rows
22
+ --pick-commands <query> ask the server for matching skills and commands
23
+ --list-sessions print pi's session catalogue and exit
24
+ --archived with --list-sessions, print the archived sessions
25
+ instead of the live ones
26
+ --per-project <n> with --list-sessions, keep at most n sessions per
27
+ working directory
28
+ --archive put --session out of the live listing
29
+ --unarchive bring --session back into it
30
+ --mark-unread hold --session unread until something reads it
31
+ --rename <text> name --session through pi's own session name;
32
+ an empty string clears it
33
+ --pin <path> pin a working directory, so its sessions sort first
34
+ --unpin <path> unpin it again
35
+ --list-models print the models this server can switch to
36
+ --upload <path> transfer a local file to the server, attach it to
37
+ --prompt (repeatable)
38
+ --cancel cancel the current turn
39
+ --dequeue take back what the turn in flight is holding,
40
+ without stopping it
41
+ --wait keep streaming after the turn ends (Ctrl-C to stop)
42
+ --quiet print only durable events
43
+ `;
44
+
45
+ const { values } = parseArgs({
46
+ options: {
47
+ url: { type: "string", default: "ws://127.0.0.1:4319" },
48
+ session: { type: "string" },
49
+ cwd: { type: "string" },
50
+ "from-seq": { type: "string", default: "0" },
51
+ prompt: { type: "string" },
52
+ "pick-files": { type: "string" },
53
+ "pick-commands": { type: "string" },
54
+ "list-sessions": { type: "boolean", default: false },
55
+ archived: { type: "boolean", default: false },
56
+ "per-project": { type: "string" },
57
+ archive: { type: "boolean", default: false },
58
+ unarchive: { type: "boolean", default: false },
59
+ "mark-unread": { type: "boolean", default: false },
60
+ rename: { type: "string" },
61
+ pin: { type: "string" },
62
+ unpin: { type: "string" },
63
+ "list-models": { type: "boolean", default: false },
64
+ upload: { type: "string", multiple: true },
65
+ cancel: { type: "boolean", default: false },
66
+ dequeue: { type: "boolean", default: false },
67
+ wait: { type: "boolean", default: false },
68
+ quiet: { type: "boolean", default: false },
69
+ help: { type: "boolean", default: false },
70
+ },
71
+ allowPositionals: false,
72
+ });
73
+
74
+ if (values.help) {
75
+ process.stdout.write(USAGE);
76
+ process.exit(0);
77
+ }
78
+
79
+ function dump(event: ServerEvent): void {
80
+ if (values.quiet && !isDurableEvent(event)) {
81
+ return;
82
+ }
83
+ process.stdout.write(`${JSON.stringify(event)}\n`);
84
+ }
85
+
86
+ const probe = new ProbeClient({
87
+ url: values.url,
88
+ ...(values.session === undefined ? {} : { sessionId: values.session }),
89
+ ...(values.cwd === undefined ? {} : { cwd: values.cwd }),
90
+ fromSeq: Number(values["from-seq"]),
91
+ onEvent: dump,
92
+ });
93
+
94
+ const attached = await probe.connect();
95
+ if (!attached.success) {
96
+ process.stderr.write(`attach failed: ${attached.error ?? "unknown error"}\n`);
97
+ process.exit(1);
98
+ }
99
+
100
+ const mark = probe.events.length;
101
+ if (values["pick-files"] !== undefined) {
102
+ const started = Bun.nanoseconds();
103
+ const items = await probe.files.rank(values["pick-files"], { limit: 50 });
104
+ const ms = (Bun.nanoseconds() - started) / 1e6;
105
+ process.stderr.write(
106
+ `pick-files: ${items?.length ?? 0} rows in ${ms.toFixed(1)}ms\n`
107
+ );
108
+ for (const item of items ?? []) {
109
+ process.stdout.write(`${JSON.stringify(item)}\n`);
110
+ }
111
+ }
112
+ if (values["list-sessions"]) {
113
+ const started = Bun.nanoseconds();
114
+ const { sessions, projects } = await probe.catalogue({
115
+ ...(values.cwd === undefined ? {} : { cwd: values.cwd }),
116
+ ...(values.archived ? { archived: true } : {}),
117
+ ...(values["per-project"] === undefined
118
+ ? {}
119
+ : { perProject: Number(values["per-project"]) }),
120
+ });
121
+ const ms = (Bun.nanoseconds() - started) / 1e6;
122
+ for (const summary of sessions) {
123
+ process.stdout.write(`${JSON.stringify(summary)}\n`);
124
+ }
125
+ process.stderr.write(
126
+ `list-sessions: ${sessions.length} rows from ${projects.length} projects in ${ms.toFixed(1)}ms\n`
127
+ );
128
+ }
129
+ if (values["list-models"]) {
130
+ process.stdout.write(`${JSON.stringify(await probe.listModels())}\n`);
131
+ }
132
+ if (values["pick-commands"] !== undefined) {
133
+ const items = await probe.pickCommands(values["pick-commands"]);
134
+ process.stderr.write(`pick-commands: ${items.length} rows\n`);
135
+ for (const item of items) {
136
+ process.stdout.write(`${JSON.stringify(item)}\n`);
137
+ }
138
+ }
139
+
140
+ const uploaded: AttachmentRef[] = [];
141
+ for (const path of values.upload ?? []) {
142
+ const file = await probe.upload(path);
143
+ uploaded.push({ id: file.id });
144
+ process.stderr.write(`uploaded: ${JSON.stringify(file)}\n`);
145
+ }
146
+
147
+ if (values.cancel) {
148
+ const response = await probe.send({
149
+ type: "cancel",
150
+ sessionId: probe.sessionId ?? "",
151
+ });
152
+ process.stderr.write(`cancel: ${JSON.stringify(response)}\n`);
153
+ }
154
+ if (values.dequeue) {
155
+ const response = await probe.send({
156
+ type: "dequeue",
157
+ sessionId: probe.sessionId ?? "",
158
+ });
159
+ process.stderr.write(`dequeue: ${JSON.stringify(response)}\n`);
160
+ }
161
+
162
+ const sessionId = probe.sessionId ?? "";
163
+ const report = (what: string, response: ResponseEvent): void => {
164
+ process.stderr.write(`${what}: ${JSON.stringify(response)}\n`);
165
+ };
166
+ if (values.rename !== undefined) {
167
+ report("rename", await probe.rename(sessionId, values.rename || null));
168
+ }
169
+ if (values.archive) {
170
+ report("archive", await probe.setArchived(sessionId, true));
171
+ }
172
+ if (values.unarchive) {
173
+ report("unarchive", await probe.setArchived(sessionId, false));
174
+ }
175
+ if (values["mark-unread"]) {
176
+ report("mark-unread", await probe.markUnread(sessionId, true));
177
+ }
178
+ if (values.pin !== undefined) {
179
+ report("pin", await probe.setPinned(values.pin, true));
180
+ }
181
+ if (values.unpin !== undefined) {
182
+ report("unpin", await probe.setPinned(values.unpin, false));
183
+ }
184
+
185
+ if (values.prompt !== undefined) {
186
+ await probe.promptWith(values.prompt, uploaded);
187
+ }
188
+
189
+ if (values.prompt !== undefined) {
190
+ await probe.waitFor(
191
+ (event) => event.type === "session_state" && event.status === "idle",
192
+ { timeoutMs: 10 * 60_000, from: mark }
193
+ );
194
+ }
195
+
196
+ if (values.wait) {
197
+ await new Promise<never>(() => {});
198
+ }
199
+ probe.close();
200
+ 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
+ }