openvibe 0.57.1

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 (636) hide show
  1. package/CHANGELOG.md +3041 -0
  2. package/README.md +569 -0
  3. package/dist/cli/args.d.ts +44 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/args.js +272 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/config-selector.d.ts +10 -0
  8. package/dist/cli/config-selector.d.ts.map +1 -0
  9. package/dist/cli/config-selector.js +26 -0
  10. package/dist/cli/config-selector.js.map +1 -0
  11. package/dist/cli/file-processor.d.ts +10 -0
  12. package/dist/cli/file-processor.d.ts.map +1 -0
  13. package/dist/cli/file-processor.js +68 -0
  14. package/dist/cli/file-processor.js.map +1 -0
  15. package/dist/cli/list-models.d.ts +3 -0
  16. package/dist/cli/list-models.d.ts.map +1 -0
  17. package/dist/cli/list-models.js +30 -0
  18. package/dist/cli/list-models.js.map +1 -0
  19. package/dist/cli/session-picker.d.ts +5 -0
  20. package/dist/cli/session-picker.d.ts.map +1 -0
  21. package/dist/cli/session-picker.js +30 -0
  22. package/dist/cli/session-picker.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +31 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +29 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +145 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/core/accelerated-client.d.ts +42 -0
  32. package/dist/core/accelerated-client.d.ts.map +1 -0
  33. package/dist/core/accelerated-client.js +97 -0
  34. package/dist/core/accelerated-client.js.map +1 -0
  35. package/dist/core/accelerated-stream.d.ts +41 -0
  36. package/dist/core/accelerated-stream.d.ts.map +1 -0
  37. package/dist/core/accelerated-stream.js +133 -0
  38. package/dist/core/accelerated-stream.js.map +1 -0
  39. package/dist/core/agent-session.d.ts +566 -0
  40. package/dist/core/agent-session.d.ts.map +1 -0
  41. package/dist/core/agent-session.js +2361 -0
  42. package/dist/core/agent-session.js.map +1 -0
  43. package/dist/core/api-concurrency.d.ts +37 -0
  44. package/dist/core/api-concurrency.d.ts.map +1 -0
  45. package/dist/core/api-concurrency.js +179 -0
  46. package/dist/core/api-concurrency.js.map +1 -0
  47. package/dist/core/auth-storage.d.ts +66 -0
  48. package/dist/core/auth-storage.d.ts.map +1 -0
  49. package/dist/core/auth-storage.js +336 -0
  50. package/dist/core/auth-storage.js.map +1 -0
  51. package/dist/core/bash-executor.d.ts +15 -0
  52. package/dist/core/bash-executor.d.ts.map +1 -0
  53. package/dist/core/bash-executor.js +166 -0
  54. package/dist/core/bash-executor.js.map +1 -0
  55. package/dist/core/branded-ai.d.ts +17 -0
  56. package/dist/core/branded-ai.d.ts.map +1 -0
  57. package/dist/core/branded-ai.js +123 -0
  58. package/dist/core/branded-ai.js.map +1 -0
  59. package/dist/core/compaction/branch-summarization.d.ts +37 -0
  60. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  61. package/dist/core/compaction/branch-summarization.js +158 -0
  62. package/dist/core/compaction/branch-summarization.js.map +1 -0
  63. package/dist/core/compaction/compaction.d.ts +56 -0
  64. package/dist/core/compaction/compaction.d.ts.map +1 -0
  65. package/dist/core/compaction/compaction.js +501 -0
  66. package/dist/core/compaction/compaction.js.map +1 -0
  67. package/dist/core/compaction/index.d.ts +4 -0
  68. package/dist/core/compaction/index.d.ts.map +1 -0
  69. package/dist/core/compaction/index.js +4 -0
  70. package/dist/core/compaction/index.js.map +1 -0
  71. package/dist/core/compaction/utils.d.ts +17 -0
  72. package/dist/core/compaction/utils.d.ts.map +1 -0
  73. package/dist/core/compaction/utils.js +120 -0
  74. package/dist/core/compaction/utils.js.map +1 -0
  75. package/dist/core/defaults.d.ts +3 -0
  76. package/dist/core/defaults.d.ts.map +1 -0
  77. package/dist/core/defaults.js +2 -0
  78. package/dist/core/defaults.js.map +1 -0
  79. package/dist/core/diagnostics.d.ts +15 -0
  80. package/dist/core/diagnostics.d.ts.map +1 -0
  81. package/dist/core/diagnostics.js +2 -0
  82. package/dist/core/diagnostics.js.map +1 -0
  83. package/dist/core/event-bus.d.ts +9 -0
  84. package/dist/core/event-bus.d.ts.map +1 -0
  85. package/dist/core/event-bus.js +25 -0
  86. package/dist/core/event-bus.js.map +1 -0
  87. package/dist/core/exec.d.ts +13 -0
  88. package/dist/core/exec.d.ts.map +1 -0
  89. package/dist/core/exec.js +61 -0
  90. package/dist/core/exec.js.map +1 -0
  91. package/dist/core/export-html/ansi-to-html.d.ts +3 -0
  92. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  93. package/dist/core/export-html/ansi-to-html.js +193 -0
  94. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  95. package/dist/core/export-html/index.d.ts +22 -0
  96. package/dist/core/export-html/index.d.ts.map +1 -0
  97. package/dist/core/export-html/index.js +193 -0
  98. package/dist/core/export-html/index.js.map +1 -0
  99. package/dist/core/export-html/template.css +971 -0
  100. package/dist/core/export-html/template.html +54 -0
  101. package/dist/core/export-html/template.js +1583 -0
  102. package/dist/core/export-html/tool-renderer.d.ts +21 -0
  103. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  104. package/dist/core/export-html/tool-renderer.js +51 -0
  105. package/dist/core/export-html/tool-renderer.js.map +1 -0
  106. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  107. package/dist/core/export-html/vendor/marked.min.js +6 -0
  108. package/dist/core/extensions/index.d.ts +8 -0
  109. package/dist/core/extensions/index.d.ts.map +1 -0
  110. package/dist/core/extensions/index.js +5 -0
  111. package/dist/core/extensions/index.js.map +1 -0
  112. package/dist/core/extensions/loader.d.ts +7 -0
  113. package/dist/core/extensions/loader.d.ts.map +1 -0
  114. package/dist/core/extensions/loader.js +349 -0
  115. package/dist/core/extensions/loader.js.map +1 -0
  116. package/dist/core/extensions/runner.d.ts +124 -0
  117. package/dist/core/extensions/runner.d.ts.map +1 -0
  118. package/dist/core/extensions/runner.js +657 -0
  119. package/dist/core/extensions/runner.js.map +1 -0
  120. package/dist/core/extensions/types.d.ts +1032 -0
  121. package/dist/core/extensions/types.d.ts.map +1 -0
  122. package/dist/core/extensions/types.js +35 -0
  123. package/dist/core/extensions/types.js.map +1 -0
  124. package/dist/core/extensions/wrapper.d.ts +8 -0
  125. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  126. package/dist/core/extensions/wrapper.js +79 -0
  127. package/dist/core/extensions/wrapper.js.map +1 -0
  128. package/dist/core/footer-data-provider.d.ts +19 -0
  129. package/dist/core/footer-data-provider.d.ts.map +1 -0
  130. package/dist/core/footer-data-provider.js +113 -0
  131. package/dist/core/footer-data-provider.js.map +1 -0
  132. package/dist/core/index.d.ts +13 -0
  133. package/dist/core/index.d.ts.map +1 -0
  134. package/dist/core/index.js +11 -0
  135. package/dist/core/index.js.map +1 -0
  136. package/dist/core/keybindings.d.ts +22 -0
  137. package/dist/core/keybindings.d.ts.map +1 -0
  138. package/dist/core/keybindings.js +124 -0
  139. package/dist/core/keybindings.js.map +1 -0
  140. package/dist/core/messages.d.ts +51 -0
  141. package/dist/core/messages.d.ts.map +1 -0
  142. package/dist/core/messages.js +102 -0
  143. package/dist/core/messages.js.map +1 -0
  144. package/dist/core/model-registry.d.ts +26 -0
  145. package/dist/core/model-registry.d.ts.map +1 -0
  146. package/dist/core/model-registry.js +61 -0
  147. package/dist/core/model-registry.js.map +1 -0
  148. package/dist/core/model-resolver.d.ts +20 -0
  149. package/dist/core/model-resolver.d.ts.map +1 -0
  150. package/dist/core/model-resolver.js +47 -0
  151. package/dist/core/model-resolver.js.map +1 -0
  152. package/dist/core/multi-gpu-executor.d.ts +50 -0
  153. package/dist/core/multi-gpu-executor.d.ts.map +1 -0
  154. package/dist/core/multi-gpu-executor.js +201 -0
  155. package/dist/core/multi-gpu-executor.js.map +1 -0
  156. package/dist/core/onboarding.d.ts +3 -0
  157. package/dist/core/onboarding.d.ts.map +1 -0
  158. package/dist/core/onboarding.js +109 -0
  159. package/dist/core/onboarding.js.map +1 -0
  160. package/dist/core/package-manager.d.ts +136 -0
  161. package/dist/core/package-manager.d.ts.map +1 -0
  162. package/dist/core/package-manager.js +1375 -0
  163. package/dist/core/package-manager.js.map +1 -0
  164. package/dist/core/prompt-templates.d.ts +18 -0
  165. package/dist/core/prompt-templates.d.ts.map +1 -0
  166. package/dist/core/prompt-templates.js +204 -0
  167. package/dist/core/prompt-templates.js.map +1 -0
  168. package/dist/core/resolve-config-value.d.ts +4 -0
  169. package/dist/core/resolve-config-value.d.ts.map +1 -0
  170. package/dist/core/resolve-config-value.js +45 -0
  171. package/dist/core/resolve-config-value.js.map +1 -0
  172. package/dist/core/resource-loader.d.ts +184 -0
  173. package/dist/core/resource-loader.d.ts.map +1 -0
  174. package/dist/core/resource-loader.js +661 -0
  175. package/dist/core/resource-loader.js.map +1 -0
  176. package/dist/core/response-accelerator.d.ts +51 -0
  177. package/dist/core/response-accelerator.d.ts.map +1 -0
  178. package/dist/core/response-accelerator.js +149 -0
  179. package/dist/core/response-accelerator.js.map +1 -0
  180. package/dist/core/sdk.d.ts +39 -0
  181. package/dist/core/sdk.d.ts.map +1 -0
  182. package/dist/core/sdk.js +151 -0
  183. package/dist/core/sdk.js.map +1 -0
  184. package/dist/core/session-manager.d.ts +160 -0
  185. package/dist/core/session-manager.d.ts.map +1 -0
  186. package/dist/core/session-manager.js +899 -0
  187. package/dist/core/session-manager.js.map +1 -0
  188. package/dist/core/settings-manager.d.ts +220 -0
  189. package/dist/core/settings-manager.d.ts.map +1 -0
  190. package/dist/core/settings-manager.js +673 -0
  191. package/dist/core/settings-manager.js.map +1 -0
  192. package/dist/core/skills.d.ts +33 -0
  193. package/dist/core/skills.d.ts.map +1 -0
  194. package/dist/core/skills.js +326 -0
  195. package/dist/core/skills.js.map +1 -0
  196. package/dist/core/slash-commands.d.ts +15 -0
  197. package/dist/core/slash-commands.d.ts.map +1 -0
  198. package/dist/core/slash-commands.js +19 -0
  199. package/dist/core/slash-commands.js.map +1 -0
  200. package/dist/core/system-prompt.d.ts +23 -0
  201. package/dist/core/system-prompt.d.ts.map +1 -0
  202. package/dist/core/system-prompt.js +154 -0
  203. package/dist/core/system-prompt.js.map +1 -0
  204. package/dist/core/timings.d.ts +3 -0
  205. package/dist/core/timings.d.ts.map +1 -0
  206. package/dist/core/timings.js +21 -0
  207. package/dist/core/timings.js.map +1 -0
  208. package/dist/core/tools/bash.d.ts +40 -0
  209. package/dist/core/tools/bash.d.ts.map +1 -0
  210. package/dist/core/tools/bash.js +212 -0
  211. package/dist/core/tools/bash.js.map +1 -0
  212. package/dist/core/tools/edit-diff.d.ts +29 -0
  213. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  214. package/dist/core/tools/edit-diff.js +182 -0
  215. package/dist/core/tools/edit-diff.js.map +1 -0
  216. package/dist/core/tools/edit.d.ts +28 -0
  217. package/dist/core/tools/edit.d.ts.map +1 -0
  218. package/dist/core/tools/edit.js +129 -0
  219. package/dist/core/tools/edit.js.map +1 -0
  220. package/dist/core/tools/fast-executor.d.ts +33 -0
  221. package/dist/core/tools/fast-executor.d.ts.map +1 -0
  222. package/dist/core/tools/fast-executor.js +83 -0
  223. package/dist/core/tools/fast-executor.js.map +1 -0
  224. package/dist/core/tools/find.d.ts +28 -0
  225. package/dist/core/tools/find.d.ts.map +1 -0
  226. package/dist/core/tools/find.js +50 -0
  227. package/dist/core/tools/find.js.map +1 -0
  228. package/dist/core/tools/grep.d.ts +37 -0
  229. package/dist/core/tools/grep.d.ts.map +1 -0
  230. package/dist/core/tools/grep.js +233 -0
  231. package/dist/core/tools/grep.js.map +1 -0
  232. package/dist/core/tools/index.d.ts +63 -0
  233. package/dist/core/tools/index.d.ts.map +1 -0
  234. package/dist/core/tools/index.js +52 -0
  235. package/dist/core/tools/index.js.map +1 -0
  236. package/dist/core/tools/local-accelerator.d.ts +65 -0
  237. package/dist/core/tools/local-accelerator.d.ts.map +1 -0
  238. package/dist/core/tools/local-accelerator.js +198 -0
  239. package/dist/core/tools/local-accelerator.js.map +1 -0
  240. package/dist/core/tools/ls.d.ts +31 -0
  241. package/dist/core/tools/ls.d.ts.map +1 -0
  242. package/dist/core/tools/ls.js +109 -0
  243. package/dist/core/tools/ls.js.map +1 -0
  244. package/dist/core/tools/parallel-executor.d.ts +60 -0
  245. package/dist/core/tools/parallel-executor.d.ts.map +1 -0
  246. package/dist/core/tools/parallel-executor.js +257 -0
  247. package/dist/core/tools/parallel-executor.js.map +1 -0
  248. package/dist/core/tools/path-utils.d.ts +4 -0
  249. package/dist/core/tools/path-utils.d.ts.map +1 -0
  250. package/dist/core/tools/path-utils.js +70 -0
  251. package/dist/core/tools/path-utils.js.map +1 -0
  252. package/dist/core/tools/read.d.ts +29 -0
  253. package/dist/core/tools/read.d.ts.map +1 -0
  254. package/dist/core/tools/read.js +146 -0
  255. package/dist/core/tools/read.js.map +1 -0
  256. package/dist/core/tools/truncate.d.ts +28 -0
  257. package/dist/core/tools/truncate.d.ts.map +1 -0
  258. package/dist/core/tools/truncate.js +161 -0
  259. package/dist/core/tools/truncate.js.map +1 -0
  260. package/dist/core/tools/write.d.ts +21 -0
  261. package/dist/core/tools/write.d.ts.map +1 -0
  262. package/dist/core/tools/write.js +69 -0
  263. package/dist/core/tools/write.js.map +1 -0
  264. package/dist/core/user-config.d.ts +26 -0
  265. package/dist/core/user-config.d.ts.map +1 -0
  266. package/dist/core/user-config.js +75 -0
  267. package/dist/core/user-config.js.map +1 -0
  268. package/dist/index.d.ts +27 -0
  269. package/dist/index.d.ts.map +1 -0
  270. package/dist/index.js +42 -0
  271. package/dist/index.js.map +1 -0
  272. package/dist/main.d.ts +2 -0
  273. package/dist/main.d.ts.map +1 -0
  274. package/dist/main.js +609 -0
  275. package/dist/main.js.map +1 -0
  276. package/dist/migrations.d.ts +8 -0
  277. package/dist/migrations.d.ts.map +1 -0
  278. package/dist/migrations.js +197 -0
  279. package/dist/migrations.js.map +1 -0
  280. package/dist/modes/index.d.ts +6 -0
  281. package/dist/modes/index.d.ts.map +1 -0
  282. package/dist/modes/index.js +5 -0
  283. package/dist/modes/index.js.map +1 -0
  284. package/dist/modes/interactive/components/armin.d.ts +31 -0
  285. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  286. package/dist/modes/interactive/components/armin.js +306 -0
  287. package/dist/modes/interactive/components/armin.js.map +1 -0
  288. package/dist/modes/interactive/components/assistant-message.d.ts +13 -0
  289. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  290. package/dist/modes/interactive/components/assistant-message.js +82 -0
  291. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  292. package/dist/modes/interactive/components/bash-execution.d.ts +23 -0
  293. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  294. package/dist/modes/interactive/components/bash-execution.js +126 -0
  295. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  296. package/dist/modes/interactive/components/bordered-loader.d.ts +15 -0
  297. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  298. package/dist/modes/interactive/components/bordered-loader.js +50 -0
  299. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  300. package/dist/modes/interactive/components/branch-summary-message.d.ts +12 -0
  301. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  302. package/dist/modes/interactive/components/branch-summary-message.js +40 -0
  303. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  304. package/dist/modes/interactive/components/compaction-summary-message.d.ts +12 -0
  305. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  306. package/dist/modes/interactive/components/compaction-summary-message.js +41 -0
  307. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  308. package/dist/modes/interactive/components/config-selector.d.ts +68 -0
  309. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  310. package/dist/modes/interactive/components/config-selector.js +451 -0
  311. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  312. package/dist/modes/interactive/components/countdown-timer.d.ts +11 -0
  313. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  314. package/dist/modes/interactive/components/countdown-timer.js +30 -0
  315. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  316. package/dist/modes/interactive/components/custom-editor.d.ts +14 -0
  317. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  318. package/dist/modes/interactive/components/custom-editor.js +52 -0
  319. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  320. package/dist/modes/interactive/components/custom-message.d.ts +16 -0
  321. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  322. package/dist/modes/interactive/components/custom-message.js +66 -0
  323. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  324. package/dist/modes/interactive/components/daxnuts.d.ts +18 -0
  325. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  326. package/dist/modes/interactive/components/daxnuts.js +130 -0
  327. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  328. package/dist/modes/interactive/components/diff.d.ts +5 -0
  329. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  330. package/dist/modes/interactive/components/diff.js +106 -0
  331. package/dist/modes/interactive/components/diff.js.map +1 -0
  332. package/dist/modes/interactive/components/dynamic-border.d.ts +8 -0
  333. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  334. package/dist/modes/interactive/components/dynamic-border.js +12 -0
  335. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  336. package/dist/modes/interactive/components/extension-editor.d.ts +16 -0
  337. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  338. package/dist/modes/interactive/components/extension-editor.js +95 -0
  339. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  340. package/dist/modes/interactive/components/extension-input.d.ts +20 -0
  341. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  342. package/dist/modes/interactive/components/extension-input.js +57 -0
  343. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  344. package/dist/modes/interactive/components/extension-selector.d.ts +20 -0
  345. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  346. package/dist/modes/interactive/components/extension-selector.js +74 -0
  347. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  348. package/dist/modes/interactive/components/footer.d.ts +14 -0
  349. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  350. package/dist/modes/interactive/components/footer.js +78 -0
  351. package/dist/modes/interactive/components/footer.js.map +1 -0
  352. package/dist/modes/interactive/components/index.d.ts +28 -0
  353. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  354. package/dist/modes/interactive/components/index.js +28 -0
  355. package/dist/modes/interactive/components/index.js.map +1 -0
  356. package/dist/modes/interactive/components/keybinding-hints.d.ts +8 -0
  357. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  358. package/dist/modes/interactive/components/keybinding-hints.js +25 -0
  359. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  360. package/dist/modes/interactive/components/onboarding-wizard.d.ts +23 -0
  361. package/dist/modes/interactive/components/onboarding-wizard.d.ts.map +1 -0
  362. package/dist/modes/interactive/components/onboarding-wizard.js +250 -0
  363. package/dist/modes/interactive/components/onboarding-wizard.js.map +1 -0
  364. package/dist/modes/interactive/components/session-selector-search.d.ts +21 -0
  365. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  366. package/dist/modes/interactive/components/session-selector-search.js +149 -0
  367. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  368. package/dist/modes/interactive/components/session-selector.d.ts +89 -0
  369. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  370. package/dist/modes/interactive/components/session-selector.js +786 -0
  371. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  372. package/dist/modes/interactive/components/settings-selector.d.ts +55 -0
  373. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  374. package/dist/modes/interactive/components/settings-selector.js +273 -0
  375. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  376. package/dist/modes/interactive/components/show-images-selector.d.ts +7 -0
  377. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  378. package/dist/modes/interactive/components/show-images-selector.js +28 -0
  379. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  380. package/dist/modes/interactive/components/skill-invocation-message.d.ts +12 -0
  381. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  382. package/dist/modes/interactive/components/skill-invocation-message.js +40 -0
  383. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  384. package/dist/modes/interactive/components/theme-selector.d.ts +8 -0
  385. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  386. package/dist/modes/interactive/components/theme-selector.js +38 -0
  387. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  388. package/dist/modes/interactive/components/thinking-selector.d.ts +8 -0
  389. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  390. package/dist/modes/interactive/components/thinking-selector.js +40 -0
  391. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  392. package/dist/modes/interactive/components/tool-execution.d.ts +54 -0
  393. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  394. package/dist/modes/interactive/components/tool-execution.js +704 -0
  395. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  396. package/dist/modes/interactive/components/tree-selector.d.ts +59 -0
  397. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  398. package/dist/modes/interactive/components/tree-selector.js +929 -0
  399. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  400. package/dist/modes/interactive/components/user-message-selector.d.ts +24 -0
  401. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  402. package/dist/modes/interactive/components/user-message-selector.js +89 -0
  403. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  404. package/dist/modes/interactive/components/user-message.d.ts +6 -0
  405. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  406. package/dist/modes/interactive/components/user-message.js +24 -0
  407. package/dist/modes/interactive/components/user-message.js.map +1 -0
  408. package/dist/modes/interactive/components/visual-truncate.d.ts +6 -0
  409. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  410. package/dist/modes/interactive/components/visual-truncate.js +15 -0
  411. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  412. package/dist/modes/interactive/interactive-mode.d.ts +177 -0
  413. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  414. package/dist/modes/interactive/interactive-mode.js +3037 -0
  415. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  416. package/dist/modes/interactive/theme/dark.json +85 -0
  417. package/dist/modes/interactive/theme/light.json +84 -0
  418. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  419. package/dist/modes/interactive/theme/theme.d.ts +60 -0
  420. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  421. package/dist/modes/interactive/theme/theme.js +852 -0
  422. package/dist/modes/interactive/theme/theme.js.map +1 -0
  423. package/dist/modes/print-mode.d.ts +10 -0
  424. package/dist/modes/print-mode.d.ts.map +1 -0
  425. package/dist/modes/print-mode.js +80 -0
  426. package/dist/modes/print-mode.js.map +1 -0
  427. package/dist/modes/rpc/jsonl.d.ts +4 -0
  428. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  429. package/dist/modes/rpc/jsonl.js +36 -0
  430. package/dist/modes/rpc/jsonl.js.map +1 -0
  431. package/dist/modes/rpc/rpc-client.d.ts +94 -0
  432. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  433. package/dist/modes/rpc/rpc-client.js +262 -0
  434. package/dist/modes/rpc/rpc-client.js.map +1 -0
  435. package/dist/modes/rpc/rpc-mode.d.ts +3 -0
  436. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  437. package/dist/modes/rpc/rpc-mode.js +227 -0
  438. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  439. package/dist/modes/rpc/rpc-types.d.ts +395 -0
  440. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  441. package/dist/modes/rpc/rpc-types.js +2 -0
  442. package/dist/modes/rpc/rpc-types.js.map +1 -0
  443. package/dist/utils/changelog.d.ts +11 -0
  444. package/dist/utils/changelog.d.ts.map +1 -0
  445. package/dist/utils/changelog.js +69 -0
  446. package/dist/utils/changelog.js.map +1 -0
  447. package/dist/utils/clipboard-image.d.ts +11 -0
  448. package/dist/utils/clipboard-image.d.ts.map +1 -0
  449. package/dist/utils/clipboard-image.js +157 -0
  450. package/dist/utils/clipboard-image.js.map +1 -0
  451. package/dist/utils/clipboard-native.d.ts +7 -0
  452. package/dist/utils/clipboard-native.d.ts.map +1 -0
  453. package/dist/utils/clipboard-native.js +14 -0
  454. package/dist/utils/clipboard-native.js.map +1 -0
  455. package/dist/utils/clipboard.d.ts +2 -0
  456. package/dist/utils/clipboard.d.ts.map +1 -0
  457. package/dist/utils/clipboard.js +55 -0
  458. package/dist/utils/clipboard.js.map +1 -0
  459. package/dist/utils/frontmatter.d.ts +8 -0
  460. package/dist/utils/frontmatter.d.ts.map +1 -0
  461. package/dist/utils/frontmatter.js +26 -0
  462. package/dist/utils/frontmatter.js.map +1 -0
  463. package/dist/utils/git.d.ts +10 -0
  464. package/dist/utils/git.d.ts.map +1 -0
  465. package/dist/utils/git.js +156 -0
  466. package/dist/utils/git.js.map +1 -0
  467. package/dist/utils/image-convert.d.ts +5 -0
  468. package/dist/utils/image-convert.d.ts.map +1 -0
  469. package/dist/utils/image-convert.js +28 -0
  470. package/dist/utils/image-convert.js.map +1 -0
  471. package/dist/utils/image-resize.d.ts +19 -0
  472. package/dist/utils/image-resize.d.ts.map +1 -0
  473. package/dist/utils/image-resize.js +152 -0
  474. package/dist/utils/image-resize.js.map +1 -0
  475. package/dist/utils/mime.d.ts +2 -0
  476. package/dist/utils/mime.d.ts.map +1 -0
  477. package/dist/utils/mime.js +26 -0
  478. package/dist/utils/mime.js.map +1 -0
  479. package/dist/utils/photon.d.ts +3 -0
  480. package/dist/utils/photon.d.ts.map +1 -0
  481. package/dist/utils/photon.js +102 -0
  482. package/dist/utils/photon.js.map +1 -0
  483. package/dist/utils/shell.d.ts +8 -0
  484. package/dist/utils/shell.d.ts.map +1 -0
  485. package/dist/utils/shell.js +135 -0
  486. package/dist/utils/shell.js.map +1 -0
  487. package/dist/utils/sleep.d.ts +2 -0
  488. package/dist/utils/sleep.d.ts.map +1 -0
  489. package/dist/utils/sleep.js +14 -0
  490. package/dist/utils/sleep.js.map +1 -0
  491. package/dist/utils/tools-manager.d.ts +3 -0
  492. package/dist/utils/tools-manager.d.ts.map +1 -0
  493. package/dist/utils/tools-manager.js +227 -0
  494. package/dist/utils/tools-manager.js.map +1 -0
  495. package/docs/compaction.md +392 -0
  496. package/docs/custom-provider.md +592 -0
  497. package/docs/development.md +69 -0
  498. package/docs/extensions.md +2023 -0
  499. package/docs/images/doom-extension.png +0 -0
  500. package/docs/images/exy.png +0 -0
  501. package/docs/images/interactive-mode.png +0 -0
  502. package/docs/images/tree-view.png +0 -0
  503. package/docs/json.md +79 -0
  504. package/docs/keybindings.md +182 -0
  505. package/docs/models.md +297 -0
  506. package/docs/packages.md +209 -0
  507. package/docs/prompt-templates.md +67 -0
  508. package/docs/providers.md +188 -0
  509. package/docs/rpc.md +1354 -0
  510. package/docs/sdk.md +968 -0
  511. package/docs/session.md +412 -0
  512. package/docs/settings.md +225 -0
  513. package/docs/shell-aliases.md +13 -0
  514. package/docs/skills.md +231 -0
  515. package/docs/terminal-setup.md +87 -0
  516. package/docs/termux.md +127 -0
  517. package/docs/themes.md +295 -0
  518. package/docs/tmux.md +61 -0
  519. package/docs/tree.md +228 -0
  520. package/docs/tui.md +887 -0
  521. package/docs/windows.md +17 -0
  522. package/examples/README.md +25 -0
  523. package/examples/extensions/README.md +205 -0
  524. package/examples/extensions/antigravity-image-gen.ts +415 -0
  525. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  526. package/examples/extensions/bash-spawn-hook.ts +30 -0
  527. package/examples/extensions/bookmark.ts +50 -0
  528. package/examples/extensions/built-in-tool-renderer.ts +246 -0
  529. package/examples/extensions/claude-rules.ts +86 -0
  530. package/examples/extensions/commands.ts +72 -0
  531. package/examples/extensions/confirm-destructive.ts +59 -0
  532. package/examples/extensions/custom-compaction.ts +114 -0
  533. package/examples/extensions/custom-footer.ts +64 -0
  534. package/examples/extensions/custom-header.ts +73 -0
  535. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  536. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  537. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  538. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  539. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  540. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  541. package/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
  542. package/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
  543. package/examples/extensions/dirty-repo-guard.ts +56 -0
  544. package/examples/extensions/doom-overlay/README.md +46 -0
  545. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  546. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  547. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  548. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  549. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  550. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  551. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  552. package/examples/extensions/doom-overlay/index.ts +74 -0
  553. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  554. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  555. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  556. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  557. package/examples/extensions/dynamic-resources/index.ts +15 -0
  558. package/examples/extensions/dynamic-tools.ts +74 -0
  559. package/examples/extensions/event-bus.ts +43 -0
  560. package/examples/extensions/file-trigger.ts +41 -0
  561. package/examples/extensions/git-checkpoint.ts +53 -0
  562. package/examples/extensions/handoff.ts +150 -0
  563. package/examples/extensions/hello.ts +25 -0
  564. package/examples/extensions/inline-bash.ts +94 -0
  565. package/examples/extensions/input-transform.ts +43 -0
  566. package/examples/extensions/interactive-shell.ts +196 -0
  567. package/examples/extensions/mac-system-theme.ts +47 -0
  568. package/examples/extensions/message-renderer.ts +59 -0
  569. package/examples/extensions/minimal-mode.ts +426 -0
  570. package/examples/extensions/modal-editor.ts +85 -0
  571. package/examples/extensions/model-status.ts +31 -0
  572. package/examples/extensions/notify.ts +55 -0
  573. package/examples/extensions/overlay-qa-tests.ts +1348 -0
  574. package/examples/extensions/overlay-test.ts +150 -0
  575. package/examples/extensions/permission-gate.ts +34 -0
  576. package/examples/extensions/pirate.ts +47 -0
  577. package/examples/extensions/plan-mode/README.md +65 -0
  578. package/examples/extensions/plan-mode/index.ts +340 -0
  579. package/examples/extensions/plan-mode/utils.ts +168 -0
  580. package/examples/extensions/preset.ts +398 -0
  581. package/examples/extensions/protected-paths.ts +30 -0
  582. package/examples/extensions/provider-payload.ts +14 -0
  583. package/examples/extensions/qna.ts +119 -0
  584. package/examples/extensions/question.ts +264 -0
  585. package/examples/extensions/questionnaire.ts +427 -0
  586. package/examples/extensions/rainbow-editor.ts +88 -0
  587. package/examples/extensions/reload-runtime.ts +37 -0
  588. package/examples/extensions/rpc-demo.ts +124 -0
  589. package/examples/extensions/sandbox/index.ts +318 -0
  590. package/examples/extensions/sandbox/package-lock.json +92 -0
  591. package/examples/extensions/sandbox/package.json +19 -0
  592. package/examples/extensions/send-user-message.ts +97 -0
  593. package/examples/extensions/session-name.ts +27 -0
  594. package/examples/extensions/shutdown-command.ts +63 -0
  595. package/examples/extensions/snake.ts +343 -0
  596. package/examples/extensions/space-invaders.ts +560 -0
  597. package/examples/extensions/ssh.ts +220 -0
  598. package/examples/extensions/status-line.ts +40 -0
  599. package/examples/extensions/subagent/README.md +172 -0
  600. package/examples/extensions/subagent/agents/planner.md +37 -0
  601. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  602. package/examples/extensions/subagent/agents/scout.md +50 -0
  603. package/examples/extensions/subagent/agents/worker.md +24 -0
  604. package/examples/extensions/subagent/agents.ts +126 -0
  605. package/examples/extensions/subagent/index.ts +964 -0
  606. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  607. package/examples/extensions/subagent/prompts/implement.md +10 -0
  608. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  609. package/examples/extensions/summarize.ts +195 -0
  610. package/examples/extensions/system-prompt-header.ts +17 -0
  611. package/examples/extensions/timed-confirm.ts +70 -0
  612. package/examples/extensions/titlebar-spinner.ts +58 -0
  613. package/examples/extensions/todo.ts +299 -0
  614. package/examples/extensions/tool-override.ts +143 -0
  615. package/examples/extensions/tools.ts +146 -0
  616. package/examples/extensions/trigger-compact.ts +40 -0
  617. package/examples/extensions/truncated-tool.ts +192 -0
  618. package/examples/extensions/widget-placement.ts +17 -0
  619. package/examples/extensions/with-deps/index.ts +32 -0
  620. package/examples/extensions/with-deps/package-lock.json +31 -0
  621. package/examples/extensions/with-deps/package.json +22 -0
  622. package/examples/rpc-extension-ui.ts +632 -0
  623. package/examples/sdk/01-minimal.ts +22 -0
  624. package/examples/sdk/02-custom-model.ts +49 -0
  625. package/examples/sdk/03-custom-prompt.ts +55 -0
  626. package/examples/sdk/04-skills.ts +46 -0
  627. package/examples/sdk/05-tools.ts +56 -0
  628. package/examples/sdk/06-extensions.ts +88 -0
  629. package/examples/sdk/07-context-files.ts +40 -0
  630. package/examples/sdk/08-prompt-templates.ts +47 -0
  631. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  632. package/examples/sdk/10-settings.ts +51 -0
  633. package/examples/sdk/11-sessions.ts +48 -0
  634. package/examples/sdk/12-full-control.ts +82 -0
  635. package/examples/sdk/README.md +144 -0
  636. package/package.json +100 -0
@@ -0,0 +1,3037 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { CombinedAutocompleteProvider, Container, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
6
+ import { spawn, spawnSync } from "child_process";
7
+ import { APP_NAME, getDebugLogPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
8
+ import { parseSkillBlock } from "../../core/agent-session.js";
9
+ import { getProductName } from "../../core/branded-ai.js";
10
+ import { FooterDataProvider } from "../../core/footer-data-provider.js";
11
+ import { KeybindingsManager } from "../../core/keybindings.js";
12
+ import { createCompactionSummaryMessage } from "../../core/messages.js";
13
+ import { hasConfiguredModels, runOnboarding } from "../../core/onboarding.js";
14
+ import { SessionManager } from "../../core/session-manager.js";
15
+ import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
16
+ import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
17
+ import { copyToClipboard } from "../../utils/clipboard.js";
18
+ import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
19
+ import { ensureTool } from "../../utils/tools-manager.js";
20
+ import { ArminComponent } from "./components/armin.js";
21
+ import { AssistantMessageComponent } from "./components/assistant-message.js";
22
+ import { BashExecutionComponent } from "./components/bash-execution.js";
23
+ import { BorderedLoader } from "./components/bordered-loader.js";
24
+ import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
25
+ import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
26
+ import { CustomEditor } from "./components/custom-editor.js";
27
+ import { CustomMessageComponent } from "./components/custom-message.js";
28
+ import { DaxnutsComponent } from "./components/daxnuts.js";
29
+ import { DynamicBorder } from "./components/dynamic-border.js";
30
+ import { ExtensionEditorComponent } from "./components/extension-editor.js";
31
+ import { ExtensionInputComponent } from "./components/extension-input.js";
32
+ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
33
+ import { FooterComponent } from "./components/footer.js";
34
+ import { appKey, appKeyHint, editorKey, keyHint, rawKeyHint } from "./components/keybinding-hints.js";
35
+ import { SessionSelectorComponent } from "./components/session-selector.js";
36
+ import { SettingsSelectorComponent } from "./components/settings-selector.js";
37
+ import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
38
+ import { ToolExecutionComponent } from "./components/tool-execution.js";
39
+ import { TreeSelectorComponent } from "./components/tree-selector.js";
40
+ import { UserMessageComponent } from "./components/user-message.js";
41
+ import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
42
+ import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, Theme, theme, } from "./theme/theme.js";
43
+ function isExpandable(obj) {
44
+ return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
45
+ }
46
+ export class InteractiveMode {
47
+ options;
48
+ session;
49
+ ui;
50
+ chatContainer;
51
+ pendingMessagesContainer;
52
+ statusContainer;
53
+ defaultEditor;
54
+ editor;
55
+ autocompleteProvider;
56
+ fdPath;
57
+ editorContainer;
58
+ footer;
59
+ footerDataProvider;
60
+ keybindings;
61
+ version;
62
+ isInitialized = false;
63
+ onInputCallback;
64
+ loadingAnimation = undefined;
65
+ pendingWorkingMessage = undefined;
66
+ defaultWorkingMessage = "Working...";
67
+ lastSigintTime = 0;
68
+ lastEscapeTime = 0;
69
+ changelogMarkdown = undefined;
70
+ lastStatusSpacer = undefined;
71
+ lastStatusText = undefined;
72
+ streamingComponent = undefined;
73
+ streamingMessage = undefined;
74
+ pendingTools = new Map();
75
+ toolOutputExpanded = false;
76
+ hideThinkingBlock = false;
77
+ skillCommands = new Map();
78
+ unsubscribe;
79
+ isBashMode = false;
80
+ bashComponent = undefined;
81
+ pendingBashComponents = [];
82
+ autoCompactionLoader = undefined;
83
+ autoCompactionEscapeHandler;
84
+ retryLoader = undefined;
85
+ retryEscapeHandler;
86
+ compactionQueuedMessages = [];
87
+ shutdownRequested = false;
88
+ extensionSelector = undefined;
89
+ extensionInput = undefined;
90
+ extensionEditor = undefined;
91
+ extensionTerminalInputUnsubscribers = new Set();
92
+ extensionWidgetsAbove = new Map();
93
+ extensionWidgetsBelow = new Map();
94
+ widgetContainerAbove;
95
+ widgetContainerBelow;
96
+ customFooter = undefined;
97
+ headerContainer;
98
+ builtInHeader = undefined;
99
+ customHeader = undefined;
100
+ get agent() {
101
+ return this.session.agent;
102
+ }
103
+ get sessionManager() {
104
+ return this.session.sessionManager;
105
+ }
106
+ get settingsManager() {
107
+ return this.session.settingsManager;
108
+ }
109
+ constructor(session, options = {}) {
110
+ this.options = options;
111
+ this.session = session;
112
+ this.version = VERSION;
113
+ this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
114
+ this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
115
+ this.headerContainer = new Container();
116
+ this.chatContainer = new Container();
117
+ this.pendingMessagesContainer = new Container();
118
+ this.statusContainer = new Container();
119
+ this.widgetContainerAbove = new Container();
120
+ this.widgetContainerBelow = new Container();
121
+ this.keybindings = KeybindingsManager.create();
122
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
123
+ const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
124
+ this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
125
+ paddingX: editorPaddingX,
126
+ autocompleteMaxVisible,
127
+ });
128
+ this.editor = this.defaultEditor;
129
+ this.editorContainer = new Container();
130
+ this.editorContainer.addChild(this.editor);
131
+ this.footerDataProvider = new FooterDataProvider();
132
+ this.footer = new FooterComponent(session, this.footerDataProvider);
133
+ this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
134
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
135
+ setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
136
+ initTheme(this.settingsManager.getTheme(), true);
137
+ }
138
+ setupAutocomplete(fdPath) {
139
+ const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
140
+ name: command.name,
141
+ description: command.description,
142
+ }));
143
+ const templateCommands = this.session.promptTemplates.map((cmd) => ({
144
+ name: cmd.name,
145
+ description: cmd.description,
146
+ }));
147
+ const builtinCommandNames = new Set(slashCommands.map((c) => c.name));
148
+ const extensionCommands = (this.session.extensionRunner?.getRegisteredCommands(builtinCommandNames) ?? []).map((cmd) => ({
149
+ name: cmd.name,
150
+ description: cmd.description ?? "(extension command)",
151
+ getArgumentCompletions: cmd.getArgumentCompletions,
152
+ }));
153
+ this.skillCommands.clear();
154
+ const skillCommandList = [];
155
+ if (this.settingsManager.getEnableSkillCommands()) {
156
+ for (const skill of this.session.resourceLoader.getSkills().skills) {
157
+ const commandName = `skill:${skill.name}`;
158
+ this.skillCommands.set(commandName, skill.filePath);
159
+ skillCommandList.push({ name: commandName, description: skill.description });
160
+ }
161
+ }
162
+ this.autocompleteProvider = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], process.cwd(), fdPath);
163
+ this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
164
+ if (this.editor !== this.defaultEditor) {
165
+ this.editor.setAutocompleteProvider?.(this.autocompleteProvider);
166
+ }
167
+ }
168
+ async init() {
169
+ if (this.isInitialized)
170
+ return;
171
+ if (!hasConfiguredModels()) {
172
+ await runOnboarding();
173
+ }
174
+ this.changelogMarkdown = this.getChangelogForDisplay();
175
+ const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
176
+ this.fdPath = fdPath;
177
+ this.ui.addChild(this.headerContainer);
178
+ if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
179
+ const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
180
+ const kb = this.keybindings;
181
+ const hint = (action, desc) => appKeyHint(kb, action, desc);
182
+ const instructions = [
183
+ hint("interrupt", "to interrupt"),
184
+ hint("clear", "to clear"),
185
+ rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
186
+ hint("exit", "to exit (empty)"),
187
+ hint("suspend", "to suspend"),
188
+ keyHint("deleteToLineEnd", "to delete to end"),
189
+ hint("cycleThinkingLevel", "to cycle thinking level"),
190
+ hint("expandTools", "to expand tools"),
191
+ hint("toggleThinking", "to expand thinking"),
192
+ hint("externalEditor", "for external editor"),
193
+ rawKeyHint("/", "for commands"),
194
+ rawKeyHint("!", "to run bash"),
195
+ rawKeyHint("!!", "to run bash (no context)"),
196
+ hint("followUp", "to queue follow-up"),
197
+ hint("dequeue", "to edit all queued messages"),
198
+ hint("pasteImage", "to paste image"),
199
+ rawKeyHint("drop files", "to attach"),
200
+ ].join("\n");
201
+ this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
202
+ this.headerContainer.addChild(new Spacer(1));
203
+ this.headerContainer.addChild(this.builtInHeader);
204
+ this.headerContainer.addChild(new Spacer(1));
205
+ if (this.changelogMarkdown) {
206
+ this.headerContainer.addChild(new DynamicBorder());
207
+ if (this.settingsManager.getCollapseChangelog()) {
208
+ const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
209
+ const latestVersion = versionMatch ? versionMatch[1] : this.version;
210
+ const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
211
+ this.headerContainer.addChild(new Text(condensedText, 1, 0));
212
+ }
213
+ else {
214
+ this.headerContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
215
+ this.headerContainer.addChild(new Spacer(1));
216
+ this.headerContainer.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
217
+ this.headerContainer.addChild(new Spacer(1));
218
+ }
219
+ this.headerContainer.addChild(new DynamicBorder());
220
+ }
221
+ }
222
+ else {
223
+ this.builtInHeader = new Text("", 0, 0);
224
+ this.headerContainer.addChild(this.builtInHeader);
225
+ if (this.changelogMarkdown) {
226
+ this.headerContainer.addChild(new Spacer(1));
227
+ const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
228
+ const latestVersion = versionMatch ? versionMatch[1] : this.version;
229
+ const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
230
+ this.headerContainer.addChild(new Text(condensedText, 1, 0));
231
+ }
232
+ }
233
+ this.ui.addChild(this.chatContainer);
234
+ this.ui.addChild(this.pendingMessagesContainer);
235
+ this.ui.addChild(this.statusContainer);
236
+ this.renderWidgets();
237
+ this.ui.addChild(this.widgetContainerAbove);
238
+ this.ui.addChild(this.editorContainer);
239
+ this.ui.addChild(this.widgetContainerBelow);
240
+ this.ui.addChild(this.footer);
241
+ this.ui.setFocus(this.editor);
242
+ this.setupKeyHandlers();
243
+ this.setupEditorSubmitHandler();
244
+ await this.initExtensions();
245
+ this.renderInitialMessages();
246
+ this.ui.start();
247
+ this.isInitialized = true;
248
+ this.updateTerminalTitle();
249
+ this.subscribeToAgent();
250
+ onThemeChange(() => {
251
+ this.ui.invalidate();
252
+ this.updateEditorBorderColor();
253
+ this.ui.requestRender();
254
+ });
255
+ this.footerDataProvider.onBranchChange(() => {
256
+ this.ui.requestRender();
257
+ });
258
+ }
259
+ updateTerminalTitle() {
260
+ const cwdBasename = path.basename(process.cwd());
261
+ const sessionName = this.sessionManager.getSessionName();
262
+ const productName = getProductName();
263
+ if (sessionName) {
264
+ this.ui.terminal.setTitle(`${productName} - ${sessionName} - ${cwdBasename}`);
265
+ }
266
+ else {
267
+ this.ui.terminal.setTitle(`${productName} - ${cwdBasename}`);
268
+ }
269
+ }
270
+ async run() {
271
+ await this.init();
272
+ this.checkForNewVersion().then((newVersion) => {
273
+ if (newVersion) {
274
+ this.showNewVersionNotification(newVersion);
275
+ }
276
+ });
277
+ this.checkTmuxKeyboardSetup().then((warning) => {
278
+ if (warning) {
279
+ this.showWarning(warning);
280
+ }
281
+ });
282
+ const { initialMessage, initialImages, initialMessages } = this.options;
283
+ if (initialMessage) {
284
+ try {
285
+ await this.session.prompt(initialMessage, { images: initialImages });
286
+ }
287
+ catch (error) {
288
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
289
+ this.showError(errorMessage);
290
+ }
291
+ }
292
+ if (initialMessages) {
293
+ for (const message of initialMessages) {
294
+ try {
295
+ await this.session.prompt(message);
296
+ }
297
+ catch (error) {
298
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
299
+ this.showError(errorMessage);
300
+ }
301
+ }
302
+ }
303
+ while (true) {
304
+ const userInput = await this.getUserInput();
305
+ try {
306
+ await this.session.prompt(userInput);
307
+ }
308
+ catch (error) {
309
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
310
+ this.showError(errorMessage);
311
+ }
312
+ }
313
+ }
314
+ async checkForNewVersion() {
315
+ if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
316
+ return undefined;
317
+ try {
318
+ const response = await fetch("https://registry.npmjs.org/@mariozechner/pi/latest", {
319
+ signal: AbortSignal.timeout(10000),
320
+ });
321
+ if (!response.ok)
322
+ return undefined;
323
+ const data = (await response.json());
324
+ const latestVersion = data.version;
325
+ if (latestVersion && latestVersion !== this.version) {
326
+ return latestVersion;
327
+ }
328
+ return undefined;
329
+ }
330
+ catch {
331
+ return undefined;
332
+ }
333
+ }
334
+ async checkTmuxKeyboardSetup() {
335
+ if (!process.env.TMUX)
336
+ return undefined;
337
+ const runTmuxShow = (option) => {
338
+ return new Promise((resolve) => {
339
+ const proc = spawn("tmux", ["show", "-gv", option], {
340
+ stdio: ["ignore", "pipe", "ignore"],
341
+ });
342
+ let stdout = "";
343
+ const timer = setTimeout(() => {
344
+ proc.kill();
345
+ resolve(undefined);
346
+ }, 2000);
347
+ proc.stdout?.on("data", (data) => {
348
+ stdout += data.toString();
349
+ });
350
+ proc.on("error", () => {
351
+ clearTimeout(timer);
352
+ resolve(undefined);
353
+ });
354
+ proc.on("close", (code) => {
355
+ clearTimeout(timer);
356
+ resolve(code === 0 ? stdout.trim() : undefined);
357
+ });
358
+ });
359
+ };
360
+ const [extendedKeys, extendedKeysFormat] = await Promise.all([
361
+ runTmuxShow("extended-keys"),
362
+ runTmuxShow("extended-keys-format"),
363
+ ]);
364
+ if (extendedKeys !== "on" && extendedKeys !== "always") {
365
+ return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
366
+ }
367
+ if (extendedKeysFormat === "xterm") {
368
+ return "tmux extended-keys-format is xterm. Pi works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.";
369
+ }
370
+ return undefined;
371
+ }
372
+ getChangelogForDisplay() {
373
+ if (this.session.state.messages.length > 0) {
374
+ return undefined;
375
+ }
376
+ const lastVersion = this.settingsManager.getLastChangelogVersion();
377
+ const changelogPath = getChangelogPath();
378
+ const entries = parseChangelog(changelogPath);
379
+ if (!lastVersion) {
380
+ this.settingsManager.setLastChangelogVersion(VERSION);
381
+ return undefined;
382
+ }
383
+ else {
384
+ const newEntries = getNewEntries(entries, lastVersion);
385
+ if (newEntries.length > 0) {
386
+ this.settingsManager.setLastChangelogVersion(VERSION);
387
+ return newEntries.map((e) => e.content).join("\n\n");
388
+ }
389
+ }
390
+ return undefined;
391
+ }
392
+ getMarkdownThemeWithSettings() {
393
+ return {
394
+ ...getMarkdownTheme(),
395
+ codeBlockIndent: this.settingsManager.getCodeBlockIndent(),
396
+ };
397
+ }
398
+ formatDisplayPath(p) {
399
+ const home = os.homedir();
400
+ let result = p;
401
+ if (result.startsWith(home)) {
402
+ result = `~${result.slice(home.length)}`;
403
+ }
404
+ return result;
405
+ }
406
+ getShortPath(fullPath, source) {
407
+ const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
408
+ if (npmMatch && source.startsWith("npm:")) {
409
+ return npmMatch[2];
410
+ }
411
+ const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
412
+ if (gitMatch && source.startsWith("git:")) {
413
+ return gitMatch[1];
414
+ }
415
+ return this.formatDisplayPath(fullPath);
416
+ }
417
+ getDisplaySourceInfo(source, scope) {
418
+ if (source === "local") {
419
+ if (scope === "user") {
420
+ return { label: "user", color: "muted" };
421
+ }
422
+ if (scope === "project") {
423
+ return { label: "project", color: "muted" };
424
+ }
425
+ if (scope === "temporary") {
426
+ return { label: "path", scopeLabel: "temp", color: "muted" };
427
+ }
428
+ return { label: "path", color: "muted" };
429
+ }
430
+ if (source === "cli") {
431
+ return { label: "path", scopeLabel: scope === "temporary" ? "temp" : undefined, color: "muted" };
432
+ }
433
+ const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
434
+ return { label: source, scopeLabel, color: "accent" };
435
+ }
436
+ getScopeGroup(source, scope) {
437
+ if (source === "cli" || scope === "temporary")
438
+ return "path";
439
+ if (scope === "user")
440
+ return "user";
441
+ if (scope === "project")
442
+ return "project";
443
+ return "path";
444
+ }
445
+ isPackageSource(source) {
446
+ return source.startsWith("npm:") || source.startsWith("git:");
447
+ }
448
+ buildScopeGroups(paths, metadata) {
449
+ const groups = {
450
+ user: { scope: "user", paths: [], packages: new Map() },
451
+ project: { scope: "project", paths: [], packages: new Map() },
452
+ path: { scope: "path", paths: [], packages: new Map() },
453
+ };
454
+ for (const p of paths) {
455
+ const meta = this.findMetadata(p, metadata);
456
+ const source = meta?.source ?? "local";
457
+ const scope = meta?.scope ?? "project";
458
+ const groupKey = this.getScopeGroup(source, scope);
459
+ const group = groups[groupKey];
460
+ if (this.isPackageSource(source)) {
461
+ const list = group.packages.get(source) ?? [];
462
+ list.push(p);
463
+ group.packages.set(source, list);
464
+ }
465
+ else {
466
+ group.paths.push(p);
467
+ }
468
+ }
469
+ return [groups.project, groups.user, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
470
+ }
471
+ formatScopeGroups(groups, options) {
472
+ const lines = [];
473
+ for (const group of groups) {
474
+ lines.push(` ${theme.fg("accent", group.scope)}`);
475
+ const sortedPaths = [...group.paths].sort((a, b) => a.localeCompare(b));
476
+ for (const p of sortedPaths) {
477
+ lines.push(theme.fg("dim", ` ${options.formatPath(p)}`));
478
+ }
479
+ const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
480
+ for (const [source, paths] of sortedPackages) {
481
+ lines.push(` ${theme.fg("mdLink", source)}`);
482
+ const sortedPackagePaths = [...paths].sort((a, b) => a.localeCompare(b));
483
+ for (const p of sortedPackagePaths) {
484
+ lines.push(theme.fg("dim", ` ${options.formatPackagePath(p, source)}`));
485
+ }
486
+ }
487
+ }
488
+ return lines.join("\n");
489
+ }
490
+ findMetadata(p, metadata) {
491
+ const exact = metadata.get(p);
492
+ if (exact)
493
+ return exact;
494
+ let current = p;
495
+ while (current.includes("/")) {
496
+ current = current.substring(0, current.lastIndexOf("/"));
497
+ const parent = metadata.get(current);
498
+ if (parent)
499
+ return parent;
500
+ }
501
+ return undefined;
502
+ }
503
+ formatPathWithSource(p, metadata) {
504
+ const meta = this.findMetadata(p, metadata);
505
+ if (meta) {
506
+ const shortPath = this.getShortPath(p, meta.source);
507
+ const { label, scopeLabel } = this.getDisplaySourceInfo(meta.source, meta.scope);
508
+ const labelText = scopeLabel ? `${label} (${scopeLabel})` : label;
509
+ return `${labelText} ${shortPath}`;
510
+ }
511
+ return this.formatDisplayPath(p);
512
+ }
513
+ formatDiagnostics(diagnostics, metadata) {
514
+ const lines = [];
515
+ const collisions = new Map();
516
+ const otherDiagnostics = [];
517
+ for (const d of diagnostics) {
518
+ if (d.type === "collision" && d.collision) {
519
+ const list = collisions.get(d.collision.name) ?? [];
520
+ list.push(d);
521
+ collisions.set(d.collision.name, list);
522
+ }
523
+ else {
524
+ otherDiagnostics.push(d);
525
+ }
526
+ }
527
+ for (const [name, collisionList] of collisions) {
528
+ const first = collisionList[0]?.collision;
529
+ if (!first)
530
+ continue;
531
+ lines.push(theme.fg("warning", ` "${name}" collision:`));
532
+ lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, metadata)}`));
533
+ for (const d of collisionList) {
534
+ if (d.collision) {
535
+ lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, metadata)} (skipped)`));
536
+ }
537
+ }
538
+ }
539
+ for (const d of otherDiagnostics) {
540
+ if (d.path) {
541
+ const sourceInfo = this.formatPathWithSource(d.path, metadata);
542
+ lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${sourceInfo}`));
543
+ lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
544
+ }
545
+ else {
546
+ lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
547
+ }
548
+ }
549
+ return lines.join("\n");
550
+ }
551
+ showLoadedResources(options) {
552
+ const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
553
+ const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
554
+ if (!showListing && !showDiagnostics) {
555
+ return;
556
+ }
557
+ const metadata = this.session.resourceLoader.getPathMetadata();
558
+ const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
559
+ const skillsResult = this.session.resourceLoader.getSkills();
560
+ const promptsResult = this.session.resourceLoader.getPrompts();
561
+ const themesResult = this.session.resourceLoader.getThemes();
562
+ if (showListing) {
563
+ const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
564
+ if (contextFiles.length > 0) {
565
+ this.chatContainer.addChild(new Spacer(1));
566
+ const contextList = contextFiles
567
+ .map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
568
+ .join("\n");
569
+ this.chatContainer.addChild(new Text(`${sectionHeader("Context")}\n${contextList}`, 0, 0));
570
+ this.chatContainer.addChild(new Spacer(1));
571
+ }
572
+ const skills = skillsResult.skills;
573
+ if (skills.length > 0) {
574
+ const skillPaths = skills.map((s) => s.filePath);
575
+ const groups = this.buildScopeGroups(skillPaths, metadata);
576
+ const skillList = this.formatScopeGroups(groups, {
577
+ formatPath: (p) => this.formatDisplayPath(p),
578
+ formatPackagePath: (p, source) => this.getShortPath(p, source),
579
+ });
580
+ this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0));
581
+ this.chatContainer.addChild(new Spacer(1));
582
+ }
583
+ const templates = this.session.promptTemplates;
584
+ if (templates.length > 0) {
585
+ const templatePaths = templates.map((t) => t.filePath);
586
+ const groups = this.buildScopeGroups(templatePaths, metadata);
587
+ const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
588
+ const templateList = this.formatScopeGroups(groups, {
589
+ formatPath: (p) => {
590
+ const template = templateByPath.get(p);
591
+ return template ? `/${template.name}` : this.formatDisplayPath(p);
592
+ },
593
+ formatPackagePath: (p) => {
594
+ const template = templateByPath.get(p);
595
+ return template ? `/${template.name}` : this.formatDisplayPath(p);
596
+ },
597
+ });
598
+ this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0));
599
+ this.chatContainer.addChild(new Spacer(1));
600
+ }
601
+ const extensionPaths = options?.extensionPaths ?? [];
602
+ if (extensionPaths.length > 0) {
603
+ const groups = this.buildScopeGroups(extensionPaths, metadata);
604
+ const extList = this.formatScopeGroups(groups, {
605
+ formatPath: (p) => this.formatDisplayPath(p),
606
+ formatPackagePath: (p, source) => this.getShortPath(p, source),
607
+ });
608
+ this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0));
609
+ this.chatContainer.addChild(new Spacer(1));
610
+ }
611
+ const loadedThemes = themesResult.themes;
612
+ const customThemes = loadedThemes.filter((t) => t.sourcePath);
613
+ if (customThemes.length > 0) {
614
+ const themePaths = customThemes.map((t) => t.sourcePath);
615
+ const groups = this.buildScopeGroups(themePaths, metadata);
616
+ const themeList = this.formatScopeGroups(groups, {
617
+ formatPath: (p) => this.formatDisplayPath(p),
618
+ formatPackagePath: (p, source) => this.getShortPath(p, source),
619
+ });
620
+ this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0));
621
+ this.chatContainer.addChild(new Spacer(1));
622
+ }
623
+ }
624
+ if (showDiagnostics) {
625
+ const skillDiagnostics = skillsResult.diagnostics;
626
+ if (skillDiagnostics.length > 0) {
627
+ const warningLines = this.formatDiagnostics(skillDiagnostics, metadata);
628
+ this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
629
+ this.chatContainer.addChild(new Spacer(1));
630
+ }
631
+ const promptDiagnostics = promptsResult.diagnostics;
632
+ if (promptDiagnostics.length > 0) {
633
+ const warningLines = this.formatDiagnostics(promptDiagnostics, metadata);
634
+ this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
635
+ this.chatContainer.addChild(new Spacer(1));
636
+ }
637
+ const extensionDiagnostics = [];
638
+ const extensionErrors = this.session.resourceLoader.getExtensions().errors;
639
+ if (extensionErrors.length > 0) {
640
+ for (const error of extensionErrors) {
641
+ extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
642
+ }
643
+ }
644
+ const commandDiagnostics = this.session.extensionRunner?.getCommandDiagnostics() ?? [];
645
+ extensionDiagnostics.push(...commandDiagnostics);
646
+ const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? [];
647
+ extensionDiagnostics.push(...shortcutDiagnostics);
648
+ if (extensionDiagnostics.length > 0) {
649
+ const warningLines = this.formatDiagnostics(extensionDiagnostics, metadata);
650
+ this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
651
+ this.chatContainer.addChild(new Spacer(1));
652
+ }
653
+ const themeDiagnostics = themesResult.diagnostics;
654
+ if (themeDiagnostics.length > 0) {
655
+ const warningLines = this.formatDiagnostics(themeDiagnostics, metadata);
656
+ this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
657
+ this.chatContainer.addChild(new Spacer(1));
658
+ }
659
+ }
660
+ }
661
+ async initExtensions() {
662
+ const uiContext = this.createExtensionUIContext();
663
+ await this.session.bindExtensions({
664
+ uiContext,
665
+ commandContextActions: {
666
+ waitForIdle: () => this.session.agent.waitForIdle(),
667
+ newSession: async (options) => {
668
+ if (this.loadingAnimation) {
669
+ this.loadingAnimation.stop();
670
+ this.loadingAnimation = undefined;
671
+ }
672
+ this.statusContainer.clear();
673
+ const success = await this.session.newSession(options);
674
+ if (!success) {
675
+ return { cancelled: true };
676
+ }
677
+ this.chatContainer.clear();
678
+ this.pendingMessagesContainer.clear();
679
+ this.compactionQueuedMessages = [];
680
+ this.streamingComponent = undefined;
681
+ this.streamingMessage = undefined;
682
+ this.pendingTools.clear();
683
+ this.renderInitialMessages();
684
+ this.ui.requestRender();
685
+ return { cancelled: false };
686
+ },
687
+ fork: async (entryId) => {
688
+ const result = await this.session.fork(entryId);
689
+ if (result.cancelled) {
690
+ return { cancelled: true };
691
+ }
692
+ this.chatContainer.clear();
693
+ this.renderInitialMessages();
694
+ this.editor.setText(result.selectedText);
695
+ this.showStatus("Forked to new session");
696
+ return { cancelled: false };
697
+ },
698
+ navigateTree: async (targetId, options) => {
699
+ const result = await this.session.navigateTree(targetId, {
700
+ summarize: options?.summarize,
701
+ customInstructions: options?.customInstructions,
702
+ replaceInstructions: options?.replaceInstructions,
703
+ label: options?.label,
704
+ });
705
+ if (result.cancelled) {
706
+ return { cancelled: true };
707
+ }
708
+ this.chatContainer.clear();
709
+ this.renderInitialMessages();
710
+ if (result.editorText && !this.editor.getText().trim()) {
711
+ this.editor.setText(result.editorText);
712
+ }
713
+ this.showStatus("Navigated to selected point");
714
+ return { cancelled: false };
715
+ },
716
+ switchSession: async (sessionPath) => {
717
+ await this.handleResumeSession(sessionPath);
718
+ return { cancelled: false };
719
+ },
720
+ reload: async () => {
721
+ await this.handleReloadCommand();
722
+ },
723
+ },
724
+ shutdownHandler: () => {
725
+ this.shutdownRequested = true;
726
+ if (!this.session.isStreaming) {
727
+ void this.shutdown();
728
+ }
729
+ },
730
+ onError: (error) => {
731
+ this.showExtensionError(error.extensionPath, error.error, error.stack);
732
+ },
733
+ });
734
+ setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
735
+ this.setupAutocomplete(this.fdPath);
736
+ const extensionRunner = this.session.extensionRunner;
737
+ if (!extensionRunner) {
738
+ this.showLoadedResources({ extensionPaths: [], force: false });
739
+ return;
740
+ }
741
+ this.setupExtensionShortcuts(extensionRunner);
742
+ this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false });
743
+ }
744
+ getRegisteredToolDefinition(toolName) {
745
+ const tools = this.session.extensionRunner?.getAllRegisteredTools() ?? [];
746
+ const registeredTool = tools.find((t) => t.definition.name === toolName);
747
+ return registeredTool?.definition;
748
+ }
749
+ setupExtensionShortcuts(extensionRunner) {
750
+ const shortcuts = extensionRunner.getShortcuts(this.keybindings.getEffectiveConfig());
751
+ if (shortcuts.size === 0)
752
+ return;
753
+ const createContext = () => ({
754
+ ui: this.createExtensionUIContext(),
755
+ hasUI: true,
756
+ cwd: process.cwd(),
757
+ sessionManager: this.sessionManager,
758
+ modelRegistry: this.session.modelRegistry,
759
+ model: this.session.model,
760
+ isIdle: () => !this.session.isStreaming,
761
+ abort: () => this.session.abort(),
762
+ hasPendingMessages: () => this.session.pendingMessageCount > 0,
763
+ shutdown: () => {
764
+ this.shutdownRequested = true;
765
+ },
766
+ getContextUsage: () => this.session.getContextUsage(),
767
+ compact: (options) => {
768
+ void (async () => {
769
+ try {
770
+ const result = await this.executeCompaction(options?.customInstructions, false);
771
+ if (result) {
772
+ options?.onComplete?.(result);
773
+ }
774
+ }
775
+ catch (error) {
776
+ const err = error instanceof Error ? error : new Error(String(error));
777
+ options?.onError?.(err);
778
+ }
779
+ })();
780
+ },
781
+ getSystemPrompt: () => this.session.systemPrompt,
782
+ });
783
+ this.defaultEditor.onExtensionShortcut = (data) => {
784
+ for (const [shortcutStr, shortcut] of shortcuts) {
785
+ if (matchesKey(data, shortcutStr)) {
786
+ Promise.resolve(shortcut.handler(createContext())).catch((err) => {
787
+ this.showError(`Shortcut handler error: ${err instanceof Error ? err.message : String(err)}`);
788
+ });
789
+ return true;
790
+ }
791
+ }
792
+ return false;
793
+ };
794
+ }
795
+ setExtensionStatus(key, text) {
796
+ this.footerDataProvider.setExtensionStatus(key, text);
797
+ this.ui.requestRender();
798
+ }
799
+ setExtensionWidget(key, content, options) {
800
+ const placement = options?.placement ?? "aboveEditor";
801
+ const removeExisting = (map) => {
802
+ const existing = map.get(key);
803
+ if (existing?.dispose)
804
+ existing.dispose();
805
+ map.delete(key);
806
+ };
807
+ removeExisting(this.extensionWidgetsAbove);
808
+ removeExisting(this.extensionWidgetsBelow);
809
+ if (content === undefined) {
810
+ this.renderWidgets();
811
+ return;
812
+ }
813
+ let component;
814
+ if (Array.isArray(content)) {
815
+ const container = new Container();
816
+ for (const line of content.slice(0, InteractiveMode.MAX_WIDGET_LINES)) {
817
+ container.addChild(new Text(line, 1, 0));
818
+ }
819
+ if (content.length > InteractiveMode.MAX_WIDGET_LINES) {
820
+ container.addChild(new Text(theme.fg("muted", "... (widget truncated)"), 1, 0));
821
+ }
822
+ component = container;
823
+ }
824
+ else {
825
+ component = content(this.ui, theme);
826
+ }
827
+ const targetMap = placement === "belowEditor" ? this.extensionWidgetsBelow : this.extensionWidgetsAbove;
828
+ targetMap.set(key, component);
829
+ this.renderWidgets();
830
+ }
831
+ clearExtensionWidgets() {
832
+ for (const widget of this.extensionWidgetsAbove.values()) {
833
+ widget.dispose?.();
834
+ }
835
+ for (const widget of this.extensionWidgetsBelow.values()) {
836
+ widget.dispose?.();
837
+ }
838
+ this.extensionWidgetsAbove.clear();
839
+ this.extensionWidgetsBelow.clear();
840
+ this.renderWidgets();
841
+ }
842
+ resetExtensionUI() {
843
+ if (this.extensionSelector) {
844
+ this.hideExtensionSelector();
845
+ }
846
+ if (this.extensionInput) {
847
+ this.hideExtensionInput();
848
+ }
849
+ if (this.extensionEditor) {
850
+ this.hideExtensionEditor();
851
+ }
852
+ this.ui.hideOverlay();
853
+ this.clearExtensionTerminalInputListeners();
854
+ this.setExtensionFooter(undefined);
855
+ this.setExtensionHeader(undefined);
856
+ this.clearExtensionWidgets();
857
+ this.footerDataProvider.clearExtensionStatuses();
858
+ this.footer.invalidate();
859
+ this.setCustomEditorComponent(undefined);
860
+ this.defaultEditor.onExtensionShortcut = undefined;
861
+ this.updateTerminalTitle();
862
+ if (this.loadingAnimation) {
863
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
864
+ }
865
+ }
866
+ static MAX_WIDGET_LINES = 10;
867
+ renderWidgets() {
868
+ if (!this.widgetContainerAbove || !this.widgetContainerBelow)
869
+ return;
870
+ this.renderWidgetContainer(this.widgetContainerAbove, this.extensionWidgetsAbove, true, true);
871
+ this.renderWidgetContainer(this.widgetContainerBelow, this.extensionWidgetsBelow, false, false);
872
+ this.ui.requestRender();
873
+ }
874
+ renderWidgetContainer(container, widgets, spacerWhenEmpty, leadingSpacer) {
875
+ container.clear();
876
+ if (widgets.size === 0) {
877
+ if (spacerWhenEmpty) {
878
+ container.addChild(new Spacer(1));
879
+ }
880
+ return;
881
+ }
882
+ if (leadingSpacer) {
883
+ container.addChild(new Spacer(1));
884
+ }
885
+ for (const component of widgets.values()) {
886
+ container.addChild(component);
887
+ }
888
+ }
889
+ setExtensionFooter(factory) {
890
+ if (this.customFooter?.dispose) {
891
+ this.customFooter.dispose();
892
+ }
893
+ if (this.customFooter) {
894
+ this.ui.removeChild(this.customFooter);
895
+ }
896
+ else {
897
+ this.ui.removeChild(this.footer);
898
+ }
899
+ if (factory) {
900
+ this.customFooter = factory(this.ui, theme, this.footerDataProvider);
901
+ this.ui.addChild(this.customFooter);
902
+ }
903
+ else {
904
+ this.customFooter = undefined;
905
+ this.ui.addChild(this.footer);
906
+ }
907
+ this.ui.requestRender();
908
+ }
909
+ setExtensionHeader(factory) {
910
+ if (!this.builtInHeader) {
911
+ return;
912
+ }
913
+ if (this.customHeader?.dispose) {
914
+ this.customHeader.dispose();
915
+ }
916
+ const currentHeader = this.customHeader || this.builtInHeader;
917
+ const index = this.headerContainer.children.indexOf(currentHeader);
918
+ if (factory) {
919
+ this.customHeader = factory(this.ui, theme);
920
+ if (index !== -1) {
921
+ this.headerContainer.children[index] = this.customHeader;
922
+ }
923
+ else {
924
+ this.headerContainer.children.unshift(this.customHeader);
925
+ }
926
+ }
927
+ else {
928
+ this.customHeader = undefined;
929
+ if (index !== -1) {
930
+ this.headerContainer.children[index] = this.builtInHeader;
931
+ }
932
+ }
933
+ this.ui.requestRender();
934
+ }
935
+ addExtensionTerminalInputListener(handler) {
936
+ const unsubscribe = this.ui.addInputListener(handler);
937
+ this.extensionTerminalInputUnsubscribers.add(unsubscribe);
938
+ return () => {
939
+ unsubscribe();
940
+ this.extensionTerminalInputUnsubscribers.delete(unsubscribe);
941
+ };
942
+ }
943
+ clearExtensionTerminalInputListeners() {
944
+ for (const unsubscribe of this.extensionTerminalInputUnsubscribers) {
945
+ unsubscribe();
946
+ }
947
+ this.extensionTerminalInputUnsubscribers.clear();
948
+ }
949
+ createExtensionUIContext() {
950
+ return {
951
+ select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
952
+ confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
953
+ input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
954
+ notify: (message, type) => this.showExtensionNotify(message, type),
955
+ onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
956
+ setStatus: (key, text) => this.setExtensionStatus(key, text),
957
+ setWorkingMessage: (message) => {
958
+ if (this.loadingAnimation) {
959
+ if (message) {
960
+ this.loadingAnimation.setMessage(message);
961
+ }
962
+ else {
963
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
964
+ }
965
+ }
966
+ else {
967
+ this.pendingWorkingMessage = message;
968
+ }
969
+ },
970
+ setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
971
+ setFooter: (factory) => this.setExtensionFooter(factory),
972
+ setHeader: (factory) => this.setExtensionHeader(factory),
973
+ setTitle: (title) => this.ui.terminal.setTitle(title),
974
+ custom: (factory, options) => this.showExtensionCustom(factory, options),
975
+ pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
976
+ setEditorText: (text) => this.editor.setText(text),
977
+ getEditorText: () => this.editor.getText(),
978
+ editor: (title, prefill) => this.showExtensionEditor(title, prefill),
979
+ setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
980
+ get theme() {
981
+ return theme;
982
+ },
983
+ getAllThemes: () => getAvailableThemesWithPaths(),
984
+ getTheme: (name) => getThemeByName(name),
985
+ setTheme: (themeOrName) => {
986
+ if (themeOrName instanceof Theme) {
987
+ setThemeInstance(themeOrName);
988
+ this.ui.requestRender();
989
+ return { success: true };
990
+ }
991
+ const result = setTheme(themeOrName, true);
992
+ if (result.success) {
993
+ if (this.settingsManager.getTheme() !== themeOrName) {
994
+ this.settingsManager.setTheme(themeOrName);
995
+ }
996
+ this.ui.requestRender();
997
+ }
998
+ return result;
999
+ },
1000
+ getToolsExpanded: () => this.toolOutputExpanded,
1001
+ setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
1002
+ };
1003
+ }
1004
+ showExtensionSelector(title, options, opts) {
1005
+ return new Promise((resolve) => {
1006
+ if (opts?.signal?.aborted) {
1007
+ resolve(undefined);
1008
+ return;
1009
+ }
1010
+ const onAbort = () => {
1011
+ this.hideExtensionSelector();
1012
+ resolve(undefined);
1013
+ };
1014
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
1015
+ this.extensionSelector = new ExtensionSelectorComponent(title, options, (option) => {
1016
+ opts?.signal?.removeEventListener("abort", onAbort);
1017
+ this.hideExtensionSelector();
1018
+ resolve(option);
1019
+ }, () => {
1020
+ opts?.signal?.removeEventListener("abort", onAbort);
1021
+ this.hideExtensionSelector();
1022
+ resolve(undefined);
1023
+ }, { tui: this.ui, timeout: opts?.timeout });
1024
+ this.editorContainer.clear();
1025
+ this.editorContainer.addChild(this.extensionSelector);
1026
+ this.ui.setFocus(this.extensionSelector);
1027
+ this.ui.requestRender();
1028
+ });
1029
+ }
1030
+ hideExtensionSelector() {
1031
+ this.extensionSelector?.dispose();
1032
+ this.editorContainer.clear();
1033
+ this.editorContainer.addChild(this.editor);
1034
+ this.extensionSelector = undefined;
1035
+ this.ui.setFocus(this.editor);
1036
+ this.ui.requestRender();
1037
+ }
1038
+ async showExtensionConfirm(title, message, opts) {
1039
+ const result = await this.showExtensionSelector(`${title}\n${message}`, ["Yes", "No"], opts);
1040
+ return result === "Yes";
1041
+ }
1042
+ showExtensionInput(title, placeholder, opts) {
1043
+ return new Promise((resolve) => {
1044
+ if (opts?.signal?.aborted) {
1045
+ resolve(undefined);
1046
+ return;
1047
+ }
1048
+ const onAbort = () => {
1049
+ this.hideExtensionInput();
1050
+ resolve(undefined);
1051
+ };
1052
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
1053
+ this.extensionInput = new ExtensionInputComponent(title, placeholder, (value) => {
1054
+ opts?.signal?.removeEventListener("abort", onAbort);
1055
+ this.hideExtensionInput();
1056
+ resolve(value);
1057
+ }, () => {
1058
+ opts?.signal?.removeEventListener("abort", onAbort);
1059
+ this.hideExtensionInput();
1060
+ resolve(undefined);
1061
+ }, { tui: this.ui, timeout: opts?.timeout });
1062
+ this.editorContainer.clear();
1063
+ this.editorContainer.addChild(this.extensionInput);
1064
+ this.ui.setFocus(this.extensionInput);
1065
+ this.ui.requestRender();
1066
+ });
1067
+ }
1068
+ hideExtensionInput() {
1069
+ this.extensionInput?.dispose();
1070
+ this.editorContainer.clear();
1071
+ this.editorContainer.addChild(this.editor);
1072
+ this.extensionInput = undefined;
1073
+ this.ui.setFocus(this.editor);
1074
+ this.ui.requestRender();
1075
+ }
1076
+ showExtensionEditor(title, prefill) {
1077
+ return new Promise((resolve) => {
1078
+ this.extensionEditor = new ExtensionEditorComponent(this.ui, this.keybindings, title, prefill, (value) => {
1079
+ this.hideExtensionEditor();
1080
+ resolve(value);
1081
+ }, () => {
1082
+ this.hideExtensionEditor();
1083
+ resolve(undefined);
1084
+ });
1085
+ this.editorContainer.clear();
1086
+ this.editorContainer.addChild(this.extensionEditor);
1087
+ this.ui.setFocus(this.extensionEditor);
1088
+ this.ui.requestRender();
1089
+ });
1090
+ }
1091
+ hideExtensionEditor() {
1092
+ this.editorContainer.clear();
1093
+ this.editorContainer.addChild(this.editor);
1094
+ this.extensionEditor = undefined;
1095
+ this.ui.setFocus(this.editor);
1096
+ this.ui.requestRender();
1097
+ }
1098
+ setCustomEditorComponent(factory) {
1099
+ const currentText = this.editor.getText();
1100
+ this.editorContainer.clear();
1101
+ if (factory) {
1102
+ const newEditor = factory(this.ui, getEditorTheme(), this.keybindings);
1103
+ newEditor.onSubmit = this.defaultEditor.onSubmit;
1104
+ newEditor.onChange = this.defaultEditor.onChange;
1105
+ newEditor.setText(currentText);
1106
+ if (newEditor.borderColor !== undefined) {
1107
+ newEditor.borderColor = this.defaultEditor.borderColor;
1108
+ }
1109
+ if (newEditor.setPaddingX !== undefined) {
1110
+ newEditor.setPaddingX(this.defaultEditor.getPaddingX());
1111
+ }
1112
+ if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
1113
+ newEditor.setAutocompleteProvider(this.autocompleteProvider);
1114
+ }
1115
+ const customEditor = newEditor;
1116
+ if ("actionHandlers" in customEditor && customEditor.actionHandlers instanceof Map) {
1117
+ if (!customEditor.onEscape) {
1118
+ customEditor.onEscape = () => this.defaultEditor.onEscape?.();
1119
+ }
1120
+ if (!customEditor.onCtrlD) {
1121
+ customEditor.onCtrlD = () => this.defaultEditor.onCtrlD?.();
1122
+ }
1123
+ if (!customEditor.onPasteImage) {
1124
+ customEditor.onPasteImage = () => this.defaultEditor.onPasteImage?.();
1125
+ }
1126
+ if (!customEditor.onExtensionShortcut) {
1127
+ customEditor.onExtensionShortcut = (data) => this.defaultEditor.onExtensionShortcut?.(data);
1128
+ }
1129
+ for (const [action, handler] of this.defaultEditor.actionHandlers) {
1130
+ customEditor.actionHandlers.set(action, handler);
1131
+ }
1132
+ }
1133
+ this.editor = newEditor;
1134
+ }
1135
+ else {
1136
+ this.defaultEditor.setText(currentText);
1137
+ this.editor = this.defaultEditor;
1138
+ }
1139
+ this.editorContainer.addChild(this.editor);
1140
+ this.ui.setFocus(this.editor);
1141
+ this.ui.requestRender();
1142
+ }
1143
+ showExtensionNotify(message, type) {
1144
+ if (type === "error") {
1145
+ this.showError(message);
1146
+ }
1147
+ else if (type === "warning") {
1148
+ this.showWarning(message);
1149
+ }
1150
+ else {
1151
+ this.showStatus(message);
1152
+ }
1153
+ }
1154
+ async showExtensionCustom(factory, options) {
1155
+ const savedText = this.editor.getText();
1156
+ const isOverlay = options?.overlay ?? false;
1157
+ const restoreEditor = () => {
1158
+ this.editorContainer.clear();
1159
+ this.editorContainer.addChild(this.editor);
1160
+ this.editor.setText(savedText);
1161
+ this.ui.setFocus(this.editor);
1162
+ this.ui.requestRender();
1163
+ };
1164
+ return new Promise((resolve, reject) => {
1165
+ let component;
1166
+ let closed = false;
1167
+ const close = (result) => {
1168
+ if (closed)
1169
+ return;
1170
+ closed = true;
1171
+ if (isOverlay)
1172
+ this.ui.hideOverlay();
1173
+ else
1174
+ restoreEditor();
1175
+ resolve(result);
1176
+ try {
1177
+ component?.dispose?.();
1178
+ }
1179
+ catch { }
1180
+ };
1181
+ Promise.resolve(factory(this.ui, theme, this.keybindings, close))
1182
+ .then((c) => {
1183
+ if (closed)
1184
+ return;
1185
+ component = c;
1186
+ if (isOverlay) {
1187
+ const resolveOptions = () => {
1188
+ if (options?.overlayOptions) {
1189
+ const opts = typeof options.overlayOptions === "function"
1190
+ ? options.overlayOptions()
1191
+ : options.overlayOptions;
1192
+ return opts;
1193
+ }
1194
+ const w = component.width;
1195
+ return w ? { width: w } : undefined;
1196
+ };
1197
+ const handle = this.ui.showOverlay(component, resolveOptions());
1198
+ options?.onHandle?.(handle);
1199
+ }
1200
+ else {
1201
+ this.editorContainer.clear();
1202
+ this.editorContainer.addChild(component);
1203
+ this.ui.setFocus(component);
1204
+ this.ui.requestRender();
1205
+ }
1206
+ })
1207
+ .catch((err) => {
1208
+ if (closed)
1209
+ return;
1210
+ if (!isOverlay)
1211
+ restoreEditor();
1212
+ reject(err);
1213
+ });
1214
+ });
1215
+ }
1216
+ showExtensionError(extensionPath, error, stack) {
1217
+ const errorMsg = `Extension "${extensionPath}" error: ${error}`;
1218
+ const errorText = new Text(theme.fg("error", errorMsg), 1, 0);
1219
+ this.chatContainer.addChild(errorText);
1220
+ if (stack) {
1221
+ const stackLines = stack
1222
+ .split("\n")
1223
+ .slice(1)
1224
+ .map((line) => theme.fg("dim", ` ${line.trim()}`))
1225
+ .join("\n");
1226
+ if (stackLines) {
1227
+ this.chatContainer.addChild(new Text(stackLines, 1, 0));
1228
+ }
1229
+ }
1230
+ this.ui.requestRender();
1231
+ }
1232
+ setupKeyHandlers() {
1233
+ this.defaultEditor.onEscape = () => {
1234
+ if (this.loadingAnimation) {
1235
+ this.restoreQueuedMessagesToEditor({ abort: true });
1236
+ }
1237
+ else if (this.session.isBashRunning) {
1238
+ this.session.abortBash();
1239
+ }
1240
+ else if (this.isBashMode) {
1241
+ this.editor.setText("");
1242
+ this.isBashMode = false;
1243
+ this.updateEditorBorderColor();
1244
+ }
1245
+ else if (!this.editor.getText().trim()) {
1246
+ const action = this.settingsManager.getDoubleEscapeAction();
1247
+ if (action !== "none") {
1248
+ const now = Date.now();
1249
+ if (now - this.lastEscapeTime < 500) {
1250
+ if (action === "tree") {
1251
+ this.showTreeSelector();
1252
+ }
1253
+ else {
1254
+ this.showUserMessageSelector();
1255
+ }
1256
+ this.lastEscapeTime = 0;
1257
+ }
1258
+ else {
1259
+ this.lastEscapeTime = now;
1260
+ }
1261
+ }
1262
+ }
1263
+ };
1264
+ this.defaultEditor.onAction("clear", () => this.handleCtrlC());
1265
+ this.defaultEditor.onCtrlD = () => this.handleCtrlD();
1266
+ this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
1267
+ this.defaultEditor.onAction("cycleThinkingLevel", () => this.cycleThinkingLevel());
1268
+ this.ui.onDebug = () => this.handleDebugCommand();
1269
+ this.defaultEditor.onAction("expandTools", () => this.toggleToolOutputExpansion());
1270
+ this.defaultEditor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
1271
+ this.defaultEditor.onAction("externalEditor", () => this.openExternalEditor());
1272
+ this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
1273
+ this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
1274
+ this.defaultEditor.onAction("newSession", () => this.handleClearCommand());
1275
+ this.defaultEditor.onAction("tree", () => this.showTreeSelector());
1276
+ this.defaultEditor.onAction("fork", () => this.showUserMessageSelector());
1277
+ this.defaultEditor.onAction("resume", () => this.showSessionSelector());
1278
+ this.defaultEditor.onChange = (text) => {
1279
+ const wasBashMode = this.isBashMode;
1280
+ this.isBashMode = text.trimStart().startsWith("!");
1281
+ if (wasBashMode !== this.isBashMode) {
1282
+ this.updateEditorBorderColor();
1283
+ }
1284
+ };
1285
+ this.defaultEditor.onPasteImage = () => {
1286
+ this.handleClipboardImagePaste();
1287
+ };
1288
+ }
1289
+ async handleClipboardImagePaste() {
1290
+ try {
1291
+ const image = await readClipboardImage();
1292
+ if (!image) {
1293
+ return;
1294
+ }
1295
+ const tmpDir = os.tmpdir();
1296
+ const ext = extensionForImageMimeType(image.mimeType) ?? "png";
1297
+ const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
1298
+ const filePath = path.join(tmpDir, fileName);
1299
+ fs.writeFileSync(filePath, Buffer.from(image.bytes));
1300
+ this.editor.insertTextAtCursor?.(filePath);
1301
+ this.ui.requestRender();
1302
+ }
1303
+ catch { }
1304
+ }
1305
+ setupEditorSubmitHandler() {
1306
+ this.defaultEditor.onSubmit = async (text) => {
1307
+ text = text.trim();
1308
+ if (!text)
1309
+ return;
1310
+ if (text === "/settings") {
1311
+ this.showSettingsSelector();
1312
+ this.editor.setText("");
1313
+ return;
1314
+ }
1315
+ if (text.startsWith("/export")) {
1316
+ await this.handleExportCommand(text);
1317
+ this.editor.setText("");
1318
+ return;
1319
+ }
1320
+ if (text === "/share") {
1321
+ await this.handleShareCommand();
1322
+ this.editor.setText("");
1323
+ return;
1324
+ }
1325
+ if (text === "/copy") {
1326
+ this.handleCopyCommand();
1327
+ this.editor.setText("");
1328
+ return;
1329
+ }
1330
+ if (text === "/name" || text.startsWith("/name ")) {
1331
+ this.handleNameCommand(text);
1332
+ this.editor.setText("");
1333
+ return;
1334
+ }
1335
+ if (text === "/session") {
1336
+ this.handleSessionCommand();
1337
+ this.editor.setText("");
1338
+ return;
1339
+ }
1340
+ if (text === "/changelog") {
1341
+ this.handleChangelogCommand();
1342
+ this.editor.setText("");
1343
+ return;
1344
+ }
1345
+ if (text === "/hotkeys") {
1346
+ this.handleHotkeysCommand();
1347
+ this.editor.setText("");
1348
+ return;
1349
+ }
1350
+ if (text === "/fork") {
1351
+ this.showUserMessageSelector();
1352
+ this.editor.setText("");
1353
+ return;
1354
+ }
1355
+ if (text === "/tree") {
1356
+ this.showTreeSelector();
1357
+ this.editor.setText("");
1358
+ return;
1359
+ }
1360
+ if (text === "/new") {
1361
+ this.editor.setText("");
1362
+ await this.handleClearCommand();
1363
+ return;
1364
+ }
1365
+ if (text === "/compact" || text.startsWith("/compact ")) {
1366
+ const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
1367
+ this.editor.setText("");
1368
+ await this.handleCompactCommand(customInstructions);
1369
+ return;
1370
+ }
1371
+ if (text === "/reload") {
1372
+ this.editor.setText("");
1373
+ await this.handleReloadCommand();
1374
+ return;
1375
+ }
1376
+ if (text === "/debug") {
1377
+ this.handleDebugCommand();
1378
+ this.editor.setText("");
1379
+ return;
1380
+ }
1381
+ if (text === "/arminsayshi") {
1382
+ this.handleArminSaysHi();
1383
+ this.editor.setText("");
1384
+ return;
1385
+ }
1386
+ if (text === "/resume") {
1387
+ this.showSessionSelector();
1388
+ this.editor.setText("");
1389
+ return;
1390
+ }
1391
+ if (text === "/think" || text.startsWith("/think ")) {
1392
+ await this.handleThinkCommand(text);
1393
+ this.editor.setText("");
1394
+ return;
1395
+ }
1396
+ if (text === "/quit") {
1397
+ this.editor.setText("");
1398
+ await this.shutdown();
1399
+ return;
1400
+ }
1401
+ if (text.startsWith("!")) {
1402
+ const isExcluded = text.startsWith("!!");
1403
+ const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
1404
+ if (command) {
1405
+ if (this.session.isBashRunning) {
1406
+ this.showWarning("A bash command is already running. Press Esc to cancel it first.");
1407
+ this.editor.setText(text);
1408
+ return;
1409
+ }
1410
+ this.editor.addToHistory?.(text);
1411
+ await this.handleBashCommand(command, isExcluded);
1412
+ this.isBashMode = false;
1413
+ this.updateEditorBorderColor();
1414
+ return;
1415
+ }
1416
+ }
1417
+ if (this.session.isCompacting) {
1418
+ if (this.isExtensionCommand(text)) {
1419
+ this.editor.addToHistory?.(text);
1420
+ this.editor.setText("");
1421
+ await this.session.prompt(text);
1422
+ }
1423
+ else {
1424
+ this.queueCompactionMessage(text, "steer");
1425
+ }
1426
+ return;
1427
+ }
1428
+ if (this.session.isStreaming) {
1429
+ this.editor.addToHistory?.(text);
1430
+ this.editor.setText("");
1431
+ await this.session.prompt(text, { streamingBehavior: "steer" });
1432
+ this.updatePendingMessagesDisplay();
1433
+ this.ui.requestRender();
1434
+ return;
1435
+ }
1436
+ this.flushPendingBashComponents();
1437
+ if (this.onInputCallback) {
1438
+ this.onInputCallback(text);
1439
+ }
1440
+ this.editor.addToHistory?.(text);
1441
+ };
1442
+ }
1443
+ subscribeToAgent() {
1444
+ this.unsubscribe = this.session.subscribe(async (event) => {
1445
+ await this.handleEvent(event);
1446
+ });
1447
+ }
1448
+ async handleEvent(event) {
1449
+ if (!this.isInitialized) {
1450
+ await this.init();
1451
+ }
1452
+ this.footer.invalidate();
1453
+ switch (event.type) {
1454
+ case "agent_start":
1455
+ if (this.retryEscapeHandler) {
1456
+ this.defaultEditor.onEscape = this.retryEscapeHandler;
1457
+ this.retryEscapeHandler = undefined;
1458
+ }
1459
+ if (this.retryLoader) {
1460
+ this.retryLoader.stop();
1461
+ this.retryLoader = undefined;
1462
+ }
1463
+ if (this.loadingAnimation) {
1464
+ this.loadingAnimation.stop();
1465
+ }
1466
+ this.statusContainer.clear();
1467
+ this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
1468
+ this.statusContainer.addChild(this.loadingAnimation);
1469
+ if (this.pendingWorkingMessage !== undefined) {
1470
+ if (this.pendingWorkingMessage) {
1471
+ this.loadingAnimation.setMessage(this.pendingWorkingMessage);
1472
+ }
1473
+ this.pendingWorkingMessage = undefined;
1474
+ }
1475
+ this.ui.requestRender();
1476
+ break;
1477
+ case "message_start":
1478
+ if (event.message.role === "custom") {
1479
+ this.addMessageToChat(event.message);
1480
+ this.ui.requestRender();
1481
+ }
1482
+ else if (event.message.role === "user") {
1483
+ this.addMessageToChat(event.message);
1484
+ this.updatePendingMessagesDisplay();
1485
+ this.ui.requestRender();
1486
+ }
1487
+ else if (event.message.role === "assistant") {
1488
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
1489
+ this.streamingMessage = event.message;
1490
+ this.chatContainer.addChild(this.streamingComponent);
1491
+ this.streamingComponent.updateContent(this.streamingMessage);
1492
+ this.ui.requestRender();
1493
+ }
1494
+ break;
1495
+ case "message_update":
1496
+ if (this.streamingComponent && event.message.role === "assistant") {
1497
+ this.streamingMessage = event.message;
1498
+ this.streamingComponent.updateContent(this.streamingMessage);
1499
+ for (const content of this.streamingMessage.content) {
1500
+ if (content.type === "toolCall") {
1501
+ if (!this.pendingTools.has(content.id)) {
1502
+ const component = new ToolExecutionComponent(content.name, content.arguments, {
1503
+ showImages: this.settingsManager.getShowImages(),
1504
+ }, this.getRegisteredToolDefinition(content.name), this.ui);
1505
+ component.setExpanded(this.toolOutputExpanded);
1506
+ this.chatContainer.addChild(component);
1507
+ this.pendingTools.set(content.id, component);
1508
+ }
1509
+ else {
1510
+ const component = this.pendingTools.get(content.id);
1511
+ if (component) {
1512
+ component.updateArgs(content.arguments);
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+ this.ui.requestRender();
1518
+ }
1519
+ break;
1520
+ case "message_end":
1521
+ if (event.message.role === "user")
1522
+ break;
1523
+ if (this.streamingComponent && event.message.role === "assistant") {
1524
+ this.streamingMessage = event.message;
1525
+ let errorMessage;
1526
+ if (this.streamingMessage.stopReason === "aborted") {
1527
+ const retryAttempt = this.session.retryAttempt;
1528
+ errorMessage =
1529
+ retryAttempt > 0
1530
+ ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
1531
+ : "Operation aborted";
1532
+ this.streamingMessage.errorMessage = errorMessage;
1533
+ }
1534
+ this.streamingComponent.updateContent(this.streamingMessage);
1535
+ if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
1536
+ if (!errorMessage) {
1537
+ errorMessage = this.streamingMessage.errorMessage || "Error";
1538
+ }
1539
+ for (const [, component] of this.pendingTools.entries()) {
1540
+ component.updateResult({
1541
+ content: [{ type: "text", text: errorMessage }],
1542
+ isError: true,
1543
+ });
1544
+ }
1545
+ this.pendingTools.clear();
1546
+ }
1547
+ else {
1548
+ for (const [, component] of this.pendingTools.entries()) {
1549
+ component.setArgsComplete();
1550
+ }
1551
+ }
1552
+ this.streamingComponent = undefined;
1553
+ this.streamingMessage = undefined;
1554
+ this.footer.invalidate();
1555
+ }
1556
+ this.ui.requestRender();
1557
+ break;
1558
+ case "tool_execution_start": {
1559
+ if (!this.pendingTools.has(event.toolCallId)) {
1560
+ const component = new ToolExecutionComponent(event.toolName, event.args, {
1561
+ showImages: this.settingsManager.getShowImages(),
1562
+ }, this.getRegisteredToolDefinition(event.toolName), this.ui);
1563
+ component.setExpanded(this.toolOutputExpanded);
1564
+ this.chatContainer.addChild(component);
1565
+ this.pendingTools.set(event.toolCallId, component);
1566
+ this.ui.requestRender();
1567
+ }
1568
+ break;
1569
+ }
1570
+ case "tool_execution_update": {
1571
+ const component = this.pendingTools.get(event.toolCallId);
1572
+ if (component) {
1573
+ component.updateResult({ ...event.partialResult, isError: false }, true);
1574
+ this.ui.requestRender();
1575
+ }
1576
+ break;
1577
+ }
1578
+ case "tool_execution_end": {
1579
+ const component = this.pendingTools.get(event.toolCallId);
1580
+ if (component) {
1581
+ component.updateResult({ ...event.result, isError: event.isError });
1582
+ this.pendingTools.delete(event.toolCallId);
1583
+ this.ui.requestRender();
1584
+ }
1585
+ break;
1586
+ }
1587
+ case "agent_end":
1588
+ if (this.loadingAnimation) {
1589
+ this.loadingAnimation.stop();
1590
+ this.loadingAnimation = undefined;
1591
+ this.statusContainer.clear();
1592
+ }
1593
+ if (this.streamingComponent) {
1594
+ this.chatContainer.removeChild(this.streamingComponent);
1595
+ this.streamingComponent = undefined;
1596
+ this.streamingMessage = undefined;
1597
+ }
1598
+ this.pendingTools.clear();
1599
+ await this.checkShutdownRequested();
1600
+ this.ui.requestRender();
1601
+ break;
1602
+ case "auto_compaction_start": {
1603
+ this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
1604
+ this.defaultEditor.onEscape = () => {
1605
+ this.session.abortCompaction();
1606
+ };
1607
+ this.statusContainer.clear();
1608
+ const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
1609
+ this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1610
+ this.statusContainer.addChild(this.autoCompactionLoader);
1611
+ this.ui.requestRender();
1612
+ break;
1613
+ }
1614
+ case "auto_compaction_end": {
1615
+ if (this.autoCompactionEscapeHandler) {
1616
+ this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
1617
+ this.autoCompactionEscapeHandler = undefined;
1618
+ }
1619
+ if (this.autoCompactionLoader) {
1620
+ this.autoCompactionLoader.stop();
1621
+ this.autoCompactionLoader = undefined;
1622
+ this.statusContainer.clear();
1623
+ }
1624
+ if (event.aborted) {
1625
+ this.showStatus("Auto-compaction cancelled");
1626
+ }
1627
+ else if (event.result) {
1628
+ this.chatContainer.clear();
1629
+ this.rebuildChatFromMessages();
1630
+ this.addMessageToChat({
1631
+ role: "compactionSummary",
1632
+ tokensBefore: event.result.tokensBefore,
1633
+ summary: event.result.summary,
1634
+ timestamp: Date.now(),
1635
+ });
1636
+ this.footer.invalidate();
1637
+ }
1638
+ else if (event.errorMessage) {
1639
+ this.chatContainer.addChild(new Spacer(1));
1640
+ this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
1641
+ }
1642
+ void this.flushCompactionQueue({ willRetry: event.willRetry });
1643
+ this.ui.requestRender();
1644
+ break;
1645
+ }
1646
+ case "auto_retry_start": {
1647
+ this.retryEscapeHandler = this.defaultEditor.onEscape;
1648
+ this.defaultEditor.onEscape = () => {
1649
+ this.session.abortRetry();
1650
+ };
1651
+ this.statusContainer.clear();
1652
+ const delaySeconds = Math.round(event.delayMs / 1000);
1653
+ this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1654
+ this.statusContainer.addChild(this.retryLoader);
1655
+ this.ui.requestRender();
1656
+ break;
1657
+ }
1658
+ case "auto_retry_end": {
1659
+ if (this.retryEscapeHandler) {
1660
+ this.defaultEditor.onEscape = this.retryEscapeHandler;
1661
+ this.retryEscapeHandler = undefined;
1662
+ }
1663
+ if (this.retryLoader) {
1664
+ this.retryLoader.stop();
1665
+ this.retryLoader = undefined;
1666
+ this.statusContainer.clear();
1667
+ }
1668
+ if (!event.success) {
1669
+ this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
1670
+ }
1671
+ this.ui.requestRender();
1672
+ break;
1673
+ }
1674
+ }
1675
+ }
1676
+ getUserMessageText(message) {
1677
+ if (message.role !== "user")
1678
+ return "";
1679
+ const textBlocks = typeof message.content === "string"
1680
+ ? [{ type: "text", text: message.content }]
1681
+ : message.content.filter((c) => c.type === "text");
1682
+ return textBlocks.map((c) => c.text).join("");
1683
+ }
1684
+ showStatus(message) {
1685
+ const children = this.chatContainer.children;
1686
+ const last = children.length > 0 ? children[children.length - 1] : undefined;
1687
+ const secondLast = children.length > 1 ? children[children.length - 2] : undefined;
1688
+ if (last && secondLast && last === this.lastStatusText && secondLast === this.lastStatusSpacer) {
1689
+ this.lastStatusText.setText(theme.fg("dim", message));
1690
+ this.ui.requestRender();
1691
+ return;
1692
+ }
1693
+ const spacer = new Spacer(1);
1694
+ const text = new Text(theme.fg("dim", message), 1, 0);
1695
+ this.chatContainer.addChild(spacer);
1696
+ this.chatContainer.addChild(text);
1697
+ this.lastStatusSpacer = spacer;
1698
+ this.lastStatusText = text;
1699
+ this.ui.requestRender();
1700
+ }
1701
+ addMessageToChat(message, options) {
1702
+ switch (message.role) {
1703
+ case "bashExecution": {
1704
+ const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
1705
+ if (message.output) {
1706
+ component.appendOutput(message.output);
1707
+ }
1708
+ component.setComplete(message.exitCode, message.cancelled, message.truncated ? { truncated: true } : undefined, message.fullOutputPath);
1709
+ this.chatContainer.addChild(component);
1710
+ break;
1711
+ }
1712
+ case "custom": {
1713
+ if (message.display) {
1714
+ const renderer = this.session.extensionRunner?.getMessageRenderer(message.customType);
1715
+ const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
1716
+ component.setExpanded(this.toolOutputExpanded);
1717
+ this.chatContainer.addChild(component);
1718
+ }
1719
+ break;
1720
+ }
1721
+ case "compactionSummary": {
1722
+ this.chatContainer.addChild(new Spacer(1));
1723
+ const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
1724
+ component.setExpanded(this.toolOutputExpanded);
1725
+ this.chatContainer.addChild(component);
1726
+ break;
1727
+ }
1728
+ case "branchSummary": {
1729
+ this.chatContainer.addChild(new Spacer(1));
1730
+ const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
1731
+ component.setExpanded(this.toolOutputExpanded);
1732
+ this.chatContainer.addChild(component);
1733
+ break;
1734
+ }
1735
+ case "user": {
1736
+ const textContent = this.getUserMessageText(message);
1737
+ if (textContent) {
1738
+ const skillBlock = parseSkillBlock(textContent);
1739
+ if (skillBlock) {
1740
+ this.chatContainer.addChild(new Spacer(1));
1741
+ const component = new SkillInvocationMessageComponent(skillBlock, this.getMarkdownThemeWithSettings());
1742
+ component.setExpanded(this.toolOutputExpanded);
1743
+ this.chatContainer.addChild(component);
1744
+ if (skillBlock.userMessage) {
1745
+ const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
1746
+ this.chatContainer.addChild(userComponent);
1747
+ }
1748
+ }
1749
+ else {
1750
+ const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
1751
+ this.chatContainer.addChild(userComponent);
1752
+ }
1753
+ if (options?.populateHistory) {
1754
+ this.editor.addToHistory?.(textContent);
1755
+ }
1756
+ }
1757
+ break;
1758
+ }
1759
+ case "assistant": {
1760
+ const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
1761
+ this.chatContainer.addChild(assistantComponent);
1762
+ break;
1763
+ }
1764
+ case "toolResult": {
1765
+ break;
1766
+ }
1767
+ default: {
1768
+ const _exhaustive = message;
1769
+ }
1770
+ }
1771
+ }
1772
+ renderSessionContext(sessionContext, options = {}) {
1773
+ this.pendingTools.clear();
1774
+ if (options.updateFooter) {
1775
+ this.footer.invalidate();
1776
+ this.updateEditorBorderColor();
1777
+ }
1778
+ for (const message of sessionContext.messages) {
1779
+ if (message.role === "assistant") {
1780
+ this.addMessageToChat(message);
1781
+ for (const content of message.content) {
1782
+ if (content.type === "toolCall") {
1783
+ const component = new ToolExecutionComponent(content.name, content.arguments, { showImages: this.settingsManager.getShowImages() }, this.getRegisteredToolDefinition(content.name), this.ui);
1784
+ component.setExpanded(this.toolOutputExpanded);
1785
+ this.chatContainer.addChild(component);
1786
+ if (message.stopReason === "aborted" || message.stopReason === "error") {
1787
+ let errorMessage;
1788
+ if (message.stopReason === "aborted") {
1789
+ const retryAttempt = this.session.retryAttempt;
1790
+ errorMessage =
1791
+ retryAttempt > 0
1792
+ ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
1793
+ : "Operation aborted";
1794
+ }
1795
+ else {
1796
+ errorMessage = message.errorMessage || "Error";
1797
+ }
1798
+ component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
1799
+ }
1800
+ else {
1801
+ this.pendingTools.set(content.id, component);
1802
+ }
1803
+ }
1804
+ }
1805
+ }
1806
+ else if (message.role === "toolResult") {
1807
+ const component = this.pendingTools.get(message.toolCallId);
1808
+ if (component) {
1809
+ component.updateResult(message);
1810
+ this.pendingTools.delete(message.toolCallId);
1811
+ }
1812
+ }
1813
+ else {
1814
+ this.addMessageToChat(message, options);
1815
+ }
1816
+ }
1817
+ this.pendingTools.clear();
1818
+ this.ui.requestRender();
1819
+ }
1820
+ renderInitialMessages() {
1821
+ const context = this.sessionManager.buildSessionContext();
1822
+ this.renderSessionContext(context, {
1823
+ updateFooter: true,
1824
+ populateHistory: true,
1825
+ });
1826
+ const allEntries = this.sessionManager.getEntries();
1827
+ const compactionCount = allEntries.filter((e) => e.type === "compaction").length;
1828
+ if (compactionCount > 0) {
1829
+ const times = compactionCount === 1 ? "1 time" : `${compactionCount} times`;
1830
+ this.showStatus(`Session compacted ${times}`);
1831
+ }
1832
+ }
1833
+ async getUserInput() {
1834
+ return new Promise((resolve) => {
1835
+ this.onInputCallback = (text) => {
1836
+ this.onInputCallback = undefined;
1837
+ resolve(text);
1838
+ };
1839
+ });
1840
+ }
1841
+ rebuildChatFromMessages() {
1842
+ this.chatContainer.clear();
1843
+ const context = this.sessionManager.buildSessionContext();
1844
+ this.renderSessionContext(context);
1845
+ }
1846
+ handleCtrlC() {
1847
+ const now = Date.now();
1848
+ if (now - this.lastSigintTime < 500) {
1849
+ void this.shutdown();
1850
+ }
1851
+ else {
1852
+ this.clearEditor();
1853
+ this.lastSigintTime = now;
1854
+ }
1855
+ }
1856
+ handleCtrlD() {
1857
+ void this.shutdown();
1858
+ }
1859
+ isShuttingDown = false;
1860
+ async shutdown() {
1861
+ if (this.isShuttingDown)
1862
+ return;
1863
+ this.isShuttingDown = true;
1864
+ const extensionRunner = this.session.extensionRunner;
1865
+ if (extensionRunner?.hasHandlers("session_shutdown")) {
1866
+ await extensionRunner.emit({
1867
+ type: "session_shutdown",
1868
+ });
1869
+ }
1870
+ await new Promise((resolve) => process.nextTick(resolve));
1871
+ await this.ui.terminal.drainInput(1000);
1872
+ this.stop();
1873
+ process.exit(0);
1874
+ }
1875
+ async checkShutdownRequested() {
1876
+ if (!this.shutdownRequested)
1877
+ return;
1878
+ await this.shutdown();
1879
+ }
1880
+ handleCtrlZ() {
1881
+ const ignoreSigint = () => { };
1882
+ process.on("SIGINT", ignoreSigint);
1883
+ process.once("SIGCONT", () => {
1884
+ process.removeListener("SIGINT", ignoreSigint);
1885
+ this.ui.start();
1886
+ this.ui.requestRender(true);
1887
+ });
1888
+ this.ui.stop();
1889
+ process.kill(0, "SIGTSTP");
1890
+ }
1891
+ async handleFollowUp() {
1892
+ const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
1893
+ if (!text)
1894
+ return;
1895
+ if (this.session.isCompacting) {
1896
+ if (this.isExtensionCommand(text)) {
1897
+ this.editor.addToHistory?.(text);
1898
+ this.editor.setText("");
1899
+ await this.session.prompt(text);
1900
+ }
1901
+ else {
1902
+ this.queueCompactionMessage(text, "followUp");
1903
+ }
1904
+ return;
1905
+ }
1906
+ if (this.session.isStreaming) {
1907
+ this.editor.addToHistory?.(text);
1908
+ this.editor.setText("");
1909
+ await this.session.prompt(text, { streamingBehavior: "followUp" });
1910
+ this.updatePendingMessagesDisplay();
1911
+ this.ui.requestRender();
1912
+ }
1913
+ else if (this.editor.onSubmit) {
1914
+ this.editor.onSubmit(text);
1915
+ }
1916
+ }
1917
+ handleDequeue() {
1918
+ const restored = this.restoreQueuedMessagesToEditor();
1919
+ if (restored === 0) {
1920
+ this.showStatus("No queued messages to restore");
1921
+ }
1922
+ else {
1923
+ this.showStatus(`Restored ${restored} queued message${restored > 1 ? "s" : ""} to editor`);
1924
+ }
1925
+ }
1926
+ updateEditorBorderColor() {
1927
+ if (this.isBashMode) {
1928
+ this.editor.borderColor = theme.getBashModeBorderColor();
1929
+ }
1930
+ else {
1931
+ const level = this.session.thinkingLevel || "off";
1932
+ this.editor.borderColor = theme.getThinkingBorderColor(level);
1933
+ }
1934
+ this.ui.requestRender();
1935
+ }
1936
+ cycleThinkingLevel() {
1937
+ const newLevel = this.session.cycleThinkingLevel();
1938
+ if (newLevel === undefined) {
1939
+ this.showStatus("Current model does not support thinking");
1940
+ }
1941
+ else {
1942
+ this.footer.invalidate();
1943
+ this.updateEditorBorderColor();
1944
+ this.showStatus(`Thinking level: ${newLevel}`);
1945
+ }
1946
+ }
1947
+ toggleToolOutputExpansion() {
1948
+ this.setToolsExpanded(!this.toolOutputExpanded);
1949
+ }
1950
+ setToolsExpanded(expanded) {
1951
+ this.toolOutputExpanded = expanded;
1952
+ for (const child of this.chatContainer.children) {
1953
+ if (isExpandable(child)) {
1954
+ child.setExpanded(expanded);
1955
+ }
1956
+ }
1957
+ this.ui.requestRender();
1958
+ }
1959
+ toggleThinkingBlockVisibility() {
1960
+ this.hideThinkingBlock = !this.hideThinkingBlock;
1961
+ this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
1962
+ this.chatContainer.clear();
1963
+ this.rebuildChatFromMessages();
1964
+ if (this.streamingComponent && this.streamingMessage) {
1965
+ this.streamingComponent.setHideThinkingBlock(this.hideThinkingBlock);
1966
+ this.streamingComponent.updateContent(this.streamingMessage);
1967
+ this.chatContainer.addChild(this.streamingComponent);
1968
+ }
1969
+ this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
1970
+ }
1971
+ openExternalEditor() {
1972
+ const editorCmd = process.env.VISUAL || process.env.EDITOR;
1973
+ if (!editorCmd) {
1974
+ this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
1975
+ return;
1976
+ }
1977
+ const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
1978
+ const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
1979
+ try {
1980
+ fs.writeFileSync(tmpFile, currentText, "utf-8");
1981
+ this.ui.stop();
1982
+ const [editor, ...editorArgs] = editorCmd.split(" ");
1983
+ const result = spawnSync(editor, [...editorArgs, tmpFile], {
1984
+ stdio: "inherit",
1985
+ shell: process.platform === "win32",
1986
+ });
1987
+ if (result.status === 0) {
1988
+ const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
1989
+ this.editor.setText(newContent);
1990
+ }
1991
+ }
1992
+ finally {
1993
+ try {
1994
+ fs.unlinkSync(tmpFile);
1995
+ }
1996
+ catch { }
1997
+ this.ui.start();
1998
+ this.ui.requestRender(true);
1999
+ }
2000
+ }
2001
+ clearEditor() {
2002
+ this.editor.setText("");
2003
+ this.ui.requestRender();
2004
+ }
2005
+ showError(errorMessage) {
2006
+ this.chatContainer.addChild(new Spacer(1));
2007
+ this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
2008
+ this.ui.requestRender();
2009
+ }
2010
+ showWarning(warningMessage) {
2011
+ this.chatContainer.addChild(new Spacer(1));
2012
+ this.chatContainer.addChild(new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
2013
+ this.ui.requestRender();
2014
+ }
2015
+ showNewVersionNotification(newVersion) {
2016
+ const action = theme.fg("accent", getUpdateInstruction("@mariozechner/pi-coding-agent"));
2017
+ const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
2018
+ const changelogUrl = theme.fg("accent", "https://github.com/mariozechner/openvibe/blob/main/CHANGELOG.md");
2019
+ const changelogLine = theme.fg("muted", "Changelog: ") + changelogUrl;
2020
+ this.chatContainer.addChild(new Spacer(1));
2021
+ this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
2022
+ this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
2023
+ this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
2024
+ this.ui.requestRender();
2025
+ }
2026
+ getAllQueuedMessages() {
2027
+ return {
2028
+ steering: [
2029
+ ...this.session.getSteeringMessages(),
2030
+ ...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
2031
+ ],
2032
+ followUp: [
2033
+ ...this.session.getFollowUpMessages(),
2034
+ ...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
2035
+ ],
2036
+ };
2037
+ }
2038
+ clearAllQueues() {
2039
+ const { steering, followUp } = this.session.clearQueue();
2040
+ const compactionSteering = this.compactionQueuedMessages
2041
+ .filter((msg) => msg.mode === "steer")
2042
+ .map((msg) => msg.text);
2043
+ const compactionFollowUp = this.compactionQueuedMessages
2044
+ .filter((msg) => msg.mode === "followUp")
2045
+ .map((msg) => msg.text);
2046
+ this.compactionQueuedMessages = [];
2047
+ return {
2048
+ steering: [...steering, ...compactionSteering],
2049
+ followUp: [...followUp, ...compactionFollowUp],
2050
+ };
2051
+ }
2052
+ updatePendingMessagesDisplay() {
2053
+ this.pendingMessagesContainer.clear();
2054
+ const { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();
2055
+ if (steeringMessages.length > 0 || followUpMessages.length > 0) {
2056
+ this.pendingMessagesContainer.addChild(new Spacer(1));
2057
+ for (const message of steeringMessages) {
2058
+ const text = theme.fg("dim", `Steering: ${message}`);
2059
+ this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
2060
+ }
2061
+ for (const message of followUpMessages) {
2062
+ const text = theme.fg("dim", `Follow-up: ${message}`);
2063
+ this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
2064
+ }
2065
+ const dequeueHint = this.getAppKeyDisplay("dequeue");
2066
+ const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
2067
+ this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
2068
+ }
2069
+ }
2070
+ restoreQueuedMessagesToEditor(options) {
2071
+ const { steering, followUp } = this.clearAllQueues();
2072
+ const allQueued = [...steering, ...followUp];
2073
+ if (allQueued.length === 0) {
2074
+ this.updatePendingMessagesDisplay();
2075
+ if (options?.abort) {
2076
+ this.agent.abort();
2077
+ }
2078
+ return 0;
2079
+ }
2080
+ const queuedText = allQueued.join("\n\n");
2081
+ const currentText = options?.currentText ?? this.editor.getText();
2082
+ const combinedText = [queuedText, currentText].filter((t) => t.trim()).join("\n\n");
2083
+ this.editor.setText(combinedText);
2084
+ this.updatePendingMessagesDisplay();
2085
+ if (options?.abort) {
2086
+ this.agent.abort();
2087
+ }
2088
+ return allQueued.length;
2089
+ }
2090
+ queueCompactionMessage(text, mode) {
2091
+ this.compactionQueuedMessages.push({ text, mode });
2092
+ this.editor.addToHistory?.(text);
2093
+ this.editor.setText("");
2094
+ this.updatePendingMessagesDisplay();
2095
+ this.showStatus("Queued message for after compaction");
2096
+ }
2097
+ isExtensionCommand(text) {
2098
+ if (!text.startsWith("/"))
2099
+ return false;
2100
+ const extensionRunner = this.session.extensionRunner;
2101
+ if (!extensionRunner)
2102
+ return false;
2103
+ const spaceIndex = text.indexOf(" ");
2104
+ const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
2105
+ return !!extensionRunner.getCommand(commandName);
2106
+ }
2107
+ async flushCompactionQueue(options) {
2108
+ if (this.compactionQueuedMessages.length === 0) {
2109
+ return;
2110
+ }
2111
+ const queuedMessages = [...this.compactionQueuedMessages];
2112
+ this.compactionQueuedMessages = [];
2113
+ this.updatePendingMessagesDisplay();
2114
+ const restoreQueue = (error) => {
2115
+ this.session.clearQueue();
2116
+ this.compactionQueuedMessages = queuedMessages;
2117
+ this.updatePendingMessagesDisplay();
2118
+ this.showError(`Failed to send queued message${queuedMessages.length > 1 ? "s" : ""}: ${error instanceof Error ? error.message : String(error)}`);
2119
+ };
2120
+ try {
2121
+ if (options?.willRetry) {
2122
+ for (const message of queuedMessages) {
2123
+ if (this.isExtensionCommand(message.text)) {
2124
+ await this.session.prompt(message.text);
2125
+ }
2126
+ else if (message.mode === "followUp") {
2127
+ await this.session.followUp(message.text);
2128
+ }
2129
+ else {
2130
+ await this.session.steer(message.text);
2131
+ }
2132
+ }
2133
+ this.updatePendingMessagesDisplay();
2134
+ return;
2135
+ }
2136
+ const firstPromptIndex = queuedMessages.findIndex((message) => !this.isExtensionCommand(message.text));
2137
+ if (firstPromptIndex === -1) {
2138
+ for (const message of queuedMessages) {
2139
+ await this.session.prompt(message.text);
2140
+ }
2141
+ return;
2142
+ }
2143
+ const preCommands = queuedMessages.slice(0, firstPromptIndex);
2144
+ const firstPrompt = queuedMessages[firstPromptIndex];
2145
+ const rest = queuedMessages.slice(firstPromptIndex + 1);
2146
+ for (const message of preCommands) {
2147
+ await this.session.prompt(message.text);
2148
+ }
2149
+ const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
2150
+ restoreQueue(error);
2151
+ });
2152
+ for (const message of rest) {
2153
+ if (this.isExtensionCommand(message.text)) {
2154
+ await this.session.prompt(message.text);
2155
+ }
2156
+ else if (message.mode === "followUp") {
2157
+ await this.session.followUp(message.text);
2158
+ }
2159
+ else {
2160
+ await this.session.steer(message.text);
2161
+ }
2162
+ }
2163
+ this.updatePendingMessagesDisplay();
2164
+ void promptPromise;
2165
+ }
2166
+ catch (error) {
2167
+ restoreQueue(error);
2168
+ }
2169
+ }
2170
+ flushPendingBashComponents() {
2171
+ for (const component of this.pendingBashComponents) {
2172
+ this.pendingMessagesContainer.removeChild(component);
2173
+ this.chatContainer.addChild(component);
2174
+ }
2175
+ this.pendingBashComponents = [];
2176
+ }
2177
+ showSelector(create) {
2178
+ const done = () => {
2179
+ this.editorContainer.clear();
2180
+ this.editorContainer.addChild(this.editor);
2181
+ this.ui.setFocus(this.editor);
2182
+ };
2183
+ const { component, focus } = create(done);
2184
+ this.editorContainer.clear();
2185
+ this.editorContainer.addChild(component);
2186
+ this.ui.setFocus(focus);
2187
+ this.ui.requestRender();
2188
+ }
2189
+ showSettingsSelector() {
2190
+ this.showSelector((done) => {
2191
+ const selector = new SettingsSelectorComponent({
2192
+ autoCompact: this.session.autoCompactionEnabled,
2193
+ showImages: this.settingsManager.getShowImages(),
2194
+ autoResizeImages: this.settingsManager.getImageAutoResize(),
2195
+ blockImages: this.settingsManager.getBlockImages(),
2196
+ enableSkillCommands: this.settingsManager.getEnableSkillCommands(),
2197
+ steeringMode: this.session.steeringMode,
2198
+ followUpMode: this.session.followUpMode,
2199
+ transport: this.settingsManager.getTransport(),
2200
+ thinkingLevel: this.session.thinkingLevel,
2201
+ availableThinkingLevels: this.session.getAvailableThinkingLevels(),
2202
+ currentTheme: this.settingsManager.getTheme() || "dark",
2203
+ availableThemes: getAvailableThemes(),
2204
+ hideThinkingBlock: this.hideThinkingBlock,
2205
+ collapseChangelog: this.settingsManager.getCollapseChangelog(),
2206
+ doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
2207
+ treeFilterMode: this.settingsManager.getTreeFilterMode(),
2208
+ showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
2209
+ editorPaddingX: this.settingsManager.getEditorPaddingX(),
2210
+ autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
2211
+ quietStartup: this.settingsManager.getQuietStartup(),
2212
+ clearOnShrink: this.settingsManager.getClearOnShrink(),
2213
+ }, {
2214
+ onAutoCompactChange: (enabled) => {
2215
+ this.session.setAutoCompactionEnabled(enabled);
2216
+ this.footer.setAutoCompactEnabled(enabled);
2217
+ },
2218
+ onShowImagesChange: (enabled) => {
2219
+ this.settingsManager.setShowImages(enabled);
2220
+ for (const child of this.chatContainer.children) {
2221
+ if (child instanceof ToolExecutionComponent) {
2222
+ child.setShowImages(enabled);
2223
+ }
2224
+ }
2225
+ },
2226
+ onAutoResizeImagesChange: (enabled) => {
2227
+ this.settingsManager.setImageAutoResize(enabled);
2228
+ },
2229
+ onBlockImagesChange: (blocked) => {
2230
+ this.settingsManager.setBlockImages(blocked);
2231
+ },
2232
+ onEnableSkillCommandsChange: (enabled) => {
2233
+ this.settingsManager.setEnableSkillCommands(enabled);
2234
+ this.setupAutocomplete(this.fdPath);
2235
+ },
2236
+ onSteeringModeChange: (mode) => {
2237
+ this.session.setSteeringMode(mode);
2238
+ },
2239
+ onFollowUpModeChange: (mode) => {
2240
+ this.session.setFollowUpMode(mode);
2241
+ },
2242
+ onTransportChange: (transport) => {
2243
+ this.settingsManager.setTransport(transport);
2244
+ this.session.agent.setTransport(transport);
2245
+ },
2246
+ onThinkingLevelChange: (level) => {
2247
+ this.session.setThinkingLevel(level);
2248
+ this.footer.invalidate();
2249
+ this.updateEditorBorderColor();
2250
+ },
2251
+ onThemeChange: (themeName) => {
2252
+ const result = setTheme(themeName, true);
2253
+ this.settingsManager.setTheme(themeName);
2254
+ this.ui.invalidate();
2255
+ if (!result.success) {
2256
+ this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
2257
+ }
2258
+ },
2259
+ onThemePreview: (themeName) => {
2260
+ const result = setTheme(themeName, true);
2261
+ if (result.success) {
2262
+ this.ui.invalidate();
2263
+ this.ui.requestRender();
2264
+ }
2265
+ },
2266
+ onHideThinkingBlockChange: (hidden) => {
2267
+ this.hideThinkingBlock = hidden;
2268
+ this.settingsManager.setHideThinkingBlock(hidden);
2269
+ for (const child of this.chatContainer.children) {
2270
+ if (child instanceof AssistantMessageComponent) {
2271
+ child.setHideThinkingBlock(hidden);
2272
+ }
2273
+ }
2274
+ this.chatContainer.clear();
2275
+ this.rebuildChatFromMessages();
2276
+ },
2277
+ onCollapseChangelogChange: (collapsed) => {
2278
+ this.settingsManager.setCollapseChangelog(collapsed);
2279
+ },
2280
+ onQuietStartupChange: (enabled) => {
2281
+ this.settingsManager.setQuietStartup(enabled);
2282
+ },
2283
+ onDoubleEscapeActionChange: (action) => {
2284
+ this.settingsManager.setDoubleEscapeAction(action);
2285
+ },
2286
+ onTreeFilterModeChange: (mode) => {
2287
+ this.settingsManager.setTreeFilterMode(mode);
2288
+ },
2289
+ onShowHardwareCursorChange: (enabled) => {
2290
+ this.settingsManager.setShowHardwareCursor(enabled);
2291
+ this.ui.setShowHardwareCursor(enabled);
2292
+ },
2293
+ onEditorPaddingXChange: (padding) => {
2294
+ this.settingsManager.setEditorPaddingX(padding);
2295
+ this.defaultEditor.setPaddingX(padding);
2296
+ if (this.editor !== this.defaultEditor && this.editor.setPaddingX !== undefined) {
2297
+ this.editor.setPaddingX(padding);
2298
+ }
2299
+ },
2300
+ onAutocompleteMaxVisibleChange: (maxVisible) => {
2301
+ this.settingsManager.setAutocompleteMaxVisible(maxVisible);
2302
+ this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
2303
+ if (this.editor !== this.defaultEditor && this.editor.setAutocompleteMaxVisible !== undefined) {
2304
+ this.editor.setAutocompleteMaxVisible(maxVisible);
2305
+ }
2306
+ },
2307
+ onClearOnShrinkChange: (enabled) => {
2308
+ this.settingsManager.setClearOnShrink(enabled);
2309
+ this.ui.setClearOnShrink(enabled);
2310
+ },
2311
+ onCancel: () => {
2312
+ done();
2313
+ this.ui.requestRender();
2314
+ },
2315
+ });
2316
+ return { component: selector, focus: selector.getSettingsList() };
2317
+ });
2318
+ }
2319
+ showUserMessageSelector() {
2320
+ const userMessages = this.session.getUserMessagesForForking();
2321
+ if (userMessages.length === 0) {
2322
+ this.showStatus("No messages to fork from");
2323
+ return;
2324
+ }
2325
+ this.showSelector((done) => {
2326
+ const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
2327
+ const result = await this.session.fork(entryId);
2328
+ if (result.cancelled) {
2329
+ done();
2330
+ this.ui.requestRender();
2331
+ return;
2332
+ }
2333
+ this.chatContainer.clear();
2334
+ this.renderInitialMessages();
2335
+ this.editor.setText(result.selectedText);
2336
+ done();
2337
+ this.showStatus("Branched to new session");
2338
+ }, () => {
2339
+ done();
2340
+ this.ui.requestRender();
2341
+ });
2342
+ return { component: selector, focus: selector.getMessageList() };
2343
+ });
2344
+ }
2345
+ showTreeSelector(initialSelectedId) {
2346
+ const tree = this.sessionManager.getTree();
2347
+ const realLeafId = this.sessionManager.getLeafId();
2348
+ const initialFilterMode = this.settingsManager.getTreeFilterMode();
2349
+ if (tree.length === 0) {
2350
+ this.showStatus("No entries in session");
2351
+ return;
2352
+ }
2353
+ this.showSelector((done) => {
2354
+ const selector = new TreeSelectorComponent(tree, realLeafId, this.ui.terminal.rows, async (entryId) => {
2355
+ if (entryId === realLeafId) {
2356
+ done();
2357
+ this.showStatus("Already at this point");
2358
+ return;
2359
+ }
2360
+ done();
2361
+ let wantsSummary = false;
2362
+ let customInstructions;
2363
+ if (!this.settingsManager.getBranchSummarySkipPrompt()) {
2364
+ while (true) {
2365
+ const summaryChoice = await this.showExtensionSelector("Summarize branch?", [
2366
+ "No summary",
2367
+ "Summarize",
2368
+ "Summarize with custom prompt",
2369
+ ]);
2370
+ if (summaryChoice === undefined) {
2371
+ this.showTreeSelector(entryId);
2372
+ return;
2373
+ }
2374
+ wantsSummary = summaryChoice !== "No summary";
2375
+ if (summaryChoice === "Summarize with custom prompt") {
2376
+ customInstructions = await this.showExtensionEditor("Custom summarization instructions");
2377
+ if (customInstructions === undefined) {
2378
+ continue;
2379
+ }
2380
+ }
2381
+ break;
2382
+ }
2383
+ }
2384
+ let summaryLoader;
2385
+ const originalOnEscape = this.defaultEditor.onEscape;
2386
+ if (wantsSummary) {
2387
+ this.defaultEditor.onEscape = () => {
2388
+ this.session.abortBranchSummary();
2389
+ };
2390
+ this.chatContainer.addChild(new Spacer(1));
2391
+ summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
2392
+ this.statusContainer.addChild(summaryLoader);
2393
+ this.ui.requestRender();
2394
+ }
2395
+ try {
2396
+ const result = await this.session.navigateTree(entryId, {
2397
+ summarize: wantsSummary,
2398
+ customInstructions,
2399
+ });
2400
+ if (result.aborted) {
2401
+ this.showStatus("Branch summarization cancelled");
2402
+ this.showTreeSelector(entryId);
2403
+ return;
2404
+ }
2405
+ if (result.cancelled) {
2406
+ this.showStatus("Navigation cancelled");
2407
+ return;
2408
+ }
2409
+ this.chatContainer.clear();
2410
+ this.renderInitialMessages();
2411
+ if (result.editorText && !this.editor.getText().trim()) {
2412
+ this.editor.setText(result.editorText);
2413
+ }
2414
+ this.showStatus("Navigated to selected point");
2415
+ }
2416
+ catch (error) {
2417
+ this.showError(error instanceof Error ? error.message : String(error));
2418
+ }
2419
+ finally {
2420
+ if (summaryLoader) {
2421
+ summaryLoader.stop();
2422
+ this.statusContainer.clear();
2423
+ }
2424
+ this.defaultEditor.onEscape = originalOnEscape;
2425
+ }
2426
+ }, () => {
2427
+ done();
2428
+ this.ui.requestRender();
2429
+ }, (entryId, label) => {
2430
+ this.sessionManager.appendLabelChange(entryId, label);
2431
+ this.ui.requestRender();
2432
+ }, initialSelectedId, initialFilterMode);
2433
+ return { component: selector, focus: selector };
2434
+ });
2435
+ }
2436
+ showSessionSelector() {
2437
+ this.showSelector((done) => {
2438
+ const selector = new SessionSelectorComponent((onProgress) => SessionManager.list(this.sessionManager.getCwd(), this.sessionManager.getSessionDir(), onProgress), SessionManager.listAll, async (sessionPath) => {
2439
+ done();
2440
+ await this.handleResumeSession(sessionPath);
2441
+ }, () => {
2442
+ done();
2443
+ this.ui.requestRender();
2444
+ }, () => {
2445
+ void this.shutdown();
2446
+ }, () => this.ui.requestRender(), {
2447
+ renameSession: async (sessionFilePath, nextName) => {
2448
+ const next = (nextName ?? "").trim();
2449
+ if (!next)
2450
+ return;
2451
+ const mgr = SessionManager.open(sessionFilePath);
2452
+ mgr.appendSessionInfo(next);
2453
+ },
2454
+ showRenameHint: true,
2455
+ keybindings: this.keybindings,
2456
+ }, this.sessionManager.getSessionFile());
2457
+ return { component: selector, focus: selector };
2458
+ });
2459
+ }
2460
+ async handleResumeSession(sessionPath) {
2461
+ if (this.loadingAnimation) {
2462
+ this.loadingAnimation.stop();
2463
+ this.loadingAnimation = undefined;
2464
+ }
2465
+ this.statusContainer.clear();
2466
+ this.pendingMessagesContainer.clear();
2467
+ this.compactionQueuedMessages = [];
2468
+ this.streamingComponent = undefined;
2469
+ this.streamingMessage = undefined;
2470
+ this.pendingTools.clear();
2471
+ await this.session.switchSession(sessionPath);
2472
+ this.chatContainer.clear();
2473
+ this.renderInitialMessages();
2474
+ this.showStatus("Resumed session");
2475
+ }
2476
+ async handleReloadCommand() {
2477
+ if (this.session.isStreaming) {
2478
+ this.showWarning("Wait for the current response to finish before reloading.");
2479
+ return;
2480
+ }
2481
+ if (this.session.isCompacting) {
2482
+ this.showWarning("Wait for compaction to finish before reloading.");
2483
+ return;
2484
+ }
2485
+ this.resetExtensionUI();
2486
+ const loader = new BorderedLoader(this.ui, theme, "Reloading extensions, skills, prompts, themes...", {
2487
+ cancellable: false,
2488
+ });
2489
+ const previousEditor = this.editor;
2490
+ this.editorContainer.clear();
2491
+ this.editorContainer.addChild(loader);
2492
+ this.ui.setFocus(loader);
2493
+ this.ui.requestRender();
2494
+ const dismissLoader = (editor) => {
2495
+ loader.dispose();
2496
+ this.editorContainer.clear();
2497
+ this.editorContainer.addChild(editor);
2498
+ this.ui.setFocus(editor);
2499
+ this.ui.requestRender();
2500
+ };
2501
+ try {
2502
+ await this.session.reload();
2503
+ setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
2504
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
2505
+ const themeName = this.settingsManager.getTheme();
2506
+ const themeResult = themeName ? setTheme(themeName, true) : { success: true };
2507
+ if (!themeResult.success) {
2508
+ this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
2509
+ }
2510
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
2511
+ const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
2512
+ this.defaultEditor.setPaddingX(editorPaddingX);
2513
+ this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
2514
+ if (this.editor !== this.defaultEditor) {
2515
+ this.editor.setPaddingX?.(editorPaddingX);
2516
+ this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
2517
+ }
2518
+ this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
2519
+ this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
2520
+ this.setupAutocomplete(this.fdPath);
2521
+ const runner = this.session.extensionRunner;
2522
+ if (runner) {
2523
+ this.setupExtensionShortcuts(runner);
2524
+ }
2525
+ this.rebuildChatFromMessages();
2526
+ dismissLoader(this.editor);
2527
+ this.showLoadedResources({
2528
+ extensionPaths: runner?.getExtensionPaths() ?? [],
2529
+ force: false,
2530
+ showDiagnosticsWhenQuiet: true,
2531
+ });
2532
+ this.showStatus("Reloaded extensions, skills, prompts, themes");
2533
+ }
2534
+ catch (error) {
2535
+ dismissLoader(previousEditor);
2536
+ this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
2537
+ }
2538
+ }
2539
+ async handleExportCommand(text) {
2540
+ const parts = text.split(/\s+/);
2541
+ const outputPath = parts.length > 1 ? parts[1] : undefined;
2542
+ try {
2543
+ const filePath = await this.session.exportToHtml(outputPath);
2544
+ this.showStatus(`Session exported to: ${filePath}`);
2545
+ }
2546
+ catch (error) {
2547
+ this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
2548
+ }
2549
+ }
2550
+ async handleShareCommand() {
2551
+ try {
2552
+ const authResult = spawnSync("gh", ["auth", "status"], { encoding: "utf-8" });
2553
+ if (authResult.status !== 0) {
2554
+ this.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
2555
+ return;
2556
+ }
2557
+ }
2558
+ catch {
2559
+ this.showError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com");
2560
+ return;
2561
+ }
2562
+ const tmpFile = path.join(os.tmpdir(), "session.html");
2563
+ try {
2564
+ await this.session.exportToHtml(tmpFile);
2565
+ }
2566
+ catch (error) {
2567
+ this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
2568
+ return;
2569
+ }
2570
+ const loader = new BorderedLoader(this.ui, theme, "Creating gist...");
2571
+ this.editorContainer.clear();
2572
+ this.editorContainer.addChild(loader);
2573
+ this.ui.setFocus(loader);
2574
+ this.ui.requestRender();
2575
+ const restoreEditor = () => {
2576
+ loader.dispose();
2577
+ this.editorContainer.clear();
2578
+ this.editorContainer.addChild(this.editor);
2579
+ this.ui.setFocus(this.editor);
2580
+ try {
2581
+ fs.unlinkSync(tmpFile);
2582
+ }
2583
+ catch { }
2584
+ };
2585
+ let proc = null;
2586
+ loader.onAbort = () => {
2587
+ proc?.kill();
2588
+ restoreEditor();
2589
+ this.showStatus("Share cancelled");
2590
+ };
2591
+ try {
2592
+ const result = await new Promise((resolve) => {
2593
+ proc = spawn("gh", ["gist", "create", "--public=false", tmpFile]);
2594
+ let stdout = "";
2595
+ let stderr = "";
2596
+ proc.stdout?.on("data", (data) => {
2597
+ stdout += data.toString();
2598
+ });
2599
+ proc.stderr?.on("data", (data) => {
2600
+ stderr += data.toString();
2601
+ });
2602
+ proc.on("close", (code) => resolve({ stdout, stderr, code }));
2603
+ });
2604
+ if (loader.signal.aborted)
2605
+ return;
2606
+ restoreEditor();
2607
+ if (result.code !== 0) {
2608
+ const errorMsg = result.stderr?.trim() || "Unknown error";
2609
+ this.showError(`Failed to create gist: ${errorMsg}`);
2610
+ return;
2611
+ }
2612
+ const gistUrl = result.stdout?.trim();
2613
+ const gistId = gistUrl?.split("/").pop();
2614
+ if (!gistId) {
2615
+ this.showError("Failed to parse gist ID from gh output");
2616
+ return;
2617
+ }
2618
+ const previewUrl = getShareViewerUrl(gistId);
2619
+ this.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
2620
+ }
2621
+ catch (error) {
2622
+ if (!loader.signal.aborted) {
2623
+ restoreEditor();
2624
+ this.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
2625
+ }
2626
+ }
2627
+ }
2628
+ handleCopyCommand() {
2629
+ const text = this.session.getLastAssistantText();
2630
+ if (!text) {
2631
+ this.showError("No agent messages to copy yet.");
2632
+ return;
2633
+ }
2634
+ try {
2635
+ copyToClipboard(text);
2636
+ this.showStatus("Copied last agent message to clipboard");
2637
+ }
2638
+ catch (error) {
2639
+ this.showError(error instanceof Error ? error.message : String(error));
2640
+ }
2641
+ }
2642
+ handleNameCommand(text) {
2643
+ const name = text.replace(/^\/name\s*/, "").trim();
2644
+ if (!name) {
2645
+ const currentName = this.sessionManager.getSessionName();
2646
+ if (currentName) {
2647
+ this.chatContainer.addChild(new Spacer(1));
2648
+ this.chatContainer.addChild(new Text(theme.fg("dim", `Session name: ${currentName}`), 1, 0));
2649
+ }
2650
+ else {
2651
+ this.showWarning("Usage: /name <name>");
2652
+ }
2653
+ this.ui.requestRender();
2654
+ return;
2655
+ }
2656
+ this.sessionManager.appendSessionInfo(name);
2657
+ this.updateTerminalTitle();
2658
+ this.chatContainer.addChild(new Spacer(1));
2659
+ this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
2660
+ this.ui.requestRender();
2661
+ }
2662
+ handleSessionCommand() {
2663
+ const stats = this.session.getSessionStats();
2664
+ const sessionName = this.sessionManager.getSessionName();
2665
+ let info = `${theme.bold("Session Info")}\n\n`;
2666
+ if (sessionName) {
2667
+ info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
2668
+ }
2669
+ info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
2670
+ info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
2671
+ info += `${theme.bold("Messages")}\n`;
2672
+ info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
2673
+ info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
2674
+ info += `${theme.fg("dim", "Tool Calls:")} ${stats.toolCalls}\n`;
2675
+ info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
2676
+ info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
2677
+ info += `${theme.bold("Tokens")}\n`;
2678
+ info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
2679
+ info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
2680
+ if (stats.tokens.cacheRead > 0) {
2681
+ info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
2682
+ }
2683
+ if (stats.tokens.cacheWrite > 0) {
2684
+ info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
2685
+ }
2686
+ info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
2687
+ if (stats.cost > 0) {
2688
+ info += `\n${theme.bold("Cost")}\n`;
2689
+ info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}`;
2690
+ }
2691
+ this.chatContainer.addChild(new Spacer(1));
2692
+ this.chatContainer.addChild(new Text(info, 1, 0));
2693
+ this.ui.requestRender();
2694
+ }
2695
+ handleChangelogCommand() {
2696
+ const changelogPath = getChangelogPath();
2697
+ const allEntries = parseChangelog(changelogPath);
2698
+ const changelogMarkdown = allEntries.length > 0
2699
+ ? allEntries
2700
+ .reverse()
2701
+ .map((e) => e.content)
2702
+ .join("\n\n")
2703
+ : "No changelog entries found.";
2704
+ this.chatContainer.addChild(new Spacer(1));
2705
+ this.chatContainer.addChild(new DynamicBorder());
2706
+ this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
2707
+ this.chatContainer.addChild(new Spacer(1));
2708
+ this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.getMarkdownThemeWithSettings()));
2709
+ this.chatContainer.addChild(new DynamicBorder());
2710
+ this.ui.requestRender();
2711
+ }
2712
+ capitalizeKey(key) {
2713
+ return key
2714
+ .split("/")
2715
+ .map((k) => k
2716
+ .split("+")
2717
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
2718
+ .join("+"))
2719
+ .join("/");
2720
+ }
2721
+ getAppKeyDisplay(action) {
2722
+ return this.capitalizeKey(appKey(this.keybindings, action));
2723
+ }
2724
+ getEditorKeyDisplay(action) {
2725
+ return this.capitalizeKey(editorKey(action));
2726
+ }
2727
+ handleHotkeysCommand() {
2728
+ const cursorWordLeft = this.getEditorKeyDisplay("cursorWordLeft");
2729
+ const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
2730
+ const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
2731
+ const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
2732
+ const jumpForward = this.getEditorKeyDisplay("jumpForward");
2733
+ const jumpBackward = this.getEditorKeyDisplay("jumpBackward");
2734
+ const pageUp = this.getEditorKeyDisplay("pageUp");
2735
+ const pageDown = this.getEditorKeyDisplay("pageDown");
2736
+ const submit = this.getEditorKeyDisplay("submit");
2737
+ const newLine = this.getEditorKeyDisplay("newLine");
2738
+ const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
2739
+ const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
2740
+ const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
2741
+ const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
2742
+ const yank = this.getEditorKeyDisplay("yank");
2743
+ const yankPop = this.getEditorKeyDisplay("yankPop");
2744
+ const undo = this.getEditorKeyDisplay("undo");
2745
+ const tab = this.getEditorKeyDisplay("tab");
2746
+ const interrupt = this.getAppKeyDisplay("interrupt");
2747
+ const clear = this.getAppKeyDisplay("clear");
2748
+ const exit = this.getAppKeyDisplay("exit");
2749
+ const suspend = this.getAppKeyDisplay("suspend");
2750
+ const cycleThinkingLevel = this.getAppKeyDisplay("cycleThinkingLevel");
2751
+ const expandTools = this.getAppKeyDisplay("expandTools");
2752
+ const toggleThinking = this.getAppKeyDisplay("toggleThinking");
2753
+ const externalEditor = this.getAppKeyDisplay("externalEditor");
2754
+ const followUp = this.getAppKeyDisplay("followUp");
2755
+ const dequeue = this.getAppKeyDisplay("dequeue");
2756
+ let hotkeys = `
2757
+ **Navigation**
2758
+ | Key | Action |
2759
+ |-----|--------|
2760
+ | \`Arrow keys\` | Move cursor / browse history (Up when empty) |
2761
+ | \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
2762
+ | \`${cursorLineStart}\` | Start of line |
2763
+ | \`${cursorLineEnd}\` | End of line |
2764
+ | \`${jumpForward}\` | Jump forward to character |
2765
+ | \`${jumpBackward}\` | Jump backward to character |
2766
+ | \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
2767
+ **Editing**
2768
+ | Key | Action |
2769
+ |-----|--------|
2770
+ | \`${submit}\` | Send message |
2771
+ | \`${newLine}\` | New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |
2772
+ | \`${deleteWordBackward}\` | Delete word backwards |
2773
+ | \`${deleteWordForward}\` | Delete word forwards |
2774
+ | \`${deleteToLineStart}\` | Delete to start of line |
2775
+ | \`${deleteToLineEnd}\` | Delete to end of line |
2776
+ | \`${yank}\` | Paste the most-recently-deleted text |
2777
+ | \`${yankPop}\` | Cycle through the deleted text after pasting |
2778
+ | \`${undo}\` | Undo |
2779
+ **Other**
2780
+ | Key | Action |
2781
+ |-----|--------|
2782
+ | \`${tab}\` | Path completion / accept autocomplete |
2783
+ | \`${interrupt}\` | Cancel autocomplete / abort streaming |
2784
+ | \`${clear}\` | Clear editor (first) / exit (second) |
2785
+ | \`${exit}\` | Exit (when editor is empty) |
2786
+ | \`${suspend}\` | Suspend to background |
2787
+ | \`${cycleThinkingLevel}\` | Cycle thinking level |
2788
+ | \`${expandTools}\` | Toggle tool output expansion |
2789
+ | \`${toggleThinking}\` | Toggle thinking block visibility |
2790
+ | \`${externalEditor}\` | Edit message in external editor |
2791
+ | \`${followUp}\` | Queue follow-up message |
2792
+ | \`${dequeue}\` | Restore queued messages |
2793
+ | \`Ctrl+V\` | Paste image from clipboard |
2794
+ | \`/\` | Slash commands |
2795
+ | \`!\` | Run bash command |
2796
+ | \`!!\` | Run bash command (excluded from context) |
2797
+ `;
2798
+ const extensionRunner = this.session.extensionRunner;
2799
+ if (extensionRunner) {
2800
+ const shortcuts = extensionRunner.getShortcuts(this.keybindings.getEffectiveConfig());
2801
+ if (shortcuts.size > 0) {
2802
+ hotkeys += `
2803
+ **Extensions**
2804
+ | Key | Action |
2805
+ |-----|--------|
2806
+ `;
2807
+ for (const [key, shortcut] of shortcuts) {
2808
+ const description = shortcut.description ?? shortcut.extensionPath;
2809
+ const keyDisplay = key.replace(/\b\w/g, (c) => c.toUpperCase());
2810
+ hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
2811
+ }
2812
+ }
2813
+ }
2814
+ this.chatContainer.addChild(new Spacer(1));
2815
+ this.chatContainer.addChild(new DynamicBorder());
2816
+ this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
2817
+ this.chatContainer.addChild(new Spacer(1));
2818
+ this.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.getMarkdownThemeWithSettings()));
2819
+ this.chatContainer.addChild(new DynamicBorder());
2820
+ this.ui.requestRender();
2821
+ }
2822
+ async handleClearCommand() {
2823
+ if (this.loadingAnimation) {
2824
+ this.loadingAnimation.stop();
2825
+ this.loadingAnimation = undefined;
2826
+ }
2827
+ this.statusContainer.clear();
2828
+ await this.session.newSession();
2829
+ this.headerContainer.clear();
2830
+ this.chatContainer.clear();
2831
+ this.pendingMessagesContainer.clear();
2832
+ this.compactionQueuedMessages = [];
2833
+ this.streamingComponent = undefined;
2834
+ this.streamingMessage = undefined;
2835
+ this.pendingTools.clear();
2836
+ this.chatContainer.addChild(new Spacer(1));
2837
+ this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
2838
+ this.ui.requestRender();
2839
+ }
2840
+ handleDebugCommand() {
2841
+ const width = this.ui.terminal.columns;
2842
+ const height = this.ui.terminal.rows;
2843
+ const allLines = this.ui.render(width);
2844
+ const debugLogPath = getDebugLogPath();
2845
+ const debugData = [
2846
+ `Debug output at ${new Date().toISOString()}`,
2847
+ `Terminal: ${width}x${height}`,
2848
+ `Total lines: ${allLines.length}`,
2849
+ "",
2850
+ "=== All rendered lines with visible widths ===",
2851
+ ...allLines.map((line, idx) => {
2852
+ const vw = visibleWidth(line);
2853
+ const escaped = JSON.stringify(line);
2854
+ return `[${idx}] (w=${vw}) ${escaped}`;
2855
+ }),
2856
+ "",
2857
+ "=== Agent messages (JSONL) ===",
2858
+ ...this.session.messages.map((msg) => JSON.stringify(msg)),
2859
+ "",
2860
+ ].join("\n");
2861
+ fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
2862
+ fs.writeFileSync(debugLogPath, debugData);
2863
+ this.chatContainer.addChild(new Spacer(1));
2864
+ this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ Debug log written")}\n${theme.fg("muted", debugLogPath)}`, 1, 1));
2865
+ this.ui.requestRender();
2866
+ }
2867
+ handleArminSaysHi() {
2868
+ this.chatContainer.addChild(new Spacer(1));
2869
+ this.chatContainer.addChild(new ArminComponent(this.ui));
2870
+ this.ui.requestRender();
2871
+ }
2872
+ async handleThinkCommand(text) {
2873
+ const levelArg = text
2874
+ .replace(/^\/think\s*/, "")
2875
+ .trim()
2876
+ .toLowerCase();
2877
+ // If no argument provided, show current level and usage
2878
+ if (!levelArg) {
2879
+ const currentLevel = this.session.thinkingLevel;
2880
+ const availableLevels = this.session.getAvailableThinkingLevels();
2881
+ let info = `${theme.bold("Thinking Level")}\n\n`;
2882
+ info += `${theme.fg("dim", "Current:")} ${currentLevel}\n`;
2883
+ info += `${theme.fg("dim", "Available:")} ${availableLevels.join(", ")}\n\n`;
2884
+ info += `${theme.fg("dim", "Usage:")} /think <level>`;
2885
+ this.chatContainer.addChild(new Spacer(1));
2886
+ this.chatContainer.addChild(new Text(info, 1, 0));
2887
+ this.ui.requestRender();
2888
+ return;
2889
+ }
2890
+ // Validate and set the thinking level
2891
+ const validLevels = ["off", "minimal", "low", "medium", "high", "xhigh"];
2892
+ const normalizedLevel = levelArg;
2893
+ if (!validLevels.includes(normalizedLevel)) {
2894
+ this.showError(`Invalid thinking level: ${levelArg}\nValid levels: ${validLevels.join(", ")}`);
2895
+ return;
2896
+ }
2897
+ const availableLevels = this.session.getAvailableThinkingLevels();
2898
+ if (!availableLevels.includes(normalizedLevel)) {
2899
+ this.showWarning(`Level '${normalizedLevel}' not supported by current model. Using closest available.`);
2900
+ }
2901
+ this.session.setThinkingLevel(normalizedLevel);
2902
+ this.footer.invalidate();
2903
+ this.updateEditorBorderColor();
2904
+ this.showStatus(`Thinking level set to: ${normalizedLevel}`);
2905
+ }
2906
+ handleDaxnuts() {
2907
+ this.chatContainer.addChild(new Spacer(1));
2908
+ this.chatContainer.addChild(new DaxnutsComponent(this.ui));
2909
+ this.ui.requestRender();
2910
+ }
2911
+ async handleBashCommand(command, excludeFromContext = false) {
2912
+ const extensionRunner = this.session.extensionRunner;
2913
+ const eventResult = extensionRunner
2914
+ ? await extensionRunner.emitUserBash({
2915
+ type: "user_bash",
2916
+ command,
2917
+ excludeFromContext,
2918
+ cwd: process.cwd(),
2919
+ })
2920
+ : undefined;
2921
+ if (eventResult?.result) {
2922
+ const result = eventResult.result;
2923
+ this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
2924
+ if (this.session.isStreaming) {
2925
+ this.pendingMessagesContainer.addChild(this.bashComponent);
2926
+ this.pendingBashComponents.push(this.bashComponent);
2927
+ }
2928
+ else {
2929
+ this.chatContainer.addChild(this.bashComponent);
2930
+ }
2931
+ if (result.output) {
2932
+ this.bashComponent.appendOutput(result.output);
2933
+ }
2934
+ this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
2935
+ this.session.recordBashResult(command, result, { excludeFromContext });
2936
+ this.bashComponent = undefined;
2937
+ this.ui.requestRender();
2938
+ return;
2939
+ }
2940
+ const isDeferred = this.session.isStreaming;
2941
+ this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
2942
+ if (isDeferred) {
2943
+ this.pendingMessagesContainer.addChild(this.bashComponent);
2944
+ this.pendingBashComponents.push(this.bashComponent);
2945
+ }
2946
+ else {
2947
+ this.chatContainer.addChild(this.bashComponent);
2948
+ }
2949
+ this.ui.requestRender();
2950
+ try {
2951
+ const result = await this.session.executeBash(command, (chunk) => {
2952
+ if (this.bashComponent) {
2953
+ this.bashComponent.appendOutput(chunk);
2954
+ this.ui.requestRender();
2955
+ }
2956
+ }, { excludeFromContext, operations: eventResult?.operations });
2957
+ if (this.bashComponent) {
2958
+ this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
2959
+ }
2960
+ }
2961
+ catch (error) {
2962
+ if (this.bashComponent) {
2963
+ this.bashComponent.setComplete(undefined, false);
2964
+ }
2965
+ this.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2966
+ }
2967
+ this.bashComponent = undefined;
2968
+ this.ui.requestRender();
2969
+ }
2970
+ async handleCompactCommand(customInstructions) {
2971
+ const entries = this.sessionManager.getEntries();
2972
+ const messageCount = entries.filter((e) => e.type === "message").length;
2973
+ if (messageCount < 2) {
2974
+ this.showWarning("Nothing to compact (no messages yet)");
2975
+ return;
2976
+ }
2977
+ await this.executeCompaction(customInstructions, false);
2978
+ }
2979
+ async executeCompaction(customInstructions, isAuto = false) {
2980
+ if (this.loadingAnimation) {
2981
+ this.loadingAnimation.stop();
2982
+ this.loadingAnimation = undefined;
2983
+ }
2984
+ this.statusContainer.clear();
2985
+ const originalOnEscape = this.defaultEditor.onEscape;
2986
+ this.defaultEditor.onEscape = () => {
2987
+ this.session.abortCompaction();
2988
+ };
2989
+ this.chatContainer.addChild(new Spacer(1));
2990
+ const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
2991
+ const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
2992
+ const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
2993
+ this.statusContainer.addChild(compactingLoader);
2994
+ this.ui.requestRender();
2995
+ let result;
2996
+ try {
2997
+ result = await this.session.compact(customInstructions);
2998
+ this.rebuildChatFromMessages();
2999
+ const msg = createCompactionSummaryMessage(result.summary, result.tokensBefore, new Date().toISOString());
3000
+ this.addMessageToChat(msg);
3001
+ this.footer.invalidate();
3002
+ }
3003
+ catch (error) {
3004
+ const message = error instanceof Error ? error.message : String(error);
3005
+ if (message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError")) {
3006
+ this.showError("Compaction cancelled");
3007
+ }
3008
+ else {
3009
+ this.showError(`Compaction failed: ${message}`);
3010
+ }
3011
+ }
3012
+ finally {
3013
+ compactingLoader.stop();
3014
+ this.statusContainer.clear();
3015
+ this.defaultEditor.onEscape = originalOnEscape;
3016
+ }
3017
+ void this.flushCompactionQueue({ willRetry: false });
3018
+ return result;
3019
+ }
3020
+ stop() {
3021
+ if (this.loadingAnimation) {
3022
+ this.loadingAnimation.stop();
3023
+ this.loadingAnimation = undefined;
3024
+ }
3025
+ this.clearExtensionTerminalInputListeners();
3026
+ this.footer.dispose();
3027
+ this.footerDataProvider.dispose();
3028
+ if (this.unsubscribe) {
3029
+ this.unsubscribe();
3030
+ }
3031
+ if (this.isInitialized) {
3032
+ this.ui.stop();
3033
+ this.isInitialized = false;
3034
+ }
3035
+ }
3036
+ }
3037
+ //# sourceMappingURL=interactive-mode.js.map