@travisennis/acai 0.0.8 → 0.0.10

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 (386) hide show
  1. package/README.md +48 -729
  2. package/bin/acai +52 -0
  3. package/dist/agent/index.d.ts +12 -2
  4. package/dist/agent/index.d.ts.map +1 -1
  5. package/dist/agent/index.js +378 -168
  6. package/dist/agent/sub-agent.d.ts +23 -0
  7. package/dist/agent/sub-agent.d.ts.map +1 -0
  8. package/dist/agent/sub-agent.js +109 -0
  9. package/dist/cli/index.d.ts +26 -0
  10. package/dist/cli/index.d.ts.map +1 -0
  11. package/dist/{cli.js → cli/index.js} +84 -76
  12. package/dist/cli/stdin.d.ts +9 -0
  13. package/dist/cli/stdin.d.ts.map +1 -0
  14. package/dist/cli/stdin.js +37 -0
  15. package/dist/commands/copy/index.js +2 -2
  16. package/dist/commands/copy/utils.d.ts.map +1 -1
  17. package/dist/commands/copy/utils.js +15 -13
  18. package/dist/commands/generate-rules/index.d.ts +1 -1
  19. package/dist/commands/generate-rules/index.d.ts.map +1 -1
  20. package/dist/commands/generate-rules/index.js +16 -100
  21. package/dist/commands/generate-rules/service.d.ts +21 -0
  22. package/dist/commands/generate-rules/service.d.ts.map +1 -0
  23. package/dist/commands/generate-rules/service.js +103 -0
  24. package/dist/commands/handoff/index.js +2 -2
  25. package/dist/commands/health/index.js +1 -1
  26. package/dist/commands/health/utils.d.ts.map +1 -1
  27. package/dist/commands/health/utils.js +6 -0
  28. package/dist/commands/history/index.d.ts +1 -1
  29. package/dist/commands/history/index.d.ts.map +1 -1
  30. package/dist/commands/history/index.js +17 -18
  31. package/dist/commands/history/types.d.ts +38 -0
  32. package/dist/commands/history/types.d.ts.map +1 -1
  33. package/dist/commands/history/utils.d.ts.map +1 -1
  34. package/dist/commands/history/utils.js +63 -58
  35. package/dist/commands/init/index.d.ts.map +1 -1
  36. package/dist/commands/init/index.js +3 -8
  37. package/dist/commands/init-project/index.d.ts.map +1 -1
  38. package/dist/commands/init-project/index.js +3 -3
  39. package/dist/commands/init-project/utils.d.ts.map +1 -1
  40. package/dist/commands/init-project/utils.js +10 -2
  41. package/dist/commands/list-tools/index.d.ts.map +1 -1
  42. package/dist/commands/list-tools/index.js +7 -31
  43. package/dist/commands/manager.d.ts +2 -2
  44. package/dist/commands/manager.d.ts.map +1 -1
  45. package/dist/commands/manager.js +57 -33
  46. package/dist/commands/model/index.d.ts.map +1 -1
  47. package/dist/commands/model/index.js +20 -151
  48. package/dist/commands/model/model-panel.d.ts +4 -0
  49. package/dist/commands/model/model-panel.d.ts.map +1 -0
  50. package/dist/commands/model/model-panel.js +144 -0
  51. package/dist/commands/paste/index.d.ts.map +1 -1
  52. package/dist/commands/paste/index.js +59 -62
  53. package/dist/commands/paste/utils.d.ts.map +1 -1
  54. package/dist/commands/paste/utils.js +88 -58
  55. package/dist/commands/pickup/index.d.ts.map +1 -1
  56. package/dist/commands/pickup/index.js +6 -3
  57. package/dist/commands/pickup/utils.js +3 -3
  58. package/dist/commands/resources/index.d.ts.map +1 -1
  59. package/dist/commands/resources/index.js +33 -50
  60. package/dist/commands/review/index.d.ts.map +1 -1
  61. package/dist/commands/review/index.js +3 -117
  62. package/dist/commands/review/review-panel.d.ts +3 -0
  63. package/dist/commands/review/review-panel.d.ts.map +1 -0
  64. package/dist/commands/review/review-panel.js +186 -0
  65. package/dist/commands/review/utils.d.ts +9 -0
  66. package/dist/commands/review/utils.d.ts.map +1 -1
  67. package/dist/commands/review/utils.js +127 -68
  68. package/dist/commands/session/index.d.ts +1 -1
  69. package/dist/commands/session/index.d.ts.map +1 -1
  70. package/dist/commands/session/index.js +134 -112
  71. package/dist/commands/session/types.d.ts +7 -0
  72. package/dist/commands/session/types.d.ts.map +1 -1
  73. package/dist/commands/share/html-renderer.d.ts +25 -0
  74. package/dist/commands/share/html-renderer.d.ts.map +1 -0
  75. package/dist/commands/share/html-renderer.js +384 -0
  76. package/dist/commands/share/index.d.ts +3 -0
  77. package/dist/commands/share/index.d.ts.map +1 -0
  78. package/dist/commands/share/index.js +122 -0
  79. package/dist/commands/shell/index.d.ts.map +1 -1
  80. package/dist/commands/shell/index.js +16 -1
  81. package/dist/commands/types.d.ts +2 -2
  82. package/dist/commands/types.d.ts.map +1 -1
  83. package/dist/{config.d.ts → config/index.d.ts} +20 -9
  84. package/dist/config/index.d.ts.map +1 -0
  85. package/dist/{config.js → config/index.js} +43 -42
  86. package/dist/execution/index.d.ts.map +1 -1
  87. package/dist/execution/index.js +75 -55
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +170 -127
  91. package/dist/middleware/cache.d.ts.map +1 -1
  92. package/dist/middleware/cache.js +18 -36
  93. package/dist/models/ai-config.d.ts +1 -0
  94. package/dist/models/ai-config.d.ts.map +1 -1
  95. package/dist/models/ai-config.js +4 -3
  96. package/dist/models/anthropic-provider.d.ts +2 -5
  97. package/dist/models/anthropic-provider.d.ts.map +1 -1
  98. package/dist/models/anthropic-provider.js +3 -70
  99. package/dist/models/deepseek-provider.d.ts +1 -0
  100. package/dist/models/deepseek-provider.d.ts.map +1 -1
  101. package/dist/models/google-provider.d.ts +2 -3
  102. package/dist/models/google-provider.d.ts.map +1 -1
  103. package/dist/models/google-provider.js +0 -26
  104. package/dist/models/groq-provider.d.ts +1 -0
  105. package/dist/models/groq-provider.d.ts.map +1 -1
  106. package/dist/models/manager.d.ts +13 -2
  107. package/dist/models/manager.d.ts.map +1 -1
  108. package/dist/models/manager.js +20 -8
  109. package/dist/models/openai-provider.d.ts +5 -5
  110. package/dist/models/openai-provider.d.ts.map +1 -1
  111. package/dist/models/openai-provider.js +27 -40
  112. package/dist/models/opencode-zen-provider.d.ts +8 -3
  113. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  114. package/dist/models/opencode-zen-provider.js +68 -11
  115. package/dist/models/openrouter-provider.d.ts +24 -30
  116. package/dist/models/openrouter-provider.d.ts.map +1 -1
  117. package/dist/models/openrouter-provider.js +92 -177
  118. package/dist/models/providers.d.ts +1 -1
  119. package/dist/models/providers.d.ts.map +1 -1
  120. package/dist/models/xai-provider.d.ts +4 -3
  121. package/dist/models/xai-provider.d.ts.map +1 -1
  122. package/dist/models/xai-provider.js +18 -18
  123. package/dist/modes/manager.d.ts +23 -0
  124. package/dist/modes/manager.d.ts.map +1 -0
  125. package/dist/modes/manager.js +77 -0
  126. package/dist/modes/prompts.d.ts +2 -0
  127. package/dist/modes/prompts.d.ts.map +1 -0
  128. package/dist/modes/prompts.js +143 -0
  129. package/dist/prompts/mentions.d.ts +11 -0
  130. package/dist/prompts/mentions.d.ts.map +1 -0
  131. package/dist/{mentions.js → prompts/mentions.js} +21 -80
  132. package/dist/prompts/system-prompt.d.ts +26 -0
  133. package/dist/prompts/system-prompt.d.ts.map +1 -0
  134. package/dist/{prompts.js → prompts/system-prompt.js} +50 -22
  135. package/dist/repl/index.d.ts +174 -0
  136. package/dist/repl/index.d.ts.map +1 -0
  137. package/dist/{repl-new.js → repl/index.js} +399 -76
  138. package/dist/repl/project-status.d.ts +1 -0
  139. package/dist/repl/project-status.d.ts.map +1 -1
  140. package/dist/repl/project-status.js +4 -1
  141. package/dist/sessions/manager.d.ts +93 -1
  142. package/dist/sessions/manager.d.ts.map +1 -1
  143. package/dist/sessions/manager.js +264 -9
  144. package/dist/sessions/summary.d.ts +4 -0
  145. package/dist/sessions/summary.d.ts.map +1 -0
  146. package/dist/sessions/summary.js +30 -0
  147. package/dist/{skills.d.ts → skills/index.d.ts} +14 -2
  148. package/dist/skills/index.d.ts.map +1 -0
  149. package/dist/skills/index.js +294 -0
  150. package/dist/subagents/index.d.ts +15 -0
  151. package/dist/subagents/index.d.ts.map +1 -0
  152. package/dist/subagents/index.js +231 -0
  153. package/dist/terminal/control.d.ts +1 -1
  154. package/dist/terminal/control.d.ts.map +1 -1
  155. package/dist/terminal/control.js +30 -9
  156. package/dist/terminal/east-asian-width.d.ts.map +1 -1
  157. package/dist/terminal/east-asian-width.js +404 -351
  158. package/dist/terminal/keys.d.ts +17 -0
  159. package/dist/terminal/keys.d.ts.map +1 -1
  160. package/dist/terminal/keys.js +37 -0
  161. package/dist/terminal/select-prompt.d.ts.map +1 -1
  162. package/dist/terminal/select-prompt.js +24 -12
  163. package/dist/terminal/string-width.d.ts.map +1 -1
  164. package/dist/terminal/string-width.js +25 -27
  165. package/dist/terminal/style.d.ts.map +1 -1
  166. package/dist/terminal/style.js +4 -7
  167. package/dist/terminal/supports-color.d.ts.map +1 -1
  168. package/dist/terminal/supports-color.js +41 -27
  169. package/dist/terminal/table/cell.d.ts +12 -0
  170. package/dist/terminal/table/cell.d.ts.map +1 -1
  171. package/dist/terminal/table/cell.js +40 -25
  172. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  173. package/dist/terminal/table/layout-manager.js +100 -68
  174. package/dist/terminal/table/utils.d.ts.map +1 -1
  175. package/dist/terminal/table/utils.js +17 -10
  176. package/dist/terminal/wrap-ansi.d.ts.map +1 -1
  177. package/dist/terminal/wrap-ansi.js +172 -103
  178. package/dist/tokens/tracker.d.ts +1 -0
  179. package/dist/tokens/tracker.d.ts.map +1 -1
  180. package/dist/tokens/tracker.js +3 -0
  181. package/dist/tools/agent.d.ts +27 -0
  182. package/dist/tools/agent.d.ts.map +1 -0
  183. package/dist/tools/agent.js +81 -0
  184. package/dist/tools/bash.d.ts +4 -3
  185. package/dist/tools/bash.d.ts.map +1 -1
  186. package/dist/tools/bash.js +343 -121
  187. package/dist/tools/code-search.d.ts +41 -0
  188. package/dist/tools/code-search.d.ts.map +1 -0
  189. package/dist/tools/code-search.js +195 -0
  190. package/dist/tools/directory-tree.d.ts +3 -3
  191. package/dist/tools/directory-tree.d.ts.map +1 -1
  192. package/dist/tools/directory-tree.js +8 -5
  193. package/dist/tools/dynamic-tool-loader.d.ts +2 -5
  194. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  195. package/dist/tools/dynamic-tool-loader.js +20 -4
  196. package/dist/tools/edit-file.d.ts +7 -7
  197. package/dist/tools/edit-file.d.ts.map +1 -1
  198. package/dist/tools/edit-file.js +164 -66
  199. package/dist/tools/glob.d.ts +6 -6
  200. package/dist/tools/glob.d.ts.map +1 -1
  201. package/dist/tools/glob.js +95 -55
  202. package/dist/tools/grep.d.ts +15 -12
  203. package/dist/tools/grep.d.ts.map +1 -1
  204. package/dist/tools/grep.js +300 -192
  205. package/dist/tools/index.d.ts +143 -5
  206. package/dist/tools/index.d.ts.map +1 -1
  207. package/dist/tools/index.js +39 -24
  208. package/dist/tools/ls.d.ts +2 -2
  209. package/dist/tools/ls.d.ts.map +1 -1
  210. package/dist/tools/ls.js +7 -5
  211. package/dist/tools/read-file.d.ts +3 -3
  212. package/dist/tools/read-file.d.ts.map +1 -1
  213. package/dist/tools/read-file.js +74 -34
  214. package/dist/tools/save-file.d.ts +3 -3
  215. package/dist/tools/save-file.d.ts.map +1 -1
  216. package/dist/tools/save-file.js +11 -11
  217. package/dist/tools/skill.d.ts +23 -0
  218. package/dist/tools/skill.d.ts.map +1 -0
  219. package/dist/tools/skill.js +65 -0
  220. package/dist/tools/think.d.ts.map +1 -1
  221. package/dist/tools/think.js +2 -9
  222. package/dist/tools/utils.d.ts +2 -0
  223. package/dist/tools/utils.d.ts.map +1 -1
  224. package/dist/tools/utils.js +12 -0
  225. package/dist/tools/web-fetch.d.ts +62 -0
  226. package/dist/tools/web-fetch.d.ts.map +1 -0
  227. package/dist/tools/web-fetch.js +429 -0
  228. package/dist/tools/web-search.d.ts +62 -0
  229. package/dist/tools/web-search.d.ts.map +1 -0
  230. package/dist/tools/web-search.js +226 -0
  231. package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
  232. package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
  233. package/dist/tui/autocomplete/attachment-provider.js +25 -78
  234. package/dist/tui/autocomplete/base-provider.d.ts +1 -0
  235. package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
  236. package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
  237. package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
  238. package/dist/tui/autocomplete/combined-provider.js +3 -17
  239. package/dist/tui/autocomplete/command-provider.d.ts +1 -0
  240. package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
  241. package/dist/tui/autocomplete/command-provider.js +3 -0
  242. package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
  243. package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
  244. package/dist/tui/autocomplete/file-search-provider.js +36 -16
  245. package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
  246. package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
  247. package/dist/tui/autocomplete/skill-provider.js +49 -0
  248. package/dist/tui/autocomplete.d.ts +2 -2
  249. package/dist/tui/autocomplete.d.ts.map +1 -1
  250. package/dist/tui/autocomplete.js +3 -5
  251. package/dist/tui/components/assistant-message.d.ts.map +1 -1
  252. package/dist/tui/components/assistant-message.js +0 -4
  253. package/dist/tui/components/editor.d.ts +21 -2
  254. package/dist/tui/components/editor.d.ts.map +1 -1
  255. package/dist/tui/components/editor.js +228 -236
  256. package/dist/tui/components/footer.d.ts +6 -4
  257. package/dist/tui/components/footer.d.ts.map +1 -1
  258. package/dist/tui/components/footer.js +49 -25
  259. package/dist/tui/components/markdown.d.ts +8 -5
  260. package/dist/tui/components/markdown.d.ts.map +1 -1
  261. package/dist/tui/components/markdown.js +57 -39
  262. package/dist/tui/components/modal.d.ts.map +1 -1
  263. package/dist/tui/components/modal.js +35 -33
  264. package/dist/tui/components/notification.d.ts +13 -2
  265. package/dist/tui/components/notification.d.ts.map +1 -1
  266. package/dist/tui/components/notification.js +37 -2
  267. package/dist/tui/components/progress-bar.js +1 -1
  268. package/dist/tui/components/select-list.d.ts +1 -0
  269. package/dist/tui/components/select-list.d.ts.map +1 -1
  270. package/dist/tui/components/select-list.js +14 -11
  271. package/dist/tui/components/text.d.ts +16 -0
  272. package/dist/tui/components/text.d.ts.map +1 -1
  273. package/dist/tui/components/text.js +72 -57
  274. package/dist/tui/components/thinking-block.d.ts +9 -0
  275. package/dist/tui/components/thinking-block.d.ts.map +1 -1
  276. package/dist/tui/components/thinking-block.js +43 -11
  277. package/dist/tui/components/tool-execution.d.ts +5 -1
  278. package/dist/tui/components/tool-execution.d.ts.map +1 -1
  279. package/dist/tui/components/tool-execution.js +19 -10
  280. package/dist/tui/components/user-message.d.ts.map +1 -1
  281. package/dist/tui/components/user-message.js +0 -3
  282. package/dist/tui/components/welcome.js +2 -2
  283. package/dist/tui/editor-launcher.d.ts +13 -0
  284. package/dist/tui/editor-launcher.d.ts.map +1 -0
  285. package/dist/tui/editor-launcher.js +39 -0
  286. package/dist/tui/index.d.ts +3 -1
  287. package/dist/tui/index.d.ts.map +1 -1
  288. package/dist/tui/index.js +1 -0
  289. package/dist/tui/terminal.d.ts +27 -0
  290. package/dist/tui/terminal.d.ts.map +1 -1
  291. package/dist/tui/terminal.js +144 -15
  292. package/dist/tui/tui.d.ts +43 -0
  293. package/dist/tui/tui.d.ts.map +1 -1
  294. package/dist/tui/tui.js +172 -41
  295. package/dist/utils/bash/parse.d.ts +19 -0
  296. package/dist/utils/bash/parse.d.ts.map +1 -0
  297. package/dist/utils/bash/parse.js +223 -0
  298. package/dist/utils/bash/quote.d.ts +6 -0
  299. package/dist/utils/bash/quote.d.ts.map +1 -0
  300. package/dist/utils/bash/quote.js +23 -0
  301. package/dist/utils/bash.d.ts.map +1 -1
  302. package/dist/utils/bash.js +211 -126
  303. package/dist/utils/command-protection.d.ts +28 -0
  304. package/dist/utils/command-protection.d.ts.map +1 -0
  305. package/dist/utils/command-protection.js +324 -0
  306. package/dist/utils/dedent.d.ts.map +1 -0
  307. package/dist/utils/env-expand.d.ts +2 -0
  308. package/dist/utils/env-expand.d.ts.map +1 -0
  309. package/dist/utils/env-expand.js +8 -0
  310. package/dist/utils/filesystem/path-display.d.ts +11 -0
  311. package/dist/utils/filesystem/path-display.d.ts.map +1 -0
  312. package/dist/utils/filesystem/path-display.js +32 -0
  313. package/dist/utils/filesystem/security.d.ts +2 -2
  314. package/dist/utils/filesystem/security.d.ts.map +1 -1
  315. package/dist/utils/filesystem/security.js +32 -31
  316. package/dist/utils/formatting.d.ts.map +1 -0
  317. package/dist/{formatting.js → utils/formatting.js} +1 -1
  318. package/dist/utils/git.d.ts +4 -0
  319. package/dist/utils/git.d.ts.map +1 -1
  320. package/dist/utils/git.js +30 -0
  321. package/dist/utils/glob.d.ts +1 -1
  322. package/dist/utils/glob.d.ts.map +1 -1
  323. package/dist/utils/logger.d.ts.map +1 -0
  324. package/dist/{logger.js → utils/logger.js} +1 -1
  325. package/dist/utils/parsing.d.ts.map +1 -0
  326. package/dist/utils/process.d.ts.map +1 -1
  327. package/dist/utils/process.js +90 -37
  328. package/dist/utils/templates.d.ts +2 -0
  329. package/dist/utils/templates.d.ts.map +1 -0
  330. package/dist/utils/templates.js +24 -0
  331. package/dist/utils/version.d.ts.map +1 -0
  332. package/dist/{version.js → utils/version.js} +1 -1
  333. package/package.json +34 -25
  334. package/dist/cli.d.ts +0 -23
  335. package/dist/cli.d.ts.map +0 -1
  336. package/dist/commands/exit/index.d.ts +0 -10
  337. package/dist/commands/exit/index.d.ts.map +0 -1
  338. package/dist/commands/exit/index.js +0 -21
  339. package/dist/commands/exit/types.d.ts +0 -8
  340. package/dist/commands/exit/types.d.ts.map +0 -1
  341. package/dist/commands/exit/types.js +0 -1
  342. package/dist/commands/exit/utils.d.ts +0 -2
  343. package/dist/commands/exit/utils.d.ts.map +0 -1
  344. package/dist/commands/exit/utils.js +0 -13
  345. package/dist/commands/prompt/index.d.ts +0 -5
  346. package/dist/commands/prompt/index.d.ts.map +0 -1
  347. package/dist/commands/prompt/index.js +0 -126
  348. package/dist/commands/prompt/types.d.ts +0 -15
  349. package/dist/commands/prompt/types.d.ts.map +0 -1
  350. package/dist/commands/prompt/types.js +0 -1
  351. package/dist/commands/prompt/utils.d.ts +0 -12
  352. package/dist/commands/prompt/utils.d.ts.map +0 -1
  353. package/dist/commands/prompt/utils.js +0 -107
  354. package/dist/commands/reset/index.d.ts +0 -3
  355. package/dist/commands/reset/index.d.ts.map +0 -1
  356. package/dist/commands/reset/index.js +0 -25
  357. package/dist/commands/reset/types.d.ts +0 -1
  358. package/dist/commands/reset/types.d.ts.map +0 -1
  359. package/dist/commands/reset/types.js +0 -3
  360. package/dist/commands/save/index.d.ts +0 -3
  361. package/dist/commands/save/index.d.ts.map +0 -1
  362. package/dist/commands/save/index.js +0 -19
  363. package/dist/config.d.ts.map +0 -1
  364. package/dist/dedent.d.ts.map +0 -1
  365. package/dist/formatting.d.ts.map +0 -1
  366. package/dist/logger.d.ts.map +0 -1
  367. package/dist/mentions.d.ts +0 -14
  368. package/dist/mentions.d.ts.map +0 -1
  369. package/dist/parsing.d.ts.map +0 -1
  370. package/dist/prompts.d.ts +0 -10
  371. package/dist/prompts.d.ts.map +0 -1
  372. package/dist/repl-new.d.ts +0 -62
  373. package/dist/repl-new.d.ts.map +0 -1
  374. package/dist/skills.d.ts.map +0 -1
  375. package/dist/skills.js +0 -233
  376. package/dist/tui/autocomplete/path-provider.d.ts +0 -21
  377. package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
  378. package/dist/tui/autocomplete/path-provider.js +0 -164
  379. package/dist/version.d.ts.map +0 -1
  380. /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
  381. /package/dist/{dedent.js → utils/dedent.js} +0 -0
  382. /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
  383. /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
  384. /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
  385. /package/dist/{parsing.js → utils/parsing.js} +0 -0
  386. /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
@@ -1,16 +1,30 @@
1
- import { logger } from "./logger.js";
2
- import { PromptError, processPrompt } from "./mentions.js";
3
- import { getProjectStatus, } from "./repl/project-status.js";
4
- import { alert, startProgress, stopProgress } from "./terminal/control.js";
5
- import style from "./terminal/style.js";
6
- import { AssistantMessageComponent } from "./tui/components/assistant-message.js";
7
- import { FooterComponent } from "./tui/components/footer.js";
8
- import { ThinkingBlockComponent } from "./tui/components/thinking-block.js";
9
- import { ToolExecutionComponent } from "./tui/components/tool-execution.js";
10
- import { Welcome } from "./tui/components/welcome.js";
11
- import { Container, Editor, Loader, NotificationComponent, ProcessTerminal, Spacer, Text, TUI, UserMessageComponent, } from "./tui/index.js";
12
- export class NewRepl {
1
+ import { generateRulesFromSession } from "../commands/generate-rules/service.js";
2
+ import { showModelSelector } from "../commands/model/model-panel.js";
3
+ import { showReviewPanel } from "../commands/review/review-panel.js";
4
+ import { ModeManager } from "../modes/manager.js";
5
+ import { processPrompt } from "../prompts/mentions.js";
6
+ import { createUserMessage } from "../sessions/manager.js";
7
+ import { loadSkills } from "../skills/index.js";
8
+ import { alert, setTerminalTitle, startProgress, stopProgress, } from "../terminal/control.js";
9
+ import style from "../terminal/style.js";
10
+ import { AttachmentProvider, CombinedProvider, CommandProvider, FileSearchProvider, SkillProvider, } from "../tui/autocomplete.js";
11
+ import { AssistantMessageComponent } from "../tui/components/assistant-message.js";
12
+ import { FooterComponent } from "../tui/components/footer.js";
13
+ import { ThinkingBlockComponent } from "../tui/components/thinking-block.js";
14
+ import { ToolExecutionComponent } from "../tui/components/tool-execution.js";
15
+ import { Welcome } from "../tui/components/welcome.js";
16
+ import { launchEditor } from "../tui/editor-launcher.js";
17
+ import { Container, Editor, Loader, NotificationComponent, ProcessTerminal, Spacer, Text, TUI, UserMessageComponent, } from "../tui/index.js";
18
+ import { logger } from "../utils/logger.js";
19
+ import { getProjectStatus } from "./project-status.js";
20
+ /**
21
+ * Interactive Read-Eval-Print Loop that provides the primary user interface
22
+ * for the AI assistant CLI. Manages the TUI layout, handles keyboard shortcuts,
23
+ * processes agent events for streaming responses, and coordinates session lifecycle.
24
+ */
25
+ export class Repl {
13
26
  options;
27
+ terminal;
14
28
  tui;
15
29
  welcome;
16
30
  editor;
@@ -32,9 +46,20 @@ export class NewRepl {
32
46
  streamingComponent = null;
33
47
  // thinking block tracking
34
48
  thinkingBlockComponent = null;
49
+ // Track all verbose-aware components for re-rendering on toggle
50
+ allThinkingBlocks = [];
51
+ allToolExecutions = [];
52
+ // verbose mode state
53
+ verboseMode = false;
54
+ // mode manager
55
+ modeManager;
56
+ // ProjectConfig - initialized in init()
57
+ config;
58
+ /** Creates a new Repl instance, initializing the TUI layout and components. */
35
59
  constructor(options) {
36
60
  this.options = options;
37
- this.tui = new TUI(new ProcessTerminal());
61
+ this.terminal = new ProcessTerminal(options.terminalOptions);
62
+ this.tui = new TUI(this.terminal);
38
63
  this.welcome = new Welcome({ type: "simple" });
39
64
  this.editor = new Editor({
40
65
  borderColor: style.gray,
@@ -42,7 +67,7 @@ export class NewRepl {
42
67
  this.chatContainer = new Container();
43
68
  this.statusContainer = new Container();
44
69
  this.editorContainer = new Container(); // Container to hold editor or selector
45
- this.footer = new FooterComponent(options.modelManager, {
70
+ this.footer = new FooterComponent(options.modelManager, options.tokenTracker, {
46
71
  projectStatus: {
47
72
  path: "",
48
73
  isGitRepository: false,
@@ -52,24 +77,43 @@ export class NewRepl {
52
77
  },
53
78
  currentContextWindow: 0,
54
79
  contextWindow: options.modelManager.getModelMetadata("repl").contextWindow,
55
- usage: this.options.tokenTracker.getUsageByApp("repl"),
56
80
  });
57
81
  this.editorContainer.addChild(this.editor); // Start with editor
58
82
  this.editor.onRenderRequested = () => this.tui.requestRender();
83
+ this.editor.onExternalEditor = async (content) => {
84
+ return launchEditor({
85
+ initialContent: content,
86
+ postfix: ".md",
87
+ terminal: this.terminal,
88
+ });
89
+ };
59
90
  this.isInitialized = false;
60
91
  this.pendingTools = new Map();
61
92
  this.tools = options.tools;
62
- this.notification = new NotificationComponent("", { r: 64, g: 64, b: 64 }, style.yellow, 1);
93
+ this.modeManager = new ModeManager();
94
+ this.notification = new NotificationComponent("", { r: 52, g: 53, b: 65 }, style.yellow, 1, () => this.tui.requestRender());
63
95
  }
96
+ /**
97
+ * Initializes the REPL by setting up autocomplete,
98
+ * keyboard handlers, and the TUI component tree.
99
+ */
64
100
  async init() {
65
101
  if (this.isInitialized) {
102
+ this.notification.setMessage("initialized");
66
103
  return;
67
104
  }
68
- // Setup autocomplete for file paths and slash commands
69
- const { createDefaultProvider } = await import("./tui/autocomplete.js");
70
- const autocompleteProvider = createDefaultProvider([...(await this.options.commands.getCompletions())], this.options.workspace.allowedDirs);
105
+ // Setup autocomplete for file paths, slash commands, and skills
106
+ const skills = await loadSkills();
107
+ const commandsList = await this.options.commands.getCompletions();
108
+ const autocompleteProvider = new CombinedProvider([
109
+ new CommandProvider(commandsList),
110
+ new AttachmentProvider(),
111
+ new FileSearchProvider(),
112
+ new SkillProvider(skills.getModelInvocable()),
113
+ ]);
71
114
  this.editor.setAutocompleteProvider(autocompleteProvider);
72
- const { promptManager, modelManager, messageHistory, commands, promptHistory, } = this.options;
115
+ const { promptManager, modelManager, sessionManager, commands, promptHistory, configManager, } = this.options;
116
+ this.config = await configManager.getConfig();
73
117
  // Listen for session title updates
74
118
  // messageHistory.on("update-title", (title: string) => {
75
119
  // this.footer.setTitle(title);
@@ -80,16 +124,45 @@ export class NewRepl {
80
124
  projectStatus: await getProjectStatus(),
81
125
  currentContextWindow: 0,
82
126
  contextWindow: modelConfig.contextWindow,
83
- usage: this.options.tokenTracker.getUsageByApp("repl"),
127
+ currentMode: this.modeManager.getDisplayName(),
84
128
  });
85
129
  this.tui.onCtrlC = () => {
86
130
  this.handleCtrlC();
87
131
  };
132
+ this.tui.onCtrlD = () => {
133
+ this.handleCtrlD();
134
+ };
135
+ this.tui.onCtrlO = () => {
136
+ this.handleCtrlO();
137
+ };
138
+ this.tui.onCtrlN = () => {
139
+ void this.handleCtrlN();
140
+ };
141
+ this.tui.onCtrlR = () => {
142
+ void showReviewPanel(this.tui, this.chatContainer, this.editorContainer, this.editor);
143
+ };
144
+ this.tui.onCtrlM = () => {
145
+ void this.handleCtrlM();
146
+ };
147
+ this.tui.onShiftTab = () => {
148
+ this.modeManager.cycleMode();
149
+ this.footer.setState({
150
+ projectStatus: this.footer.getProjectStatus(),
151
+ currentContextWindow: this.options.sessionManager.getLastTurnContextWindow(),
152
+ contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
153
+ currentMode: this.modeManager.getDisplayName(),
154
+ });
155
+ this.tui.requestRender();
156
+ };
157
+ // Set callback for session reconstruction (used by /history command)
158
+ this.tui.onReconstructSession = () => this.rerender();
88
159
  this.tui.addChild(this.welcome);
89
160
  // Initialize footer with current title if one exists
90
161
  // this.footer.setTitle(messageHistory.getTitle());
91
162
  this.tui.addChild(this.chatContainer);
92
163
  this.tui.addChild(this.statusContainer);
164
+ // Everything below here is fixed to the bottom of the terminal
165
+ this.tui.setFixedFooterStart();
93
166
  this.tui.addChild(new Spacer(1));
94
167
  this.tui.addChild(this.editorContainer); // Use container that can hold editor or selector
95
168
  this.tui.addChild(this.footer);
@@ -112,38 +185,20 @@ export class NewRepl {
112
185
  inputContainer: this.editorContainer,
113
186
  editor: this.editor,
114
187
  });
115
- if (commandResult.break) {
116
- this.stop(true);
117
- process.exit(0);
118
- }
119
188
  if (commandResult.continue) {
120
189
  this.editor.setText("");
121
190
  this.tui.requestRender();
122
191
  return;
123
192
  }
124
193
  if (!promptManager.isPending()) {
125
- try {
126
- const processedPrompt = await processPrompt(text, {
127
- baseDir: process.cwd(),
128
- model: modelConfig,
129
- });
130
- for (const context of processedPrompt.context) {
131
- promptManager.addContext(context);
132
- }
133
- promptManager.set(processedPrompt.message);
134
- }
135
- catch (error) {
136
- if (error instanceof PromptError) {
137
- this.chatContainer.addChild(new Text(style.red(`Prompt processing failed: ${error.message}`), 1, 1));
138
- if (error.cause &&
139
- typeof error.cause === "object" &&
140
- "command" in error.cause &&
141
- typeof error.cause.command === "string") {
142
- this.chatContainer.addChild(new Text(style.red(`Command: ${error.cause.command}`, 1, 1)));
143
- }
144
- }
145
- throw error; // Re-throw other errors
194
+ const processedPrompt = await processPrompt(text, {
195
+ baseDir: process.cwd(),
196
+ model: modelConfig,
197
+ });
198
+ for (const context of processedPrompt.context) {
199
+ promptManager.addContext(context);
146
200
  }
201
+ promptManager.set(processedPrompt.message);
147
202
  }
148
203
  else {
149
204
  promptHistory.push(promptManager.get());
@@ -153,11 +208,34 @@ export class NewRepl {
153
208
  const hasAddedContext = promptManager.hasContext();
154
209
  if (hasAddedContext) {
155
210
  const contextTokenCount = promptManager.getContextTokenCount();
156
- this.chatContainer.addChild(new Text(style.green(`Context will be added to prompt. (${contextTokenCount} tokens)`)));
211
+ this.addComponentWithSpacing(new Text(style.green(`Context will be added to prompt. (${contextTokenCount} tokens)`)));
157
212
  }
158
213
  const userPrompt = promptManager.get();
159
214
  const userMsg = promptManager.getUserMessage();
160
- messageHistory.appendUserMessage(userMsg);
215
+ if (!this.modeManager.isNormal()) {
216
+ if (this.modeManager.isFirstMessage()) {
217
+ const initialPrompt = this.modeManager.getInitialPrompt();
218
+ if (initialPrompt) {
219
+ const modeMessage = createUserMessage([], initialPrompt);
220
+ sessionManager.appendUserMessage(modeMessage);
221
+ }
222
+ sessionManager.appendUserMessage(userMsg);
223
+ this.modeManager.markFirstMessageSent();
224
+ }
225
+ else {
226
+ sessionManager.appendUserMessage(userMsg);
227
+ const reminderMessage = this.modeManager.getReminderMessage();
228
+ if (reminderMessage) {
229
+ sessionManager.setTransientMessages([reminderMessage]);
230
+ }
231
+ }
232
+ }
233
+ else {
234
+ sessionManager.appendUserMessage(userMsg);
235
+ }
236
+ this.addMessageToChat({ role: "user", content: userPrompt });
237
+ this.editor.setText("");
238
+ this.tui.requestRender();
161
239
  if (this.onInputCallback) {
162
240
  this.onInputCallback(userPrompt);
163
241
  }
@@ -167,18 +245,25 @@ export class NewRepl {
167
245
  this.tui.start();
168
246
  this.isInitialized = true;
169
247
  }
248
+ /**
249
+ * Handles an agent event by updating the TUI accordingly.
250
+ * Processes events such as streaming messages, tool calls, thinking blocks,
251
+ * and agent lifecycle transitions (start, stop, error).
252
+ */
170
253
  async handle(event, state) {
171
254
  if (!this.isInitialized) {
172
255
  await this.init();
173
256
  }
174
257
  // Update footer with current stats
175
258
  // this.footer.updateState(state);
259
+ // Use cached project status for all events to avoid blocking on git
260
+ // subprocess calls; refresh asynchronously at agent-stop
176
261
  this.footer.setState({
177
- projectStatus: await getProjectStatus(),
178
- currentContextWindow: this.options.messageHistory.getContextWindow(),
262
+ projectStatus: this.footer.getProjectStatus(),
263
+ currentContextWindow: this.options.sessionManager.getLastTurnContextWindow(),
179
264
  contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
180
265
  agentState: state,
181
- usage: this.options.tokenTracker.getUsageByApp("repl"),
266
+ currentMode: this.modeManager.getDisplayName(),
182
267
  });
183
268
  const eventType = event.type;
184
269
  switch (eventType) {
@@ -209,19 +294,13 @@ export class NewRepl {
209
294
  // Create assistant component for streaming
210
295
  const assistantMessageComponent = new AssistantMessageComponent();
211
296
  this.streamingComponent = assistantMessageComponent;
212
- this.chatContainer.addChild(assistantMessageComponent);
297
+ this.addComponentWithSpacing(assistantMessageComponent);
213
298
  this.streamingComponent.updateContent(event);
214
299
  this.tui.requestRender();
215
300
  }
216
301
  break;
217
302
  case "message":
218
- if (event.role === "user") {
219
- // Show user message immediately and clear editor
220
- this.addMessageToChat(event);
221
- this.editor.setText("");
222
- this.tui.requestRender();
223
- }
224
- else if (event.role === "assistant") {
303
+ if (event.role === "assistant") {
225
304
  // Update streaming component
226
305
  if (this.streamingComponent && event.role === "assistant") {
227
306
  this.streamingComponent.updateContent(event);
@@ -243,9 +322,12 @@ export class NewRepl {
243
322
  }
244
323
  else {
245
324
  // Create tool component for new tool call
246
- const newComponent = new ToolExecutionComponent(event.events);
325
+ const newComponent = new ToolExecutionComponent(event.events, {
326
+ verboseMode: this.verboseMode,
327
+ });
247
328
  this.pendingTools.set(event.toolCallId, newComponent);
248
- this.chatContainer.addChild(newComponent);
329
+ this.allToolExecutions.push(newComponent);
330
+ this.addComponentWithSpacing(newComponent);
249
331
  }
250
332
  this.tui.requestRender();
251
333
  break;
@@ -267,10 +349,30 @@ export class NewRepl {
267
349
  }
268
350
  this.pendingTools.clear();
269
351
  this.editor.disableSubmit = false;
352
+ this.options.sessionManager.clearTransientMessages();
353
+ this.options.sessionManager.setMetadata("modeState", this.modeManager.toJson());
354
+ if (!this.options.noSession) {
355
+ await this.options.sessionManager.save();
356
+ }
357
+ // Refresh project status now that agent may have modified files
358
+ await getProjectStatus().then((ps) => {
359
+ this.footer.setState({
360
+ projectStatus: ps,
361
+ currentContextWindow: this.options.sessionManager.getLastTurnContextWindow(),
362
+ contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
363
+ currentMode: this.modeManager.getDisplayName(),
364
+ });
365
+ this.tui.requestRender();
366
+ });
270
367
  this.tui.requestRender();
271
368
  break;
272
369
  case "agent-error":
273
370
  logger.error(event, "agent-error");
371
+ this.options.sessionManager.clearTransientMessages();
372
+ this.options.sessionManager.setMetadata("modeState", this.modeManager.toJson());
373
+ if (!this.options.noSession) {
374
+ await this.options.sessionManager.save();
375
+ }
274
376
  // Stop loading animation
275
377
  if (this.loadingAnimation) {
276
378
  this.loadingAnimation.stop();
@@ -286,9 +388,12 @@ export class NewRepl {
286
388
  this.tui.requestRender();
287
389
  break;
288
390
  case "thinking-start": {
289
- const component = new ThinkingBlockComponent();
391
+ const component = new ThinkingBlockComponent(undefined, {
392
+ verboseMode: this.verboseMode,
393
+ });
290
394
  this.thinkingBlockComponent = component;
291
- this.chatContainer.addChild(component);
395
+ this.allThinkingBlocks.push(component);
396
+ this.addComponentWithSpacing(component);
292
397
  this.thinkingBlockComponent.updateContent(event);
293
398
  this.tui.requestRender();
294
399
  break;
@@ -301,7 +406,7 @@ export class NewRepl {
301
406
  break;
302
407
  case "thinking-end":
303
408
  if (this.thinkingBlockComponent) {
304
- this.thinkingBlockComponent.updateContent(event);
409
+ this.thinkingBlockComponent.endThinking();
305
410
  this.thinkingBlockComponent = null;
306
411
  this.tui.requestRender();
307
412
  }
@@ -310,16 +415,26 @@ export class NewRepl {
310
415
  eventType;
311
416
  }
312
417
  }
418
+ /** Adds a user message component to the chat container. */
313
419
  addMessageToChat(message) {
314
420
  if (message.role === "user") {
315
421
  // Extract text content from content blocks
316
422
  const textContent = message.content;
317
423
  if (textContent) {
318
424
  const userComponent = new UserMessageComponent(textContent);
319
- this.chatContainer.addChild(userComponent);
425
+ this.addComponentWithSpacing(userComponent);
320
426
  }
321
427
  }
322
428
  }
429
+ /**
430
+ * Adds a component to the chat container with spacing.
431
+ * Adds a Spacer(1) before every component (including the first).
432
+ */
433
+ addComponentWithSpacing(component) {
434
+ this.chatContainer.addChild(new Spacer(1));
435
+ this.chatContainer.addChild(component);
436
+ }
437
+ /** Returns a promise that resolves with the user's next input submission. */
323
438
  async getUserInput() {
324
439
  return new Promise((resolve) => {
325
440
  this.onInputCallback = (text) => {
@@ -328,33 +443,75 @@ export class NewRepl {
328
443
  };
329
444
  });
330
445
  }
446
+ /** Clears the editor input and triggers a re-render. */
331
447
  clearEditor() {
332
448
  this.editor.setText("");
333
449
  this.tui.requestRender();
334
450
  }
451
+ /**
452
+ * Sets the callback invoked when the user presses
453
+ * Escape to interrupt the agent.
454
+ */
335
455
  setInterruptCallback(callback) {
336
456
  this.onInterruptCallback = callback;
337
457
  }
458
+ /** Sets the callback invoked on exit, receiving the current session ID. */
338
459
  setExitCallback(callback) {
339
460
  this.onExitCallback = callback;
340
461
  }
462
+ /**
463
+ * Re-renders the entire session by restoring mode state,
464
+ * replaying token usage, and reconstructing the chat
465
+ * display.
466
+ */
341
467
  async rerender() {
468
+ const modeState = this.options.sessionManager.getMetadata("modeState");
469
+ if (modeState && typeof modeState === "object" && "mode" in modeState) {
470
+ this.modeManager.fromJson(modeState);
471
+ }
472
+ // When resuming a session, populate tokenTracker with historical usage
473
+ // so the footer displays the correct total session usage
474
+ const totalUsage = this.options.sessionManager.getTotalTokenUsage();
475
+ if (totalUsage.inputTokens > 0 || totalUsage.outputTokens > 0) {
476
+ this.options.tokenTracker.trackUsage("repl", {
477
+ inputTokens: totalUsage.inputTokens,
478
+ outputTokens: totalUsage.outputTokens,
479
+ totalTokens: totalUsage.totalTokens,
480
+ inputTokenDetails: {
481
+ noCacheTokens: totalUsage.inputTokens - totalUsage.cachedInputTokens,
482
+ cacheReadTokens: totalUsage.cachedInputTokens,
483
+ cacheWriteTokens: 0,
484
+ },
485
+ outputTokenDetails: {
486
+ textTokens: totalUsage.outputTokens,
487
+ reasoningTokens: totalUsage.reasoningTokens,
488
+ },
489
+ });
490
+ }
342
491
  this.footer.setState({
343
492
  projectStatus: await getProjectStatus(),
344
- currentContextWindow: this.options.messageHistory.getContextWindow(),
493
+ currentContextWindow: this.options.sessionManager.getLastTurnContextWindow(),
345
494
  contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
346
- usage: this.options.tokenTracker.getUsageByApp("repl"),
495
+ currentMode: this.modeManager.getDisplayName(),
347
496
  });
348
497
  // Reconstruct entire session display from messages
349
498
  this.reconstructSession();
499
+ this.tui.scrollToBottom();
350
500
  this.tui.requestRender();
351
501
  }
502
+ /**
503
+ * Rebuilds the chat container from the full session
504
+ * message history, including user messages, assistant
505
+ * responses, and tool executions.
506
+ */
352
507
  reconstructSession() {
353
508
  // Clear existing display
354
509
  this.pendingTools.clear();
510
+ this.allThinkingBlocks = [];
511
+ this.allToolExecutions = [];
355
512
  this.chatContainer.clear();
356
513
  // Get session messages
357
- const messages = this.options.messageHistory.get();
514
+ const messages = this.options.sessionManager.get();
358
515
  // First pass: collect all tool results
359
516
  const toolResults = new Map();
360
517
  for (const message of messages) {
@@ -385,7 +542,7 @@ export class NewRepl {
385
542
  const textContent = this.extractUserMessageText(message);
386
543
  if (textContent) {
387
544
  const userComponent = new UserMessageComponent(textContent);
388
- this.chatContainer.addChild(userComponent);
545
+ this.addComponentWithSpacing(userComponent);
389
546
  }
390
547
  }
391
548
  else if (message.role === "assistant") {
@@ -397,15 +554,22 @@ export class NewRepl {
397
554
  const toolCallId = toolCallContent.toolCallId;
398
555
  const events = this.createToolEvents(toolCallContent);
399
556
  if (events.length > 0) {
400
- const component = new ToolExecutionComponent(events);
557
+ const component = new ToolExecutionComponent(events, {
558
+ verboseMode: this.verboseMode,
559
+ });
401
560
  this.pendingTools.set(toolCallId, component);
402
- this.chatContainer.addChild(component);
561
+ this.allToolExecutions.push(component);
562
+ this.addComponentWithSpacing(component);
403
563
  }
404
564
  }
405
565
  }
406
566
  // Tool messages are handled through their associated assistant message
407
567
  }
408
568
  }
569
+ /**
570
+ * Extracts the text content from a user message,
571
+ * handling both string and array content formats.
572
+ */
409
573
  extractUserMessageText(message) {
410
574
  if (typeof message.content === "string") {
411
575
  return message.content;
@@ -418,6 +582,10 @@ export class NewRepl {
418
582
  }
419
583
  return null;
420
584
  }
585
+ /**
586
+ * Renders an assistant message into the chat container,
587
+ * including reasoning/thinking blocks and text content.
588
+ */
421
589
  renderAssistantMessage(message) {
422
590
  if (typeof message.content === "string") {
423
591
  if (message.content.trim()) {
@@ -427,10 +595,25 @@ export class NewRepl {
427
595
  role: "assistant",
428
596
  content: message.content,
429
597
  });
430
- this.chatContainer.addChild(assistantComponent);
598
+ this.addComponentWithSpacing(assistantComponent);
431
599
  }
432
600
  }
433
601
  else if (Array.isArray(message.content)) {
602
+ const reasoningParts = message.content
603
+ .filter((part) => part.type === "reasoning" &&
604
+ typeof part.text === "string" &&
605
+ part.text.trim().length > 0)
606
+ .map((part) => part.text)
607
+ .join("\n");
608
+ if (reasoningParts.trim()) {
609
+ const thinkingComponent = new ThinkingBlockComponent(undefined, {
610
+ verboseMode: this.verboseMode,
611
+ });
612
+ thinkingComponent.updateContent({ content: reasoningParts });
613
+ thinkingComponent.endThinking();
614
+ this.allThinkingBlocks.push(thinkingComponent);
615
+ this.addComponentWithSpacing(thinkingComponent);
616
+ }
434
617
  const textParts = message.content
435
618
  .filter((part) => part.type === "text" && part.text?.trim() !== undefined)
436
619
  .map((part) => part.text)
@@ -442,10 +625,15 @@ export class NewRepl {
442
625
  role: "assistant",
443
626
  content: textParts,
444
627
  });
445
- this.chatContainer.addChild(assistantComponent);
628
+ this.addComponentWithSpacing(assistantComponent);
446
629
  }
447
630
  }
448
631
  }
632
+ /**
633
+ * Extracts tool call content parts from an assistant
634
+ * message, matching them with their corresponding
635
+ * tool results.
636
+ */
449
637
  extractToolCallsFromAssistant(message, toolResults) {
450
638
  const toolCallContents = [];
451
639
  if (typeof message.content === "string") {
@@ -470,6 +658,11 @@ export class NewRepl {
470
658
  }
471
659
  return toolCallContents;
472
660
  }
661
+ /**
662
+ * Creates start and end/error ToolEvent pairs from a
663
+ * resolved tool call, using the tool's display function
664
+ * when available.
665
+ */
473
666
  createToolEvents(toolCallContent) {
474
667
  const events = [];
475
668
  // tool-call-start: use the tool's display function
@@ -498,6 +691,88 @@ export class NewRepl {
498
691
  });
499
692
  return events;
500
693
  }
694
+ /**
695
+ * Toggles verbose mode on/off, updating all thinking
696
+ * block and tool execution components.
697
+ */
698
+ handleCtrlO() {
699
+ this.verboseMode = !this.verboseMode;
700
+ const modeText = this.verboseMode ? "ON" : "OFF";
701
+ this.notification.setMessage(`Verbose mode: ${modeText}`);
702
+ // Update all verbose-aware components to reflect new verbose mode
703
+ for (const component of this.allThinkingBlocks) {
704
+ component.setVerboseMode(this.verboseMode);
705
+ }
706
+ for (const component of this.allToolExecutions) {
707
+ component.setVerboseMode(this.verboseMode);
708
+ }
709
+ this.tui.requestRender();
710
+ }
711
+ /**
712
+ * Starts a new session by saving the current one,
713
+ * resetting mode/tokens/UI, and clearing the chat.
714
+ */
715
+ async handleCtrlN() {
716
+ if (!this.options.sessionManager.isEmpty()) {
717
+ // Auto-generate rules before starting new session if enabled
718
+ // Run in background - don't block new session from starting
719
+ this.maybeGenerateRules().catch((err) => logger.debug({ err }, "Background rule generation failed"));
720
+ this.options.sessionManager.setMetadata("modeState", this.modeManager.toJson());
721
+ if (!this.options.noSession) {
722
+ await this.options.sessionManager.save();
723
+ }
724
+ this.options.sessionManager.create(this.options.modelManager.getModel("repl").modelId);
725
+ }
726
+ this.config = await this.options.configManager.getConfig();
727
+ this.modeManager.reset();
728
+ this.options.sessionManager.clearTransientMessages();
729
+ this.options.tokenTracker.reset();
730
+ setTerminalTitle(`acai: ${process.cwd()}`);
731
+ this.chatContainer.clear();
732
+ this.editor.setText("");
733
+ // Reset footer state to clear usage/cost/steps/tools/time
734
+ const footer = this.tui.children.find((child) => child.constructor.name === "FooterComponent");
735
+ if (footer) {
736
+ footer.resetState();
737
+ }
738
+ this.footer.setState({
739
+ projectStatus: this.footer.getProjectStatus(),
740
+ currentContextWindow: 0,
741
+ contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
742
+ currentMode: this.modeManager.getDisplayName(),
743
+ });
744
+ this.tui.requestRender();
745
+ }
746
+ handleCtrlM() {
747
+ showModelSelector(this.tui, this.editorContainer, this.editor, this.options.modelManager);
748
+ }
749
+ /** Handles Ctrl+D to exit the REPL when the editor is empty. */
750
+ handleCtrlD() {
751
+ // Only exit if the editor is empty
752
+ if (this.editor.getText().trim() !== "") {
753
+ // Editor has content, do nothing
754
+ return;
755
+ }
756
+ // Editor is empty - proceed with exit
757
+ // Clear any pending notification timer
758
+ if (this.exitNotificationTimer) {
759
+ clearTimeout(this.exitNotificationTimer);
760
+ this.exitNotificationTimer = undefined;
761
+ }
762
+ this.notification.setMessage("");
763
+ this.tui.requestRender();
764
+ this.options.sessionManager.setMetadata("modeState", this.modeManager.toJson());
765
+ if (!this.options.noSession) {
766
+ void this.options.sessionManager.save();
767
+ }
768
+ this.stop(true);
769
+ process.exit(0);
770
+ }
771
+ /**
772
+ * Handles Ctrl+C with double-press-to-exit logic.
773
+ * First press clears the editor; second press within
774
+ * 1 second exits.
775
+ */
501
776
  handleCtrlC() {
502
777
  // Handle Ctrl+C double-press logic
503
778
  const now = Date.now();
@@ -512,14 +787,17 @@ export class NewRepl {
512
787
  }
513
788
  this.notification.setMessage("");
514
789
  this.tui.requestRender();
515
- void this.options.messageHistory.save();
790
+ this.options.sessionManager.setMetadata("modeState", this.modeManager.toJson());
791
+ if (!this.options.noSession) {
792
+ void this.options.sessionManager.save();
793
+ }
516
794
  this.stop(true);
517
795
  process.exit(0);
518
796
  }
519
797
  else {
520
798
  // First Ctrl+C - clear the editor and show notification
521
799
  this.clearEditor();
522
- this.notification.setMessage("Press Ctrl+C again to exit");
800
+ this.notification.setMessage("Press Ctrl+C again to exit", 1000);
523
801
  this.tui.requestRender();
524
802
  this.lastSigintTime = now;
525
803
  // Clear notification after threshold if no second Ctrl+C
@@ -535,6 +813,10 @@ export class NewRepl {
535
813
  }, DoublePressThreshold);
536
814
  }
537
815
  }
816
+ /**
817
+ * Stops the REPL, cleaning up timers, animations,
818
+ * and the TUI. Optionally triggers the exit callback.
819
+ */
538
820
  stop(showExitMessage = false) {
539
821
  this.notification.setMessage("");
540
822
  // Clear any pending notification timer
@@ -543,7 +825,11 @@ export class NewRepl {
543
825
  this.exitNotificationTimer = undefined;
544
826
  }
545
827
  if (showExitMessage && this.onExitCallback) {
546
- this.onExitCallback(this.options.messageHistory.getSessionId());
828
+ const result = this.onExitCallback(this.options.sessionManager.getSessionId());
829
+ // Await if the callback is async
830
+ if (result instanceof Promise) {
831
+ void result;
832
+ }
547
833
  }
548
834
  if (this.loadingAnimation) {
549
835
  this.loadingAnimation.stop();
@@ -554,4 +840,41 @@ export class NewRepl {
554
840
  this.isInitialized = false;
555
841
  }
556
842
  }
843
+ /**
844
+ * Generates rules automatically if autoGenerateRules is enabled
845
+ * and the session has messages.
846
+ */
847
+ async maybeGenerateRules() {
848
+ try {
849
+ const config = this.config;
850
+ if (!config.autoGenerateRules) {
851
+ return;
852
+ }
853
+ if (this.options.sessionManager.isEmpty()) {
854
+ return;
855
+ }
856
+ const { rules } = await generateRulesFromSession({
857
+ modelManager: this.options.modelManager,
858
+ messages: this.options.sessionManager.get(),
859
+ tokenTracker: this.options.tokenTracker,
860
+ config: this.options.configManager,
861
+ workspace: this.options.workspace,
862
+ });
863
+ if (rules.length > 0) {
864
+ this.notification.setMessage(`Auto-generated ${rules.length} rule(s) saved`);
865
+ this.tui.requestRender();
866
+ }
867
+ }
868
+ catch (error) {
869
+ // Don't fail the session transition on rule generation errors
870
+ logger.debug({ error }, "Auto rule generation failed");
871
+ }
872
+ }
873
+ /**
874
+ * Triggers rule generation for use before exit.
875
+ * Returns a promise that resolves when rules are generated.
876
+ */
877
+ async triggerRuleGeneration() {
878
+ await this.maybeGenerateRules();
879
+ }
557
880
  }