@oh-my-pi/pi-coding-agent 8.0.20 → 8.2.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 (421) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/docs/session.md +111 -46
  3. package/examples/custom-tools/hello/index.ts +1 -1
  4. package/examples/custom-tools/todo/index.ts +3 -4
  5. package/examples/extensions/api-demo.ts +0 -1
  6. package/examples/extensions/chalk-logger.ts +2 -3
  7. package/examples/extensions/hello.ts +0 -1
  8. package/examples/extensions/pirate.ts +0 -1
  9. package/examples/extensions/plan-mode.ts +15 -16
  10. package/examples/extensions/todo.ts +3 -4
  11. package/examples/extensions/tools.ts +1 -2
  12. package/examples/extensions/with-deps/index.ts +0 -1
  13. package/examples/hooks/auto-commit-on-exit.ts +1 -2
  14. package/examples/hooks/confirm-destructive.ts +0 -1
  15. package/examples/hooks/custom-compaction.ts +1 -2
  16. package/examples/hooks/dirty-repo-guard.ts +0 -1
  17. package/examples/hooks/file-trigger.ts +3 -4
  18. package/examples/hooks/git-checkpoint.ts +0 -1
  19. package/examples/hooks/handoff.ts +3 -4
  20. package/examples/hooks/permission-gate.ts +1 -2
  21. package/examples/hooks/protected-paths.ts +1 -2
  22. package/examples/hooks/qna.ts +2 -3
  23. package/examples/hooks/snake.ts +4 -5
  24. package/examples/hooks/status-line.ts +0 -1
  25. package/examples/sdk/01-minimal.ts +2 -3
  26. package/examples/sdk/02-custom-model.ts +2 -3
  27. package/examples/sdk/03-custom-prompt.ts +3 -4
  28. package/examples/sdk/04-skills.ts +2 -3
  29. package/examples/sdk/06-extensions.ts +1 -2
  30. package/examples/sdk/06-hooks.ts +6 -7
  31. package/examples/sdk/07-context-files.ts +0 -1
  32. package/examples/sdk/08-prompt-templates.ts +0 -1
  33. package/examples/sdk/08-slash-commands.ts +0 -1
  34. package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
  35. package/examples/sdk/10-settings.ts +0 -1
  36. package/examples/sdk/11-sessions.ts +0 -1
  37. package/package.json +54 -23
  38. package/scripts/format-prompts.ts +0 -1
  39. package/src/capability/context-file.ts +3 -4
  40. package/src/capability/extension-module.ts +3 -4
  41. package/src/capability/extension.ts +3 -4
  42. package/src/capability/fs.ts +20 -21
  43. package/src/capability/hook.ts +3 -4
  44. package/src/capability/index.ts +15 -16
  45. package/src/capability/instruction.ts +3 -4
  46. package/src/capability/mcp.ts +3 -4
  47. package/src/capability/prompt.ts +3 -4
  48. package/src/capability/rule.ts +3 -4
  49. package/src/capability/settings.ts +2 -3
  50. package/src/capability/skill.ts +3 -4
  51. package/src/capability/slash-command.ts +3 -4
  52. package/src/capability/ssh.ts +3 -4
  53. package/src/capability/system-prompt.ts +3 -4
  54. package/src/capability/tool.ts +3 -4
  55. package/src/cli/args.ts +5 -6
  56. package/src/cli/config-cli.ts +6 -7
  57. package/src/cli/file-processor.ts +19 -17
  58. package/src/cli/jupyter-cli.ts +105 -0
  59. package/src/cli/list-models.ts +10 -11
  60. package/src/cli/plugin-cli.ts +20 -25
  61. package/src/cli/session-picker.ts +2 -3
  62. package/src/cli/setup-cli.ts +2 -3
  63. package/src/cli/stats-cli.ts +2 -3
  64. package/src/cli/update-cli.ts +25 -22
  65. package/src/commit/agentic/agent.ts +307 -0
  66. package/src/commit/agentic/fallback.ts +96 -0
  67. package/src/commit/agentic/index.ts +351 -0
  68. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  69. package/src/commit/agentic/prompts/session-user.md +26 -0
  70. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  71. package/src/commit/agentic/prompts/system.md +40 -0
  72. package/src/commit/agentic/state.ts +69 -0
  73. package/src/commit/agentic/tools/analyze-file.ts +131 -0
  74. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  75. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  76. package/src/commit/agentic/tools/git-overview.ts +84 -0
  77. package/src/commit/agentic/tools/index.ts +56 -0
  78. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  79. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  80. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  81. package/src/commit/agentic/tools/split-commit.ts +280 -0
  82. package/src/commit/agentic/topo-sort.ts +44 -0
  83. package/src/commit/agentic/trivial.ts +51 -0
  84. package/src/commit/agentic/validation.ts +200 -0
  85. package/src/commit/analysis/conventional.ts +165 -0
  86. package/src/commit/analysis/index.ts +4 -0
  87. package/src/commit/analysis/scope.ts +242 -0
  88. package/src/commit/analysis/summary.ts +112 -0
  89. package/src/commit/analysis/validation.ts +66 -0
  90. package/src/commit/changelog/detect.ts +36 -0
  91. package/src/commit/changelog/generate.ts +110 -0
  92. package/src/commit/changelog/index.ts +233 -0
  93. package/src/commit/changelog/parse.ts +44 -0
  94. package/src/commit/cli.ts +93 -0
  95. package/src/commit/git/diff.ts +148 -0
  96. package/src/commit/git/errors.ts +11 -0
  97. package/src/commit/git/index.ts +212 -0
  98. package/src/commit/git/operations.ts +53 -0
  99. package/src/commit/index.ts +5 -0
  100. package/src/commit/map-reduce/index.ts +63 -0
  101. package/src/commit/map-reduce/map-phase.ts +178 -0
  102. package/src/commit/map-reduce/reduce-phase.ts +145 -0
  103. package/src/commit/map-reduce/utils.ts +9 -0
  104. package/src/commit/message.ts +11 -0
  105. package/src/commit/model-selection.ts +80 -0
  106. package/src/commit/pipeline.ts +240 -0
  107. package/src/commit/prompts/analysis-system.md +155 -0
  108. package/src/commit/prompts/analysis-user.md +41 -0
  109. package/src/commit/prompts/changelog-system.md +56 -0
  110. package/src/commit/prompts/changelog-user.md +19 -0
  111. package/src/commit/prompts/file-observer-system.md +26 -0
  112. package/src/commit/prompts/file-observer-user.md +9 -0
  113. package/src/commit/prompts/reduce-system.md +60 -0
  114. package/src/commit/prompts/reduce-user.md +17 -0
  115. package/src/commit/prompts/summary-retry.md +4 -0
  116. package/src/commit/prompts/summary-system.md +52 -0
  117. package/src/commit/prompts/summary-user.md +13 -0
  118. package/src/commit/prompts/types-description.md +2 -0
  119. package/src/commit/types.ts +109 -0
  120. package/src/commit/utils/exclusions.ts +42 -0
  121. package/src/config/file-lock.ts +121 -0
  122. package/src/config/keybindings.ts +6 -8
  123. package/src/config/model-registry.ts +65 -38
  124. package/src/config/model-resolver.ts +18 -19
  125. package/src/config/prompt-templates.ts +11 -11
  126. package/src/config/settings-manager.ts +141 -50
  127. package/src/config.ts +64 -66
  128. package/src/cursor.ts +11 -9
  129. package/src/discovery/agents-md.ts +11 -12
  130. package/src/discovery/builtin.ts +68 -73
  131. package/src/discovery/claude.ts +41 -42
  132. package/src/discovery/cline.ts +11 -12
  133. package/src/discovery/codex.ts +52 -53
  134. package/src/discovery/cursor.ts +9 -10
  135. package/src/discovery/gemini.ts +17 -22
  136. package/src/discovery/github.ts +13 -14
  137. package/src/discovery/helpers.ts +35 -34
  138. package/src/discovery/index.ts +22 -24
  139. package/src/discovery/mcp-json.ts +8 -9
  140. package/src/discovery/ssh.ts +8 -9
  141. package/src/discovery/vscode.ts +4 -5
  142. package/src/discovery/windsurf.ts +6 -7
  143. package/src/exa/company.ts +1 -2
  144. package/src/exa/index.ts +2 -3
  145. package/src/exa/linkedin.ts +1 -2
  146. package/src/exa/mcp-client.ts +14 -16
  147. package/src/exa/render.ts +10 -11
  148. package/src/exa/researcher.ts +1 -2
  149. package/src/exa/search.ts +1 -2
  150. package/src/exa/types.ts +0 -1
  151. package/src/exa/websets.ts +1 -2
  152. package/src/exec/bash-executor.ts +3 -4
  153. package/src/exec/exec.ts +0 -1
  154. package/src/export/custom-share.ts +5 -6
  155. package/src/export/html/index.ts +24 -21
  156. package/src/export/ttsr.ts +2 -3
  157. package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
  158. package/src/extensibility/custom-commands/loader.ts +18 -15
  159. package/src/extensibility/custom-commands/types.ts +2 -3
  160. package/src/extensibility/custom-tools/loader.ts +11 -12
  161. package/src/extensibility/custom-tools/types.ts +7 -8
  162. package/src/extensibility/custom-tools/wrapper.ts +2 -3
  163. package/src/extensibility/extensions/loader.ts +76 -54
  164. package/src/extensibility/extensions/runner.ts +11 -12
  165. package/src/extensibility/extensions/types.ts +20 -27
  166. package/src/extensibility/extensions/wrapper.ts +3 -4
  167. package/src/extensibility/hooks/index.ts +1 -1
  168. package/src/extensibility/hooks/loader.ts +9 -10
  169. package/src/extensibility/hooks/runner.ts +7 -8
  170. package/src/extensibility/hooks/tool-wrapper.ts +0 -1
  171. package/src/extensibility/hooks/types.ts +11 -18
  172. package/src/extensibility/plugins/doctor.ts +3 -3
  173. package/src/extensibility/plugins/installer.ts +27 -27
  174. package/src/extensibility/plugins/loader.ts +59 -56
  175. package/src/extensibility/plugins/manager.ts +211 -171
  176. package/src/extensibility/plugins/parser.ts +1 -1
  177. package/src/extensibility/plugins/paths.ts +8 -8
  178. package/src/extensibility/skills.ts +63 -60
  179. package/src/extensibility/slash-commands.ts +10 -10
  180. package/src/index.ts +54 -54
  181. package/src/internal-urls/agent-protocol.ts +21 -11
  182. package/src/internal-urls/artifact-protocol.ts +17 -13
  183. package/src/internal-urls/router.ts +1 -2
  184. package/src/internal-urls/rule-protocol.ts +3 -4
  185. package/src/internal-urls/skill-protocol.ts +3 -4
  186. package/src/ipy/executor.ts +109 -9
  187. package/src/ipy/gateway-coordinator.ts +79 -90
  188. package/src/ipy/kernel.ts +32 -30
  189. package/src/ipy/modules.ts +13 -13
  190. package/src/lsp/client.ts +21 -10
  191. package/src/lsp/clients/biome-client.ts +1 -2
  192. package/src/lsp/clients/index.ts +3 -3
  193. package/src/lsp/clients/lsp-linter-client.ts +4 -5
  194. package/src/lsp/config.ts +15 -15
  195. package/src/lsp/edits.ts +4 -5
  196. package/src/lsp/index.ts +43 -44
  197. package/src/lsp/lspmux.ts +8 -8
  198. package/src/lsp/render.ts +99 -61
  199. package/src/lsp/utils.ts +3 -3
  200. package/src/main.ts +71 -37
  201. package/src/mcp/client.ts +2 -3
  202. package/src/mcp/config.ts +5 -6
  203. package/src/mcp/json-rpc.ts +0 -1
  204. package/src/mcp/loader.ts +6 -7
  205. package/src/mcp/manager.ts +17 -18
  206. package/src/mcp/tool-bridge.ts +4 -9
  207. package/src/mcp/tool-cache.ts +2 -3
  208. package/src/mcp/transports/http.ts +2 -4
  209. package/src/mcp/transports/stdio.ts +1 -2
  210. package/src/migrations.ts +63 -52
  211. package/src/modes/components/armin.ts +4 -5
  212. package/src/modes/components/assistant-message.ts +33 -5
  213. package/src/modes/components/bash-execution.ts +7 -8
  214. package/src/modes/components/bordered-loader.ts +3 -3
  215. package/src/modes/components/branch-summary-message.ts +3 -3
  216. package/src/modes/components/compaction-summary-message.ts +3 -3
  217. package/src/modes/components/countdown-timer.ts +0 -1
  218. package/src/modes/components/custom-message.ts +5 -5
  219. package/src/modes/components/diff.ts +1 -1
  220. package/src/modes/components/dynamic-border.ts +2 -2
  221. package/src/modes/components/extensions/extension-dashboard.ts +6 -7
  222. package/src/modes/components/extensions/extension-list.ts +2 -3
  223. package/src/modes/components/extensions/inspector-panel.ts +3 -4
  224. package/src/modes/components/extensions/state-manager.ts +25 -26
  225. package/src/modes/components/extensions/types.ts +1 -2
  226. package/src/modes/components/footer.ts +47 -43
  227. package/src/modes/components/history-search.ts +2 -2
  228. package/src/modes/components/hook-editor.ts +3 -4
  229. package/src/modes/components/hook-input.ts +2 -3
  230. package/src/modes/components/hook-message.ts +5 -5
  231. package/src/modes/components/hook-selector.ts +2 -3
  232. package/src/modes/components/keybinding-hints.ts +2 -3
  233. package/src/modes/components/login-dialog.ts +2 -2
  234. package/src/modes/components/model-selector.ts +12 -12
  235. package/src/modes/components/oauth-selector.ts +2 -2
  236. package/src/modes/components/plugin-settings.ts +20 -20
  237. package/src/modes/components/python-execution.ts +7 -8
  238. package/src/modes/components/queue-mode-selector.ts +3 -3
  239. package/src/modes/components/read-tool-group.ts +2 -2
  240. package/src/modes/components/session-selector.ts +4 -4
  241. package/src/modes/components/settings-defs.ts +77 -69
  242. package/src/modes/components/settings-selector.ts +16 -16
  243. package/src/modes/components/show-images-selector.ts +2 -2
  244. package/src/modes/components/status-line/segments.ts +4 -4
  245. package/src/modes/components/status-line/separators.ts +1 -1
  246. package/src/modes/components/status-line/types.ts +2 -2
  247. package/src/modes/components/status-line-segment-editor.ts +7 -8
  248. package/src/modes/components/status-line.ts +12 -12
  249. package/src/modes/components/theme-selector.ts +8 -7
  250. package/src/modes/components/thinking-selector.ts +4 -4
  251. package/src/modes/components/todo-display.ts +2 -2
  252. package/src/modes/components/todo-reminder.ts +4 -4
  253. package/src/modes/components/tool-execution.ts +16 -19
  254. package/src/modes/components/tree-selector.ts +12 -12
  255. package/src/modes/components/ttsr-notification.ts +5 -5
  256. package/src/modes/components/user-message-selector.ts +1 -1
  257. package/src/modes/components/user-message.ts +1 -1
  258. package/src/modes/components/visual-truncate.ts +0 -1
  259. package/src/modes/components/welcome.ts +4 -4
  260. package/src/modes/controllers/command-controller.ts +46 -47
  261. package/src/modes/controllers/event-controller.ts +16 -20
  262. package/src/modes/controllers/extension-ui-controller.ts +40 -46
  263. package/src/modes/controllers/input-controller.ts +17 -18
  264. package/src/modes/controllers/selector-controller.ts +103 -91
  265. package/src/modes/index.ts +3 -3
  266. package/src/modes/interactive-mode.ts +31 -31
  267. package/src/modes/print-mode.ts +12 -13
  268. package/src/modes/rpc/rpc-client.ts +7 -8
  269. package/src/modes/rpc/rpc-mode.ts +24 -28
  270. package/src/modes/rpc/rpc-types.ts +3 -4
  271. package/src/modes/theme/mermaid-cache.ts +89 -0
  272. package/src/modes/theme/theme.ts +130 -53
  273. package/src/modes/types.ts +10 -10
  274. package/src/modes/utils/ui-helpers.ts +17 -17
  275. package/src/patch/applicator.ts +18 -19
  276. package/src/patch/diff.ts +1 -2
  277. package/src/patch/fuzzy.ts +1 -2
  278. package/src/patch/index.ts +11 -18
  279. package/src/patch/normalize.ts +4 -4
  280. package/src/patch/normative.ts +1 -2
  281. package/src/patch/parser.ts +8 -9
  282. package/src/patch/shared.ts +43 -16
  283. package/src/prompts/tools/task.md +2 -0
  284. package/src/sdk.ts +100 -65
  285. package/src/session/agent-session.ts +84 -85
  286. package/src/session/agent-storage.ts +43 -39
  287. package/src/session/artifacts.ts +32 -10
  288. package/src/session/auth-storage.ts +50 -39
  289. package/src/session/compaction/branch-summarization.ts +7 -10
  290. package/src/session/compaction/compaction.ts +8 -19
  291. package/src/session/compaction/utils.ts +6 -9
  292. package/src/session/history-storage.ts +10 -10
  293. package/src/session/messages.ts +4 -5
  294. package/src/session/session-manager.ts +76 -65
  295. package/src/session/session-storage.ts +57 -69
  296. package/src/session/storage-migration.ts +14 -56
  297. package/src/session/streaming-output.ts +2 -2
  298. package/src/ssh/connection-manager.ts +43 -50
  299. package/src/ssh/ssh-executor.ts +2 -2
  300. package/src/ssh/sshfs-mount.ts +11 -18
  301. package/src/system-prompt.ts +28 -35
  302. package/src/task/agents.ts +45 -30
  303. package/src/task/commands.ts +6 -7
  304. package/src/task/discovery.ts +39 -76
  305. package/src/task/executor.ts +14 -15
  306. package/src/task/index.ts +40 -34
  307. package/src/task/output-manager.ts +93 -0
  308. package/src/task/parallel.ts +0 -1
  309. package/src/task/render.ts +24 -30
  310. package/src/task/subprocess-tool-registry.ts +1 -2
  311. package/src/task/worker-protocol.ts +3 -3
  312. package/src/task/worker.ts +33 -39
  313. package/src/task/worktree.ts +19 -19
  314. package/src/tools/ask.ts +41 -20
  315. package/src/tools/bash-interceptor.ts +1 -5
  316. package/src/tools/bash.ts +91 -97
  317. package/src/tools/calculator.ts +49 -47
  318. package/src/tools/complete.ts +4 -5
  319. package/src/tools/context.ts +2 -2
  320. package/src/tools/fetch.ts +84 -124
  321. package/src/tools/find.ts +94 -98
  322. package/src/tools/gemini-image.ts +14 -14
  323. package/src/tools/grep.ts +100 -116
  324. package/src/tools/index.ts +80 -55
  325. package/src/tools/list-limit.ts +1 -1
  326. package/src/tools/ls.ts +44 -70
  327. package/src/tools/notebook.ts +51 -67
  328. package/src/tools/output-meta.ts +3 -4
  329. package/src/tools/output-utils.ts +2 -2
  330. package/src/tools/path-utils.ts +5 -5
  331. package/src/tools/python.ts +104 -217
  332. package/src/tools/read.ts +92 -33
  333. package/src/tools/render-utils.ts +8 -23
  334. package/src/tools/renderers.ts +6 -7
  335. package/src/tools/review.ts +8 -11
  336. package/src/tools/ssh.ts +69 -49
  337. package/src/tools/todo-write.ts +37 -25
  338. package/src/tools/tool-errors.ts +3 -3
  339. package/src/tools/tool-result.ts +3 -8
  340. package/src/tools/write.ts +99 -75
  341. package/src/tui/code-cell.ts +109 -0
  342. package/src/tui/file-list.ts +47 -0
  343. package/src/tui/index.ts +11 -0
  344. package/src/tui/output-block.ts +72 -0
  345. package/src/tui/status-line.ts +39 -0
  346. package/src/tui/tree-list.ts +55 -0
  347. package/src/tui/types.ts +16 -0
  348. package/src/tui/utils.ts +48 -0
  349. package/src/utils/changelog.ts +9 -10
  350. package/src/utils/clipboard.ts +11 -11
  351. package/src/utils/file-mentions.ts +4 -10
  352. package/src/utils/frontmatter.ts +6 -3
  353. package/src/utils/fuzzy.ts +2 -2
  354. package/src/utils/image-convert.ts +1 -1
  355. package/src/utils/image-resize.ts +1 -1
  356. package/src/utils/mime.ts +2 -2
  357. package/src/utils/shell-snapshot.ts +11 -13
  358. package/src/utils/shell.ts +4 -5
  359. package/src/utils/title-generator.ts +8 -9
  360. package/src/utils/tools-manager.ts +23 -23
  361. package/src/vendor/photon/index.js +1099 -1059
  362. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  363. package/src/web/scrapers/artifacthub.ts +1 -1
  364. package/src/web/scrapers/arxiv.ts +2 -2
  365. package/src/web/scrapers/bluesky.ts +2 -2
  366. package/src/web/scrapers/cheatsh.ts +1 -1
  367. package/src/web/scrapers/chocolatey.ts +2 -2
  368. package/src/web/scrapers/choosealicense.ts +5 -5
  369. package/src/web/scrapers/cisa-kev.ts +1 -1
  370. package/src/web/scrapers/crossref.ts +2 -2
  371. package/src/web/scrapers/devto.ts +3 -3
  372. package/src/web/scrapers/discogs.ts +3 -4
  373. package/src/web/scrapers/discourse.ts +1 -1
  374. package/src/web/scrapers/dockerhub.ts +1 -1
  375. package/src/web/scrapers/fdroid.ts +2 -2
  376. package/src/web/scrapers/firefox-addons.ts +3 -3
  377. package/src/web/scrapers/flathub.ts +1 -1
  378. package/src/web/scrapers/github.ts +3 -3
  379. package/src/web/scrapers/gitlab.ts +4 -4
  380. package/src/web/scrapers/hackernews.ts +2 -2
  381. package/src/web/scrapers/huggingface.ts +1 -1
  382. package/src/web/scrapers/iacr.ts +2 -2
  383. package/src/web/scrapers/index.ts +0 -1
  384. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  385. package/src/web/scrapers/lemmy.ts +2 -2
  386. package/src/web/scrapers/maven.ts +2 -2
  387. package/src/web/scrapers/mdn.ts +2 -4
  388. package/src/web/scrapers/metacpan.ts +2 -2
  389. package/src/web/scrapers/musicbrainz.ts +1 -2
  390. package/src/web/scrapers/npm.ts +1 -1
  391. package/src/web/scrapers/nuget.ts +2 -2
  392. package/src/web/scrapers/nvd.ts +3 -3
  393. package/src/web/scrapers/ollama.ts +7 -9
  394. package/src/web/scrapers/opencorporates.ts +2 -2
  395. package/src/web/scrapers/openlibrary.ts +6 -6
  396. package/src/web/scrapers/orcid.ts +0 -1
  397. package/src/web/scrapers/osv.ts +2 -2
  398. package/src/web/scrapers/packagist.ts +1 -1
  399. package/src/web/scrapers/pubmed.ts +1 -2
  400. package/src/web/scrapers/rawg.ts +2 -2
  401. package/src/web/scrapers/readthedocs.ts +1 -2
  402. package/src/web/scrapers/repology.ts +2 -2
  403. package/src/web/scrapers/rfc.ts +1 -1
  404. package/src/web/scrapers/searchcode.ts +2 -2
  405. package/src/web/scrapers/semantic-scholar.ts +1 -1
  406. package/src/web/scrapers/snapcraft.ts +2 -2
  407. package/src/web/scrapers/sourcegraph.ts +1 -1
  408. package/src/web/scrapers/spdx.ts +3 -3
  409. package/src/web/scrapers/spotify.ts +0 -1
  410. package/src/web/scrapers/twitter.ts +1 -1
  411. package/src/web/scrapers/types.ts +1 -2
  412. package/src/web/scrapers/utils.ts +5 -5
  413. package/src/web/scrapers/wikidata.ts +3 -3
  414. package/src/web/scrapers/youtube.ts +9 -14
  415. package/src/web/search/auth.ts +5 -10
  416. package/src/web/search/index.ts +11 -21
  417. package/src/web/search/providers/anthropic.ts +3 -9
  418. package/src/web/search/providers/exa.ts +6 -10
  419. package/src/web/search/providers/perplexity.ts +5 -5
  420. package/src/web/search/render.ts +129 -175
  421. package/tsconfig.json +0 -42
package/src/tools/ask.ts CHANGED
@@ -14,16 +14,16 @@
14
14
  * - If you recommend a specific option, make that the first option in the list
15
15
  * and add "(Recommended)" at the end of the label
16
16
  */
17
-
18
17
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
19
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
20
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
21
- import { type Theme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
22
- import askDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/ask.md" with { type: "text" };
23
18
  import type { Component } from "@oh-my-pi/pi-tui";
24
19
  import { Text } from "@oh-my-pi/pi-tui";
25
20
  import { Type } from "@sinclair/typebox";
26
- import type { ToolSession } from "./index";
21
+ import { renderPromptTemplate } from "../config/prompt-templates";
22
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
23
+ import { type Theme, theme } from "../modes/theme/theme";
24
+ import askDescription from "../prompts/tools/ask.md" with { type: "text" };
25
+ import { renderStatusLine } from "../tui";
26
+ import type { ToolSession } from ".";
27
27
  import { ToolUIKit } from "./render-utils";
28
28
 
29
29
  // =============================================================================
@@ -237,7 +237,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
237
237
  const results: QuestionResult[] = [];
238
238
 
239
239
  for (const q of params.questions) {
240
- const optionLabels = q.options.map((o) => o.label);
240
+ const optionLabels = q.options.map(o => o.label);
241
241
  const { selectedOptions, customInput } = await askSingleQuestion(
242
242
  ui,
243
243
  q.question,
@@ -266,7 +266,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
266
266
  const question = params.question ?? "";
267
267
  const options = params.options ?? [];
268
268
  const multi = params.multi ?? false;
269
- const optionLabels = options.map((o) => o.label);
269
+ const optionLabels = options.map(o => o.label);
270
270
 
271
271
  if (!question || optionLabels.length === 0) {
272
272
  return {
@@ -381,36 +381,56 @@ export const askToolRenderer = {
381
381
  const { details } = result;
382
382
  if (!details) {
383
383
  const txt = result.content[0];
384
- return new Text(txt?.type === "text" && txt.text ? txt.text : "", 0, 0);
384
+ const fallback = txt?.type === "text" && txt.text ? txt.text : "";
385
+ const header = renderStatusLine({ icon: "warning", title: "Ask" }, uiTheme);
386
+ return new Text([header, uiTheme.fg("dim", fallback)].join("\n"), 0, 0);
385
387
  }
386
388
 
387
389
  // Multi-part results
388
390
  if (details.results && details.results.length > 0) {
389
391
  const lines: string[] = [];
390
-
391
- for (const r of details.results) {
392
+ const hasAnySelection = details.results.some(
393
+ r => r.customInput || (r.selectedOptions && r.selectedOptions.length > 0),
394
+ );
395
+ const header = renderStatusLine(
396
+ {
397
+ icon: hasAnySelection ? "success" : "warning",
398
+ title: "Ask",
399
+ meta: [`${details.results.length} questions`],
400
+ },
401
+ uiTheme,
402
+ );
403
+ lines.push(header);
404
+
405
+ for (let i = 0; i < details.results.length; i++) {
406
+ const r = details.results[i];
407
+ const isLastQuestion = i === details.results.length - 1;
408
+ const branch = isLastQuestion ? uiTheme.tree.last : uiTheme.tree.branch;
409
+ const continuation = isLastQuestion ? " " : `${uiTheme.fg("dim", uiTheme.tree.vertical)} `;
392
410
  const hasSelection = r.customInput || r.selectedOptions.length > 0;
393
411
  const statusIcon = hasSelection
394
412
  ? uiTheme.styledSymbol("status.success", "success")
395
413
  : uiTheme.styledSymbol("status.warning", "warning");
396
414
 
397
- lines.push(`${statusIcon} ${uiTheme.fg("dim", `[${r.id}]`)} ${uiTheme.fg("accent", r.question)}`);
415
+ lines.push(
416
+ ` ${uiTheme.fg("dim", branch)} ${statusIcon} ${uiTheme.fg("dim", `[${r.id}]`)} ${uiTheme.fg("accent", r.question)}`,
417
+ );
398
418
 
399
419
  if (r.customInput) {
400
420
  lines.push(
401
- ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", r.customInput)}`,
421
+ `${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", r.customInput)}`,
402
422
  );
403
423
  } else if (r.selectedOptions.length > 0) {
404
424
  for (let j = 0; j < r.selectedOptions.length; j++) {
405
425
  const isLast = j === r.selectedOptions.length - 1;
406
- const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
426
+ const optBranch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
407
427
  lines.push(
408
- ` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${uiTheme.fg("toolOutput", r.selectedOptions[j])}`,
428
+ `${continuation}${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("success", uiTheme.checkbox.checked)} ${uiTheme.fg("toolOutput", r.selectedOptions[j])}`,
409
429
  );
410
430
  }
411
431
  } else {
412
432
  lines.push(
413
- ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`,
433
+ `${continuation}${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`,
414
434
  );
415
435
  }
416
436
  }
@@ -425,11 +445,12 @@ export const askToolRenderer = {
425
445
  }
426
446
 
427
447
  const hasSelection = details.customInput || (details.selectedOptions && details.selectedOptions.length > 0);
428
- const statusIcon = hasSelection
429
- ? uiTheme.styledSymbol("status.success", "success")
430
- : uiTheme.styledSymbol("status.warning", "warning");
448
+ const header = renderStatusLine(
449
+ { icon: hasSelection ? "success" : "warning", title: "Ask", description: details.question },
450
+ uiTheme,
451
+ );
431
452
 
432
- let text = `${statusIcon} ${uiTheme.fg("accent", details.question)}`;
453
+ let text = header;
433
454
 
434
455
  if (details.customInput) {
435
456
  text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.success", "success")} ${uiTheme.fg("toolOutput", details.customInput)}`;
@@ -5,11 +5,7 @@
5
5
  * this interceptor provides helpful error messages directing them to use
6
6
  * the specialized tools instead.
7
7
  */
8
-
9
- import {
10
- type BashInterceptorRule,
11
- DEFAULT_BASH_INTERCEPTOR_RULES,
12
- } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
8
+ import { type BashInterceptorRule, DEFAULT_BASH_INTERCEPTOR_RULES } from "../config/settings-manager";
13
9
 
14
10
  export interface InterceptionResult {
15
11
  /** If true, the bash command should be blocked */
package/src/tools/bash.ts CHANGED
@@ -1,22 +1,22 @@
1
- import { relative, resolve, sep } from "node:path";
1
+ import * as path from "node:path";
2
2
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
3
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
4
- import { type BashExecutorOptions, executeBash } from "@oh-my-pi/pi-coding-agent/exec/bash-executor";
5
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
6
- import { truncateToVisualLines } from "@oh-my-pi/pi-coding-agent/modes/components/visual-truncate";
7
- import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
8
- import bashDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/bash.md" with { type: "text" };
9
- import type { OutputMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta";
10
- import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
11
3
  import type { Component } from "@oh-my-pi/pi-tui";
12
- import { Text, truncateToWidth } from "@oh-my-pi/pi-tui";
4
+ import { Text } from "@oh-my-pi/pi-tui";
13
5
  import { Type } from "@sinclair/typebox";
14
-
6
+ import { renderPromptTemplate } from "../config/prompt-templates";
7
+ import { type BashExecutorOptions, executeBash } from "../exec/bash-executor";
8
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
+ import { truncateToVisualLines } from "../modes/components/visual-truncate";
10
+ import type { Theme } from "../modes/theme/theme";
11
+ import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
12
+ import { renderOutputBlock, renderStatusLine } from "../tui";
13
+ import type { ToolSession } from ".";
15
14
  import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor";
16
- import type { ToolSession } from "./index";
15
+ import type { OutputMeta } from "./output-meta";
17
16
  import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
18
17
  import { resolveToCwd } from "./path-utils";
19
- import { ToolUIKit } from "./render-utils";
18
+ import { formatBytes, wrapBrackets } from "./render-utils";
19
+ import { ToolError } from "./tool-errors";
20
20
  import { toolResult } from "./tool-result";
21
21
  import { DEFAULT_MAX_BYTES } from "./truncate";
22
22
 
@@ -106,7 +106,7 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
106
106
  env: extraEnv,
107
107
  artifactPath,
108
108
  artifactId,
109
- onChunk: (chunk) => {
109
+ onChunk: chunk => {
110
110
  tailBuffer.append(chunk);
111
111
  if (onUpdate) {
112
112
  onUpdate({
@@ -158,35 +158,37 @@ interface BashRenderContext {
158
158
  timeout?: number;
159
159
  }
160
160
 
161
+ function formatBashCommand(args: BashRenderArgs, uiTheme: Theme): string {
162
+ const command = args.command || uiTheme.format.ellipsis;
163
+ const prompt = "$";
164
+ const cwd = process.cwd();
165
+ let displayWorkdir = args.cwd;
166
+
167
+ if (displayWorkdir) {
168
+ const resolvedCwd = path.resolve(cwd);
169
+ const resolvedWorkdir = path.resolve(displayWorkdir);
170
+ if (resolvedWorkdir === resolvedCwd) {
171
+ displayWorkdir = undefined;
172
+ } else {
173
+ const relativePath = path.relative(resolvedCwd, resolvedWorkdir);
174
+ const isWithinCwd =
175
+ relativePath && !relativePath.startsWith("..") && !relativePath.startsWith(`..${path.sep}`);
176
+ if (isWithinCwd) {
177
+ displayWorkdir = relativePath;
178
+ }
179
+ }
180
+ }
181
+
182
+ return displayWorkdir ? `${prompt} cd ${displayWorkdir} && ${command}` : `${prompt} ${command}`;
183
+ }
184
+
161
185
  // Preview line limit when not expanded (matches tool-execution behavior)
162
186
  export const BASH_PREVIEW_LINES = 10;
163
187
 
164
188
  export const bashToolRenderer = {
165
189
  renderCall(args: BashRenderArgs, uiTheme: Theme): Component {
166
- const ui = new ToolUIKit(uiTheme);
167
- const command = args.command || uiTheme.format.ellipsis;
168
- const prompt = uiTheme.fg("accent", "$");
169
- const cwd = process.cwd();
170
- let displayWorkdir = args.cwd;
171
-
172
- if (displayWorkdir) {
173
- const resolvedCwd = resolve(cwd);
174
- const resolvedWorkdir = resolve(displayWorkdir);
175
- if (resolvedWorkdir === resolvedCwd) {
176
- displayWorkdir = undefined;
177
- } else {
178
- const relativePath = relative(resolvedCwd, resolvedWorkdir);
179
- const isWithinCwd = relativePath && !relativePath.startsWith("..") && !relativePath.startsWith(`..${sep}`);
180
- if (isWithinCwd) {
181
- displayWorkdir = relativePath;
182
- }
183
- }
184
- }
185
-
186
- const cmdText = displayWorkdir
187
- ? `${prompt} ${uiTheme.fg("dim", `cd ${displayWorkdir} &&`)} ${command}`
188
- : `${prompt} ${command}`;
189
- const text = ui.title(cmdText);
190
+ const cmdText = formatBashCommand(args, uiTheme);
191
+ const text = renderStatusLine({ icon: "pending", title: "Bash", description: cmdText }, uiTheme);
190
192
  return new Text(text, 0, 0);
191
193
  },
192
194
 
@@ -194,19 +196,23 @@ export const bashToolRenderer = {
194
196
  result: {
195
197
  content: Array<{ type: string; text?: string }>;
196
198
  details?: BashToolDetails;
199
+ isError?: boolean;
197
200
  },
198
201
  options: RenderResultOptions & { renderContext?: BashRenderContext },
199
202
  uiTheme: Theme,
203
+ args?: BashRenderArgs,
200
204
  ): Component {
201
- const ui = new ToolUIKit(uiTheme);
205
+ const cmdText = args ? formatBashCommand(args, uiTheme) : undefined;
206
+ const isError = result.isError === true;
207
+ const header = renderStatusLine({ icon: isError ? "error" : "success", title: "Bash" }, uiTheme);
202
208
  const { renderContext } = options;
203
209
  const details = result.details;
204
210
  const expanded = renderContext?.expanded ?? options.expanded;
205
211
  const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
206
212
 
207
213
  // Get output from context (preferred) or fall back to result content
208
- const output = renderContext?.output ?? (result.content?.find((c) => c.type === "text")?.text ?? "").trim();
209
- const displayOutput = output;
214
+ const output = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
215
+ const displayOutput = output.trimEnd();
210
216
  const showingFullOutput = expanded && renderContext?.isFullOutput === true;
211
217
 
212
218
  // Build truncation warning lines (static, doesn't depend on width)
@@ -214,7 +220,10 @@ export const bashToolRenderer = {
214
220
  const timeoutSeconds = renderContext?.timeout;
215
221
  const timeoutLine =
216
222
  typeof timeoutSeconds === "number"
217
- ? uiTheme.fg("dim", ui.wrapBrackets(`Timeout: ${timeoutSeconds}s`))
223
+ ? uiTheme.fg(
224
+ "dim",
225
+ `${uiTheme.format.bracketLeft}Timeout: ${timeoutSeconds}s${uiTheme.format.bracketRight}`,
226
+ )
218
227
  : undefined;
219
228
  let warningLine: string | undefined;
220
229
  if (truncation && !showingFullOutput) {
@@ -226,72 +235,57 @@ export const bashToolRenderer = {
226
235
  warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
227
236
  } else {
228
237
  warnings.push(
229
- `Truncated: ${truncation.outputLines} lines shown (${ui.formatBytes(truncation.outputBytes)} limit)`,
238
+ `Truncated: ${truncation.outputLines} lines shown (${formatBytes(truncation.outputBytes)} limit)`,
230
239
  );
231
240
  }
232
241
  if (warnings.length > 0) {
233
- warningLine = uiTheme.fg("warning", ui.wrapBrackets(warnings.join(". ")));
242
+ warningLine = uiTheme.fg("warning", wrapBrackets(warnings.join(". "), uiTheme));
234
243
  }
235
244
  }
236
245
 
237
- if (!displayOutput) {
238
- // No output - just show warning if any
239
- const lines = [timeoutLine, warningLine].filter(Boolean) as string[];
240
- return new Text(lines.join("\n"), 0, 0);
241
- }
242
-
243
- if (expanded) {
244
- // Show all lines when expanded
245
- const styledOutput = displayOutput
246
- .split("\n")
247
- .map((line) => uiTheme.fg("toolOutput", line))
248
- .join("\n");
249
- const lines = [styledOutput, timeoutLine, warningLine].filter(Boolean) as string[];
250
- return new Text(lines.join("\n"), 0, 0);
251
- }
252
-
253
- // Collapsed: use width-aware caching component
254
- const styledOutput = displayOutput
255
- .split("\n")
256
- .map((line) => uiTheme.fg("toolOutput", line))
257
- .join("\n");
258
- const textContent = `\n${styledOutput}`;
259
-
260
- let cachedWidth: number | undefined;
261
- let cachedLines: string[] | undefined;
262
- let cachedSkipped: number | undefined;
263
-
264
246
  return {
265
247
  render: (width: number): string[] => {
266
- if (cachedLines === undefined || cachedWidth !== width) {
267
- const result = truncateToVisualLines(textContent, previewLines, width);
268
- cachedLines = result.visualLines;
269
- cachedSkipped = result.skippedCount;
270
- cachedWidth = width;
271
- }
272
248
  const outputLines: string[] = [];
273
- if (cachedSkipped && cachedSkipped > 0) {
274
- outputLines.push("");
275
- const skippedLine = uiTheme.fg(
276
- "dim",
277
- `${uiTheme.format.ellipsis} (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (ctrl+o to expand)`,
278
- );
279
- outputLines.push(truncateToWidth(skippedLine, width, uiTheme.fg("dim", uiTheme.format.ellipsis)));
280
- }
281
- outputLines.push(...cachedLines);
282
- if (timeoutLine) {
283
- outputLines.push(truncateToWidth(timeoutLine, width, uiTheme.fg("dim", uiTheme.format.ellipsis)));
249
+ const hasOutput = displayOutput.trim().length > 0;
250
+ if (hasOutput) {
251
+ if (expanded) {
252
+ outputLines.push(...displayOutput.split("\n").map(line => uiTheme.fg("toolOutput", line)));
253
+ } else {
254
+ const styledOutput = displayOutput
255
+ .split("\n")
256
+ .map(line => uiTheme.fg("toolOutput", line))
257
+ .join("\n");
258
+ const textContent = styledOutput;
259
+ const result = truncateToVisualLines(textContent, previewLines, width);
260
+ if (result.skippedCount > 0) {
261
+ outputLines.push(
262
+ uiTheme.fg(
263
+ "dim",
264
+ `${uiTheme.format.ellipsis} (${result.skippedCount} earlier lines, showing ${result.visualLines.length} of ${result.skippedCount + result.visualLines.length}) (ctrl+o to expand)`,
265
+ ),
266
+ );
267
+ }
268
+ outputLines.push(...result.visualLines);
269
+ }
284
270
  }
285
- if (warningLine) {
286
- outputLines.push(truncateToWidth(warningLine, width, uiTheme.fg("warning", uiTheme.format.ellipsis)));
287
- }
288
- return outputLines;
289
- },
290
- invalidate: () => {
291
- cachedWidth = undefined;
292
- cachedLines = undefined;
293
- cachedSkipped = undefined;
271
+ if (timeoutLine) outputLines.push(timeoutLine);
272
+ if (warningLine) outputLines.push(warningLine);
273
+
274
+ return renderOutputBlock(
275
+ {
276
+ header,
277
+ state: isError ? "error" : "success",
278
+ sections: [
279
+ { lines: cmdText ? [uiTheme.fg("dim", cmdText)] : [] },
280
+ { label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
281
+ ],
282
+ width,
283
+ },
284
+ uiTheme,
285
+ );
294
286
  },
287
+ invalidate: () => {},
295
288
  };
296
289
  },
290
+ mergeCallAndResult: true,
297
291
  };
@@ -1,19 +1,18 @@
1
1
  import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
3
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
4
- import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
5
- import calculatorDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/calculator.md" with { type: "text" };
6
2
  import type { Component } from "@oh-my-pi/pi-tui";
7
3
  import { Text } from "@oh-my-pi/pi-tui";
8
4
  import { untilAborted } from "@oh-my-pi/pi-utils";
9
5
  import { Type } from "@sinclair/typebox";
10
- import type { ToolSession } from "./index";
6
+ import { renderPromptTemplate } from "../config/prompt-templates";
7
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
8
+ import type { Theme } from "../modes/theme/theme";
9
+ import calculatorDescription from "../prompts/tools/calculator.md" with { type: "text" };
10
+ import { renderStatusLine, renderTreeList } from "../tui";
11
+ import type { ToolSession } from ".";
11
12
  import {
12
13
  formatCount,
13
14
  formatEmptyMessage,
14
- formatExpandHint,
15
- formatMeta,
16
- formatMoreItems,
15
+ formatErrorMessage,
17
16
  PREVIEW_LIMITS,
18
17
  TRUNCATE_LENGTHS,
19
18
  truncate,
@@ -418,13 +417,13 @@ export class CalculatorTool implements AgentTool<typeof calculatorSchema, Calcul
418
417
  signal?: AbortSignal,
419
418
  ): Promise<AgentToolResult<CalculatorToolDetails>> {
420
419
  return untilAborted(signal, async () => {
421
- const results = calculations.map((calc) => {
420
+ const results = calculations.map(calc => {
422
421
  const value = evaluateExpression(calc.expression);
423
422
  const output = `${calc.prefix}${formatResult(value)}${calc.suffix}`;
424
423
  return { expression: calc.expression, value, output };
425
424
  });
426
425
 
427
- const outputText = results.map((result) => result.output).join("\n");
426
+ const outputText = results.map(result => result.output).join("\n");
428
427
  return {
429
428
  content: [{ type: "text", text: outputText }],
430
429
  details: { results },
@@ -453,16 +452,11 @@ export const calculatorToolRenderer = {
453
452
  * Format: "Calc <expression> (N calcs)"
454
453
  */
455
454
  renderCall(args: CalculatorRenderArgs, uiTheme: Theme): Component {
456
- const label = uiTheme.fg("toolTitle", uiTheme.bold("Calc"));
457
455
  const count = args.calculations?.length ?? 0;
458
456
  const firstExpression = args.calculations?.[0]?.expression;
459
- let text = label;
460
- if (firstExpression) {
461
- text += ` ${uiTheme.fg("accent", truncate(firstExpression, TRUNCATE_LENGTHS.TITLE, "..."))}`;
462
- }
463
- const meta: string[] = [];
464
- if (count > 0) meta.push(formatCount("calc", count));
465
- text += formatMeta(meta, uiTheme);
457
+ const description = firstExpression ? truncate(firstExpression, TRUNCATE_LENGTHS.TITLE, "...") : undefined;
458
+ const meta = count > 0 ? [formatCount("calc", count)] : [];
459
+ const text = renderStatusLine({ icon: "pending", title: "Calc", description, meta }, uiTheme);
466
460
  return new Text(text, 0, 0);
467
461
  },
468
462
 
@@ -471,46 +465,54 @@ export const calculatorToolRenderer = {
471
465
  * Collapsed mode shows first N items with expand hint; expanded shows all.
472
466
  */
473
467
  renderResult(
474
- result: { content: Array<{ type: string; text?: string }>; details?: CalculatorToolDetails },
468
+ result: { content: Array<{ type: string; text?: string }>; details?: CalculatorToolDetails; isError?: boolean },
475
469
  { expanded }: RenderResultOptions,
476
470
  uiTheme: Theme,
471
+ args?: CalculatorRenderArgs,
477
472
  ): Component {
478
473
  const details = result.details;
479
- const textContent = result.content?.find((c) => c.type === "text")?.text ?? "";
474
+ const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
475
+ if (result.isError) {
476
+ const header = renderStatusLine({ icon: "error", title: "Calc" }, uiTheme);
477
+ return new Text([header, formatErrorMessage(textContent, uiTheme)].join("\n"), 0, 0);
478
+ }
480
479
 
481
480
  // Prefer structured details; fall back to parsing text content
482
- let outputs = details?.results?.map((entry) => entry.output) ?? [];
481
+ let outputs = details?.results?.map(entry => `${entry.expression} = ${entry.output}`) ?? [];
483
482
  if (outputs.length === 0 && textContent.trim()) {
484
- outputs = textContent.split("\n").filter((line) => line.trim().length > 0);
483
+ const rawOutputs = textContent.split("\n").filter(line => line.trim().length > 0);
484
+ const expressions = args?.calculations?.map(calc => calc.expression) ?? [];
485
+ if (expressions.length === rawOutputs.length && expressions.length > 0) {
486
+ outputs = rawOutputs.map((output, index) => `${expressions[index]} = ${output}`);
487
+ } else {
488
+ outputs = rawOutputs;
489
+ }
485
490
  }
486
491
 
487
492
  if (outputs.length === 0) {
488
- return new Text(formatEmptyMessage("No results", uiTheme), 0, 0);
489
- }
490
-
491
- // Limit visible items in collapsed mode
492
- const maxItems = expanded ? outputs.length : Math.min(outputs.length, COLLAPSED_LIST_LIMIT);
493
- const hasMore = outputs.length > maxItems;
494
- const icon = uiTheme.styledSymbol("status.success", "success");
495
- const summary = uiTheme.fg("dim", formatCount("result", outputs.length));
496
- const expandHint = formatExpandHint(uiTheme, expanded, hasMore);
497
- let text = `${icon} ${summary}${expandHint}`;
498
-
499
- // Render each result as a tree branch
500
- for (let i = 0; i < maxItems; i += 1) {
501
- const isLast = i === maxItems - 1 && !hasMore;
502
- const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
503
- text += `\n ${uiTheme.fg("dim", branch)} ${uiTheme.fg("toolOutput", outputs[i])}`;
504
- }
505
-
506
- // Show overflow indicator for collapsed mode
507
- if (hasMore) {
508
- text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
509
- "muted",
510
- formatMoreItems(outputs.length - maxItems, "result", uiTheme),
511
- )}`;
493
+ const header = renderStatusLine({ icon: "warning", title: "Calc" }, uiTheme);
494
+ return new Text([header, formatEmptyMessage("No results", uiTheme)].join("\n"), 0, 0);
512
495
  }
513
496
 
514
- return new Text(text, 0, 0);
497
+ const description = args?.calculations?.[0]?.expression
498
+ ? truncate(args.calculations[0].expression, TRUNCATE_LENGTHS.TITLE, "...")
499
+ : undefined;
500
+ const header = renderStatusLine(
501
+ { icon: "success", title: "Calc", description, meta: [formatCount("result", outputs.length)] },
502
+ uiTheme,
503
+ );
504
+ const lines = renderTreeList(
505
+ {
506
+ items: outputs,
507
+ expanded,
508
+ maxCollapsed: COLLAPSED_LIST_LIMIT,
509
+ itemType: "result",
510
+ renderItem: output => uiTheme.fg("toolOutput", output),
511
+ },
512
+ uiTheme,
513
+ );
514
+
515
+ return new Text([header, ...lines].join("\n"), 0, 0);
515
516
  },
517
+ mergeCallAndResult: true,
516
518
  };
@@ -3,14 +3,13 @@
3
3
  *
4
4
  * Subagents must call this tool to finish and return structured JSON output.
5
5
  */
6
-
7
6
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
8
7
  import { StringEnum } from "@oh-my-pi/pi-ai";
9
- import { subprocessToolRegistry } from "@oh-my-pi/pi-coding-agent/task/subprocess-tool-registry";
10
8
  import type { Static, TObject } from "@sinclair/typebox";
11
9
  import { Type } from "@sinclair/typebox";
12
10
  import Ajv, { type ErrorObject, type ValidateFunction } from "ajv";
13
- import type { ToolSession } from "./index";
11
+ import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
12
+ import type { ToolSession } from ".";
14
13
  import { jtdToJsonSchema } from "./jtd-to-json-schema";
15
14
 
16
15
  export interface CompleteDetails {
@@ -46,7 +45,7 @@ function formatSchema(schema: unknown): string {
46
45
  function formatAjvErrors(errors: ErrorObject[] | null | undefined): string {
47
46
  if (!errors || errors.length === 0) return "Unknown schema validation error.";
48
47
  return errors
49
- .map((err) => {
48
+ .map(err => {
50
49
  const path = err.instancePath ? `${err.instancePath}: ` : "";
51
50
  return `${path}${err.message ?? "invalid"}`;
52
51
  })
@@ -137,6 +136,6 @@ export class CompleteTool implements AgentTool<TObject, CompleteDetails> {
137
136
 
138
137
  // Register subprocess tool handler for extraction + termination.
139
138
  subprocessToolRegistry.register<CompleteDetails>("complete", {
140
- extractData: (event) => event.result?.details as CompleteDetails | undefined,
139
+ extractData: event => event.result?.details as CompleteDetails | undefined,
141
140
  shouldTerminate: () => true,
142
141
  });
@@ -1,6 +1,6 @@
1
1
  import type { AgentToolContext, ToolCallContext } from "@oh-my-pi/pi-agent-core";
2
- import type { CustomToolContext } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
3
- import type { ExtensionUIContext } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/types";
2
+ import type { CustomToolContext } from "../extensibility/custom-tools/types";
3
+ import type { ExtensionUIContext } from "../extensibility/extensions/types";
4
4
 
5
5
  declare module "@oh-my-pi/pi-agent-core" {
6
6
  interface AgentToolContext extends CustomToolContext {