@oh-my-pi/pi-coding-agent 7.0.0 → 8.0.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 (501) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +1 -1
  3. package/docs/hooks.md +2 -2
  4. package/docs/sdk.md +1 -1
  5. package/package.json +10 -10
  6. package/scripts/format-prompts.ts +143 -0
  7. package/scripts/generate-template.ts +1 -1
  8. package/src/cli/args.ts +3 -3
  9. package/src/cli/config-cli.ts +4 -4
  10. package/src/cli/file-processor.ts +3 -3
  11. package/src/cli/list-models.ts +2 -2
  12. package/src/cli/plugin-cli.ts +3 -3
  13. package/src/cli/session-picker.ts +2 -2
  14. package/src/cli/setup-cli.ts +2 -2
  15. package/src/cli/stats-cli.ts +1 -1
  16. package/src/cli/update-cli.ts +2 -2
  17. package/src/{core → config}/keybindings.ts +1 -1
  18. package/src/{core → config}/model-registry.ts +1 -1
  19. package/src/{core → config}/model-resolver.ts +3 -3
  20. package/src/{core → config}/prompt-templates.ts +2 -2
  21. package/src/{core → config}/settings-manager.ts +6 -6
  22. package/src/{core/cursor/exec-bridge.ts → cursor.ts} +4 -4
  23. package/src/discovery/agents-md.ts +4 -4
  24. package/src/discovery/builtin.ts +17 -17
  25. package/src/discovery/claude.ts +12 -12
  26. package/src/discovery/cline.ts +6 -6
  27. package/src/discovery/codex.ts +21 -21
  28. package/src/discovery/cursor.ts +9 -9
  29. package/src/discovery/gemini.ts +9 -9
  30. package/src/discovery/github.ts +6 -6
  31. package/src/discovery/helpers.ts +4 -4
  32. package/src/discovery/index.ts +16 -16
  33. package/src/discovery/mcp-json.ts +4 -4
  34. package/src/discovery/ssh.ts +4 -4
  35. package/src/discovery/vscode.ts +4 -4
  36. package/src/discovery/windsurf.ts +6 -6
  37. package/src/{core/tools/exa → exa}/company.ts +2 -3
  38. package/src/{core/tools/exa → exa}/index.ts +2 -2
  39. package/src/{core/tools/exa → exa}/linkedin.ts +2 -3
  40. package/src/{core/tools/exa → exa}/mcp-client.ts +2 -2
  41. package/src/{core/tools/exa → exa}/render.ts +3 -3
  42. package/src/{core/tools/exa → exa}/researcher.ts +1 -1
  43. package/src/{core/tools/exa → exa}/search.ts +2 -10
  44. package/src/{core/tools/exa → exa}/websets.ts +1 -1
  45. package/src/{core → exec}/bash-executor.ts +22 -6
  46. package/src/{core → export}/custom-share.ts +1 -1
  47. package/src/{core/export-html → export/html}/index.ts +3 -3
  48. package/src/{core → export}/ttsr.ts +2 -2
  49. package/src/{core → extensibility}/custom-commands/bundled/review/index.ts +4 -4
  50. package/src/{core → extensibility}/custom-commands/loader.ts +3 -3
  51. package/src/{core → extensibility}/custom-commands/types.ts +1 -1
  52. package/src/{core → extensibility}/custom-tools/loader.ts +9 -9
  53. package/src/{core → extensibility}/custom-tools/types.ts +6 -6
  54. package/src/{core → extensibility}/custom-tools/wrapper.ts +1 -1
  55. package/src/{core → extensibility}/extensions/loader.ts +8 -8
  56. package/src/{core → extensibility}/extensions/runner.ts +3 -3
  57. package/src/{core → extensibility}/extensions/types.ts +15 -15
  58. package/src/{core → extensibility}/extensions/wrapper.ts +1 -1
  59. package/src/{core → extensibility}/hooks/index.ts +1 -1
  60. package/src/{core → extensibility}/hooks/loader.ts +7 -7
  61. package/src/{core → extensibility}/hooks/runner.ts +4 -4
  62. package/src/{core → extensibility}/hooks/types.ts +9 -9
  63. package/src/{core → extensibility}/plugins/doctor.ts +1 -1
  64. package/src/{core → extensibility}/plugins/installer.ts +2 -3
  65. package/src/{core → extensibility}/plugins/paths.ts +1 -1
  66. package/src/{core → extensibility}/skills.ts +8 -48
  67. package/src/{core → extensibility}/slash-commands.ts +6 -6
  68. package/src/index.ts +127 -128
  69. package/src/internal-urls/agent-protocol.ts +126 -0
  70. package/src/internal-urls/artifact-protocol.ts +93 -0
  71. package/src/internal-urls/index.ts +28 -0
  72. package/src/internal-urls/json-query.ts +126 -0
  73. package/src/internal-urls/router.ts +69 -0
  74. package/src/internal-urls/rule-protocol.ts +56 -0
  75. package/src/internal-urls/skill-protocol.ts +112 -0
  76. package/src/internal-urls/types.ts +48 -0
  77. package/src/{core/python-executor.ts → ipy/executor.ts} +51 -11
  78. package/src/{core/python-gateway-coordinator.ts → ipy/gateway-coordinator.ts} +41 -325
  79. package/src/{core/python-kernel.ts → ipy/kernel.ts} +38 -10
  80. package/src/ipy/prelude.ts +3 -0
  81. package/src/{core/tools/lsp → lsp}/client.ts +7 -6
  82. package/src/{core/tools/lsp → lsp}/clients/biome-client.ts +1 -1
  83. package/src/{core/tools/lsp → lsp}/clients/index.ts +1 -1
  84. package/src/{core/tools/lsp → lsp}/clients/lsp-linter-client.ts +4 -4
  85. package/src/{core/tools/lsp → lsp}/config.ts +1 -1
  86. package/src/{core/tools/lsp → lsp}/index.ts +16 -15
  87. package/src/{core/tools/lsp → lsp}/render.ts +2 -2
  88. package/src/{core/tools/lsp → lsp}/types.ts +14 -16
  89. package/src/{core/tools/lsp → lsp}/utils.ts +1 -1
  90. package/src/main.ts +12 -12
  91. package/src/{core/mcp → mcp}/config.ts +8 -8
  92. package/src/{core/mcp → mcp}/loader.ts +5 -6
  93. package/src/{core/mcp → mcp}/manager.ts +2 -2
  94. package/src/{core/mcp → mcp}/tool-bridge.ts +35 -6
  95. package/src/{core/mcp → mcp}/tool-cache.ts +1 -1
  96. package/src/{core/mcp → mcp}/transports/http.ts +7 -1
  97. package/src/{core/mcp → mcp}/transports/stdio.ts +1 -1
  98. package/src/{core/mcp → mcp}/types.ts +1 -1
  99. package/src/migrations.ts +2 -2
  100. package/src/modes/{interactive/components → components}/armin.ts +1 -1
  101. package/src/modes/{interactive/components → components}/assistant-message.ts +1 -1
  102. package/src/modes/{interactive/components → components}/bash-execution.ts +37 -29
  103. package/src/modes/{interactive/components → components}/bordered-loader.ts +1 -1
  104. package/src/modes/{interactive/components → components}/branch-summary-message.ts +2 -2
  105. package/src/modes/{interactive/components → components}/compaction-summary-message.ts +2 -2
  106. package/src/modes/{interactive/components → components}/custom-message.ts +3 -3
  107. package/src/modes/{interactive/components → components}/diff.ts +1 -1
  108. package/src/modes/{interactive/components → components}/dynamic-border.ts +1 -1
  109. package/src/modes/{interactive/components → components}/extensions/extension-dashboard.ts +3 -3
  110. package/src/modes/{interactive/components → components}/extensions/extension-list.ts +2 -2
  111. package/src/modes/{interactive/components → components}/extensions/inspector-panel.ts +1 -1
  112. package/src/modes/{interactive/components → components}/extensions/state-manager.ts +11 -17
  113. package/src/modes/{interactive/components → components}/extensions/types.ts +1 -1
  114. package/src/modes/{interactive/components → components}/footer.ts +3 -3
  115. package/src/modes/{interactive/components → components}/history-search.ts +2 -2
  116. package/src/modes/{interactive/components → components}/hook-editor.ts +1 -1
  117. package/src/modes/{interactive/components → components}/hook-input.ts +1 -1
  118. package/src/modes/{interactive/components → components}/hook-message.ts +3 -3
  119. package/src/modes/{interactive/components → components}/hook-selector.ts +1 -1
  120. package/src/modes/{interactive/components → components}/keybinding-hints.ts +2 -2
  121. package/src/modes/{interactive/components → components}/login-dialog.ts +1 -1
  122. package/src/modes/{interactive/components → components}/model-selector.ts +5 -5
  123. package/src/modes/{interactive/components → components}/oauth-selector.ts +2 -2
  124. package/src/modes/{interactive/components → components}/plugin-settings.ts +3 -3
  125. package/src/modes/{interactive/components → components}/python-execution.ts +35 -24
  126. package/src/modes/{interactive/components → components}/queue-mode-selector.ts +1 -1
  127. package/src/modes/{interactive/components → components}/read-tool-group.ts +2 -2
  128. package/src/modes/{interactive/components → components}/session-selector.ts +3 -3
  129. package/src/modes/{interactive/components → components}/settings-defs.ts +2 -2
  130. package/src/modes/{interactive/components → components}/settings-selector.ts +2 -2
  131. package/src/modes/{interactive/components → components}/show-images-selector.ts +1 -1
  132. package/src/modes/{interactive/components → components}/status-line/segments.ts +2 -2
  133. package/src/modes/{interactive/components → components}/status-line/separators.ts +1 -1
  134. package/src/modes/{interactive/components → components}/status-line/types.ts +2 -2
  135. package/src/modes/{interactive/components → components}/status-line-segment-editor.ts +2 -2
  136. package/src/modes/{interactive/components → components}/status-line.ts +3 -3
  137. package/src/modes/{interactive/components → components}/theme-selector.ts +1 -1
  138. package/src/modes/{interactive/components → components}/thinking-selector.ts +1 -1
  139. package/src/modes/{interactive/components → components}/todo-display.ts +3 -4
  140. package/src/modes/{interactive/components → components}/todo-reminder.ts +2 -2
  141. package/src/modes/{interactive/components → components}/tool-execution.ts +8 -8
  142. package/src/modes/{interactive/components → components}/tree-selector.ts +3 -3
  143. package/src/modes/{interactive/components → components}/ttsr-notification.ts +2 -2
  144. package/src/modes/{interactive/components → components}/user-message-selector.ts +1 -1
  145. package/src/modes/{interactive/components → components}/user-message.ts +1 -1
  146. package/src/modes/{interactive/components → components}/welcome.ts +2 -2
  147. package/src/modes/{interactive/controllers → controllers}/command-controller.ts +32 -30
  148. package/src/modes/{interactive/controllers → controllers}/event-controller.ts +9 -9
  149. package/src/modes/{interactive/controllers → controllers}/extension-ui-controller.ts +8 -8
  150. package/src/modes/{interactive/controllers → controllers}/input-controller.ts +6 -6
  151. package/src/modes/{interactive/controllers → controllers}/selector-controller.ts +16 -16
  152. package/src/modes/index.ts +1 -1
  153. package/src/modes/{interactive/interactive-mode.ts → interactive-mode.ts} +14 -14
  154. package/src/modes/print-mode.ts +1 -1
  155. package/src/modes/rpc/rpc-client.ts +3 -3
  156. package/src/modes/rpc/rpc-mode.ts +3 -3
  157. package/src/modes/rpc/rpc-types.ts +3 -3
  158. package/src/modes/{interactive/theme → theme}/theme.ts +1 -1
  159. package/src/modes/{interactive/types.ts → types.ts} +8 -9
  160. package/src/modes/{interactive/utils → utils}/ui-helpers.ts +20 -27
  161. package/src/{core/tools/patch → patch}/applicator.ts +1 -1
  162. package/src/{core/tools/patch → patch}/diff.ts +1 -1
  163. package/src/{core/tools/patch → patch}/index.ts +31 -36
  164. package/src/{core/tools/patch → patch}/shared.ts +9 -6
  165. package/src/prompts/agents/explore.md +83 -46
  166. package/src/prompts/agents/init.md +9 -4
  167. package/src/prompts/agents/plan.md +8 -7
  168. package/src/prompts/agents/reviewer.md +36 -18
  169. package/src/prompts/agents/task.md +4 -4
  170. package/src/prompts/compaction/branch-summary-preamble.md +0 -1
  171. package/src/prompts/review-request.md +0 -1
  172. package/src/prompts/system/custom-system-prompt.md +2 -14
  173. package/src/prompts/system/file-operations.md +0 -2
  174. package/src/prompts/system/system-prompt.md +147 -138
  175. package/src/prompts/system/web-search.md +26 -0
  176. package/src/prompts/tools/ask.md +31 -24
  177. package/src/prompts/tools/bash.md +20 -17
  178. package/src/prompts/tools/calculator.md +9 -5
  179. package/src/prompts/tools/fetch.md +16 -0
  180. package/src/prompts/tools/find.md +15 -5
  181. package/src/prompts/tools/gemini-image.md +21 -6
  182. package/src/prompts/tools/grep.md +28 -12
  183. package/src/prompts/tools/lsp.md +35 -14
  184. package/src/prompts/tools/patch.md +39 -41
  185. package/src/prompts/tools/python.md +59 -76
  186. package/src/prompts/tools/read.md +23 -22
  187. package/src/prompts/tools/replace.md +19 -12
  188. package/src/prompts/tools/ssh.md +21 -28
  189. package/src/prompts/tools/task.md +54 -44
  190. package/src/prompts/tools/todo-write.md +52 -163
  191. package/src/prompts/tools/web-search.md +16 -9
  192. package/src/prompts/tools/write.md +13 -2
  193. package/src/{core/sdk.ts → sdk.ts} +65 -34
  194. package/src/{core → session}/agent-session.ts +45 -37
  195. package/src/{core → session}/agent-storage.ts +2 -2
  196. package/src/session/artifacts.ts +110 -0
  197. package/src/{core → session}/auth-storage.ts +1 -1
  198. package/src/{core → session}/compaction/branch-summarization.ts +5 -5
  199. package/src/{core → session}/compaction/compaction.ts +6 -6
  200. package/src/{core → session}/compaction/utils.ts +3 -3
  201. package/src/{core → session}/history-storage.ts +1 -1
  202. package/src/{core → session}/messages.ts +6 -8
  203. package/src/{core → session}/session-manager.ts +2 -2
  204. package/src/{core → session}/storage-migration.ts +2 -2
  205. package/src/session/streaming-output.ts +177 -0
  206. package/src/{core/ssh → ssh}/connection-manager.ts +1 -1
  207. package/src/{core/ssh → ssh}/ssh-executor.ts +19 -4
  208. package/src/{core/ssh → ssh}/sshfs-mount.ts +1 -1
  209. package/src/{core/system-prompt.ts → system-prompt.ts} +8 -37
  210. package/src/{core/tools/task → task}/agents.ts +8 -8
  211. package/src/{core/tools/task → task}/commands.ts +5 -6
  212. package/src/{core/tools/task → task}/discovery.ts +3 -3
  213. package/src/{core/tools/task → task}/executor.ts +34 -44
  214. package/src/{core/tools/task → task}/index.ts +206 -50
  215. package/src/{core/tools/task → task}/render.ts +80 -23
  216. package/src/{core/tools/task → task}/subprocess-tool-registry.ts +1 -1
  217. package/src/task/template.ts +47 -0
  218. package/src/{core/tools/task → task}/types.ts +19 -27
  219. package/src/{core/tools/task → task}/worker-protocol.ts +8 -4
  220. package/src/{core/tools/task → task}/worker.ts +34 -29
  221. package/src/task/worktree.ts +166 -0
  222. package/src/{core/tools → tools}/ask.ts +13 -21
  223. package/src/{core/tools → tools}/bash-interceptor.ts +1 -1
  224. package/src/{core/tools → tools}/bash.ts +61 -63
  225. package/src/{core/tools → tools}/calculator.ts +4 -4
  226. package/src/{core/tools → tools}/complete.ts +1 -1
  227. package/src/{core/tools → tools}/context.ts +2 -2
  228. package/src/{core/tools/web-fetch.ts → tools/fetch.ts} +97 -76
  229. package/src/{core/tools → tools}/find.ts +80 -104
  230. package/src/{core/tools → tools}/gemini-image.ts +420 -29
  231. package/src/{core/tools → tools}/grep.ts +155 -164
  232. package/src/{core/tools → tools}/index.ts +63 -56
  233. package/src/tools/list-limit.ts +40 -0
  234. package/src/{core/tools → tools}/ls.ts +44 -35
  235. package/src/{core/tools → tools}/notebook.ts +3 -3
  236. package/src/tools/output-meta.ts +443 -0
  237. package/src/tools/output-utils.ts +63 -0
  238. package/src/{core/tools → tools}/python.ts +105 -89
  239. package/src/tools/read.ts +882 -0
  240. package/src/{core/tools → tools}/render-utils.ts +1 -1
  241. package/src/{core/tools → tools}/renderers.ts +8 -10
  242. package/src/{core/tools → tools}/review.ts +2 -2
  243. package/src/{core/tools → tools}/ssh.ts +56 -59
  244. package/src/{core/tools → tools}/todo-write.ts +12 -23
  245. package/src/tools/tool-errors.ts +95 -0
  246. package/src/tools/tool-result.ts +92 -0
  247. package/src/{core/tools → tools}/truncate.ts +2 -2
  248. package/src/{core/tools → tools}/write.ts +15 -13
  249. package/src/utils/changelog.ts +1 -1
  250. package/src/{core → utils}/file-mentions.ts +4 -4
  251. package/src/utils/image-convert.ts +1 -1
  252. package/src/utils/image-resize.ts +1 -1
  253. package/src/utils/shell.ts +1 -1
  254. package/src/{core → utils}/title-generator.ts +4 -4
  255. package/src/utils/tools-manager.ts +1 -1
  256. package/src/{core/tools/web-scrapers → web/scrapers}/choosealicense.ts +1 -1
  257. package/src/{core/tools/web-scrapers → web/scrapers}/twitter.ts +3 -2
  258. package/src/{core/tools/web-scrapers → web/scrapers}/types.ts +4 -2
  259. package/src/{core/tools/web-scrapers → web/scrapers}/utils.ts +1 -1
  260. package/src/{core/tools/web-scrapers → web/scrapers}/youtube.ts +14 -13
  261. package/src/{core/tools/web-search → web/search}/auth.ts +4 -4
  262. package/src/{core/tools/web-search → web/search}/index.ts +22 -71
  263. package/src/{core/tools/web-search → web/search}/providers/anthropic.ts +7 -10
  264. package/src/{core/tools/web-search → web/search}/providers/exa.ts +2 -2
  265. package/src/{core/tools/web-search → web/search}/providers/perplexity.ts +4 -16
  266. package/src/{core/tools/web-search → web/search}/render.ts +3 -3
  267. package/scripts/migrate-sessions.sh +0 -93
  268. package/src/core/index.ts +0 -56
  269. package/src/core/python-prelude.ts +0 -3
  270. package/src/core/ssh-executor.ts +0 -5
  271. package/src/core/streaming-output.ts +0 -115
  272. package/src/core/tools/output.ts +0 -519
  273. package/src/core/tools/read.ts +0 -717
  274. package/src/core/tools/task/template.ts +0 -37
  275. package/src/prompts/tools/output.md +0 -47
  276. package/src/prompts/tools/web-fetch.md +0 -9
  277. /package/src/{core/tools/exa → exa}/types.ts +0 -0
  278. /package/src/{core → exec}/exec.ts +0 -0
  279. /package/src/{core/export-html → export/html}/template.css +0 -0
  280. /package/src/{core/export-html → export/html}/template.generated.ts +0 -0
  281. /package/src/{core/export-html → export/html}/template.html +0 -0
  282. /package/src/{core/export-html → export/html}/template.js +0 -0
  283. /package/src/{core/export-html → export/html}/template.macro.ts +0 -0
  284. /package/src/{core/export-html → export/html}/vendor/highlight.min.js +0 -0
  285. /package/src/{core/export-html → export/html}/vendor/marked.min.js +0 -0
  286. /package/src/{core → extensibility}/custom-commands/index.ts +0 -0
  287. /package/src/{core → extensibility}/custom-tools/index.ts +0 -0
  288. /package/src/{core → extensibility}/extensions/index.ts +0 -0
  289. /package/src/{core → extensibility}/hooks/tool-wrapper.ts +0 -0
  290. /package/src/{core → extensibility}/plugins/index.ts +0 -0
  291. /package/src/{core → extensibility}/plugins/loader.ts +0 -0
  292. /package/src/{core → extensibility}/plugins/manager.ts +0 -0
  293. /package/src/{core → extensibility}/plugins/parser.ts +0 -0
  294. /package/src/{core → extensibility}/plugins/types.ts +0 -0
  295. /package/src/{core/python-modules.ts → ipy/modules.ts} +0 -0
  296. /package/src/{core/python-prelude.py → ipy/prelude.py} +0 -0
  297. /package/src/{core/tools/lsp → lsp}/defaults.json +0 -0
  298. /package/src/{core/tools/lsp → lsp}/edits.ts +0 -0
  299. /package/src/{core/tools/lsp → lsp}/lspmux.ts +0 -0
  300. /package/src/{core/tools/lsp → lsp}/rust-analyzer.ts +0 -0
  301. /package/src/{core/mcp → mcp}/client.ts +0 -0
  302. /package/src/{core/mcp → mcp}/index.ts +0 -0
  303. /package/src/{core/mcp → mcp}/json-rpc.ts +0 -0
  304. /package/src/{core/mcp → mcp}/transports/index.ts +0 -0
  305. /package/src/modes/{interactive/components → components}/countdown-timer.ts +0 -0
  306. /package/src/modes/{interactive/components → components}/custom-editor.ts +0 -0
  307. /package/src/modes/{interactive/components → components}/extensions/index.ts +0 -0
  308. /package/src/modes/{interactive/components → components}/index.ts +0 -0
  309. /package/src/modes/{interactive/components → components}/status-line/index.ts +0 -0
  310. /package/src/modes/{interactive/components → components}/status-line/presets.ts +0 -0
  311. /package/src/modes/{interactive/components → components}/visual-truncate.ts +0 -0
  312. /package/src/modes/{interactive/theme → theme}/dark.json +0 -0
  313. /package/src/modes/{interactive/theme → theme}/defaults/alabaster.json +0 -0
  314. /package/src/modes/{interactive/theme → theme}/defaults/amethyst.json +0 -0
  315. /package/src/modes/{interactive/theme → theme}/defaults/anthracite.json +0 -0
  316. /package/src/modes/{interactive/theme → theme}/defaults/basalt.json +0 -0
  317. /package/src/modes/{interactive/theme → theme}/defaults/birch.json +0 -0
  318. /package/src/modes/{interactive/theme → theme}/defaults/dark-abyss.json +0 -0
  319. /package/src/modes/{interactive/theme → theme}/defaults/dark-arctic.json +0 -0
  320. /package/src/modes/{interactive/theme → theme}/defaults/dark-aurora.json +0 -0
  321. /package/src/modes/{interactive/theme → theme}/defaults/dark-catppuccin.json +0 -0
  322. /package/src/modes/{interactive/theme → theme}/defaults/dark-cavern.json +0 -0
  323. /package/src/modes/{interactive/theme → theme}/defaults/dark-copper.json +0 -0
  324. /package/src/modes/{interactive/theme → theme}/defaults/dark-cosmos.json +0 -0
  325. /package/src/modes/{interactive/theme → theme}/defaults/dark-cyberpunk.json +0 -0
  326. /package/src/modes/{interactive/theme → theme}/defaults/dark-dracula.json +0 -0
  327. /package/src/modes/{interactive/theme → theme}/defaults/dark-eclipse.json +0 -0
  328. /package/src/modes/{interactive/theme → theme}/defaults/dark-ember.json +0 -0
  329. /package/src/modes/{interactive/theme → theme}/defaults/dark-equinox.json +0 -0
  330. /package/src/modes/{interactive/theme → theme}/defaults/dark-forest.json +0 -0
  331. /package/src/modes/{interactive/theme → theme}/defaults/dark-github.json +0 -0
  332. /package/src/modes/{interactive/theme → theme}/defaults/dark-gruvbox.json +0 -0
  333. /package/src/modes/{interactive/theme → theme}/defaults/dark-lavender.json +0 -0
  334. /package/src/modes/{interactive/theme → theme}/defaults/dark-lunar.json +0 -0
  335. /package/src/modes/{interactive/theme → theme}/defaults/dark-midnight.json +0 -0
  336. /package/src/modes/{interactive/theme → theme}/defaults/dark-monochrome.json +0 -0
  337. /package/src/modes/{interactive/theme → theme}/defaults/dark-monokai.json +0 -0
  338. /package/src/modes/{interactive/theme → theme}/defaults/dark-nebula.json +0 -0
  339. /package/src/modes/{interactive/theme → theme}/defaults/dark-nord.json +0 -0
  340. /package/src/modes/{interactive/theme → theme}/defaults/dark-ocean.json +0 -0
  341. /package/src/modes/{interactive/theme → theme}/defaults/dark-one.json +0 -0
  342. /package/src/modes/{interactive/theme → theme}/defaults/dark-rainforest.json +0 -0
  343. /package/src/modes/{interactive/theme → theme}/defaults/dark-reef.json +0 -0
  344. /package/src/modes/{interactive/theme → theme}/defaults/dark-retro.json +0 -0
  345. /package/src/modes/{interactive/theme → theme}/defaults/dark-rose-pine.json +0 -0
  346. /package/src/modes/{interactive/theme → theme}/defaults/dark-sakura.json +0 -0
  347. /package/src/modes/{interactive/theme → theme}/defaults/dark-slate.json +0 -0
  348. /package/src/modes/{interactive/theme → theme}/defaults/dark-solarized.json +0 -0
  349. /package/src/modes/{interactive/theme → theme}/defaults/dark-solstice.json +0 -0
  350. /package/src/modes/{interactive/theme → theme}/defaults/dark-starfall.json +0 -0
  351. /package/src/modes/{interactive/theme → theme}/defaults/dark-sunset.json +0 -0
  352. /package/src/modes/{interactive/theme → theme}/defaults/dark-swamp.json +0 -0
  353. /package/src/modes/{interactive/theme → theme}/defaults/dark-synthwave.json +0 -0
  354. /package/src/modes/{interactive/theme → theme}/defaults/dark-taiga.json +0 -0
  355. /package/src/modes/{interactive/theme → theme}/defaults/dark-terminal.json +0 -0
  356. /package/src/modes/{interactive/theme → theme}/defaults/dark-tokyo-night.json +0 -0
  357. /package/src/modes/{interactive/theme → theme}/defaults/dark-tundra.json +0 -0
  358. /package/src/modes/{interactive/theme → theme}/defaults/dark-twilight.json +0 -0
  359. /package/src/modes/{interactive/theme → theme}/defaults/dark-volcanic.json +0 -0
  360. /package/src/modes/{interactive/theme → theme}/defaults/graphite.json +0 -0
  361. /package/src/modes/{interactive/theme → theme}/defaults/index.ts +0 -0
  362. /package/src/modes/{interactive/theme → theme}/defaults/light-arctic.json +0 -0
  363. /package/src/modes/{interactive/theme → theme}/defaults/light-aurora-day.json +0 -0
  364. /package/src/modes/{interactive/theme → theme}/defaults/light-canyon.json +0 -0
  365. /package/src/modes/{interactive/theme → theme}/defaults/light-catppuccin.json +0 -0
  366. /package/src/modes/{interactive/theme → theme}/defaults/light-cirrus.json +0 -0
  367. /package/src/modes/{interactive/theme → theme}/defaults/light-coral.json +0 -0
  368. /package/src/modes/{interactive/theme → theme}/defaults/light-cyberpunk.json +0 -0
  369. /package/src/modes/{interactive/theme → theme}/defaults/light-dawn.json +0 -0
  370. /package/src/modes/{interactive/theme → theme}/defaults/light-dunes.json +0 -0
  371. /package/src/modes/{interactive/theme → theme}/defaults/light-eucalyptus.json +0 -0
  372. /package/src/modes/{interactive/theme → theme}/defaults/light-forest.json +0 -0
  373. /package/src/modes/{interactive/theme → theme}/defaults/light-frost.json +0 -0
  374. /package/src/modes/{interactive/theme → theme}/defaults/light-github.json +0 -0
  375. /package/src/modes/{interactive/theme → theme}/defaults/light-glacier.json +0 -0
  376. /package/src/modes/{interactive/theme → theme}/defaults/light-gruvbox.json +0 -0
  377. /package/src/modes/{interactive/theme → theme}/defaults/light-haze.json +0 -0
  378. /package/src/modes/{interactive/theme → theme}/defaults/light-honeycomb.json +0 -0
  379. /package/src/modes/{interactive/theme → theme}/defaults/light-lagoon.json +0 -0
  380. /package/src/modes/{interactive/theme → theme}/defaults/light-lavender.json +0 -0
  381. /package/src/modes/{interactive/theme → theme}/defaults/light-meadow.json +0 -0
  382. /package/src/modes/{interactive/theme → theme}/defaults/light-mint.json +0 -0
  383. /package/src/modes/{interactive/theme → theme}/defaults/light-monochrome.json +0 -0
  384. /package/src/modes/{interactive/theme → theme}/defaults/light-ocean.json +0 -0
  385. /package/src/modes/{interactive/theme → theme}/defaults/light-one.json +0 -0
  386. /package/src/modes/{interactive/theme → theme}/defaults/light-opal.json +0 -0
  387. /package/src/modes/{interactive/theme → theme}/defaults/light-orchard.json +0 -0
  388. /package/src/modes/{interactive/theme → theme}/defaults/light-paper.json +0 -0
  389. /package/src/modes/{interactive/theme → theme}/defaults/light-prism.json +0 -0
  390. /package/src/modes/{interactive/theme → theme}/defaults/light-retro.json +0 -0
  391. /package/src/modes/{interactive/theme → theme}/defaults/light-sand.json +0 -0
  392. /package/src/modes/{interactive/theme → theme}/defaults/light-savanna.json +0 -0
  393. /package/src/modes/{interactive/theme → theme}/defaults/light-solarized.json +0 -0
  394. /package/src/modes/{interactive/theme → theme}/defaults/light-soleil.json +0 -0
  395. /package/src/modes/{interactive/theme → theme}/defaults/light-sunset.json +0 -0
  396. /package/src/modes/{interactive/theme → theme}/defaults/light-synthwave.json +0 -0
  397. /package/src/modes/{interactive/theme → theme}/defaults/light-tokyo-night.json +0 -0
  398. /package/src/modes/{interactive/theme → theme}/defaults/light-wetland.json +0 -0
  399. /package/src/modes/{interactive/theme → theme}/defaults/light-zenith.json +0 -0
  400. /package/src/modes/{interactive/theme → theme}/defaults/limestone.json +0 -0
  401. /package/src/modes/{interactive/theme → theme}/defaults/mahogany.json +0 -0
  402. /package/src/modes/{interactive/theme → theme}/defaults/marble.json +0 -0
  403. /package/src/modes/{interactive/theme → theme}/defaults/obsidian.json +0 -0
  404. /package/src/modes/{interactive/theme → theme}/defaults/onyx.json +0 -0
  405. /package/src/modes/{interactive/theme → theme}/defaults/pearl.json +0 -0
  406. /package/src/modes/{interactive/theme → theme}/defaults/porcelain.json +0 -0
  407. /package/src/modes/{interactive/theme → theme}/defaults/quartz.json +0 -0
  408. /package/src/modes/{interactive/theme → theme}/defaults/sandstone.json +0 -0
  409. /package/src/modes/{interactive/theme → theme}/defaults/titanium.json +0 -0
  410. /package/src/modes/{interactive/theme → theme}/light.json +0 -0
  411. /package/src/modes/{interactive/theme → theme}/theme-schema.json +0 -0
  412. /package/src/{core/tools/patch → patch}/fuzzy.ts +0 -0
  413. /package/src/{core/tools/patch → patch}/normalize.ts +0 -0
  414. /package/src/{core/tools/patch → patch}/normative.ts +0 -0
  415. /package/src/{core/tools/patch → patch}/parser.ts +0 -0
  416. /package/src/{core/tools/patch → patch}/types.ts +0 -0
  417. /package/src/{core → session}/compaction/index.ts +0 -0
  418. /package/src/{core → session}/session-storage.ts +0 -0
  419. /package/src/{core/tools/task → task}/name-generator.ts +0 -0
  420. /package/src/{core/tools/task → task}/omp-command.ts +0 -0
  421. /package/src/{core/tools/task → task}/parallel.ts +0 -0
  422. /package/src/{core/tools → tools}/jtd-to-json-schema.ts +0 -0
  423. /package/src/{core/tools → tools}/path-utils.ts +0 -0
  424. /package/src/{core → utils}/event-bus.ts +0 -0
  425. /package/src/{core → utils}/frontmatter.ts +0 -0
  426. /package/src/{core → utils}/terminal-notify.ts +0 -0
  427. /package/src/{core → utils}/timings.ts +0 -0
  428. /package/src/{core → utils}/utils.ts +0 -0
  429. /package/src/{core/tools/web-scrapers → web/scrapers}/artifacthub.ts +0 -0
  430. /package/src/{core/tools/web-scrapers → web/scrapers}/arxiv.ts +0 -0
  431. /package/src/{core/tools/web-scrapers → web/scrapers}/aur.ts +0 -0
  432. /package/src/{core/tools/web-scrapers → web/scrapers}/biorxiv.ts +0 -0
  433. /package/src/{core/tools/web-scrapers → web/scrapers}/bluesky.ts +0 -0
  434. /package/src/{core/tools/web-scrapers → web/scrapers}/brew.ts +0 -0
  435. /package/src/{core/tools/web-scrapers → web/scrapers}/cheatsh.ts +0 -0
  436. /package/src/{core/tools/web-scrapers → web/scrapers}/chocolatey.ts +0 -0
  437. /package/src/{core/tools/web-scrapers → web/scrapers}/cisa-kev.ts +0 -0
  438. /package/src/{core/tools/web-scrapers → web/scrapers}/clojars.ts +0 -0
  439. /package/src/{core/tools/web-scrapers → web/scrapers}/coingecko.ts +0 -0
  440. /package/src/{core/tools/web-scrapers → web/scrapers}/crates-io.ts +0 -0
  441. /package/src/{core/tools/web-scrapers → web/scrapers}/crossref.ts +0 -0
  442. /package/src/{core/tools/web-scrapers → web/scrapers}/devto.ts +0 -0
  443. /package/src/{core/tools/web-scrapers → web/scrapers}/discogs.ts +0 -0
  444. /package/src/{core/tools/web-scrapers → web/scrapers}/discourse.ts +0 -0
  445. /package/src/{core/tools/web-scrapers → web/scrapers}/dockerhub.ts +0 -0
  446. /package/src/{core/tools/web-scrapers → web/scrapers}/fdroid.ts +0 -0
  447. /package/src/{core/tools/web-scrapers → web/scrapers}/firefox-addons.ts +0 -0
  448. /package/src/{core/tools/web-scrapers → web/scrapers}/flathub.ts +0 -0
  449. /package/src/{core/tools/web-scrapers → web/scrapers}/github-gist.ts +0 -0
  450. /package/src/{core/tools/web-scrapers → web/scrapers}/github.ts +0 -0
  451. /package/src/{core/tools/web-scrapers → web/scrapers}/gitlab.ts +0 -0
  452. /package/src/{core/tools/web-scrapers → web/scrapers}/go-pkg.ts +0 -0
  453. /package/src/{core/tools/web-scrapers → web/scrapers}/hackage.ts +0 -0
  454. /package/src/{core/tools/web-scrapers → web/scrapers}/hackernews.ts +0 -0
  455. /package/src/{core/tools/web-scrapers → web/scrapers}/hex.ts +0 -0
  456. /package/src/{core/tools/web-scrapers → web/scrapers}/huggingface.ts +0 -0
  457. /package/src/{core/tools/web-scrapers → web/scrapers}/iacr.ts +0 -0
  458. /package/src/{core/tools/web-scrapers → web/scrapers}/index.ts +0 -0
  459. /package/src/{core/tools/web-scrapers → web/scrapers}/jetbrains-marketplace.ts +0 -0
  460. /package/src/{core/tools/web-scrapers → web/scrapers}/lemmy.ts +0 -0
  461. /package/src/{core/tools/web-scrapers → web/scrapers}/lobsters.ts +0 -0
  462. /package/src/{core/tools/web-scrapers → web/scrapers}/mastodon.ts +0 -0
  463. /package/src/{core/tools/web-scrapers → web/scrapers}/maven.ts +0 -0
  464. /package/src/{core/tools/web-scrapers → web/scrapers}/mdn.ts +0 -0
  465. /package/src/{core/tools/web-scrapers → web/scrapers}/metacpan.ts +0 -0
  466. /package/src/{core/tools/web-scrapers → web/scrapers}/musicbrainz.ts +0 -0
  467. /package/src/{core/tools/web-scrapers → web/scrapers}/npm.ts +0 -0
  468. /package/src/{core/tools/web-scrapers → web/scrapers}/nuget.ts +0 -0
  469. /package/src/{core/tools/web-scrapers → web/scrapers}/nvd.ts +0 -0
  470. /package/src/{core/tools/web-scrapers → web/scrapers}/ollama.ts +0 -0
  471. /package/src/{core/tools/web-scrapers → web/scrapers}/open-vsx.ts +0 -0
  472. /package/src/{core/tools/web-scrapers → web/scrapers}/opencorporates.ts +0 -0
  473. /package/src/{core/tools/web-scrapers → web/scrapers}/openlibrary.ts +0 -0
  474. /package/src/{core/tools/web-scrapers → web/scrapers}/orcid.ts +0 -0
  475. /package/src/{core/tools/web-scrapers → web/scrapers}/osv.ts +0 -0
  476. /package/src/{core/tools/web-scrapers → web/scrapers}/packagist.ts +0 -0
  477. /package/src/{core/tools/web-scrapers → web/scrapers}/pub-dev.ts +0 -0
  478. /package/src/{core/tools/web-scrapers → web/scrapers}/pubmed.ts +0 -0
  479. /package/src/{core/tools/web-scrapers → web/scrapers}/pypi.ts +0 -0
  480. /package/src/{core/tools/web-scrapers → web/scrapers}/rawg.ts +0 -0
  481. /package/src/{core/tools/web-scrapers → web/scrapers}/readthedocs.ts +0 -0
  482. /package/src/{core/tools/web-scrapers → web/scrapers}/reddit.ts +0 -0
  483. /package/src/{core/tools/web-scrapers → web/scrapers}/repology.ts +0 -0
  484. /package/src/{core/tools/web-scrapers → web/scrapers}/rfc.ts +0 -0
  485. /package/src/{core/tools/web-scrapers → web/scrapers}/rubygems.ts +0 -0
  486. /package/src/{core/tools/web-scrapers → web/scrapers}/searchcode.ts +0 -0
  487. /package/src/{core/tools/web-scrapers → web/scrapers}/sec-edgar.ts +0 -0
  488. /package/src/{core/tools/web-scrapers → web/scrapers}/semantic-scholar.ts +0 -0
  489. /package/src/{core/tools/web-scrapers → web/scrapers}/snapcraft.ts +0 -0
  490. /package/src/{core/tools/web-scrapers → web/scrapers}/sourcegraph.ts +0 -0
  491. /package/src/{core/tools/web-scrapers → web/scrapers}/spdx.ts +0 -0
  492. /package/src/{core/tools/web-scrapers → web/scrapers}/spotify.ts +0 -0
  493. /package/src/{core/tools/web-scrapers → web/scrapers}/stackoverflow.ts +0 -0
  494. /package/src/{core/tools/web-scrapers → web/scrapers}/terraform.ts +0 -0
  495. /package/src/{core/tools/web-scrapers → web/scrapers}/tldr.ts +0 -0
  496. /package/src/{core/tools/web-scrapers → web/scrapers}/vimeo.ts +0 -0
  497. /package/src/{core/tools/web-scrapers → web/scrapers}/vscode-marketplace.ts +0 -0
  498. /package/src/{core/tools/web-scrapers → web/scrapers}/w3c.ts +0 -0
  499. /package/src/{core/tools/web-scrapers → web/scrapers}/wikidata.ts +0 -0
  500. /package/src/{core/tools/web-scrapers → web/scrapers}/wikipedia.ts +0 -0
  501. /package/src/{core/tools/web-search → web/search}/types.ts +0 -0
@@ -3,30 +3,33 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
3
3
  import type { Component } from "@oh-my-pi/pi-tui";
4
4
  import { Text, truncateToWidth } from "@oh-my-pi/pi-tui";
5
5
  import { Type } from "@sinclair/typebox";
6
- import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate";
7
- import type { Theme } from "../../modes/interactive/theme/theme";
8
- import bashDescription from "../../prompts/tools/bash.md" with { type: "text" };
9
- import { type BashExecutorOptions, executeBash } from "../bash-executor";
10
- import type { RenderResultOptions } from "../custom-tools/types";
11
- import { renderPromptTemplate } from "../prompt-templates";
6
+ import { renderPromptTemplate } from "$c/config/prompt-templates";
7
+ import { type BashExecutorOptions, executeBash } from "$c/exec/bash-executor";
8
+ import type { RenderResultOptions } from "$c/extensibility/custom-tools/types";
9
+ import { truncateToVisualLines } from "$c/modes/components/visual-truncate";
10
+ import type { Theme } from "$c/modes/theme/theme";
11
+ import bashDescription from "$c/prompts/tools/bash.md" with { type: "text" };
12
+ import type { OutputMeta } from "$c/tools/output-meta";
13
+ import { ToolError } from "$c/tools/tool-errors";
14
+
12
15
  import { checkBashInterception, checkSimpleLsInterception } from "./bash-interceptor";
13
16
  import type { ToolSession } from "./index";
17
+ import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
14
18
  import { resolveToCwd } from "./path-utils";
15
19
  import { ToolUIKit } from "./render-utils";
16
- import { formatTailTruncationNotice, type TruncationResult, truncateTail } from "./truncate";
20
+ import { toolResult } from "./tool-result";
21
+ import { DEFAULT_MAX_BYTES } from "./truncate";
17
22
 
18
23
  export const BASH_DEFAULT_PREVIEW_LINES = 10;
19
24
 
20
25
  const bashSchema = Type.Object({
21
- command: Type.String({ description: "Bash command to execute" }),
22
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
23
- cwd: Type.Optional(Type.String({ description: "Working directory for the command (default: current directory)" })),
26
+ command: Type.String({ description: "Command to execute" }),
27
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 300)" })),
28
+ cwd: Type.Optional(Type.String({ description: "Working directory (default: cwd)" })),
24
29
  });
25
30
 
26
31
  export interface BashToolDetails {
27
- truncation?: TruncationResult;
28
- fullOutputPath?: string;
29
- fullOutput?: string;
32
+ meta?: OutputMeta;
30
33
  }
31
34
 
32
35
  export interface BashToolOptions {}
@@ -51,7 +54,7 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
51
54
 
52
55
  public async execute(
53
56
  _toolCallId: string,
54
- { command, timeout, cwd }: { command: string; timeout?: number; cwd?: string },
57
+ { command, timeout: rawTimeout = 300, cwd }: { command: string; timeout?: number; cwd?: string },
55
58
  signal?: AbortSignal,
56
59
  onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
57
60
  ctx?: AgentToolContext,
@@ -61,12 +64,12 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
61
64
  const rules = this.session.settings?.getBashInterceptorRules?.();
62
65
  const interception = checkBashInterception(command, ctx?.toolNames ?? [], rules);
63
66
  if (interception.block) {
64
- throw new Error(interception.message);
67
+ throw new ToolError(interception.message ?? "Command blocked");
65
68
  }
66
69
  if (this.session.settings?.getBashInterceptorSimpleLsEnabled?.() !== false) {
67
70
  const lsInterception = checkSimpleLsInterception(command, ctx?.toolNames ?? []);
68
71
  if (lsInterception.block) {
69
- throw new Error(lsInterception.message);
72
+ throw new ToolError(lsInterception.message ?? "Command blocked");
70
73
  }
71
74
  }
72
75
  }
@@ -76,26 +79,39 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
76
79
  try {
77
80
  cwdStat = await Bun.file(commandCwd).stat();
78
81
  } catch {
79
- throw new Error(`Working directory does not exist: ${commandCwd}`);
82
+ throw new ToolError(`Working directory does not exist: ${commandCwd}`);
80
83
  }
81
84
  if (!cwdStat.isDirectory()) {
82
- throw new Error(`Working directory is not a directory: ${commandCwd}`);
85
+ throw new ToolError(`Working directory is not a directory: ${commandCwd}`);
83
86
  }
84
87
 
85
- // Track output for streaming updates
86
- let currentOutput = "";
88
+ // Auto-convert milliseconds to seconds if value > 1000 (16+ min is unreasonable)
89
+ let timeoutSec = rawTimeout > 1000 ? rawTimeout / 1000 : rawTimeout;
90
+ // Clamp to reasonable range: 1s - 3600s (1 hour)
91
+ timeoutSec = Math.max(1, Math.min(3600, timeoutSec));
92
+ const timeoutMs = timeoutSec * 1000;
93
+
94
+ // Track output for streaming updates (tail only)
95
+ const tailBuffer = createTailBuffer(DEFAULT_MAX_BYTES);
96
+
97
+ // Set up artifacts environment and allocation
98
+ const artifactsDir = this.session.getArtifactsDir?.();
99
+ const extraEnv = artifactsDir ? { ARTIFACTS: artifactsDir } : undefined;
100
+ const { artifactPath, artifactId } = await allocateOutputArtifact(this.session, "bash");
87
101
 
88
102
  const executorOptions: BashExecutorOptions = {
89
103
  cwd: commandCwd,
90
- timeout: timeout ? timeout * 1000 : undefined, // Convert to milliseconds
104
+ timeout: timeoutMs,
91
105
  signal,
106
+ env: extraEnv,
107
+ artifactPath,
108
+ artifactId,
92
109
  onChunk: (chunk) => {
93
- currentOutput += chunk;
110
+ tailBuffer.append(chunk);
94
111
  if (onUpdate) {
95
- const truncation = truncateTail(currentOutput);
96
112
  onUpdate({
97
- content: [{ type: "text", text: truncation.content || "" }],
98
- details: truncation.truncated ? { truncation, fullOutput: currentOutput } : {},
113
+ content: [{ type: "text", text: tailBuffer.text() }],
114
+ details: {},
99
115
  });
100
116
  }
101
117
  },
@@ -104,33 +120,18 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
104
120
  // Handle errors
105
121
  const result = await executeBash(command, executorOptions);
106
122
  if (result.cancelled) {
107
- throw new Error(result.output || "Command aborted");
123
+ throw new ToolError(result.output || "Command aborted");
108
124
  }
109
125
 
110
- // Apply tail truncation for final output
111
- const truncation = truncateTail(result.output);
112
- let outputText = truncation.content || "(no output)";
113
-
114
- let details: BashToolDetails | undefined;
115
-
116
- if (truncation.truncated) {
117
- details = {
118
- truncation,
119
- fullOutputPath: result.fullOutputPath,
120
- fullOutput: currentOutput,
121
- };
122
- outputText += formatTailTruncationNotice(truncation, {
123
- fullOutputPath: result.fullOutputPath,
124
- originalContent: result.output,
125
- });
126
- }
126
+ const outputText = result.output || "(no output)";
127
+ const details: BashToolDetails = {};
128
+ const resultBuilder = toolResult(details).text(outputText).truncationFromSummary(result, { direction: "tail" });
127
129
 
128
130
  if (result.exitCode !== 0 && result.exitCode !== undefined) {
129
- outputText += `\n\nCommand exited with code ${result.exitCode}`;
130
- throw new Error(outputText);
131
+ throw new ToolError(`${outputText}\n\nCommand exited with code ${result.exitCode}`);
131
132
  }
132
133
 
133
- return { content: [{ type: "text", text: outputText }], details };
134
+ return resultBuilder.done();
134
135
  }
135
136
  }
136
137
 
@@ -147,6 +148,8 @@ interface BashRenderArgs {
147
148
  interface BashRenderContext {
148
149
  /** Raw output text */
149
150
  output?: string;
151
+ /** Whether output came from artifact storage */
152
+ isFullOutput?: boolean;
150
153
  /** Whether output is expanded */
151
154
  expanded?: boolean;
152
155
  /** Number of preview lines when collapsed */
@@ -198,38 +201,33 @@ export const bashToolRenderer = {
198
201
  const ui = new ToolUIKit(uiTheme);
199
202
  const { renderContext } = options;
200
203
  const details = result.details;
201
-
202
204
  const expanded = renderContext?.expanded ?? options.expanded;
203
205
  const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
204
206
 
205
207
  // Get output from context (preferred) or fall back to result content
206
208
  const output = renderContext?.output ?? (result.content?.find((c) => c.type === "text")?.text ?? "").trim();
207
- const fullOutput = details?.fullOutput;
208
- const displayOutput = expanded ? (fullOutput ?? output) : output;
209
- const showingFullOutput = expanded && fullOutput !== undefined;
209
+ const displayOutput = output;
210
+ const showingFullOutput = expanded && renderContext?.isFullOutput === true;
210
211
 
211
212
  // Build truncation warning lines (static, doesn't depend on width)
212
- const truncation = details?.truncation;
213
- const fullOutputPath = details?.fullOutputPath;
213
+ const truncation = details?.meta?.truncation;
214
214
  const timeoutSeconds = renderContext?.timeout;
215
215
  const timeoutLine =
216
216
  typeof timeoutSeconds === "number"
217
217
  ? uiTheme.fg("dim", ui.wrapBrackets(`Timeout: ${timeoutSeconds}s`))
218
218
  : undefined;
219
219
  let warningLine: string | undefined;
220
- if (fullOutputPath || (truncation?.truncated && !showingFullOutput)) {
220
+ if (truncation && !showingFullOutput) {
221
221
  const warnings: string[] = [];
222
- if (fullOutputPath) {
223
- warnings.push(`Full output: ${fullOutputPath}`);
222
+ if (truncation?.artifactId) {
223
+ warnings.push(`Full output: artifact://${truncation.artifactId}`);
224
224
  }
225
- if (truncation?.truncated && !showingFullOutput) {
226
- if (truncation.truncatedBy === "lines") {
227
- warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
228
- } else {
229
- warnings.push(
230
- `Truncated: ${truncation.outputLines} lines shown (${ui.formatBytes(truncation.maxBytes)} limit)`,
231
- );
232
- }
225
+ if (truncation.truncatedBy === "lines") {
226
+ warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
227
+ } else {
228
+ warnings.push(
229
+ `Truncated: ${truncation.outputLines} lines shown (${ui.formatBytes(truncation.outputBytes)} limit)`,
230
+ );
233
231
  }
234
232
  if (warnings.length > 0) {
235
233
  warningLine = uiTheme.fg("warning", ui.wrapBrackets(warnings.join(". ")));
@@ -3,10 +3,10 @@ import type { Component } from "@oh-my-pi/pi-tui";
3
3
  import { Text } from "@oh-my-pi/pi-tui";
4
4
  import { untilAborted } from "@oh-my-pi/pi-utils";
5
5
  import { Type } from "@sinclair/typebox";
6
- import type { Theme } from "../../modes/interactive/theme/theme";
7
- import calculatorDescription from "../../prompts/tools/calculator.md" with { type: "text" };
8
- import type { RenderResultOptions } from "../custom-tools/types";
9
- import { renderPromptTemplate } from "../prompt-templates";
6
+ import { renderPromptTemplate } from "$c/config/prompt-templates";
7
+ import type { RenderResultOptions } from "$c/extensibility/custom-tools/types";
8
+ import type { Theme } from "$c/modes/theme/theme";
9
+ import calculatorDescription from "$c/prompts/tools/calculator.md" with { type: "text" };
10
10
  import type { ToolSession } from "./index";
11
11
  import {
12
12
  formatCount,
@@ -9,9 +9,9 @@ import { StringEnum } from "@oh-my-pi/pi-ai";
9
9
  import type { Static, TObject } from "@sinclair/typebox";
10
10
  import { Type } from "@sinclair/typebox";
11
11
  import Ajv, { type ErrorObject, type ValidateFunction } from "ajv";
12
+ import { subprocessToolRegistry } from "$c/task/subprocess-tool-registry";
12
13
  import type { ToolSession } from "./index";
13
14
  import { jtdToJsonSchema } from "./jtd-to-json-schema";
14
- import { subprocessToolRegistry } from "./task/subprocess-tool-registry";
15
15
 
16
16
  export interface CompleteDetails {
17
17
  data: unknown;
@@ -1,6 +1,6 @@
1
1
  import type { AgentToolContext, ToolCallContext } from "@oh-my-pi/pi-agent-core";
2
- import type { CustomToolContext } from "../custom-tools/types";
3
- import type { ExtensionUIContext } from "../extensions/types";
2
+ import type { CustomToolContext } from "$c/extensibility/custom-tools/types";
3
+ import type { ExtensionUIContext } from "$c/extensibility/extensions/types";
4
4
 
5
5
  declare module "@oh-my-pi/pi-agent-core" {
6
6
  interface AgentToolContext extends CustomToolContext {
@@ -1,5 +1,4 @@
1
- import { rm } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
1
+ import { mkdir, rm } from "node:fs/promises";
3
2
  import * as path from "node:path";
4
3
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
4
  import type { Component } from "@oh-my-pi/pi-tui";
@@ -8,26 +7,26 @@ import { ptree } from "@oh-my-pi/pi-utils";
8
7
  import { type Static, Type } from "@sinclair/typebox";
9
8
  import { nanoid } from "nanoid";
10
9
  import { parse as parseHtml } from "node-html-parser";
11
- import { type Theme, theme } from "../../modes/interactive/theme/theme";
12
- import webFetchDescription from "../../prompts/tools/web-fetch.md" with { type: "text" };
13
- import { ensureTool } from "../../utils/tools-manager";
14
- import type { RenderResultOptions } from "../custom-tools/types";
15
- import { renderPromptTemplate } from "../prompt-templates";
10
+ import { renderPromptTemplate } from "$c/config/prompt-templates";
11
+ import type { RenderResultOptions } from "$c/extensibility/custom-tools/types";
12
+ import { type Theme, theme } from "$c/modes/theme/theme";
13
+ import fetchDescription from "$c/prompts/tools/fetch.md" with { type: "text" };
14
+ import type { OutputMeta } from "$c/tools/output-meta";
15
+ import { ToolAbortError } from "$c/tools/tool-errors";
16
+ import { ensureTool } from "$c/utils/tools-manager";
17
+ import { specialHandlers } from "$c/web/scrapers/index";
18
+ import type { RenderResult } from "$c/web/scrapers/types";
19
+ import { finalizeOutput, loadPage, MAX_OUTPUT_CHARS } from "$c/web/scrapers/types";
20
+ import { convertWithMarkitdown, fetchBinary } from "$c/web/scrapers/utils";
16
21
  import type { ToolSession } from "./index";
22
+ import { applyListLimit } from "./list-limit";
17
23
  import { formatExpandHint } from "./render-utils";
18
- import { specialHandlers } from "./web-scrapers/index";
19
- import type { RenderResult } from "./web-scrapers/types";
20
- import { finalizeOutput, loadPage } from "./web-scrapers/types";
21
- import { convertWithMarkitdown, fetchBinary } from "./web-scrapers/utils";
24
+ import { toolResult } from "./tool-result";
22
25
 
23
26
  // =============================================================================
24
27
  // Types and Constants
25
28
  // =============================================================================
26
29
 
27
- const MIN_TIMEOUT = 1_000;
28
- const DEFAULT_TIMEOUT = 20_000;
29
- const MAX_TIMEOUT = 45_000;
30
-
31
30
  // Convertible document types (markitdown supported)
32
31
  const CONVERTIBLE_MIMES = new Set([
33
32
  "application/pdf",
@@ -246,7 +245,7 @@ async function tryMdSuffix(url: string, timeout: number, signal?: AbortSignal):
246
245
  if (signal?.aborted) {
247
246
  return null;
248
247
  }
249
- const result = await loadPage(candidate, { timeout: Math.min(timeout, MAX_TIMEOUT), signal });
248
+ const result = await loadPage(candidate, { timeout, signal });
250
249
  if (result.ok && result.content.trim().length > 100 && !looksLikeHtml(result.content)) {
251
250
  return result.content;
252
251
  }
@@ -448,9 +447,10 @@ function parseFeedToMarkdown(content: string, maxItems = 10): string {
448
447
  async function renderHtmlToText(
449
448
  html: string,
450
449
  timeout: number,
450
+ scratchDir: string,
451
451
  ): Promise<{ content: string; ok: boolean; method: string }> {
452
- const tmpDir = tmpdir();
453
- const tmpFile = path.join(tmpDir, `omp-${nanoid()}.html`);
452
+ await mkdir(scratchDir, { recursive: true });
453
+ const tmpFile = path.join(scratchDir, `omp-${nanoid()}.html`);
454
454
 
455
455
  try {
456
456
  await Bun.write(tmpFile, html);
@@ -532,7 +532,7 @@ function formatJson(content: string): string {
532
532
  async function handleSpecialUrls(url: string, timeout: number, signal?: AbortSignal): Promise<RenderResult | null> {
533
533
  for (const handler of specialHandlers) {
534
534
  if (signal?.aborted) {
535
- throw new Error("Operation aborted");
535
+ throw new ToolAbortError();
536
536
  }
537
537
  const result = await handler(url, timeout, signal);
538
538
  if (result) return result;
@@ -550,13 +550,14 @@ async function handleSpecialUrls(url: string, timeout: number, signal?: AbortSig
550
550
  async function renderUrl(
551
551
  url: string,
552
552
  timeout: number,
553
- raw: boolean = false,
553
+ raw: boolean,
554
+ scratchDir: string,
554
555
  signal?: AbortSignal,
555
556
  ): Promise<RenderResult> {
556
557
  const notes: string[] = [];
557
558
  const fetchedAt = new Date().toISOString();
558
559
  if (signal?.aborted) {
559
- throw new Error("Operation aborted");
560
+ throw new ToolAbortError();
560
561
  }
561
562
 
562
563
  // Handle internal protocol URLs (e.g., pi-internal://) - return empty
@@ -586,7 +587,7 @@ async function renderUrl(
586
587
  // Step 2: Fetch page
587
588
  const response = await loadPage(url, { timeout, signal });
588
589
  if (signal?.aborted) {
589
- throw new Error("Operation aborted");
590
+ throw new ToolAbortError();
590
591
  }
591
592
  if (!response.ok) {
592
593
  return {
@@ -787,11 +788,11 @@ async function renderUrl(
787
788
  }
788
789
 
789
790
  if (signal?.aborted) {
790
- throw new Error("Operation aborted");
791
+ throw new ToolAbortError();
791
792
  }
792
793
 
793
794
  // Step 6: Render HTML with lynx or html2text
794
- const htmlResult = await renderHtmlToText(rawContent, timeout);
795
+ const htmlResult = await renderHtmlToText(rawContent, timeout, scratchDir);
795
796
  if (!htmlResult.ok) {
796
797
  notes.push("html rendering failed (lynx/html2text unavailable)");
797
798
  const output = finalizeOutput(rawContent);
@@ -871,66 +872,68 @@ async function renderUrl(
871
872
  // Tool Definition
872
873
  // =============================================================================
873
874
 
874
- const webFetchSchema = Type.Object({
875
- url: Type.String({ description: "The URL to fetch and render" }),
876
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20, max: 120)" })),
877
- raw: Type.Optional(
878
- Type.Boolean({ description: "Return raw content without site-specific rendering or LLM-friendly transforms" }),
879
- ),
875
+ const fetchSchema = Type.Object({
876
+ url: Type.String({ description: "URL to fetch" }),
877
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })),
878
+ raw: Type.Optional(Type.Boolean({ description: "Return raw HTML without transforms" })),
880
879
  });
881
880
 
882
- export interface WebFetchToolDetails {
881
+ export interface FetchToolDetails {
883
882
  url: string;
884
883
  finalUrl: string;
885
884
  contentType: string;
886
885
  method: string;
887
886
  truncated: boolean;
888
887
  notes: string[];
888
+ meta?: OutputMeta;
889
889
  }
890
890
 
891
- export class WebFetchTool implements AgentTool<typeof webFetchSchema, WebFetchToolDetails> {
892
- public readonly name = "web_fetch";
893
- public readonly label = "Web Fetch";
891
+ export class FetchTool implements AgentTool<typeof fetchSchema, FetchToolDetails> {
892
+ public readonly name = "fetch";
893
+ public readonly label = "Fetch";
894
894
  public readonly description: string;
895
- public readonly parameters = webFetchSchema;
895
+ public readonly parameters = fetchSchema;
896
+ private readonly session: ToolSession;
896
897
 
897
- constructor(_session: ToolSession) {
898
- this.description = renderPromptTemplate(webFetchDescription);
898
+ constructor(session: ToolSession) {
899
+ this.session = session;
900
+ this.description = renderPromptTemplate(fetchDescription);
899
901
  }
900
902
 
901
903
  public async execute(
902
904
  _toolCallId: string,
903
- params: Static<typeof webFetchSchema>,
905
+ params: Static<typeof fetchSchema>,
904
906
  signal?: AbortSignal,
905
- _onUpdate?: AgentToolUpdateCallback<WebFetchToolDetails>,
907
+ _onUpdate?: AgentToolUpdateCallback<FetchToolDetails>,
906
908
  _context?: AgentToolContext,
907
- ): Promise<AgentToolResult<WebFetchToolDetails>> {
908
- const { url, timeout = DEFAULT_TIMEOUT, raw = false } = params;
909
+ ): Promise<AgentToolResult<FetchToolDetails>> {
910
+ const { url, timeout: rawTimeout = 20, raw = false } = params;
911
+
912
+ // Auto-convert milliseconds to seconds if value > 1000 (16+ min is unreasonable)
913
+ const timeoutSec = rawTimeout > 1000 ? rawTimeout / 1000 : rawTimeout;
914
+
915
+ // Clamp to valid range (seconds)
916
+ const effectiveTimeout = Math.min(Math.max(timeoutSec, 1), 45);
909
917
 
910
918
  if (signal?.aborted) {
911
- throw new Error("Operation aborted");
919
+ throw new ToolAbortError();
912
920
  }
913
921
 
914
- // Clamp timeout
915
- const effectiveTimeout = Math.min(Math.max(timeout, MIN_TIMEOUT), MAX_TIMEOUT);
916
-
917
- const result = await renderUrl(url, effectiveTimeout, raw, signal);
922
+ const scratchDir = this.session.getArtifactsDir?.() ?? this.session.cwd;
923
+ const result = await renderUrl(url, effectiveTimeout, raw, scratchDir, signal);
918
924
 
919
925
  // Format output
920
926
  let output = "";
921
927
  output += `URL: ${result.finalUrl}\n`;
922
928
  output += `Content-Type: ${result.contentType}\n`;
923
929
  output += `Method: ${result.method}\n`;
924
- if (result.truncated) {
925
- output += `Warning: Output was truncated\n`;
926
- }
927
930
  if (result.notes.length > 0) {
928
931
  output += `Notes: ${result.notes.join("; ")}\n`;
929
932
  }
930
933
  output += `\n---\n\n`;
931
934
  output += result.content;
932
935
 
933
- const details: WebFetchToolDetails = {
936
+ const details: FetchToolDetails = {
934
937
  url: result.url,
935
938
  finalUrl: result.finalUrl,
936
939
  contentType: result.contentType,
@@ -939,10 +942,21 @@ export class WebFetchTool implements AgentTool<typeof webFetchSchema, WebFetchTo
939
942
  notes: result.notes,
940
943
  };
941
944
 
942
- return {
943
- content: [{ type: "text", text: output }],
944
- details,
945
- };
945
+ const resultBuilder = toolResult(details).text(output).sourceUrl(result.finalUrl);
946
+ if (result.truncated) {
947
+ const outputLines = result.content.split("\n").length;
948
+ const outputBytes = Buffer.byteLength(result.content, "utf-8");
949
+ const totalBytes = Math.max(outputBytes + 1, MAX_OUTPUT_CHARS + 1);
950
+ const totalLines = outputLines + 1;
951
+ resultBuilder.truncationFromText(result.content, {
952
+ direction: "tail",
953
+ totalLines,
954
+ totalBytes,
955
+ maxBytes: MAX_OUTPUT_CHARS,
956
+ });
957
+ }
958
+
959
+ return resultBuilder.done();
946
960
  }
947
961
  }
948
962
 
@@ -967,32 +981,26 @@ function getDomain(url: string): string {
967
981
  }
968
982
  }
969
983
 
970
- /** Get first N lines of text as preview */
971
- function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
972
- const lines = text.split("\n").filter((l) => l.trim());
973
- return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen, ellipsis));
974
- }
975
-
976
984
  /** Count non-empty lines */
977
985
  function countNonEmptyLines(text: string): number {
978
986
  return text.split("\n").filter((l) => l.trim()).length;
979
987
  }
980
988
 
981
- /** Render web fetch call (URL preview) */
982
- export function renderWebFetchCall(
989
+ /** Render fetch call (URL preview) */
990
+ export function renderFetchCall(
983
991
  args: { url: string; timeout?: number; raw?: boolean },
984
992
  uiTheme: Theme = theme,
985
993
  ): Component {
986
994
  const domain = getDomain(args.url);
987
995
  const path = truncate(args.url.replace(/^https?:\/\/[^/]+/, ""), 50, uiTheme.format.ellipsis);
988
996
  const icon = uiTheme.styledSymbol("status.pending", "muted");
989
- const text = `${icon} ${uiTheme.fg("toolTitle", "Web Fetch")} ${uiTheme.fg("accent", domain)}${uiTheme.fg("dim", path)}`;
997
+ const text = `${icon} ${uiTheme.fg("toolTitle", "Fetch")} ${uiTheme.fg("accent", domain)}${uiTheme.fg("dim", path)}`;
990
998
  return new Text(text, 0, 0);
991
999
  }
992
1000
 
993
- /** Render web fetch result with tree-based layout */
994
- export function renderWebFetchResult(
995
- result: { content: Array<{ type: string; text?: string }>; details?: WebFetchToolDetails },
1001
+ /** Render fetch result with tree-based layout */
1002
+ export function renderFetchResult(
1003
+ result: { content: Array<{ type: string; text?: string }>; details?: FetchToolDetails },
996
1004
  options: RenderResultOptions,
997
1005
  uiTheme: Theme = theme,
998
1006
  ): Component {
@@ -1006,7 +1014,9 @@ export function renderWebFetchResult(
1006
1014
  const domain = getDomain(details.finalUrl);
1007
1015
  const hasRedirect = details.url !== details.finalUrl;
1008
1016
  const hasNotes = details.notes.length > 0;
1009
- const statusIcon = details.truncated
1017
+ const truncation = details.meta?.truncation;
1018
+ const truncated = Boolean(details.truncated || truncation);
1019
+ const statusIcon = truncated
1010
1020
  ? uiTheme.styledSymbol("status.warning", "warning")
1011
1021
  : uiTheme.styledSymbol("status.success", "success");
1012
1022
  const expandHint = formatExpandHint(uiTheme, expanded);
@@ -1021,6 +1031,7 @@ export function renderWebFetchResult(
1021
1031
  : contentText;
1022
1032
  const lineCount = countNonEmptyLines(contentBody);
1023
1033
  const charCount = contentBody.trim().length;
1034
+ const contentLines = contentBody.split("\n").filter((l) => l.trim());
1024
1035
 
1025
1036
  if (!expanded) {
1026
1037
  // Collapsed view: metadata + preview
@@ -1031,14 +1042,19 @@ export function renderWebFetchResult(
1031
1042
  if (hasRedirect) {
1032
1043
  metaLines.push(`${uiTheme.fg("muted", "Final URL:")} ${uiTheme.fg("mdLinkUrl", details.finalUrl)}`);
1033
1044
  }
1034
- if (details.truncated) {
1045
+ if (truncated) {
1035
1046
  metaLines.push(uiTheme.fg("warning", `${uiTheme.status.warning} Output truncated`));
1047
+ if (truncation?.artifactId) {
1048
+ metaLines.push(uiTheme.fg("warning", `Full output: artifact://${truncation.artifactId}`));
1049
+ }
1036
1050
  }
1037
1051
  if (hasNotes) {
1038
1052
  metaLines.push(`${uiTheme.fg("muted", "Notes:")} ${details.notes.join("; ")}`);
1039
1053
  }
1040
1054
 
1041
- const previewLines = getPreviewLines(contentBody, 3, 100, uiTheme.format.ellipsis);
1055
+ const previewLimit = 3;
1056
+ const previewList = applyListLimit(contentLines, { headLimit: previewLimit });
1057
+ const previewLines = previewList.items.map((line) => truncate(line.trim(), 100, uiTheme.format.ellipsis));
1042
1058
  const detailLines: string[] = [...metaLines];
1043
1059
 
1044
1060
  if (previewLines.length === 0) {
@@ -1049,7 +1065,7 @@ export function renderWebFetchResult(
1049
1065
  }
1050
1066
  }
1051
1067
 
1052
- const remaining = Math.max(0, lineCount - previewLines.length);
1068
+ const remaining = Math.max(0, contentLines.length - previewLines.length);
1053
1069
  if (remaining > 0) {
1054
1070
  detailLines.push(uiTheme.fg("muted", `${uiTheme.format.ellipsis} ${remaining} more lines`));
1055
1071
  } else {
@@ -1074,8 +1090,11 @@ export function renderWebFetchResult(
1074
1090
  const lineLabel = `${lineCount} line${lineCount === 1 ? "" : "s"}`;
1075
1091
  metaLines.push(`${uiTheme.fg("muted", "Lines:")} ${lineLabel}`);
1076
1092
  metaLines.push(`${uiTheme.fg("muted", "Chars:")} ${charCount}`);
1077
- if (details.truncated) {
1093
+ if (truncated) {
1078
1094
  metaLines.push(uiTheme.fg("warning", `${uiTheme.status.warning} Output truncated`));
1095
+ if (truncation?.artifactId) {
1096
+ metaLines.push(uiTheme.fg("warning", `Full output: artifact://${truncation.artifactId}`));
1097
+ }
1079
1098
  }
1080
1099
  if (hasNotes) {
1081
1100
  metaLines.push(`${uiTheme.fg("muted", "Notes:")} ${details.notes.join("; ")}`);
@@ -1089,8 +1108,10 @@ export function renderWebFetchResult(
1089
1108
  }
1090
1109
 
1091
1110
  text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("accent", "Content Preview")}`;
1092
- const previewLines = getPreviewLines(contentBody, 12, 120, uiTheme.format.ellipsis);
1093
- const remaining = Math.max(0, lineCount - previewLines.length);
1111
+ const previewLimit = 12;
1112
+ const previewList = applyListLimit(contentLines, { headLimit: previewLimit });
1113
+ const previewLines = previewList.items.map((line) => truncate(line.trim(), 120, uiTheme.format.ellipsis));
1114
+ const remaining = Math.max(0, contentLines.length - previewLines.length);
1094
1115
  const contentPrefix = uiTheme.fg("dim", " ");
1095
1116
 
1096
1117
  if (previewLines.length === 0) {
@@ -1109,7 +1130,7 @@ export function renderWebFetchResult(
1109
1130
  return new Text(text, 0, 0);
1110
1131
  }
1111
1132
 
1112
- export const webFetchToolRenderer = {
1113
- renderCall: renderWebFetchCall,
1114
- renderResult: renderWebFetchResult,
1133
+ export const fetchToolRenderer = {
1134
+ renderCall: renderFetchCall,
1135
+ renderResult: renderFetchResult,
1115
1136
  };