@hyperspaceng/neural-coding-agent 0.62.1 → 0.63.2

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 (374) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +10 -0
  3. package/dist/bun/register-bedrock.d.ts.map +1 -1
  4. package/dist/bun/register-bedrock.js +2 -2
  5. package/dist/bun/register-bedrock.js.map +1 -1
  6. package/dist/cli/args.d.ts +1 -1
  7. package/dist/cli/args.d.ts.map +1 -1
  8. package/dist/cli/args.js.map +1 -1
  9. package/dist/cli/config-selector.d.ts.map +1 -1
  10. package/dist/cli/config-selector.js +1 -1
  11. package/dist/cli/config-selector.js.map +1 -1
  12. package/dist/cli/file-processor.d.ts +1 -1
  13. package/dist/cli/file-processor.d.ts.map +1 -1
  14. package/dist/cli/file-processor.js +4 -0
  15. package/dist/cli/file-processor.js.map +1 -1
  16. package/dist/cli/initial-message.d.ts +1 -1
  17. package/dist/cli/initial-message.d.ts.map +1 -1
  18. package/dist/cli/initial-message.js.map +1 -1
  19. package/dist/cli/list-models.d.ts.map +1 -1
  20. package/dist/cli/list-models.js +1 -1
  21. package/dist/cli/list-models.js.map +1 -1
  22. package/dist/cli/session-picker.d.ts.map +1 -1
  23. package/dist/cli/session-picker.js +1 -1
  24. package/dist/cli/session-picker.js.map +1 -1
  25. package/dist/core/agent-session.d.ts +21 -11
  26. package/dist/core/agent-session.d.ts.map +1 -1
  27. package/dist/core/agent-session.js +159 -101
  28. package/dist/core/agent-session.js.map +1 -1
  29. package/dist/core/auth-storage.d.ts +5 -3
  30. package/dist/core/auth-storage.d.ts.map +1 -1
  31. package/dist/core/auth-storage.js +7 -4
  32. package/dist/core/auth-storage.js.map +1 -1
  33. package/dist/core/compaction/branch-summarization.d.ts +4 -2
  34. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  35. package/dist/core/compaction/branch-summarization.js +3 -3
  36. package/dist/core/compaction/branch-summarization.js.map +1 -1
  37. package/dist/core/compaction/compaction.d.ts +5 -5
  38. package/dist/core/compaction/compaction.d.ts.map +1 -1
  39. package/dist/core/compaction/compaction.js +28 -27
  40. package/dist/core/compaction/compaction.js.map +1 -1
  41. package/dist/core/compaction/utils.d.ts +2 -2
  42. package/dist/core/compaction/utils.d.ts.map +1 -1
  43. package/dist/core/compaction/utils.js.map +1 -1
  44. package/dist/core/defaults.d.ts +1 -1
  45. package/dist/core/defaults.d.ts.map +1 -1
  46. package/dist/core/defaults.js.map +1 -1
  47. package/dist/core/export-html/index.d.ts +3 -3
  48. package/dist/core/export-html/index.d.ts.map +1 -1
  49. package/dist/core/export-html/index.js +7 -6
  50. package/dist/core/export-html/index.js.map +1 -1
  51. package/dist/core/export-html/tool-renderer.d.ts +2 -2
  52. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  53. package/dist/core/export-html/tool-renderer.js +41 -16
  54. package/dist/core/export-html/tool-renderer.js.map +1 -1
  55. package/dist/core/extensions/index.d.ts +3 -2
  56. package/dist/core/extensions/index.d.ts.map +1 -1
  57. package/dist/core/extensions/index.js.map +1 -1
  58. package/dist/core/extensions/loader.d.ts.map +1 -1
  59. package/dist/core/extensions/loader.js +27 -17
  60. package/dist/core/extensions/loader.js.map +1 -1
  61. package/dist/core/extensions/runner.d.ts +7 -10
  62. package/dist/core/extensions/runner.d.ts.map +1 -1
  63. package/dist/core/extensions/runner.js +27 -38
  64. package/dist/core/extensions/runner.js.map +1 -1
  65. package/dist/core/extensions/types.d.ts +56 -15
  66. package/dist/core/extensions/types.d.ts.map +1 -1
  67. package/dist/core/extensions/types.js.map +1 -1
  68. package/dist/core/extensions/wrapper.d.ts +1 -1
  69. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  70. package/dist/core/extensions/wrapper.js +2 -8
  71. package/dist/core/extensions/wrapper.js.map +1 -1
  72. package/dist/core/index.d.ts +2 -1
  73. package/dist/core/index.d.ts.map +1 -1
  74. package/dist/core/index.js +1 -0
  75. package/dist/core/index.js.map +1 -1
  76. package/dist/core/keybindings.d.ts +2 -2
  77. package/dist/core/keybindings.d.ts.map +1 -1
  78. package/dist/core/keybindings.js +1 -1
  79. package/dist/core/keybindings.js.map +1 -1
  80. package/dist/core/messages.d.ts +3 -3
  81. package/dist/core/messages.d.ts.map +1 -1
  82. package/dist/core/messages.js.map +1 -1
  83. package/dist/core/model-registry.d.ts +19 -3
  84. package/dist/core/model-registry.d.ts.map +1 -1
  85. package/dist/core/model-registry.js +85 -71
  86. package/dist/core/model-registry.js.map +1 -1
  87. package/dist/core/model-resolver.d.ts +2 -2
  88. package/dist/core/model-resolver.d.ts.map +1 -1
  89. package/dist/core/model-resolver.js +5 -5
  90. package/dist/core/model-resolver.js.map +1 -1
  91. package/dist/core/output-guard.d.ts +6 -0
  92. package/dist/core/output-guard.d.ts.map +1 -0
  93. package/dist/core/output-guard.js +59 -0
  94. package/dist/core/output-guard.js.map +1 -0
  95. package/dist/core/package-manager.d.ts +3 -0
  96. package/dist/core/package-manager.d.ts.map +1 -1
  97. package/dist/core/package-manager.js +153 -29
  98. package/dist/core/package-manager.js.map +1 -1
  99. package/dist/core/prompt-templates.d.ts +2 -1
  100. package/dist/core/prompt-templates.d.ts.map +1 -1
  101. package/dist/core/prompt-templates.js +30 -32
  102. package/dist/core/prompt-templates.js.map +1 -1
  103. package/dist/core/resolve-config-value.d.ts +6 -0
  104. package/dist/core/resolve-config-value.d.ts.map +1 -1
  105. package/dist/core/resolve-config-value.js +37 -5
  106. package/dist/core/resolve-config-value.js.map +1 -1
  107. package/dist/core/resource-loader.d.ts +6 -5
  108. package/dist/core/resource-loader.d.ts.map +1 -1
  109. package/dist/core/resource-loader.js +136 -108
  110. package/dist/core/resource-loader.js.map +1 -1
  111. package/dist/core/sdk.d.ts +4 -4
  112. package/dist/core/sdk.d.ts.map +1 -1
  113. package/dist/core/sdk.js +15 -24
  114. package/dist/core/sdk.js.map +1 -1
  115. package/dist/core/session-manager.d.ts +2 -2
  116. package/dist/core/session-manager.d.ts.map +1 -1
  117. package/dist/core/session-manager.js.map +1 -1
  118. package/dist/core/settings-manager.d.ts +3 -1
  119. package/dist/core/settings-manager.d.ts.map +1 -1
  120. package/dist/core/settings-manager.js +3 -0
  121. package/dist/core/settings-manager.js.map +1 -1
  122. package/dist/core/skills.d.ts +2 -1
  123. package/dist/core/skills.d.ts.map +1 -1
  124. package/dist/core/skills.js +25 -1
  125. package/dist/core/skills.js.map +1 -1
  126. package/dist/core/slash-commands.d.ts +2 -3
  127. package/dist/core/slash-commands.d.ts.map +1 -1
  128. package/dist/core/slash-commands.js.map +1 -1
  129. package/dist/core/source-info.d.ts +18 -0
  130. package/dist/core/source-info.d.ts.map +1 -0
  131. package/dist/core/source-info.js +19 -0
  132. package/dist/core/source-info.js.map +1 -0
  133. package/dist/core/system-prompt.d.ts.map +1 -1
  134. package/dist/core/system-prompt.js +3 -38
  135. package/dist/core/system-prompt.js.map +1 -1
  136. package/dist/core/timings.d.ts +1 -0
  137. package/dist/core/timings.d.ts.map +1 -1
  138. package/dist/core/timings.js +6 -0
  139. package/dist/core/timings.js.map +1 -1
  140. package/dist/core/tools/bash.d.ts +20 -10
  141. package/dist/core/tools/bash.d.ts.map +1 -1
  142. package/dist/core/tools/bash.js +151 -59
  143. package/dist/core/tools/bash.js.map +1 -1
  144. package/dist/core/tools/edit-diff.d.ts +23 -1
  145. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  146. package/dist/core/tools/edit-diff.js +150 -57
  147. package/dist/core/tools/edit-diff.js.map +1 -1
  148. package/dist/core/tools/edit.d.ts +31 -7
  149. package/dist/core/tools/edit.d.ts.map +1 -1
  150. package/dist/core/tools/edit.js +179 -59
  151. package/dist/core/tools/edit.js.map +1 -1
  152. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  153. package/dist/core/tools/file-mutation-queue.js +4 -4
  154. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  155. package/dist/core/tools/find.d.ts +12 -5
  156. package/dist/core/tools/find.d.ts.map +1 -1
  157. package/dist/core/tools/find.js +76 -27
  158. package/dist/core/tools/find.js.map +1 -1
  159. package/dist/core/tools/grep.d.ts +16 -5
  160. package/dist/core/tools/grep.d.ts.map +1 -1
  161. package/dist/core/tools/grep.js +83 -29
  162. package/dist/core/tools/grep.js.map +1 -1
  163. package/dist/core/tools/index.d.ts +68 -22
  164. package/dist/core/tools/index.d.ts.map +1 -1
  165. package/dist/core/tools/index.js +50 -26
  166. package/dist/core/tools/index.js.map +1 -1
  167. package/dist/core/tools/ls.d.ts +10 -4
  168. package/dist/core/tools/ls.d.ts.map +1 -1
  169. package/dist/core/tools/ls.js +67 -13
  170. package/dist/core/tools/ls.js.map +1 -1
  171. package/dist/core/tools/read.d.ts +11 -4
  172. package/dist/core/tools/read.d.ts.map +1 -1
  173. package/dist/core/tools/read.js +110 -51
  174. package/dist/core/tools/read.js.map +1 -1
  175. package/dist/core/tools/render-utils.d.ts +21 -0
  176. package/dist/core/tools/render-utils.d.ts.map +1 -0
  177. package/dist/core/tools/render-utils.js +49 -0
  178. package/dist/core/tools/render-utils.js.map +1 -0
  179. package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  180. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
  181. package/dist/core/tools/tool-definition-wrapper.js +30 -0
  182. package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
  183. package/dist/core/tools/write.d.ts +10 -4
  184. package/dist/core/tools/write.d.ts.map +1 -1
  185. package/dist/core/tools/write.js +162 -27
  186. package/dist/core/tools/write.js.map +1 -1
  187. package/dist/index.d.ts +3 -2
  188. package/dist/index.d.ts.map +1 -1
  189. package/dist/index.js +2 -1
  190. package/dist/index.js.map +1 -1
  191. package/dist/main.d.ts.map +1 -1
  192. package/dist/main.js +57 -19
  193. package/dist/main.js.map +1 -1
  194. package/dist/modes/interactive/components/armin.d.ts +1 -1
  195. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  196. package/dist/modes/interactive/components/armin.js.map +1 -1
  197. package/dist/modes/interactive/components/assistant-message.d.ts +2 -2
  198. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/assistant-message.js +1 -1
  200. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  201. package/dist/modes/interactive/components/bash-execution.d.ts +1 -2
  202. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  203. package/dist/modes/interactive/components/bash-execution.js +19 -6
  204. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  205. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
  206. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/bordered-loader.js +1 -1
  208. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  209. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  210. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  212. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  213. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  214. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  216. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  217. package/dist/modes/interactive/components/config-selector.d.ts +1 -1
  218. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  219. package/dist/modes/interactive/components/config-selector.js +1 -1
  220. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  221. package/dist/modes/interactive/components/countdown-timer.d.ts +1 -1
  222. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  224. package/dist/modes/interactive/components/custom-editor.d.ts +1 -1
  225. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  226. package/dist/modes/interactive/components/custom-editor.js +1 -1
  227. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  228. package/dist/modes/interactive/components/custom-message.d.ts +1 -1
  229. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  230. package/dist/modes/interactive/components/custom-message.js +1 -1
  231. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  232. package/dist/modes/interactive/components/daxnuts.d.ts +1 -1
  233. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  235. package/dist/modes/interactive/components/dynamic-border.d.ts +1 -1
  236. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  237. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  238. package/dist/modes/interactive/components/extension-editor.d.ts +1 -1
  239. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/extension-editor.js +1 -1
  241. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  242. package/dist/modes/interactive/components/extension-input.d.ts +1 -1
  243. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/extension-input.js +1 -1
  245. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  246. package/dist/modes/interactive/components/extension-selector.d.ts +1 -1
  247. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  248. package/dist/modes/interactive/components/extension-selector.js +1 -1
  249. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  250. package/dist/modes/interactive/components/footer.d.ts +1 -1
  251. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  252. package/dist/modes/interactive/components/footer.js +1 -1
  253. package/dist/modes/interactive/components/footer.js.map +1 -1
  254. package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -1
  255. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  256. package/dist/modes/interactive/components/keybinding-hints.js +1 -1
  257. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  258. package/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  259. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/login-dialog.js +2 -2
  261. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  262. package/dist/modes/interactive/components/model-selector.d.ts +2 -2
  263. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  264. package/dist/modes/interactive/components/model-selector.js +2 -2
  265. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  266. package/dist/modes/interactive/components/oauth-selector.d.ts +1 -1
  267. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/oauth-selector.js +2 -2
  269. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  270. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  271. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  272. package/dist/modes/interactive/components/scoped-models-selector.js +1 -1
  273. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  274. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  275. package/dist/modes/interactive/components/session-selector-search.js +1 -1
  276. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  277. package/dist/modes/interactive/components/session-selector.d.ts +1 -1
  278. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  279. package/dist/modes/interactive/components/session-selector.js +1 -1
  280. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  281. package/dist/modes/interactive/components/settings-selector.d.ts +3 -3
  282. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  283. package/dist/modes/interactive/components/settings-selector.js +1 -1
  284. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  285. package/dist/modes/interactive/components/show-images-selector.d.ts +1 -1
  286. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  287. package/dist/modes/interactive/components/show-images-selector.js +1 -1
  288. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  289. package/dist/modes/interactive/components/skill-invocation-message.d.ts +1 -1
  290. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  291. package/dist/modes/interactive/components/skill-invocation-message.js +1 -1
  292. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  293. package/dist/modes/interactive/components/theme-selector.d.ts +1 -1
  294. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  295. package/dist/modes/interactive/components/theme-selector.js +1 -1
  296. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  297. package/dist/modes/interactive/components/thinking-selector.d.ts +2 -2
  298. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  299. package/dist/modes/interactive/components/thinking-selector.js +1 -1
  300. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  301. package/dist/modes/interactive/components/tool-execution.d.ts +15 -41
  302. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  303. package/dist/modes/interactive/components/tool-execution.js +121 -679
  304. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  305. package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
  306. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  307. package/dist/modes/interactive/components/tree-selector.js +1 -1
  308. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  309. package/dist/modes/interactive/components/user-message-selector.d.ts +1 -1
  310. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  311. package/dist/modes/interactive/components/user-message-selector.js +1 -1
  312. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  313. package/dist/modes/interactive/components/user-message.d.ts +1 -1
  314. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  315. package/dist/modes/interactive/components/user-message.js +1 -1
  316. package/dist/modes/interactive/components/user-message.js.map +1 -1
  317. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  318. package/dist/modes/interactive/components/visual-truncate.js +1 -1
  319. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  320. package/dist/modes/interactive/interactive-mode.d.ts +5 -13
  321. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  322. package/dist/modes/interactive/interactive-mode.js +175 -160
  323. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  324. package/dist/modes/interactive/theme/theme.d.ts +5 -2
  325. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  326. package/dist/modes/interactive/theme/theme.js +15 -1
  327. package/dist/modes/interactive/theme/theme.js.map +1 -1
  328. package/dist/modes/print-mode.d.ts +2 -2
  329. package/dist/modes/print-mode.d.ts.map +1 -1
  330. package/dist/modes/print-mode.js +84 -78
  331. package/dist/modes/print-mode.js.map +1 -1
  332. package/dist/modes/rpc/rpc-client.d.ts +2 -2
  333. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  334. package/dist/modes/rpc/rpc-client.js.map +1 -1
  335. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  336. package/dist/modes/rpc/rpc-mode.js +27 -20
  337. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  338. package/dist/modes/rpc/rpc-types.d.ts +5 -6
  339. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  340. package/dist/modes/rpc/rpc-types.js.map +1 -1
  341. package/dist/utils/image-resize.d.ts +6 -6
  342. package/dist/utils/image-resize.d.ts.map +1 -1
  343. package/dist/utils/image-resize.js +45 -94
  344. package/dist/utils/image-resize.js.map +1 -1
  345. package/docs/development.md +3 -1
  346. package/docs/extensions.md +85 -34
  347. package/docs/models.md +6 -0
  348. package/docs/rpc.md +11 -2
  349. package/docs/settings.md +12 -0
  350. package/docs/skills.md +3 -2
  351. package/docs/tui.md +2 -2
  352. package/examples/extensions/built-in-tool-renderer.ts +8 -8
  353. package/examples/extensions/commands.ts +3 -3
  354. package/examples/extensions/custom-compaction.ts +17 -4
  355. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  356. package/examples/extensions/custom-provider-anthropic/package.json +17 -17
  357. package/examples/extensions/custom-provider-gitlab-duo/package.json +14 -14
  358. package/examples/extensions/custom-provider-qwen-cli/package.json +14 -14
  359. package/examples/extensions/handoff.ts +5 -2
  360. package/examples/extensions/minimal-mode.ts +14 -14
  361. package/examples/extensions/qna.ts +5 -2
  362. package/examples/extensions/question.ts +2 -2
  363. package/examples/extensions/questionnaire.ts +2 -2
  364. package/examples/extensions/subagent/index.ts +23 -3
  365. package/examples/extensions/summarize.ts +15 -4
  366. package/examples/extensions/todo.ts +2 -2
  367. package/examples/extensions/trigger-compact.ts +11 -1
  368. package/examples/extensions/truncated-tool.ts +2 -2
  369. package/examples/extensions/with-deps/package-lock.json +2 -2
  370. package/examples/extensions/with-deps/package.json +20 -20
  371. package/examples/sdk/04-skills.ts +8 -2
  372. package/examples/sdk/08-prompt-templates.ts +2 -1
  373. package/examples/sdk/12-full-control.ts +0 -1
  374. package/package.json +5 -4
@@ -1,96 +1,47 @@
1
- import * as os from "node:os";
2
- import { Box, Container, getCapabilities, getImageDimensions, Image, imageFallback, Spacer, Text, truncateToWidth, } from "@mariozechner/pi-tui";
3
- import stripAnsi from "strip-ansi";
4
- import { computeEditDiff } from "../../../core/tools/edit-diff.js";
5
- import { allTools } from "../../../core/tools/index.js";
6
- import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate.js";
1
+ import { Box, Container, getCapabilities, Image, Spacer, Text } from "@hyperspaceng/neural-tui";
2
+ import { allToolDefinitions } from "../../../core/tools/index.js";
3
+ import { getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
7
4
  import { convertToPng } from "../../../utils/image-convert.js";
8
- import { sanitizeBinaryOutput } from "../../../utils/shell.js";
9
- import { getLanguageFromPath, highlightCode, theme } from "../theme/theme.js";
10
- import { renderDiff } from "./diff.js";
11
- import { keyHint } from "./keybinding-hints.js";
12
- import { truncateToVisualLines } from "./visual-truncate.js";
13
- // Preview line limit for bash when not expanded
14
- const BASH_PREVIEW_LINES = 5;
15
- // During partial write tool-call streaming, re-highlight the first N lines fully
16
- // to keep multiline tokenization mostly correct without re-highlighting the full file.
17
- const WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;
18
- /**
19
- * Convert absolute path to tilde notation if it's in home directory
20
- */
21
- function shortenPath(path) {
22
- if (typeof path !== "string")
23
- return "";
24
- const home = os.homedir();
25
- if (path.startsWith(home)) {
26
- return `~${path.slice(home.length)}`;
27
- }
28
- return path;
29
- }
30
- /**
31
- * Replace tabs with spaces for consistent rendering
32
- */
33
- function replaceTabs(text) {
34
- return text.replace(/\t/g, " ");
35
- }
36
- /**
37
- * Normalize control characters for terminal preview rendering.
38
- * Keep tool arguments unchanged, sanitize only display text.
39
- */
40
- function normalizeDisplayText(text) {
41
- return text.replace(/\r/g, "");
42
- }
43
- /** Safely coerce value to string for display. Returns null if invalid type. */
44
- function str(value) {
45
- if (typeof value === "string")
46
- return value;
47
- if (value == null)
48
- return "";
49
- return null; // Invalid type
50
- }
51
- /**
52
- * Component that renders a tool call with its result (updateable)
53
- */
5
+ import { theme } from "../theme/theme.js";
54
6
  export class ToolExecutionComponent extends Container {
55
- contentBox; // Used for custom tools and bash visual truncation
56
- contentText; // For built-in tools (with its own padding/bg)
7
+ contentBox;
8
+ contentText;
9
+ callRendererComponent;
10
+ resultRendererComponent;
11
+ rendererState = {};
57
12
  imageComponents = [];
58
13
  imageSpacers = [];
59
14
  toolName;
15
+ toolCallId;
60
16
  args;
61
17
  expanded = false;
62
18
  showImages;
63
19
  isPartial = true;
64
20
  toolDefinition;
21
+ builtInToolDefinition;
65
22
  ui;
66
23
  cwd;
24
+ executionStarted = false;
25
+ argsComplete = false;
67
26
  result;
68
- // Cached edit diff preview (computed when args arrive, before tool executes)
69
- editDiffPreview;
70
- editDiffArgsKey; // Track which args the preview is for
71
- // Cached converted images for Kitty protocol (which requires PNG), keyed by index
72
27
  convertedImages = new Map();
73
- // Incremental syntax highlighting cache for write tool call args
74
- writeHighlightCache;
75
- // When true, this component intentionally renders no lines
76
28
  hideComponent = false;
77
- bashStartedAt;
78
- bashElapsedInterval;
79
- constructor(toolName, args, options = {}, toolDefinition, ui, cwd = process.cwd()) {
29
+ constructor(toolName, toolCallId, args, options = {}, toolDefinition, ui, cwd = process.cwd()) {
80
30
  super();
81
31
  this.toolName = toolName;
32
+ this.toolCallId = toolCallId;
82
33
  this.args = args;
83
- this.showImages = options.showImages ?? true;
84
34
  this.toolDefinition = toolDefinition;
35
+ this.builtInToolDefinition = allToolDefinitions[toolName];
36
+ this.showImages = options.showImages ?? true;
85
37
  this.ui = ui;
86
38
  this.cwd = cwd;
87
39
  this.addChild(new Spacer(1));
88
- // Always create both - contentBox for custom tools/bash, contentText for other built-ins
40
+ // Always create both. contentBox is used for tools with renderer-based call/result composition.
41
+ // contentText is reserved for generic fallback rendering when no tool definition exists.
89
42
  this.contentBox = new Box(1, 1, (text) => theme.bg("toolPendingBg", text));
90
43
  this.contentText = new Text("", 1, 1, (text) => theme.bg("toolPendingBg", text));
91
- // Use contentBox for bash (visual truncation) or custom tools with custom renderers
92
- // Use contentText for built-in tools (including overrides without custom renderers)
93
- if (toolName === "bash" || (toolDefinition && !this.shouldUseBuiltInRenderer())) {
44
+ if (this.hasRendererDefinition()) {
94
45
  this.addChild(this.contentBox);
95
46
  }
96
47
  else {
@@ -98,217 +49,91 @@ export class ToolExecutionComponent extends Container {
98
49
  }
99
50
  this.updateDisplay();
100
51
  }
101
- /**
102
- * Check if we should use built-in rendering for this tool.
103
- * Returns true if the tool name is a built-in AND either there's no toolDefinition
104
- * or the toolDefinition doesn't provide custom renderers.
105
- */
106
- shouldUseBuiltInRenderer() {
107
- const isBuiltInName = this.toolName in allTools;
108
- const hasCustomRenderers = this.toolDefinition?.renderCall || this.toolDefinition?.renderResult;
109
- return isBuiltInName && !hasCustomRenderers;
52
+ getCallRenderer() {
53
+ if (!this.builtInToolDefinition) {
54
+ return this.toolDefinition?.renderCall;
55
+ }
56
+ if (!this.toolDefinition) {
57
+ return this.builtInToolDefinition.renderCall;
58
+ }
59
+ return this.toolDefinition.renderCall ?? this.builtInToolDefinition.renderCall;
110
60
  }
111
- updateArgs(args) {
112
- this.args = args;
113
- if (this.toolName === "write" && this.isPartial) {
114
- this.updateWriteHighlightCacheIncremental();
61
+ getResultRenderer() {
62
+ if (!this.builtInToolDefinition) {
63
+ return this.toolDefinition?.renderResult;
115
64
  }
116
- this.updateDisplay();
65
+ if (!this.toolDefinition) {
66
+ return this.builtInToolDefinition.renderResult;
67
+ }
68
+ return this.toolDefinition.renderResult ?? this.builtInToolDefinition.renderResult;
117
69
  }
118
- markExecutionStarted() {
119
- if (this.toolName !== "bash" || this.bashStartedAt !== undefined)
120
- return;
121
- this.bashStartedAt = Date.now();
122
- this.ensureBashElapsedTimer();
123
- this.updateDisplay();
124
- this.ui.requestRender();
70
+ hasRendererDefinition() {
71
+ return this.builtInToolDefinition !== undefined || this.toolDefinition !== undefined;
125
72
  }
126
- ensureBashElapsedTimer() {
127
- if (this.toolName !== "bash" || !this.isPartial || this.bashStartedAt === undefined || this.bashElapsedInterval)
128
- return;
129
- this.bashElapsedInterval = setInterval(() => {
130
- this.updateDisplay();
131
- this.ui.requestRender();
132
- }, 1000);
73
+ getRenderContext(lastComponent) {
74
+ return {
75
+ args: this.args,
76
+ toolCallId: this.toolCallId,
77
+ invalidate: () => {
78
+ this.invalidate();
79
+ this.ui.requestRender();
80
+ },
81
+ lastComponent,
82
+ state: this.rendererState,
83
+ cwd: this.cwd,
84
+ executionStarted: this.executionStarted,
85
+ argsComplete: this.argsComplete,
86
+ isPartial: this.isPartial,
87
+ expanded: this.expanded,
88
+ showImages: this.showImages,
89
+ isError: this.result?.isError ?? false,
90
+ };
133
91
  }
134
- stopBashElapsedTimer() {
135
- if (!this.bashElapsedInterval)
136
- return;
137
- clearInterval(this.bashElapsedInterval);
138
- this.bashElapsedInterval = undefined;
92
+ createCallFallback() {
93
+ return new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0);
139
94
  }
140
- getBashDurationMs() {
141
- if (this.toolName !== "bash" || this.bashStartedAt === undefined)
95
+ createResultFallback() {
96
+ const output = this.getTextOutput();
97
+ if (!output) {
142
98
  return undefined;
143
- return Date.now() - this.bashStartedAt;
144
- }
145
- formatDuration(ms) {
146
- return `${(ms / 1000).toFixed(1)}s`;
147
- }
148
- highlightSingleLine(line, lang) {
149
- const highlighted = highlightCode(line, lang);
150
- return highlighted[0] ?? "";
151
- }
152
- refreshWriteHighlightPrefix(cache) {
153
- const prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);
154
- if (prefixCount === 0)
155
- return;
156
- const prefixSource = cache.normalizedLines.slice(0, prefixCount).join("\n");
157
- const prefixHighlighted = highlightCode(prefixSource, cache.lang);
158
- for (let i = 0; i < prefixCount; i++) {
159
- cache.highlightedLines[i] =
160
- prefixHighlighted[i] ?? this.highlightSingleLine(cache.normalizedLines[i] ?? "", cache.lang);
161
99
  }
100
+ return new Text(theme.fg("toolOutput", output), 0, 0);
162
101
  }
163
- rebuildWriteHighlightCacheFull(rawPath, fileContent) {
164
- const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
165
- if (!lang) {
166
- this.writeHighlightCache = undefined;
167
- return;
168
- }
169
- const displayContent = normalizeDisplayText(fileContent);
170
- const normalized = replaceTabs(displayContent);
171
- this.writeHighlightCache = {
172
- rawPath,
173
- lang,
174
- rawContent: fileContent,
175
- normalizedLines: normalized.split("\n"),
176
- highlightedLines: highlightCode(normalized, lang),
177
- };
102
+ updateArgs(args) {
103
+ this.args = args;
104
+ this.updateDisplay();
178
105
  }
179
- updateWriteHighlightCacheIncremental() {
180
- const rawPath = str(this.args?.file_path ?? this.args?.path);
181
- const fileContent = str(this.args?.content);
182
- if (rawPath === null || fileContent === null) {
183
- this.writeHighlightCache = undefined;
184
- return;
185
- }
186
- const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
187
- if (!lang) {
188
- this.writeHighlightCache = undefined;
189
- return;
190
- }
191
- if (!this.writeHighlightCache) {
192
- this.rebuildWriteHighlightCacheFull(rawPath, fileContent);
193
- return;
194
- }
195
- const cache = this.writeHighlightCache;
196
- if (cache.lang !== lang || cache.rawPath !== rawPath) {
197
- this.rebuildWriteHighlightCacheFull(rawPath, fileContent);
198
- return;
199
- }
200
- if (!fileContent.startsWith(cache.rawContent)) {
201
- this.rebuildWriteHighlightCacheFull(rawPath, fileContent);
202
- return;
203
- }
204
- if (fileContent.length === cache.rawContent.length) {
205
- return;
206
- }
207
- const deltaRaw = fileContent.slice(cache.rawContent.length);
208
- const deltaDisplay = normalizeDisplayText(deltaRaw);
209
- const deltaNormalized = replaceTabs(deltaDisplay);
210
- cache.rawContent = fileContent;
211
- if (cache.normalizedLines.length === 0) {
212
- cache.normalizedLines.push("");
213
- cache.highlightedLines.push("");
214
- }
215
- const segments = deltaNormalized.split("\n");
216
- const lastIndex = cache.normalizedLines.length - 1;
217
- cache.normalizedLines[lastIndex] += segments[0];
218
- cache.highlightedLines[lastIndex] = this.highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);
219
- for (let i = 1; i < segments.length; i++) {
220
- cache.normalizedLines.push(segments[i]);
221
- cache.highlightedLines.push(this.highlightSingleLine(segments[i], cache.lang));
222
- }
223
- this.refreshWriteHighlightPrefix(cache);
106
+ markExecutionStarted() {
107
+ this.executionStarted = true;
108
+ this.updateDisplay();
109
+ this.ui.requestRender();
224
110
  }
225
- /**
226
- * Signal that args are complete (tool is about to execute).
227
- * This triggers diff computation for edit tool.
228
- */
229
111
  setArgsComplete() {
230
- if (this.toolName === "write") {
231
- const rawPath = str(this.args?.file_path ?? this.args?.path);
232
- const fileContent = str(this.args?.content);
233
- if (rawPath !== null && fileContent !== null) {
234
- this.rebuildWriteHighlightCacheFull(rawPath, fileContent);
235
- }
236
- }
237
- this.maybeComputeEditDiff();
238
- }
239
- /**
240
- * Compute edit diff preview when we have complete args.
241
- * This runs async and updates display when done.
242
- */
243
- maybeComputeEditDiff() {
244
- if (this.toolName !== "edit")
245
- return;
246
- const path = this.args?.path;
247
- const oldText = this.args?.oldText;
248
- const newText = this.args?.newText;
249
- // Need all three params to compute diff
250
- if (!path || oldText === undefined || newText === undefined)
251
- return;
252
- // Create a key to track which args this computation is for
253
- const argsKey = JSON.stringify({ path, oldText, newText });
254
- // Skip if we already computed for these exact args
255
- if (this.editDiffArgsKey === argsKey)
256
- return;
257
- this.editDiffArgsKey = argsKey;
258
- // Compute diff async
259
- computeEditDiff(path, oldText, newText, this.cwd).then((result) => {
260
- // Only update if args haven't changed since we started
261
- if (this.editDiffArgsKey === argsKey) {
262
- this.editDiffPreview = result;
263
- this.updateDisplay();
264
- this.ui.requestRender();
265
- }
266
- });
112
+ this.argsComplete = true;
113
+ this.updateDisplay();
114
+ this.ui.requestRender();
267
115
  }
268
116
  updateResult(result, isPartial = false) {
269
117
  this.result = result;
270
118
  this.isPartial = isPartial;
271
- if (this.toolName === "bash") {
272
- if (isPartial) {
273
- this.ensureBashElapsedTimer();
274
- }
275
- else {
276
- this.stopBashElapsedTimer();
277
- }
278
- }
279
- if (this.toolName === "write" && !isPartial) {
280
- const rawPath = str(this.args?.file_path ?? this.args?.path);
281
- const fileContent = str(this.args?.content);
282
- if (rawPath !== null && fileContent !== null) {
283
- this.rebuildWriteHighlightCacheFull(rawPath, fileContent);
284
- }
285
- }
286
119
  this.updateDisplay();
287
- // Convert non-PNG images to PNG for Kitty protocol (async)
288
120
  this.maybeConvertImagesForKitty();
289
121
  }
290
- /**
291
- * Convert non-PNG images to PNG for Kitty graphics protocol.
292
- * Kitty requires PNG format (f=100), so JPEG/GIF/WebP won't display.
293
- */
294
122
  maybeConvertImagesForKitty() {
295
123
  const caps = getCapabilities();
296
- // Only needed for Kitty protocol
297
124
  if (caps.images !== "kitty")
298
125
  return;
299
126
  if (!this.result)
300
127
  return;
301
- const imageBlocks = this.result.content?.filter((c) => c.type === "image") || [];
128
+ const imageBlocks = this.result.content.filter((c) => c.type === "image");
302
129
  for (let i = 0; i < imageBlocks.length; i++) {
303
130
  const img = imageBlocks[i];
304
131
  if (!img.data || !img.mimeType)
305
132
  continue;
306
- // Skip if already PNG or already converted
307
133
  if (img.mimeType === "image/png")
308
134
  continue;
309
135
  if (this.convertedImages.has(i))
310
136
  continue;
311
- // Convert async
312
137
  const index = i;
313
138
  convertToPng(img.data, img.mimeType).then((converted) => {
314
139
  if (converted) {
@@ -338,86 +163,66 @@ export class ToolExecutionComponent extends Container {
338
163
  return super.render(width);
339
164
  }
340
165
  updateDisplay() {
341
- // Set background based on state
342
166
  const bgFn = this.isPartial
343
167
  ? (text) => theme.bg("toolPendingBg", text)
344
168
  : this.result?.isError
345
169
  ? (text) => theme.bg("toolErrorBg", text)
346
170
  : (text) => theme.bg("toolSuccessBg", text);
347
- const useBuiltInRenderer = this.shouldUseBuiltInRenderer();
348
- let customRendererHasContent = false;
171
+ let hasContent = false;
349
172
  this.hideComponent = false;
350
- // Use built-in rendering for built-in tools (or overrides without custom renderers)
351
- if (useBuiltInRenderer) {
352
- if (this.toolName === "bash") {
353
- // Bash uses Box with visual line truncation
354
- this.contentBox.setBgFn(bgFn);
355
- this.contentBox.clear();
356
- this.renderBashContent();
357
- }
358
- else {
359
- // Other built-in tools: use Text directly with caching
360
- this.contentText.setCustomBgFn(bgFn);
361
- this.contentText.setText(this.formatToolExecution());
362
- }
363
- }
364
- else if (this.toolDefinition) {
365
- // Custom tools use Box for flexible component rendering
173
+ if (this.hasRendererDefinition()) {
366
174
  this.contentBox.setBgFn(bgFn);
367
175
  this.contentBox.clear();
368
- // Render call component
369
- if (this.toolDefinition.renderCall) {
176
+ const callRenderer = this.getCallRenderer();
177
+ if (!callRenderer) {
178
+ this.contentBox.addChild(this.createCallFallback());
179
+ hasContent = true;
180
+ }
181
+ else {
370
182
  try {
371
- const callComponent = this.toolDefinition.renderCall(this.args, theme);
372
- if (callComponent !== undefined) {
373
- this.contentBox.addChild(callComponent);
374
- customRendererHasContent = true;
375
- }
183
+ const component = callRenderer(this.args, theme, this.getRenderContext(this.callRendererComponent));
184
+ this.callRendererComponent = component;
185
+ this.contentBox.addChild(component);
186
+ hasContent = true;
376
187
  }
377
188
  catch {
378
- // Fall back to default on error
379
- this.contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0));
380
- customRendererHasContent = true;
189
+ this.callRendererComponent = undefined;
190
+ this.contentBox.addChild(this.createCallFallback());
191
+ hasContent = true;
381
192
  }
382
193
  }
383
- else {
384
- // No custom renderCall, show tool name
385
- this.contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0));
386
- customRendererHasContent = true;
387
- }
388
- // Render result component if we have a result
389
- if (this.result && this.toolDefinition.renderResult) {
390
- try {
391
- const resultComponent = this.toolDefinition.renderResult({ content: this.result.content, details: this.result.details }, { expanded: this.expanded, isPartial: this.isPartial }, theme);
392
- if (resultComponent !== undefined) {
393
- this.contentBox.addChild(resultComponent);
394
- customRendererHasContent = true;
194
+ if (this.result) {
195
+ const resultRenderer = this.getResultRenderer();
196
+ if (!resultRenderer) {
197
+ const component = this.createResultFallback();
198
+ if (component) {
199
+ this.contentBox.addChild(component);
200
+ hasContent = true;
395
201
  }
396
202
  }
397
- catch {
398
- // Fall back to showing raw output on error
399
- const output = this.getTextOutput();
400
- if (output) {
401
- this.contentBox.addChild(new Text(theme.fg("toolOutput", output), 0, 0));
402
- customRendererHasContent = true;
203
+ else {
204
+ try {
205
+ const component = resultRenderer({ content: this.result.content, details: this.result.details }, { expanded: this.expanded, isPartial: this.isPartial }, theme, this.getRenderContext(this.resultRendererComponent));
206
+ this.resultRendererComponent = component;
207
+ this.contentBox.addChild(component);
208
+ hasContent = true;
209
+ }
210
+ catch {
211
+ this.resultRendererComponent = undefined;
212
+ const component = this.createResultFallback();
213
+ if (component) {
214
+ this.contentBox.addChild(component);
215
+ hasContent = true;
216
+ }
403
217
  }
404
- }
405
- }
406
- else if (this.result) {
407
- // Has result but no custom renderResult
408
- const output = this.getTextOutput();
409
- if (output) {
410
- this.contentBox.addChild(new Text(theme.fg("toolOutput", output), 0, 0));
411
- customRendererHasContent = true;
412
218
  }
413
219
  }
414
220
  }
415
221
  else {
416
- // Unknown tool with no registered definition - show generic fallback
417
222
  this.contentText.setCustomBgFn(bgFn);
418
223
  this.contentText.setText(this.formatToolExecution());
224
+ hasContent = true;
419
225
  }
420
- // Handle images (same for both custom and built-in)
421
226
  for (const img of this.imageComponents) {
422
227
  this.removeChild(img);
423
228
  }
@@ -427,19 +232,16 @@ export class ToolExecutionComponent extends Container {
427
232
  }
428
233
  this.imageSpacers = [];
429
234
  if (this.result) {
430
- const imageBlocks = this.result.content?.filter((c) => c.type === "image") || [];
235
+ const imageBlocks = this.result.content.filter((c) => c.type === "image");
431
236
  const caps = getCapabilities();
432
237
  for (let i = 0; i < imageBlocks.length; i++) {
433
238
  const img = imageBlocks[i];
434
239
  if (caps.images && this.showImages && img.data && img.mimeType) {
435
- // Use converted PNG for Kitty protocol if available
436
240
  const converted = this.convertedImages.get(i);
437
241
  const imageData = converted?.data ?? img.data;
438
242
  const imageMimeType = converted?.mimeType ?? img.mimeType;
439
- // For Kitty, skip non-PNG images that haven't been converted yet
440
- if (caps.images === "kitty" && imageMimeType !== "image/png") {
243
+ if (caps.images === "kitty" && imageMimeType !== "image/png")
441
244
  continue;
442
- }
443
245
  const spacer = new Spacer(1);
444
246
  this.addChild(spacer);
445
247
  this.imageSpacers.push(spacer);
@@ -449,382 +251,22 @@ export class ToolExecutionComponent extends Container {
449
251
  }
450
252
  }
451
253
  }
452
- if (!useBuiltInRenderer && this.toolDefinition) {
453
- this.hideComponent = !customRendererHasContent && this.imageComponents.length === 0;
454
- }
455
- }
456
- /**
457
- * Render bash content using visual line truncation (like bash-execution.ts)
458
- */
459
- renderBashContent() {
460
- const command = str(this.args?.command);
461
- const timeout = this.args?.timeout;
462
- // Header
463
- const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
464
- const commandDisplay = command === null ? theme.fg("error", "[invalid arg]") : command ? command : theme.fg("toolOutput", "...");
465
- this.contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`)) + timeoutSuffix, 0, 0));
466
- if (this.result) {
467
- const output = this.getTextOutput().trim();
468
- if (output) {
469
- // Style each line for the output
470
- const styledOutput = output
471
- .split("\n")
472
- .map((line) => theme.fg("toolOutput", line))
473
- .join("\n");
474
- if (this.expanded) {
475
- // Show all lines when expanded
476
- this.contentBox.addChild(new Text(`\n${styledOutput}`, 0, 0));
477
- }
478
- else {
479
- // Use visual line truncation when collapsed with width-aware caching
480
- let cachedWidth;
481
- let cachedLines;
482
- let cachedSkipped;
483
- this.contentBox.addChild({
484
- render: (width) => {
485
- if (cachedLines === undefined || cachedWidth !== width) {
486
- const result = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, width);
487
- cachedLines = result.visualLines;
488
- cachedSkipped = result.skippedCount;
489
- cachedWidth = width;
490
- }
491
- if (cachedSkipped && cachedSkipped > 0) {
492
- const hint = theme.fg("muted", `... (${cachedSkipped} earlier lines,`) +
493
- ` ${keyHint("app.tools.expand", "to expand")})`;
494
- return ["", truncateToWidth(hint, width, "..."), ...cachedLines];
495
- }
496
- // Add blank line for spacing (matches expanded case)
497
- return ["", ...cachedLines];
498
- },
499
- invalidate: () => {
500
- cachedWidth = undefined;
501
- cachedLines = undefined;
502
- cachedSkipped = undefined;
503
- },
504
- });
505
- }
506
- }
507
- // Truncation warnings
508
- const truncation = this.result.details?.truncation;
509
- const fullOutputPath = this.result.details?.fullOutputPath;
510
- if (truncation?.truncated || fullOutputPath) {
511
- const warnings = [];
512
- if (fullOutputPath) {
513
- warnings.push(`Full output: ${fullOutputPath}`);
514
- }
515
- if (truncation?.truncated) {
516
- if (truncation.truncatedBy === "lines") {
517
- warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
518
- }
519
- else {
520
- warnings.push(`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`);
521
- }
522
- }
523
- this.contentBox.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
524
- }
525
- }
526
- const bashDurationMs = this.getBashDurationMs();
527
- if (bashDurationMs !== undefined) {
528
- const label = this.isPartial ? "Elapsed" : "Took";
529
- this.contentBox.addChild(new Text(`\n${theme.fg("muted", `${label} ${this.formatDuration(bashDurationMs)}`)}`, 0, 0));
254
+ if (this.hasRendererDefinition() && !hasContent && this.imageComponents.length === 0) {
255
+ this.hideComponent = true;
530
256
  }
531
257
  }
532
258
  getTextOutput() {
533
- if (!this.result)
534
- return "";
535
- const textBlocks = this.result.content?.filter((c) => c.type === "text") || [];
536
- const imageBlocks = this.result.content?.filter((c) => c.type === "image") || [];
537
- let output = textBlocks
538
- .map((c) => {
539
- // Use sanitizeBinaryOutput to handle binary data that crashes string-width
540
- return sanitizeBinaryOutput(stripAnsi(c.text || "")).replace(/\r/g, "");
541
- })
542
- .join("\n");
543
- const caps = getCapabilities();
544
- if (imageBlocks.length > 0 && (!caps.images || !this.showImages)) {
545
- const imageIndicators = imageBlocks
546
- .map((img) => {
547
- const dims = img.data ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;
548
- return imageFallback(img.mimeType, dims);
549
- })
550
- .join("\n");
551
- output = output ? `${output}\n${imageIndicators}` : imageIndicators;
552
- }
553
- return output;
259
+ return getRenderedTextOutput(this.result, this.showImages);
554
260
  }
555
261
  formatToolExecution() {
556
- let text = "";
557
- const invalidArg = theme.fg("error", "[invalid arg]");
558
- if (this.toolName === "read") {
559
- const rawPath = str(this.args?.file_path ?? this.args?.path);
560
- const path = rawPath !== null ? shortenPath(rawPath) : null;
561
- const offset = this.args?.offset;
562
- const limit = this.args?.limit;
563
- let pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
564
- if (offset !== undefined || limit !== undefined) {
565
- const startLine = offset ?? 1;
566
- const endLine = limit !== undefined ? startLine + limit - 1 : "";
567
- pathDisplay += theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
568
- }
569
- text = `${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}`;
570
- if (this.result) {
571
- const output = this.getTextOutput();
572
- const rawPath = str(this.args?.file_path ?? this.args?.path);
573
- const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
574
- const lines = lang ? highlightCode(replaceTabs(output), lang) : output.split("\n");
575
- const maxLines = this.expanded ? lines.length : 10;
576
- const displayLines = lines.slice(0, maxLines);
577
- const remaining = lines.length - maxLines;
578
- text +=
579
- "\n\n" +
580
- displayLines
581
- .map((line) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
582
- .join("\n");
583
- if (remaining > 0) {
584
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
585
- }
586
- const truncation = this.result.details?.truncation;
587
- if (truncation?.truncated) {
588
- if (truncation.firstLineExceedsLimit) {
589
- text +=
590
- "\n" +
591
- theme.fg("warning", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`);
592
- }
593
- else if (truncation.truncatedBy === "lines") {
594
- text +=
595
- "\n" +
596
- theme.fg("warning", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`);
597
- }
598
- else {
599
- text +=
600
- "\n" +
601
- theme.fg("warning", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`);
602
- }
603
- }
604
- }
605
- }
606
- else if (this.toolName === "write") {
607
- const rawPath = str(this.args?.file_path ?? this.args?.path);
608
- const fileContent = str(this.args?.content);
609
- const path = rawPath !== null ? shortenPath(rawPath) : null;
610
- text =
611
- theme.fg("toolTitle", theme.bold("write")) +
612
- " " +
613
- (path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "..."));
614
- if (fileContent === null) {
615
- text += `\n\n${theme.fg("error", "[invalid content arg - expected string]")}`;
616
- }
617
- else if (fileContent) {
618
- const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
619
- let lines;
620
- if (lang) {
621
- const cache = this.writeHighlightCache;
622
- if (cache && cache.lang === lang && cache.rawPath === rawPath && cache.rawContent === fileContent) {
623
- lines = cache.highlightedLines;
624
- }
625
- else {
626
- const displayContent = normalizeDisplayText(fileContent);
627
- const normalized = replaceTabs(displayContent);
628
- lines = highlightCode(normalized, lang);
629
- this.writeHighlightCache = {
630
- rawPath,
631
- lang,
632
- rawContent: fileContent,
633
- normalizedLines: normalized.split("\n"),
634
- highlightedLines: lines,
635
- };
636
- }
637
- }
638
- else {
639
- lines = normalizeDisplayText(fileContent).split("\n");
640
- this.writeHighlightCache = undefined;
641
- }
642
- const totalLines = lines.length;
643
- const maxLines = this.expanded ? lines.length : 10;
644
- const displayLines = lines.slice(0, maxLines);
645
- const remaining = lines.length - maxLines;
646
- text +=
647
- "\n\n" +
648
- displayLines.map((line) => (lang ? line : theme.fg("toolOutput", replaceTabs(line)))).join("\n");
649
- if (remaining > 0) {
650
- text +=
651
- theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total,`) +
652
- ` ${keyHint("app.tools.expand", "to expand")})`;
653
- }
654
- }
655
- // Show error if tool execution failed
656
- if (this.result?.isError) {
657
- const errorText = this.getTextOutput();
658
- if (errorText) {
659
- text += `\n\n${theme.fg("error", errorText)}`;
660
- }
661
- }
662
- }
663
- else if (this.toolName === "edit") {
664
- const rawPath = str(this.args?.file_path ?? this.args?.path);
665
- const path = rawPath !== null ? shortenPath(rawPath) : null;
666
- // Build path display, appending :line if we have diff info
667
- let pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
668
- const firstChangedLine = (this.editDiffPreview && "firstChangedLine" in this.editDiffPreview
669
- ? this.editDiffPreview.firstChangedLine
670
- : undefined) ||
671
- (this.result && !this.result.isError ? this.result.details?.firstChangedLine : undefined);
672
- if (firstChangedLine) {
673
- pathDisplay += theme.fg("warning", `:${firstChangedLine}`);
674
- }
675
- text = `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
676
- if (this.result?.isError) {
677
- // Show error from result
678
- const errorText = this.getTextOutput();
679
- if (errorText) {
680
- text += `\n\n${theme.fg("error", errorText)}`;
681
- }
682
- }
683
- else if (this.result?.details?.diff) {
684
- // Tool executed successfully - use the diff from result
685
- // This takes priority over editDiffPreview which may have a stale error
686
- // due to race condition (async preview computed after file was modified)
687
- text += `\n\n${renderDiff(this.result.details.diff, { filePath: rawPath ?? undefined })}`;
688
- }
689
- else if (this.editDiffPreview) {
690
- // Use cached diff preview (before tool executes)
691
- if ("error" in this.editDiffPreview) {
692
- text += `\n\n${theme.fg("error", this.editDiffPreview.error)}`;
693
- }
694
- else if (this.editDiffPreview.diff) {
695
- text += `\n\n${renderDiff(this.editDiffPreview.diff, { filePath: rawPath ?? undefined })}`;
696
- }
697
- }
698
- }
699
- else if (this.toolName === "ls") {
700
- const rawPath = str(this.args?.path);
701
- const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
702
- const limit = this.args?.limit;
703
- text = `${theme.fg("toolTitle", theme.bold("ls"))} ${path === null ? invalidArg : theme.fg("accent", path)}`;
704
- if (limit !== undefined) {
705
- text += theme.fg("toolOutput", ` (limit ${limit})`);
706
- }
707
- if (this.result) {
708
- const output = this.getTextOutput().trim();
709
- if (output) {
710
- const lines = output.split("\n");
711
- const maxLines = this.expanded ? lines.length : 20;
712
- const displayLines = lines.slice(0, maxLines);
713
- const remaining = lines.length - maxLines;
714
- text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
715
- if (remaining > 0) {
716
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
717
- }
718
- }
719
- const entryLimit = this.result.details?.entryLimitReached;
720
- const truncation = this.result.details?.truncation;
721
- if (entryLimit || truncation?.truncated) {
722
- const warnings = [];
723
- if (entryLimit) {
724
- warnings.push(`${entryLimit} entries limit`);
725
- }
726
- if (truncation?.truncated) {
727
- warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
728
- }
729
- text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
730
- }
731
- }
732
- }
733
- else if (this.toolName === "find") {
734
- const pattern = str(this.args?.pattern);
735
- const rawPath = str(this.args?.path);
736
- const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
737
- const limit = this.args?.limit;
738
- text =
739
- theme.fg("toolTitle", theme.bold("find")) +
740
- " " +
741
- (pattern === null ? invalidArg : theme.fg("accent", pattern || "")) +
742
- theme.fg("toolOutput", ` in ${path === null ? invalidArg : path}`);
743
- if (limit !== undefined) {
744
- text += theme.fg("toolOutput", ` (limit ${limit})`);
745
- }
746
- if (this.result) {
747
- const output = this.getTextOutput().trim();
748
- if (output) {
749
- const lines = output.split("\n");
750
- const maxLines = this.expanded ? lines.length : 20;
751
- const displayLines = lines.slice(0, maxLines);
752
- const remaining = lines.length - maxLines;
753
- text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
754
- if (remaining > 0) {
755
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
756
- }
757
- }
758
- const resultLimit = this.result.details?.resultLimitReached;
759
- const truncation = this.result.details?.truncation;
760
- if (resultLimit || truncation?.truncated) {
761
- const warnings = [];
762
- if (resultLimit) {
763
- warnings.push(`${resultLimit} results limit`);
764
- }
765
- if (truncation?.truncated) {
766
- warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
767
- }
768
- text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
769
- }
770
- }
771
- }
772
- else if (this.toolName === "grep") {
773
- const pattern = str(this.args?.pattern);
774
- const rawPath = str(this.args?.path);
775
- const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
776
- const glob = str(this.args?.glob);
777
- const limit = this.args?.limit;
778
- text =
779
- theme.fg("toolTitle", theme.bold("grep")) +
780
- " " +
781
- (pattern === null ? invalidArg : theme.fg("accent", `/${pattern || ""}/`)) +
782
- theme.fg("toolOutput", ` in ${path === null ? invalidArg : path}`);
783
- if (glob) {
784
- text += theme.fg("toolOutput", ` (${glob})`);
785
- }
786
- if (limit !== undefined) {
787
- text += theme.fg("toolOutput", ` limit ${limit}`);
788
- }
789
- if (this.result) {
790
- const output = this.getTextOutput().trim();
791
- if (output) {
792
- const lines = output.split("\n");
793
- const maxLines = this.expanded ? lines.length : 15;
794
- const displayLines = lines.slice(0, maxLines);
795
- const remaining = lines.length - maxLines;
796
- text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
797
- if (remaining > 0) {
798
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
799
- }
800
- }
801
- const matchLimit = this.result.details?.matchLimitReached;
802
- const truncation = this.result.details?.truncation;
803
- const linesTruncated = this.result.details?.linesTruncated;
804
- if (matchLimit || truncation?.truncated || linesTruncated) {
805
- const warnings = [];
806
- if (matchLimit) {
807
- warnings.push(`${matchLimit} matches limit`);
808
- }
809
- if (truncation?.truncated) {
810
- warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
811
- }
812
- if (linesTruncated) {
813
- warnings.push("some lines truncated");
814
- }
815
- text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
816
- }
817
- }
818
- }
819
- else {
820
- // Generic tool (shouldn't reach here for custom tools)
821
- text = theme.fg("toolTitle", theme.bold(this.toolName));
822
- const content = JSON.stringify(this.args, null, 2);
262
+ let text = theme.fg("toolTitle", theme.bold(this.toolName));
263
+ const content = JSON.stringify(this.args, null, 2);
264
+ if (content) {
823
265
  text += `\n\n${content}`;
824
- const output = this.getTextOutput();
825
- if (output) {
826
- text += `\n${output}`;
827
- }
266
+ }
267
+ const output = this.getTextOutput();
268
+ if (output) {
269
+ text += `\n${output}`;
828
270
  }
829
271
  return text;
830
272
  }