@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/read.ts CHANGED
@@ -1,27 +1,28 @@
1
- import { homedir } from "node:os";
1
+ import * as os from "node:os";
2
2
  import path from "node:path";
3
3
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
4
4
  import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
5
- import { CONFIG_DIR_NAME } from "@oh-my-pi/pi-coding-agent/config";
6
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
7
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
8
- import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
9
- import readDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/read.md" with { type: "text" };
10
- import type { ToolSession } from "@oh-my-pi/pi-coding-agent/sdk";
11
- import type { OutputMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta";
12
- import { ToolAbortError, ToolError, throwIfAborted } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
13
- import { formatDimensionNote, resizeImage } from "@oh-my-pi/pi-coding-agent/utils/image-resize";
14
- import { detectSupportedImageMimeTypeFromFile } from "@oh-my-pi/pi-coding-agent/utils/mime";
15
- import { ensureTool } from "@oh-my-pi/pi-coding-agent/utils/tools-manager";
16
5
  import type { Component } from "@oh-my-pi/pi-tui";
17
6
  import { Text } from "@oh-my-pi/pi-tui";
18
7
  import { ptree } from "@oh-my-pi/pi-utils";
19
8
  import { Type } from "@sinclair/typebox";
9
+ import { CONFIG_DIR_NAME } from "../config";
10
+ import { renderPromptTemplate } from "../config/prompt-templates";
11
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
+ import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
13
+ import readDescription from "../prompts/tools/read.md" with { type: "text" };
14
+ import type { ToolSession } from "../sdk";
15
+ import { renderCodeCell, renderOutputBlock, renderStatusLine } from "../tui";
16
+ import { formatDimensionNote, resizeImage } from "../utils/image-resize";
17
+ import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
18
+ import { ensureTool } from "../utils/tools-manager";
20
19
  import { runFd } from "./find";
21
20
  import { applyListLimit } from "./list-limit";
22
21
  import { LsTool } from "./ls";
22
+ import type { OutputMeta } from "./output-meta";
23
23
  import { resolveReadPath, resolveToCwd } from "./path-utils";
24
24
  import { shortenPath, wrapBrackets } from "./render-utils";
25
+ import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
25
26
  import { toolResult } from "./tool-result";
26
27
  import {
27
28
  DEFAULT_MAX_BYTES,
@@ -36,7 +37,7 @@ import {
36
37
  const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
37
38
 
38
39
  // Remote mount path prefix (sshfs mounts) - skip fuzzy matching to avoid hangs
39
- const REMOTE_MOUNT_PREFIX = path.join(homedir(), CONFIG_DIR_NAME, "remote") + path.sep;
40
+ const REMOTE_MOUNT_PREFIX = path.join(os.homedir(), CONFIG_DIR_NAME, "remote") + path.sep;
40
41
 
41
42
  function isRemoteMountPath(absolutePath: string): boolean {
42
43
  return absolutePath.startsWith(REMOTE_MOUNT_PREFIX);
@@ -201,8 +202,8 @@ async function listCandidateFiles(
201
202
  if (output) {
202
203
  const nestedGitignores = output
203
204
  .split("\n")
204
- .map((line) => line.replace(/\r$/, "").trim())
205
- .filter((line) => line.length > 0);
205
+ .map(line => line.replace(/\r$/, "").trim())
206
+ .filter(line => line.length > 0);
206
207
  for (const file of nestedGitignores) {
207
208
  const normalized = file.replace(/\\/g, "/");
208
209
  if (normalized.includes("/node_modules/") || normalized.includes("/.git/")) {
@@ -237,8 +238,8 @@ async function listCandidateFiles(
237
238
 
238
239
  const files = output
239
240
  .split("\n")
240
- .map((line) => line.replace(/\r$/, "").trim())
241
- .filter((line) => line.length > 0);
241
+ .map(line => line.replace(/\r$/, "").trim())
242
+ .filter(line => line.length > 0);
242
243
 
243
244
  return { files, truncated: files.length >= MAX_FUZZY_CANDIDATES };
244
245
  }
@@ -336,7 +337,7 @@ async function findReadPathSuggestions(
336
337
  });
337
338
 
338
339
  const listLimit = applyListLimit(matches, { limit: MAX_FUZZY_RESULTS });
339
- const suggestions = listLimit.items.map((match) => match.path);
340
+ const suggestions = listLimit.items.map(match => match.path);
340
341
 
341
342
  return { suggestions, scopeLabel, truncated };
342
343
  }
@@ -378,6 +379,7 @@ const readSchema = Type.Object({
378
379
  export interface ReadToolDetails {
379
380
  truncation?: TruncationResult;
380
381
  redirectedTo?: "ls";
382
+ resolvedPath?: string;
381
383
  meta?: OutputMeta;
382
384
  }
383
385
 
@@ -444,7 +446,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
444
446
 
445
447
  if (suggestions?.suggestions.length) {
446
448
  const scopeLabel = suggestions.scopeLabel ? ` in ${suggestions.scopeLabel}` : "";
447
- message += `\n\nClosest matches${scopeLabel}:\n${suggestions.suggestions.map((match) => `- ${match}`).join("\n")}`;
449
+ message += `\n\nClosest matches${scopeLabel}:\n${suggestions.suggestions.map(match => `- ${match}`).join("\n")}`;
448
450
  if (suggestions.truncated) {
449
451
  message += `\n[Search truncated to first ${MAX_FUZZY_CANDIDATES} paths. Refine the path if the match isn't listed.]`;
450
452
  }
@@ -708,11 +710,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
708
710
 
709
711
  // If extraction was used, return directly (no pagination)
710
712
  if (hasExtraction) {
711
- let text = resource.content;
713
+ const details: ReadToolDetails = {};
712
714
  if (resource.sourcePath) {
713
- text += `\n\n[Resolved path: ${resource.sourcePath}]`;
715
+ details.resolvedPath = resource.sourcePath;
714
716
  }
715
- return toolResult<ReadToolDetails>().text(text).sourceInternal(url).done();
717
+ return toolResult(details).text(resource.content).sourceInternal(url).done();
716
718
  }
717
719
 
718
720
  // Apply pagination similar to file reading
@@ -806,9 +808,8 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
806
808
  details = {};
807
809
  }
808
810
 
809
- // Append resolved path notice
810
811
  if (resource.sourcePath) {
811
- outputText += `\n\n[Resolved path: ${resource.sourcePath}]`;
812
+ details.resolvedPath = resource.sourcePath;
812
813
  }
813
814
 
814
815
  const resultBuilder = toolResult(details).text(outputText).sourceInternal(url);
@@ -837,14 +838,14 @@ export const readToolRenderer = {
837
838
  const offset = args.offset;
838
839
  const limit = args.limit;
839
840
 
840
- let pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", uiTheme.format.ellipsis);
841
+ let pathDisplay = filePath || uiTheme.format.ellipsis;
841
842
  if (offset !== undefined || limit !== undefined) {
842
843
  const startLine = offset ?? 1;
843
844
  const endLine = limit !== undefined ? startLine + limit - 1 : "";
844
- pathDisplay += uiTheme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
845
+ pathDisplay += `:${startLine}${endLine ? `-${endLine}` : ""}`;
845
846
  }
846
847
 
847
- const text = `${uiTheme.fg("toolTitle", uiTheme.bold("Read"))} ${pathDisplay}`;
848
+ const text = renderStatusLine({ icon: "pending", title: "Read", description: pathDisplay }, uiTheme);
848
849
  return new Text(text, 0, 0);
849
850
  },
850
851
 
@@ -852,15 +853,24 @@ export const readToolRenderer = {
852
853
  result: { content: Array<{ type: string; text?: string }>; details?: ReadToolDetails },
853
854
  _options: RenderResultOptions,
854
855
  uiTheme: Theme,
855
- _args?: ReadRenderArgs,
856
+ args?: ReadRenderArgs,
856
857
  ): Component {
857
858
  const details = result.details;
858
- const lines: string[] = [];
859
-
860
- lines.push(uiTheme.fg("dim", "Content hidden"));
859
+ const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
860
+ const imageContent = result.content?.find(c => c.type === "image");
861
+ const rawPath = args?.file_path || args?.path || "";
862
+ const filePath = shortenPath(rawPath);
863
+ const lang = getLanguageFromPath(rawPath);
861
864
 
865
+ const warningLines: string[] = [];
862
866
  const truncation = details?.meta?.truncation;
863
867
  const fallback = details?.truncation;
868
+ if (details?.redirectedTo) {
869
+ warningLines.push(uiTheme.fg("warning", wrapBrackets(`Redirected to ${details.redirectedTo}`, uiTheme)));
870
+ }
871
+ if (details?.resolvedPath) {
872
+ warningLines.push(uiTheme.fg("dim", wrapBrackets(`Resolved path: ${details.resolvedPath}`, uiTheme)));
873
+ }
864
874
  if (truncation) {
865
875
  let warning: string;
866
876
  if (fallback?.firstLineExceedsLimit) {
@@ -874,9 +884,58 @@ export const readToolRenderer = {
874
884
  if (truncation.artifactId) {
875
885
  warning += `. Full output: artifact://${truncation.artifactId}`;
876
886
  }
877
- lines.push(uiTheme.fg("warning", wrapBrackets(warning, uiTheme)));
887
+ warningLines.push(uiTheme.fg("warning", wrapBrackets(warning, uiTheme)));
878
888
  }
879
889
 
880
- return new Text(lines.join("\n"), 0, 0);
890
+ if (imageContent) {
891
+ const header = renderStatusLine(
892
+ { icon: "success", title: "Read", description: filePath || rawPath || "image" },
893
+ uiTheme,
894
+ );
895
+ const detailLines = contentText ? contentText.split("\n").map(line => uiTheme.fg("toolOutput", line)) : [];
896
+ const lines = [...detailLines, ...warningLines];
897
+ return {
898
+ render: (width: number) =>
899
+ renderOutputBlock(
900
+ {
901
+ header,
902
+ state: "success",
903
+ sections: [
904
+ {
905
+ label: uiTheme.fg("toolTitle", "Details"),
906
+ lines: lines.length > 0 ? lines : [uiTheme.fg("dim", "(image)")],
907
+ },
908
+ ],
909
+ width,
910
+ },
911
+ uiTheme,
912
+ ),
913
+ invalidate: () => {},
914
+ };
915
+ }
916
+
917
+ let title = filePath ? `Read ${filePath}` : "Read";
918
+ if (args?.offset !== undefined || args?.limit !== undefined) {
919
+ const startLine = args.offset ?? 1;
920
+ const endLine = args.limit !== undefined ? startLine + args.limit - 1 : "";
921
+ title += `:${startLine}${endLine ? `-${endLine}` : ""}`;
922
+ }
923
+ return {
924
+ render: (width: number) =>
925
+ renderCodeCell(
926
+ {
927
+ code: contentText,
928
+ language: lang,
929
+ title,
930
+ status: "complete",
931
+ output: warningLines.length > 0 ? warningLines.join("\n") : undefined,
932
+ expanded: true,
933
+ width,
934
+ },
935
+ uiTheme,
936
+ ),
937
+ invalidate: () => {},
938
+ };
881
939
  },
940
+ mergeCallAndResult: true,
882
941
  };
@@ -4,9 +4,9 @@
4
4
  * Provides consistent formatting, truncation, and display patterns across all
5
5
  * tool renderers to ensure a unified TUI experience.
6
6
  */
7
-
8
- import { homedir } from "node:os";
9
- import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
7
+ import * as os from "node:os";
8
+ import type { Theme } from "../modes/theme/theme";
9
+ import { getTreeBranch } from "../tui/utils";
10
10
 
11
11
  // =============================================================================
12
12
  // Standardized Display Constants
@@ -61,8 +61,8 @@ export function truncate(text: string, maxLen: number, ellipsis: string): string
61
61
  * Get first N lines of text as preview, with each line truncated.
62
62
  */
63
63
  export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
64
- const lines = text.split("\n").filter((l) => l.trim());
65
- return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen, ellipsis));
64
+ const lines = text.split("\n").filter(l => l.trim());
65
+ return lines.slice(0, maxLines).map(l => truncate(l.trim(), maxLineLen, ellipsis));
66
66
  }
67
67
 
68
68
  // =============================================================================
@@ -553,7 +553,7 @@ export function truncateDiffByHunk(
553
553
 
554
554
  const segments = parseDiffSegments(lines);
555
555
 
556
- const changeSegments = segments.filter((s) => s.isChange);
556
+ const changeSegments = segments.filter(s => s.isChange);
557
557
  const changeLineCount = changeSegments.reduce((sum, s) => sum + s.lines.length, 0);
558
558
 
559
559
  if (changeLineCount > maxLines) {
@@ -578,7 +578,7 @@ export function truncateDiffByHunk(
578
578
  }
579
579
 
580
580
  const contextBudget = maxLines - changeLineCount;
581
- const contextSegments = segments.filter((s) => !s.isChange && !s.isEllipsis);
581
+ const contextSegments = segments.filter(s => !s.isChange && !s.isEllipsis);
582
582
  const totalContextLines = contextSegments.reduce((sum, s) => sum + s.lines.length, 0);
583
583
 
584
584
  const kept: string[] = [];
@@ -642,7 +642,7 @@ export function truncateDiffByHunk(
642
642
  // =============================================================================
643
643
 
644
644
  export function shortenPath(filePath: string, homeDir?: string): string {
645
- const home = homeDir ?? homedir();
645
+ const home = homeDir ?? os.homedir();
646
646
  if (home && filePath.startsWith(home)) {
647
647
  return `~${filePath.slice(home.length)}`;
648
648
  }
@@ -667,21 +667,6 @@ function pluralize(label: string, count: number): string {
667
667
  // =============================================================================
668
668
  // Tree Rendering Utilities
669
669
  // =============================================================================
670
-
671
- /**
672
- * Get the branch character for a tree item.
673
- */
674
- export function getTreeBranch(isLast: boolean, theme: Theme): string {
675
- return isLast ? theme.tree.last : theme.tree.branch;
676
- }
677
-
678
- /**
679
- * Get the continuation prefix for nested content under a tree item.
680
- */
681
- export function getTreeContinuePrefix(isLast: boolean, theme: Theme): string {
682
- return isLast ? " " : `${theme.tree.vertical} `;
683
- }
684
-
685
670
  /**
686
671
  * Render a list of items with tree branches, handling truncation.
687
672
  *
@@ -3,14 +3,13 @@
3
3
  *
4
4
  * These provide rich visualization for tool calls and results in the TUI.
5
5
  */
6
-
7
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
8
- import { lspToolRenderer } from "@oh-my-pi/pi-coding-agent/lsp/render";
9
- import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
10
- import { editToolRenderer } from "@oh-my-pi/pi-coding-agent/patch";
11
- import { taskToolRenderer } from "@oh-my-pi/pi-coding-agent/task/render";
12
- import { webSearchToolRenderer } from "@oh-my-pi/pi-coding-agent/web/search/render";
13
6
  import type { Component } from "@oh-my-pi/pi-tui";
7
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
8
+ import { lspToolRenderer } from "../lsp/render";
9
+ import type { Theme } from "../modes/theme/theme";
10
+ import { editToolRenderer } from "../patch";
11
+ import { taskToolRenderer } from "../task/render";
12
+ import { webSearchToolRenderer } from "../web/search/render";
14
13
  import { askToolRenderer } from "./ask";
15
14
  import { bashToolRenderer } from "./bash";
16
15
  import { calculatorToolRenderer } from "./calculator";
@@ -5,12 +5,17 @@
5
5
  * Hidden by default - only enabled when explicitly listed in agent's tools.
6
6
  * Reviewers finish via `complete` tool with SubmitReviewDetails schema.
7
7
  */
8
-
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // Subprocess tool handlers - registered for extraction/rendering in task tool
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ import path from "node:path";
9
12
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
10
- import type { Theme, ThemeColor } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
13
+ import { StringEnum } from "@oh-my-pi/pi-ai";
11
14
  import type { Component } from "@oh-my-pi/pi-tui";
12
15
  import { Container, Text } from "@oh-my-pi/pi-tui";
13
16
  import { Type } from "@sinclair/typebox";
17
+ import type { Theme, ThemeColor } from "../modes/theme/theme";
18
+ import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
14
19
 
15
20
  export type FindingPriority = "P0" | "P1" | "P2" | "P3";
16
21
 
@@ -145,17 +150,9 @@ export interface SubmitReviewDetails {
145
150
  // Re-export types for external use
146
151
  export type { ReportFindingDetails };
147
152
 
148
- // ─────────────────────────────────────────────────────────────────────────────
149
- // Subprocess tool handlers - registered for extraction/rendering in task tool
150
- // ─────────────────────────────────────────────────────────────────────────────
151
-
152
- import path from "node:path";
153
- import { StringEnum } from "@oh-my-pi/pi-ai";
154
- import { subprocessToolRegistry } from "@oh-my-pi/pi-coding-agent/task/subprocess-tool-registry";
155
-
156
153
  // Register report_finding handler
157
154
  subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
158
- extractData: (event) => event.result?.details as ReportFindingDetails | undefined,
155
+ extractData: event => event.result?.details as ReportFindingDetails | undefined,
159
156
 
160
157
  renderInline: (data, theme) => {
161
158
  const { label, icon, color } = getPriorityDisplay(data.priority, theme);
package/src/tools/ssh.ts CHANGED
@@ -1,22 +1,23 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
2
- import type { SSHHost } from "@oh-my-pi/pi-coding-agent/capability/ssh";
3
- import { sshCapability } from "@oh-my-pi/pi-coding-agent/capability/ssh";
4
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
5
- import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery/index";
6
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
7
- import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
8
- import sshDescriptionBase from "@oh-my-pi/pi-coding-agent/prompts/tools/ssh.md" with { type: "text" };
9
- import type { SSHHostInfo } from "@oh-my-pi/pi-coding-agent/ssh/connection-manager";
10
- import { ensureHostInfo, getHostInfoForHost } from "@oh-my-pi/pi-coding-agent/ssh/connection-manager";
11
- import { executeSSH } from "@oh-my-pi/pi-coding-agent/ssh/ssh-executor";
12
- import type { OutputMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta";
13
- import { ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
14
2
  import type { Component } from "@oh-my-pi/pi-tui";
15
3
  import { Text } from "@oh-my-pi/pi-tui";
16
4
  import { Type } from "@sinclair/typebox";
17
- import type { ToolSession } from "./index";
5
+ import type { SSHHost } from "../capability/ssh";
6
+ import { sshCapability } from "../capability/ssh";
7
+ import { renderPromptTemplate } from "../config/prompt-templates";
8
+ import { loadCapability } from "../discovery";
9
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
10
+ import type { Theme } from "../modes/theme/theme";
11
+ import sshDescriptionBase from "../prompts/tools/ssh.md" with { type: "text" };
12
+ import type { SSHHostInfo } from "../ssh/connection-manager";
13
+ import { ensureHostInfo, getHostInfoForHost } from "../ssh/connection-manager";
14
+ import { executeSSH } from "../ssh/ssh-executor";
15
+ import { renderOutputBlock, renderStatusLine } from "../tui";
16
+ import type { ToolSession } from ".";
17
+ import type { OutputMeta } from "./output-meta";
18
18
  import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
19
- import { ToolUIKit } from "./render-utils";
19
+ import { formatBytes, wrapBrackets } from "./render-utils";
20
+ import { ToolError } from "./tool-errors";
20
21
  import { toolResult } from "./tool-result";
21
22
  import { DEFAULT_MAX_BYTES } from "./truncate";
22
23
 
@@ -31,8 +32,8 @@ export interface SSHToolDetails {
31
32
  meta?: OutputMeta;
32
33
  }
33
34
 
34
- function formatHostEntry(host: SSHHost): string {
35
- const info = getHostInfoForHost(host);
35
+ async function formatHostEntry(host: SSHHost): Promise<string> {
36
+ const info = await getHostInfoForHost(host);
36
37
 
37
38
  let shell: string;
38
39
  if (!info) {
@@ -57,12 +58,12 @@ function formatHostEntry(host: SSHHost): string {
57
58
  return `- ${host.name} (${host.host}) | ${shell}`;
58
59
  }
59
60
 
60
- function formatDescription(hosts: SSHHost[]): string {
61
+ async function formatDescription(hosts: SSHHost[]): Promise<string> {
61
62
  const baseDescription = renderPromptTemplate(sshDescriptionBase);
62
63
  if (hosts.length === 0) {
63
64
  return baseDescription;
64
65
  }
65
- const hostList = hosts.map(formatHostEntry).join("\n");
66
+ const hostList = (await Promise.all(hosts.map(formatHostEntry))).join("\n");
66
67
  return `${baseDescription}\n\nAvailable hosts:\n${hostList}`;
67
68
  }
68
69
 
@@ -134,17 +135,12 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
134
135
  private readonly hostsByName: Map<string, SSHHost>;
135
136
  private readonly hostNames: string[];
136
137
 
137
- constructor(session: ToolSession, hostNames: string[], hostsByName: Map<string, SSHHost>) {
138
+ constructor(session: ToolSession, hostNames: string[], hostsByName: Map<string, SSHHost>, description: string) {
138
139
  this.session = session;
139
140
  this.hostNames = hostNames;
140
141
  this.hostsByName = hostsByName;
141
142
  this.allowedHosts = new Set(hostNames);
142
-
143
- const descriptionHosts = hostNames
144
- .map((name) => hostsByName.get(name))
145
- .filter((host): host is SSHHost => host !== undefined);
146
-
147
- this.description = formatDescription(descriptionHosts);
143
+ this.description = description;
148
144
  }
149
145
 
150
146
  public async execute(
@@ -181,7 +177,7 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
181
177
  compatEnabled: hostInfo.compatEnabled,
182
178
  artifactPath,
183
179
  artifactId,
184
- onChunk: (chunk) => {
180
+ onChunk: chunk => {
185
181
  tailBuffer.append(chunk);
186
182
  if (onUpdate) {
187
183
  onUpdate({
@@ -213,7 +209,13 @@ export async function loadSshTool(session: ToolSession): Promise<SshTool | null>
213
209
  if (hostNames.length === 0) {
214
210
  return null;
215
211
  }
216
- return new SshTool(session, hostNames, hostsByName);
212
+
213
+ const descriptionHosts = hostNames
214
+ .map(name => hostsByName.get(name))
215
+ .filter((host): host is SSHHost => host !== undefined);
216
+ const description = await formatDescription(descriptionHosts);
217
+
218
+ return new SshTool(session, hostNames, hostsByName, description);
217
219
  }
218
220
 
219
221
  // =============================================================================
@@ -237,10 +239,9 @@ interface SshRenderContext {
237
239
 
238
240
  export const sshToolRenderer = {
239
241
  renderCall(args: SshRenderArgs, uiTheme: Theme): Component {
240
- const ui = new ToolUIKit(uiTheme);
241
242
  const host = args.host || uiTheme.format.ellipsis;
242
243
  const command = args.command || uiTheme.format.ellipsis;
243
- const text = ui.title(`[${host}] $ ${command}`);
244
+ const text = renderStatusLine({ icon: "pending", title: "SSH", description: `[${host}] $ ${command}` }, uiTheme);
244
245
  return new Text(text, 0, 0);
245
246
  },
246
247
 
@@ -251,42 +252,48 @@ export const sshToolRenderer = {
251
252
  },
252
253
  options: RenderResultOptions & { renderContext?: SshRenderContext },
253
254
  uiTheme: Theme,
255
+ args?: SshRenderArgs,
254
256
  ): Component {
255
- const ui = new ToolUIKit(uiTheme);
256
257
  const { expanded, renderContext } = options;
257
258
  const details = result.details;
258
- const lines: string[] = [];
259
+ const host = args?.host || uiTheme.format.ellipsis;
260
+ const command = args?.command || uiTheme.format.ellipsis;
261
+ const header = renderStatusLine(
262
+ { icon: "success", title: "SSH", description: `[${host}] $ ${command}` },
263
+ uiTheme,
264
+ );
265
+ const outputLines: string[] = [];
259
266
 
260
- const textContent = result.content?.find((c) => c.type === "text")?.text ?? "";
261
- const output = textContent.trim();
267
+ const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
268
+ const output = textContent.trimEnd();
262
269
 
263
270
  if (output) {
264
271
  if (expanded) {
265
- const styledOutput = output
266
- .split("\n")
267
- .map((line) => uiTheme.fg("toolOutput", line))
268
- .join("\n");
269
- lines.push(styledOutput);
272
+ outputLines.push(...output.split("\n").map(line => uiTheme.fg("toolOutput", line)));
270
273
  } else if (renderContext?.visualLines) {
271
274
  const { visualLines, skippedCount = 0, totalVisualLines = visualLines.length } = renderContext;
272
275
  if (skippedCount > 0) {
273
- lines.push(
276
+ outputLines.push(
274
277
  uiTheme.fg(
275
278
  "dim",
276
279
  `${uiTheme.format.ellipsis} (${skippedCount} earlier lines, showing ${visualLines.length} of ${totalVisualLines}) (ctrl+o to expand)`,
277
280
  ),
278
281
  );
279
282
  }
280
- lines.push(...visualLines);
283
+ const styledVisual = visualLines.map(line =>
284
+ line.includes("\x1b[") ? line : uiTheme.fg("toolOutput", line),
285
+ );
286
+ outputLines.push(...styledVisual);
281
287
  } else {
282
- const outputLines = output.split("\n");
288
+ const outputLinesRaw = output.split("\n");
283
289
  const maxLines = 5;
284
- const displayLines = outputLines.slice(0, maxLines);
285
- const remaining = outputLines.length - maxLines;
286
-
287
- lines.push(...displayLines.map((line) => uiTheme.fg("toolOutput", line)));
290
+ const displayLines = outputLinesRaw.slice(0, maxLines);
291
+ const remaining = outputLinesRaw.length - maxLines;
292
+ outputLines.push(...displayLines.map(line => uiTheme.fg("toolOutput", line)));
288
293
  if (remaining > 0) {
289
- lines.push(uiTheme.fg("dim", `${uiTheme.format.ellipsis} (${remaining} more lines) (ctrl+o to expand)`));
294
+ outputLines.push(
295
+ uiTheme.fg("dim", `${uiTheme.format.ellipsis} (${remaining} more lines) (ctrl+o to expand)`),
296
+ );
290
297
  }
291
298
  }
292
299
  }
@@ -301,12 +308,25 @@ export const sshToolRenderer = {
301
308
  warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
302
309
  } else {
303
310
  warnings.push(
304
- `Truncated: ${truncation.outputLines} lines shown (${ui.formatBytes(truncation.outputBytes)} limit)`,
311
+ `Truncated: ${truncation.outputLines} lines shown (${formatBytes(truncation.outputBytes)} limit)`,
305
312
  );
306
313
  }
307
- lines.push(uiTheme.fg("warning", ui.wrapBrackets(warnings.join(". "))));
314
+ outputLines.push(uiTheme.fg("warning", wrapBrackets(warnings.join(". "), uiTheme)));
308
315
  }
309
316
 
310
- return new Text(lines.join("\n"), 0, 0);
317
+ return {
318
+ render: (width: number) =>
319
+ renderOutputBlock(
320
+ {
321
+ header,
322
+ state: "success",
323
+ sections: [{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines }],
324
+ width,
325
+ },
326
+ uiTheme,
327
+ ),
328
+ invalidate: () => {},
329
+ };
311
330
  },
331
+ mergeCallAndResult: true,
312
332
  };