@travisennis/acai 0.0.9 → 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 (371) hide show
  1. package/README.md +49 -762
  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 -199
  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 -77
  12. package/dist/cli/stdin.d.ts.map +1 -0
  13. package/dist/{stdin.js → cli/stdin.js} +11 -0
  14. package/dist/commands/copy/index.js +2 -2
  15. package/dist/commands/copy/utils.d.ts.map +1 -1
  16. package/dist/commands/copy/utils.js +15 -13
  17. package/dist/commands/generate-rules/index.d.ts +1 -1
  18. package/dist/commands/generate-rules/index.d.ts.map +1 -1
  19. package/dist/commands/generate-rules/index.js +16 -101
  20. package/dist/commands/generate-rules/service.d.ts +21 -0
  21. package/dist/commands/generate-rules/service.d.ts.map +1 -0
  22. package/dist/commands/generate-rules/service.js +103 -0
  23. package/dist/commands/handoff/index.js +2 -2
  24. package/dist/commands/health/index.js +1 -1
  25. package/dist/commands/health/utils.d.ts.map +1 -1
  26. package/dist/commands/health/utils.js +6 -0
  27. package/dist/commands/history/index.d.ts +1 -1
  28. package/dist/commands/history/index.d.ts.map +1 -1
  29. package/dist/commands/history/index.js +17 -18
  30. package/dist/commands/history/types.d.ts +38 -0
  31. package/dist/commands/history/types.d.ts.map +1 -1
  32. package/dist/commands/history/utils.d.ts.map +1 -1
  33. package/dist/commands/history/utils.js +63 -58
  34. package/dist/commands/init/index.d.ts.map +1 -1
  35. package/dist/commands/init/index.js +3 -8
  36. package/dist/commands/init-project/index.d.ts.map +1 -1
  37. package/dist/commands/init-project/index.js +3 -3
  38. package/dist/commands/init-project/utils.d.ts.map +1 -1
  39. package/dist/commands/init-project/utils.js +10 -2
  40. package/dist/commands/list-tools/index.d.ts.map +1 -1
  41. package/dist/commands/list-tools/index.js +7 -31
  42. package/dist/commands/manager.d.ts +2 -2
  43. package/dist/commands/manager.d.ts.map +1 -1
  44. package/dist/commands/manager.js +55 -33
  45. package/dist/commands/model/index.d.ts.map +1 -1
  46. package/dist/commands/model/index.js +20 -151
  47. package/dist/commands/model/model-panel.d.ts +4 -0
  48. package/dist/commands/model/model-panel.d.ts.map +1 -0
  49. package/dist/commands/model/model-panel.js +144 -0
  50. package/dist/commands/paste/index.d.ts.map +1 -1
  51. package/dist/commands/paste/index.js +59 -62
  52. package/dist/commands/paste/utils.d.ts.map +1 -1
  53. package/dist/commands/paste/utils.js +88 -58
  54. package/dist/commands/pickup/index.d.ts.map +1 -1
  55. package/dist/commands/pickup/index.js +6 -3
  56. package/dist/commands/pickup/utils.js +3 -3
  57. package/dist/commands/resources/index.d.ts.map +1 -1
  58. package/dist/commands/resources/index.js +33 -50
  59. package/dist/commands/review/index.d.ts.map +1 -1
  60. package/dist/commands/review/index.js +3 -117
  61. package/dist/commands/review/review-panel.d.ts +3 -0
  62. package/dist/commands/review/review-panel.d.ts.map +1 -0
  63. package/dist/commands/review/review-panel.js +186 -0
  64. package/dist/commands/review/utils.d.ts +9 -0
  65. package/dist/commands/review/utils.d.ts.map +1 -1
  66. package/dist/commands/review/utils.js +127 -68
  67. package/dist/commands/session/index.d.ts +1 -1
  68. package/dist/commands/session/index.d.ts.map +1 -1
  69. package/dist/commands/session/index.js +124 -135
  70. package/dist/commands/shell/index.d.ts.map +1 -1
  71. package/dist/commands/shell/index.js +16 -1
  72. package/dist/commands/types.d.ts +2 -2
  73. package/dist/commands/types.d.ts.map +1 -1
  74. package/dist/{config.d.ts → config/index.d.ts} +20 -9
  75. package/dist/config/index.d.ts.map +1 -0
  76. package/dist/{config.js → config/index.js} +43 -42
  77. package/dist/execution/index.d.ts.map +1 -1
  78. package/dist/execution/index.js +75 -55
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +148 -141
  82. package/dist/middleware/cache.d.ts.map +1 -1
  83. package/dist/middleware/cache.js +18 -36
  84. package/dist/models/ai-config.d.ts +1 -0
  85. package/dist/models/ai-config.d.ts.map +1 -1
  86. package/dist/models/ai-config.js +4 -3
  87. package/dist/models/anthropic-provider.d.ts +2 -5
  88. package/dist/models/anthropic-provider.d.ts.map +1 -1
  89. package/dist/models/anthropic-provider.js +3 -70
  90. package/dist/models/deepseek-provider.d.ts +1 -0
  91. package/dist/models/deepseek-provider.d.ts.map +1 -1
  92. package/dist/models/google-provider.d.ts +2 -3
  93. package/dist/models/google-provider.d.ts.map +1 -1
  94. package/dist/models/google-provider.js +0 -26
  95. package/dist/models/groq-provider.d.ts +1 -0
  96. package/dist/models/groq-provider.d.ts.map +1 -1
  97. package/dist/models/manager.d.ts +13 -2
  98. package/dist/models/manager.d.ts.map +1 -1
  99. package/dist/models/manager.js +20 -8
  100. package/dist/models/openai-provider.d.ts +2 -5
  101. package/dist/models/openai-provider.d.ts.map +1 -1
  102. package/dist/models/openai-provider.js +0 -52
  103. package/dist/models/opencode-zen-provider.d.ts +7 -3
  104. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  105. package/dist/models/opencode-zen-provider.js +49 -10
  106. package/dist/models/openrouter-provider.d.ts +24 -31
  107. package/dist/models/openrouter-provider.d.ts.map +1 -1
  108. package/dist/models/openrouter-provider.js +84 -182
  109. package/dist/models/providers.d.ts +1 -1
  110. package/dist/models/providers.d.ts.map +1 -1
  111. package/dist/models/xai-provider.d.ts +4 -3
  112. package/dist/models/xai-provider.d.ts.map +1 -1
  113. package/dist/models/xai-provider.js +18 -18
  114. package/dist/modes/manager.d.ts +23 -0
  115. package/dist/modes/manager.d.ts.map +1 -0
  116. package/dist/modes/manager.js +77 -0
  117. package/dist/modes/prompts.d.ts +2 -0
  118. package/dist/modes/prompts.d.ts.map +1 -0
  119. package/dist/modes/prompts.js +143 -0
  120. package/dist/prompts/mentions.d.ts +11 -0
  121. package/dist/prompts/mentions.d.ts.map +1 -0
  122. package/dist/{mentions.js → prompts/mentions.js} +21 -80
  123. package/dist/{prompts.d.ts → prompts/system-prompt.d.ts} +7 -2
  124. package/dist/prompts/system-prompt.d.ts.map +1 -0
  125. package/dist/{prompts.js → prompts/system-prompt.js} +31 -16
  126. package/dist/repl/index.d.ts +174 -0
  127. package/dist/repl/index.d.ts.map +1 -0
  128. package/dist/{repl-new.js → repl/index.js} +389 -76
  129. package/dist/repl/project-status.d.ts +1 -0
  130. package/dist/repl/project-status.d.ts.map +1 -1
  131. package/dist/repl/project-status.js +4 -1
  132. package/dist/sessions/manager.d.ts +93 -1
  133. package/dist/sessions/manager.d.ts.map +1 -1
  134. package/dist/sessions/manager.js +262 -9
  135. package/dist/sessions/summary.d.ts +4 -0
  136. package/dist/sessions/summary.d.ts.map +1 -0
  137. package/dist/sessions/summary.js +30 -0
  138. package/dist/{skills.d.ts → skills/index.d.ts} +14 -2
  139. package/dist/skills/index.d.ts.map +1 -0
  140. package/dist/skills/index.js +294 -0
  141. package/dist/subagents/index.d.ts +15 -0
  142. package/dist/subagents/index.d.ts.map +1 -0
  143. package/dist/subagents/index.js +231 -0
  144. package/dist/terminal/control.d.ts +1 -1
  145. package/dist/terminal/control.d.ts.map +1 -1
  146. package/dist/terminal/control.js +3 -3
  147. package/dist/terminal/east-asian-width.d.ts.map +1 -1
  148. package/dist/terminal/east-asian-width.js +404 -351
  149. package/dist/terminal/keys.d.ts +17 -0
  150. package/dist/terminal/keys.d.ts.map +1 -1
  151. package/dist/terminal/keys.js +37 -0
  152. package/dist/terminal/select-prompt.d.ts.map +1 -1
  153. package/dist/terminal/select-prompt.js +24 -12
  154. package/dist/terminal/string-width.d.ts.map +1 -1
  155. package/dist/terminal/string-width.js +25 -27
  156. package/dist/terminal/style.d.ts.map +1 -1
  157. package/dist/terminal/style.js +4 -7
  158. package/dist/terminal/supports-color.d.ts.map +1 -1
  159. package/dist/terminal/supports-color.js +41 -27
  160. package/dist/terminal/table/cell.d.ts +12 -0
  161. package/dist/terminal/table/cell.d.ts.map +1 -1
  162. package/dist/terminal/table/cell.js +40 -25
  163. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  164. package/dist/terminal/table/layout-manager.js +100 -68
  165. package/dist/terminal/table/utils.d.ts.map +1 -1
  166. package/dist/terminal/table/utils.js +17 -10
  167. package/dist/terminal/wrap-ansi.d.ts.map +1 -1
  168. package/dist/terminal/wrap-ansi.js +172 -103
  169. package/dist/tokens/tracker.d.ts +1 -0
  170. package/dist/tokens/tracker.d.ts.map +1 -1
  171. package/dist/tokens/tracker.js +3 -0
  172. package/dist/tools/agent.d.ts +27 -0
  173. package/dist/tools/agent.d.ts.map +1 -0
  174. package/dist/tools/agent.js +81 -0
  175. package/dist/tools/bash.d.ts +4 -3
  176. package/dist/tools/bash.d.ts.map +1 -1
  177. package/dist/tools/bash.js +324 -137
  178. package/dist/tools/code-search.d.ts +41 -0
  179. package/dist/tools/code-search.d.ts.map +1 -0
  180. package/dist/tools/code-search.js +195 -0
  181. package/dist/tools/directory-tree.d.ts +3 -3
  182. package/dist/tools/directory-tree.d.ts.map +1 -1
  183. package/dist/tools/directory-tree.js +8 -5
  184. package/dist/tools/dynamic-tool-loader.d.ts +2 -5
  185. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  186. package/dist/tools/dynamic-tool-loader.js +20 -4
  187. package/dist/tools/edit-file.d.ts +7 -7
  188. package/dist/tools/edit-file.d.ts.map +1 -1
  189. package/dist/tools/edit-file.js +164 -66
  190. package/dist/tools/glob.d.ts +6 -6
  191. package/dist/tools/glob.d.ts.map +1 -1
  192. package/dist/tools/glob.js +95 -55
  193. package/dist/tools/grep.d.ts +15 -12
  194. package/dist/tools/grep.d.ts.map +1 -1
  195. package/dist/tools/grep.js +300 -192
  196. package/dist/tools/index.d.ts +143 -5
  197. package/dist/tools/index.d.ts.map +1 -1
  198. package/dist/tools/index.js +39 -24
  199. package/dist/tools/ls.d.ts +2 -2
  200. package/dist/tools/ls.d.ts.map +1 -1
  201. package/dist/tools/ls.js +7 -5
  202. package/dist/tools/read-file.d.ts +3 -3
  203. package/dist/tools/read-file.d.ts.map +1 -1
  204. package/dist/tools/read-file.js +74 -34
  205. package/dist/tools/save-file.d.ts +3 -3
  206. package/dist/tools/save-file.d.ts.map +1 -1
  207. package/dist/tools/save-file.js +11 -11
  208. package/dist/tools/skill.d.ts +23 -0
  209. package/dist/tools/skill.d.ts.map +1 -0
  210. package/dist/tools/skill.js +65 -0
  211. package/dist/tools/think.d.ts.map +1 -1
  212. package/dist/tools/think.js +2 -9
  213. package/dist/tools/utils.d.ts +2 -0
  214. package/dist/tools/utils.d.ts.map +1 -1
  215. package/dist/tools/utils.js +12 -0
  216. package/dist/tools/web-fetch.d.ts +62 -0
  217. package/dist/tools/web-fetch.d.ts.map +1 -0
  218. package/dist/tools/web-fetch.js +429 -0
  219. package/dist/tools/web-search.d.ts +62 -0
  220. package/dist/tools/web-search.d.ts.map +1 -0
  221. package/dist/tools/web-search.js +226 -0
  222. package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
  223. package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
  224. package/dist/tui/autocomplete/attachment-provider.js +25 -78
  225. package/dist/tui/autocomplete/base-provider.d.ts +1 -0
  226. package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
  227. package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
  228. package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
  229. package/dist/tui/autocomplete/combined-provider.js +3 -17
  230. package/dist/tui/autocomplete/command-provider.d.ts +1 -0
  231. package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
  232. package/dist/tui/autocomplete/command-provider.js +3 -0
  233. package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
  234. package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
  235. package/dist/tui/autocomplete/file-search-provider.js +36 -16
  236. package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
  237. package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
  238. package/dist/tui/autocomplete/skill-provider.js +49 -0
  239. package/dist/tui/autocomplete.d.ts +2 -2
  240. package/dist/tui/autocomplete.d.ts.map +1 -1
  241. package/dist/tui/autocomplete.js +3 -5
  242. package/dist/tui/components/assistant-message.d.ts.map +1 -1
  243. package/dist/tui/components/assistant-message.js +0 -4
  244. package/dist/tui/components/editor.d.ts +16 -2
  245. package/dist/tui/components/editor.d.ts.map +1 -1
  246. package/dist/tui/components/editor.js +211 -237
  247. package/dist/tui/components/footer.d.ts +6 -4
  248. package/dist/tui/components/footer.d.ts.map +1 -1
  249. package/dist/tui/components/footer.js +49 -25
  250. package/dist/tui/components/markdown.d.ts +8 -5
  251. package/dist/tui/components/markdown.d.ts.map +1 -1
  252. package/dist/tui/components/markdown.js +57 -39
  253. package/dist/tui/components/modal.d.ts.map +1 -1
  254. package/dist/tui/components/modal.js +35 -33
  255. package/dist/tui/components/notification.d.ts +13 -2
  256. package/dist/tui/components/notification.d.ts.map +1 -1
  257. package/dist/tui/components/notification.js +36 -2
  258. package/dist/tui/components/progress-bar.js +1 -1
  259. package/dist/tui/components/select-list.d.ts +1 -0
  260. package/dist/tui/components/select-list.d.ts.map +1 -1
  261. package/dist/tui/components/select-list.js +14 -11
  262. package/dist/tui/components/text.d.ts +16 -0
  263. package/dist/tui/components/text.d.ts.map +1 -1
  264. package/dist/tui/components/text.js +72 -57
  265. package/dist/tui/components/thinking-block.d.ts +9 -0
  266. package/dist/tui/components/thinking-block.d.ts.map +1 -1
  267. package/dist/tui/components/thinking-block.js +43 -11
  268. package/dist/tui/components/tool-execution.d.ts +5 -1
  269. package/dist/tui/components/tool-execution.d.ts.map +1 -1
  270. package/dist/tui/components/tool-execution.js +19 -10
  271. package/dist/tui/components/user-message.d.ts.map +1 -1
  272. package/dist/tui/components/user-message.js +0 -3
  273. package/dist/tui/components/welcome.js +2 -2
  274. package/dist/tui/terminal.d.ts.map +1 -1
  275. package/dist/tui/terminal.js +10 -2
  276. package/dist/tui/tui.d.ts +42 -0
  277. package/dist/tui/tui.d.ts.map +1 -1
  278. package/dist/tui/tui.js +157 -41
  279. package/dist/utils/bash/parse.d.ts +19 -0
  280. package/dist/utils/bash/parse.d.ts.map +1 -0
  281. package/dist/utils/bash/parse.js +223 -0
  282. package/dist/utils/bash/quote.d.ts +6 -0
  283. package/dist/utils/bash/quote.d.ts.map +1 -0
  284. package/dist/utils/bash/quote.js +23 -0
  285. package/dist/utils/bash.d.ts.map +1 -1
  286. package/dist/utils/bash.js +211 -126
  287. package/dist/utils/command-protection.d.ts +28 -0
  288. package/dist/utils/command-protection.d.ts.map +1 -0
  289. package/dist/utils/command-protection.js +324 -0
  290. package/dist/utils/dedent.d.ts.map +1 -0
  291. package/dist/utils/env-expand.d.ts +2 -0
  292. package/dist/utils/env-expand.d.ts.map +1 -0
  293. package/dist/utils/env-expand.js +8 -0
  294. package/dist/utils/filesystem/path-display.d.ts +11 -0
  295. package/dist/utils/filesystem/path-display.d.ts.map +1 -0
  296. package/dist/utils/filesystem/path-display.js +32 -0
  297. package/dist/utils/filesystem/security.d.ts +2 -2
  298. package/dist/utils/filesystem/security.d.ts.map +1 -1
  299. package/dist/utils/filesystem/security.js +28 -30
  300. package/dist/utils/formatting.d.ts.map +1 -0
  301. package/dist/{formatting.js → utils/formatting.js} +1 -1
  302. package/dist/utils/git.d.ts +4 -0
  303. package/dist/utils/git.d.ts.map +1 -1
  304. package/dist/utils/git.js +30 -0
  305. package/dist/utils/glob.d.ts +1 -1
  306. package/dist/utils/glob.d.ts.map +1 -1
  307. package/dist/utils/logger.d.ts.map +1 -0
  308. package/dist/{logger.js → utils/logger.js} +1 -1
  309. package/dist/utils/parsing.d.ts.map +1 -0
  310. package/dist/utils/process.d.ts.map +1 -1
  311. package/dist/utils/process.js +90 -37
  312. package/dist/utils/templates.d.ts +2 -0
  313. package/dist/utils/templates.d.ts.map +1 -0
  314. package/dist/utils/templates.js +24 -0
  315. package/dist/utils/version.d.ts.map +1 -0
  316. package/dist/{version.js → utils/version.js} +1 -1
  317. package/package.json +34 -25
  318. package/dist/cli.d.ts +0 -23
  319. package/dist/cli.d.ts.map +0 -1
  320. package/dist/commands/exit/index.d.ts +0 -10
  321. package/dist/commands/exit/index.d.ts.map +0 -1
  322. package/dist/commands/exit/index.js +0 -21
  323. package/dist/commands/exit/types.d.ts +0 -8
  324. package/dist/commands/exit/types.d.ts.map +0 -1
  325. package/dist/commands/exit/types.js +0 -1
  326. package/dist/commands/exit/utils.d.ts +0 -2
  327. package/dist/commands/exit/utils.d.ts.map +0 -1
  328. package/dist/commands/exit/utils.js +0 -13
  329. package/dist/commands/prompt/index.d.ts +0 -5
  330. package/dist/commands/prompt/index.d.ts.map +0 -1
  331. package/dist/commands/prompt/index.js +0 -122
  332. package/dist/commands/prompt/types.d.ts +0 -15
  333. package/dist/commands/prompt/types.d.ts.map +0 -1
  334. package/dist/commands/prompt/types.js +0 -1
  335. package/dist/commands/prompt/utils.d.ts +0 -12
  336. package/dist/commands/prompt/utils.d.ts.map +0 -1
  337. package/dist/commands/prompt/utils.js +0 -107
  338. package/dist/commands/reset/index.d.ts +0 -3
  339. package/dist/commands/reset/index.d.ts.map +0 -1
  340. package/dist/commands/reset/index.js +0 -25
  341. package/dist/commands/reset/types.d.ts +0 -1
  342. package/dist/commands/reset/types.d.ts.map +0 -1
  343. package/dist/commands/reset/types.js +0 -3
  344. package/dist/commands/save/index.d.ts +0 -3
  345. package/dist/commands/save/index.d.ts.map +0 -1
  346. package/dist/commands/save/index.js +0 -19
  347. package/dist/config.d.ts.map +0 -1
  348. package/dist/dedent.d.ts.map +0 -1
  349. package/dist/formatting.d.ts.map +0 -1
  350. package/dist/logger.d.ts.map +0 -1
  351. package/dist/mentions.d.ts +0 -14
  352. package/dist/mentions.d.ts.map +0 -1
  353. package/dist/parsing.d.ts.map +0 -1
  354. package/dist/prompts.d.ts.map +0 -1
  355. package/dist/repl-new.d.ts +0 -65
  356. package/dist/repl-new.d.ts.map +0 -1
  357. package/dist/skills.d.ts.map +0 -1
  358. package/dist/skills.js +0 -233
  359. package/dist/stdin.d.ts.map +0 -1
  360. package/dist/tui/autocomplete/path-provider.d.ts +0 -21
  361. package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
  362. package/dist/tui/autocomplete/path-provider.js +0 -164
  363. package/dist/version.d.ts.map +0 -1
  364. /package/dist/{stdin.d.ts → cli/stdin.d.ts} +0 -0
  365. /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
  366. /package/dist/{dedent.js → utils/dedent.js} +0 -0
  367. /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
  368. /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
  369. /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
  370. /package/dist/{parsing.js → utils/parsing.js} +0 -0
  371. /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
@@ -1,16 +1,28 @@
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 { launchEditor } from "./tui/editor-launcher.js";
12
- import { Container, Editor, Loader, NotificationComponent, ProcessTerminal, Spacer, Text, TUI, UserMessageComponent, } from "./tui/index.js";
13
- 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 {
14
26
  options;
15
27
  terminal;
16
28
  tui;
@@ -34,6 +46,16 @@ export class NewRepl {
34
46
  streamingComponent = null;
35
47
  // thinking block tracking
36
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. */
37
59
  constructor(options) {
38
60
  this.options = options;
39
61
  this.terminal = new ProcessTerminal(options.terminalOptions);
@@ -45,7 +67,7 @@ export class NewRepl {
45
67
  this.chatContainer = new Container();
46
68
  this.statusContainer = new Container();
47
69
  this.editorContainer = new Container(); // Container to hold editor or selector
48
- this.footer = new FooterComponent(options.modelManager, {
70
+ this.footer = new FooterComponent(options.modelManager, options.tokenTracker, {
49
71
  projectStatus: {
50
72
  path: "",
51
73
  isGitRepository: false,
@@ -55,7 +77,6 @@ export class NewRepl {
55
77
  },
56
78
  currentContextWindow: 0,
57
79
  contextWindow: options.modelManager.getModelMetadata("repl").contextWindow,
58
- usage: this.options.tokenTracker.getUsageByApp("repl"),
59
80
  });
60
81
  this.editorContainer.addChild(this.editor); // Start with editor
61
82
  this.editor.onRenderRequested = () => this.tui.requestRender();
@@ -69,17 +90,30 @@ export class NewRepl {
69
90
  this.isInitialized = false;
70
91
  this.pendingTools = new Map();
71
92
  this.tools = options.tools;
72
- 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());
73
95
  }
96
+ /**
97
+ * Initializes the REPL by setting up autocomplete,
98
+ * keyboard handlers, and the TUI component tree.
99
+ */
74
100
  async init() {
75
101
  if (this.isInitialized) {
102
+ this.notification.setMessage("initialized");
76
103
  return;
77
104
  }
78
- // Setup autocomplete for file paths and slash commands
79
- const { createDefaultProvider } = await import("./tui/autocomplete.js");
80
- 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
+ ]);
81
114
  this.editor.setAutocompleteProvider(autocompleteProvider);
82
- const { promptManager, modelManager, messageHistory, commands, promptHistory, } = this.options;
115
+ const { promptManager, modelManager, sessionManager, commands, promptHistory, configManager, } = this.options;
116
+ this.config = await configManager.getConfig();
83
117
  // Listen for session title updates
84
118
  // messageHistory.on("update-title", (title: string) => {
85
119
  // this.footer.setTitle(title);
@@ -90,16 +124,45 @@ export class NewRepl {
90
124
  projectStatus: await getProjectStatus(),
91
125
  currentContextWindow: 0,
92
126
  contextWindow: modelConfig.contextWindow,
93
- usage: this.options.tokenTracker.getUsageByApp("repl"),
127
+ currentMode: this.modeManager.getDisplayName(),
94
128
  });
95
129
  this.tui.onCtrlC = () => {
96
130
  this.handleCtrlC();
97
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();
98
159
  this.tui.addChild(this.welcome);
99
160
  // Initialize footer with current title if one exists
100
161
  // this.footer.setTitle(messageHistory.getTitle());
101
162
  this.tui.addChild(this.chatContainer);
102
163
  this.tui.addChild(this.statusContainer);
164
+ // Everything below here is fixed to the bottom of the terminal
165
+ this.tui.setFixedFooterStart();
103
166
  this.tui.addChild(new Spacer(1));
104
167
  this.tui.addChild(this.editorContainer); // Use container that can hold editor or selector
105
168
  this.tui.addChild(this.footer);
@@ -122,38 +185,20 @@ export class NewRepl {
122
185
  inputContainer: this.editorContainer,
123
186
  editor: this.editor,
124
187
  });
125
- if (commandResult.break) {
126
- this.stop(true);
127
- process.exit(0);
128
- }
129
188
  if (commandResult.continue) {
130
189
  this.editor.setText("");
131
190
  this.tui.requestRender();
132
191
  return;
133
192
  }
134
193
  if (!promptManager.isPending()) {
135
- try {
136
- const processedPrompt = await processPrompt(text, {
137
- baseDir: process.cwd(),
138
- model: modelConfig,
139
- });
140
- for (const context of processedPrompt.context) {
141
- promptManager.addContext(context);
142
- }
143
- promptManager.set(processedPrompt.message);
144
- }
145
- catch (error) {
146
- if (error instanceof PromptError) {
147
- this.chatContainer.addChild(new Text(style.red(`Prompt processing failed: ${error.message}`), 1, 1));
148
- if (error.cause &&
149
- typeof error.cause === "object" &&
150
- "command" in error.cause &&
151
- typeof error.cause.command === "string") {
152
- this.chatContainer.addChild(new Text(style.red(`Command: ${error.cause.command}`, 1, 1)));
153
- }
154
- }
155
- 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);
156
200
  }
201
+ promptManager.set(processedPrompt.message);
157
202
  }
158
203
  else {
159
204
  promptHistory.push(promptManager.get());
@@ -163,11 +208,34 @@ export class NewRepl {
163
208
  const hasAddedContext = promptManager.hasContext();
164
209
  if (hasAddedContext) {
165
210
  const contextTokenCount = promptManager.getContextTokenCount();
166
- 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)`)));
167
212
  }
168
213
  const userPrompt = promptManager.get();
169
214
  const userMsg = promptManager.getUserMessage();
170
- 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();
171
239
  if (this.onInputCallback) {
172
240
  this.onInputCallback(userPrompt);
173
241
  }
@@ -177,18 +245,25 @@ export class NewRepl {
177
245
  this.tui.start();
178
246
  this.isInitialized = true;
179
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
+ */
180
253
  async handle(event, state) {
181
254
  if (!this.isInitialized) {
182
255
  await this.init();
183
256
  }
184
257
  // Update footer with current stats
185
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
186
261
  this.footer.setState({
187
- projectStatus: await getProjectStatus(),
188
- currentContextWindow: this.options.messageHistory.getContextWindow(),
262
+ projectStatus: this.footer.getProjectStatus(),
263
+ currentContextWindow: this.options.sessionManager.getLastTurnContextWindow(),
189
264
  contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
190
265
  agentState: state,
191
- usage: this.options.tokenTracker.getUsageByApp("repl"),
266
+ currentMode: this.modeManager.getDisplayName(),
192
267
  });
193
268
  const eventType = event.type;
194
269
  switch (eventType) {
@@ -219,19 +294,13 @@ export class NewRepl {
219
294
  // Create assistant component for streaming
220
295
  const assistantMessageComponent = new AssistantMessageComponent();
221
296
  this.streamingComponent = assistantMessageComponent;
222
- this.chatContainer.addChild(assistantMessageComponent);
297
+ this.addComponentWithSpacing(assistantMessageComponent);
223
298
  this.streamingComponent.updateContent(event);
224
299
  this.tui.requestRender();
225
300
  }
226
301
  break;
227
302
  case "message":
228
- if (event.role === "user") {
229
- // Show user message immediately and clear editor
230
- this.addMessageToChat(event);
231
- this.editor.setText("");
232
- this.tui.requestRender();
233
- }
234
- else if (event.role === "assistant") {
303
+ if (event.role === "assistant") {
235
304
  // Update streaming component
236
305
  if (this.streamingComponent && event.role === "assistant") {
237
306
  this.streamingComponent.updateContent(event);
@@ -253,9 +322,12 @@ export class NewRepl {
253
322
  }
254
323
  else {
255
324
  // Create tool component for new tool call
256
- const newComponent = new ToolExecutionComponent(event.events);
325
+ const newComponent = new ToolExecutionComponent(event.events, {
326
+ verboseMode: this.verboseMode,
327
+ });
257
328
  this.pendingTools.set(event.toolCallId, newComponent);
258
- this.chatContainer.addChild(newComponent);
329
+ this.allToolExecutions.push(newComponent);
330
+ this.addComponentWithSpacing(newComponent);
259
331
  }
260
332
  this.tui.requestRender();
261
333
  break;
@@ -277,10 +349,30 @@ export class NewRepl {
277
349
  }
278
350
  this.pendingTools.clear();
279
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
+ });
280
367
  this.tui.requestRender();
281
368
  break;
282
369
  case "agent-error":
283
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
+ }
284
376
  // Stop loading animation
285
377
  if (this.loadingAnimation) {
286
378
  this.loadingAnimation.stop();
@@ -296,9 +388,12 @@ export class NewRepl {
296
388
  this.tui.requestRender();
297
389
  break;
298
390
  case "thinking-start": {
299
- const component = new ThinkingBlockComponent();
391
+ const component = new ThinkingBlockComponent(undefined, {
392
+ verboseMode: this.verboseMode,
393
+ });
300
394
  this.thinkingBlockComponent = component;
301
- this.chatContainer.addChild(component);
395
+ this.allThinkingBlocks.push(component);
396
+ this.addComponentWithSpacing(component);
302
397
  this.thinkingBlockComponent.updateContent(event);
303
398
  this.tui.requestRender();
304
399
  break;
@@ -311,7 +406,7 @@ export class NewRepl {
311
406
  break;
312
407
  case "thinking-end":
313
408
  if (this.thinkingBlockComponent) {
314
- this.thinkingBlockComponent.updateContent(event);
409
+ this.thinkingBlockComponent.endThinking();
315
410
  this.thinkingBlockComponent = null;
316
411
  this.tui.requestRender();
317
412
  }
@@ -320,16 +415,26 @@ export class NewRepl {
320
415
  eventType;
321
416
  }
322
417
  }
418
+ /** Adds a user message component to the chat container. */
323
419
  addMessageToChat(message) {
324
420
  if (message.role === "user") {
325
421
  // Extract text content from content blocks
326
422
  const textContent = message.content;
327
423
  if (textContent) {
328
424
  const userComponent = new UserMessageComponent(textContent);
329
- this.chatContainer.addChild(userComponent);
425
+ this.addComponentWithSpacing(userComponent);
330
426
  }
331
427
  }
332
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. */
333
438
  async getUserInput() {
334
439
  return new Promise((resolve) => {
335
440
  this.onInputCallback = (text) => {
@@ -338,33 +443,75 @@ export class NewRepl {
338
443
  };
339
444
  });
340
445
  }
446
+ /** Clears the editor input and triggers a re-render. */
341
447
  clearEditor() {
342
448
  this.editor.setText("");
343
449
  this.tui.requestRender();
344
450
  }
451
+ /**
452
+ * Sets the callback invoked when the user presses
453
+ * Escape to interrupt the agent.
454
+ */
345
455
  setInterruptCallback(callback) {
346
456
  this.onInterruptCallback = callback;
347
457
  }
458
+ /** Sets the callback invoked on exit, receiving the current session ID. */
348
459
  setExitCallback(callback) {
349
460
  this.onExitCallback = callback;
350
461
  }
462
+ /**
463
+ * Re-renders the entire session by restoring mode state,
464
+ * replaying token usage, and reconstructing the chat
465
+ * display.
466
+ */
351
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
+ }
352
491
  this.footer.setState({
353
492
  projectStatus: await getProjectStatus(),
354
- currentContextWindow: this.options.messageHistory.getContextWindow(),
493
+ currentContextWindow: this.options.sessionManager.getLastTurnContextWindow(),
355
494
  contextWindow: this.options.modelManager.getModelMetadata("repl").contextWindow,
356
- usage: this.options.tokenTracker.getUsageByApp("repl"),
495
+ currentMode: this.modeManager.getDisplayName(),
357
496
  });
358
497
  // Reconstruct entire session display from messages
359
498
  this.reconstructSession();
499
+ this.tui.scrollToBottom();
360
500
  this.tui.requestRender();
361
501
  }
502
+ /**
503
+ * Rebuilds the chat container from the full session
504
+ * message history, including user messages, assistant
505
+ * responses, and tool executions.
506
+ */
362
507
  reconstructSession() {
363
508
  // Clear existing display
364
509
  this.pendingTools.clear();
510
+ this.allThinkingBlocks = [];
511
+ this.allToolExecutions = [];
365
512
  this.chatContainer.clear();
366
513
  // Get session messages
367
- const messages = this.options.messageHistory.get();
514
+ const messages = this.options.sessionManager.get();
368
515
  // First pass: collect all tool results
369
516
  const toolResults = new Map();
370
517
  for (const message of messages) {
@@ -395,7 +542,7 @@ export class NewRepl {
395
542
  const textContent = this.extractUserMessageText(message);
396
543
  if (textContent) {
397
544
  const userComponent = new UserMessageComponent(textContent);
398
- this.chatContainer.addChild(userComponent);
545
+ this.addComponentWithSpacing(userComponent);
399
546
  }
400
547
  }
401
548
  else if (message.role === "assistant") {
@@ -407,15 +554,22 @@ export class NewRepl {
407
554
  const toolCallId = toolCallContent.toolCallId;
408
555
  const events = this.createToolEvents(toolCallContent);
409
556
  if (events.length > 0) {
410
- const component = new ToolExecutionComponent(events);
557
+ const component = new ToolExecutionComponent(events, {
558
+ verboseMode: this.verboseMode,
559
+ });
411
560
  this.pendingTools.set(toolCallId, component);
412
- this.chatContainer.addChild(component);
561
+ this.allToolExecutions.push(component);
562
+ this.addComponentWithSpacing(component);
413
563
  }
414
564
  }
415
565
  }
416
566
  // Tool messages are handled through their associated assistant message
417
567
  }
418
568
  }
569
+ /**
570
+ * Extracts the text content from a user message,
571
+ * handling both string and array content formats.
572
+ */
419
573
  extractUserMessageText(message) {
420
574
  if (typeof message.content === "string") {
421
575
  return message.content;
@@ -428,6 +582,10 @@ export class NewRepl {
428
582
  }
429
583
  return null;
430
584
  }
585
+ /**
586
+ * Renders an assistant message into the chat container,
587
+ * including reasoning/thinking blocks and text content.
588
+ */
431
589
  renderAssistantMessage(message) {
432
590
  if (typeof message.content === "string") {
433
591
  if (message.content.trim()) {
@@ -437,10 +595,25 @@ export class NewRepl {
437
595
  role: "assistant",
438
596
  content: message.content,
439
597
  });
440
- this.chatContainer.addChild(assistantComponent);
598
+ this.addComponentWithSpacing(assistantComponent);
441
599
  }
442
600
  }
443
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
+ }
444
617
  const textParts = message.content
445
618
  .filter((part) => part.type === "text" && part.text?.trim() !== undefined)
446
619
  .map((part) => part.text)
@@ -452,10 +625,15 @@ export class NewRepl {
452
625
  role: "assistant",
453
626
  content: textParts,
454
627
  });
455
- this.chatContainer.addChild(assistantComponent);
628
+ this.addComponentWithSpacing(assistantComponent);
456
629
  }
457
630
  }
458
631
  }
632
+ /**
633
+ * Extracts tool call content parts from an assistant
634
+ * message, matching them with their corresponding
635
+ * tool results.
636
+ */
459
637
  extractToolCallsFromAssistant(message, toolResults) {
460
638
  const toolCallContents = [];
461
639
  if (typeof message.content === "string") {
@@ -480,6 +658,11 @@ export class NewRepl {
480
658
  }
481
659
  return toolCallContents;
482
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
+ */
483
666
  createToolEvents(toolCallContent) {
484
667
  const events = [];
485
668
  // tool-call-start: use the tool's display function
@@ -508,6 +691,88 @@ export class NewRepl {
508
691
  });
509
692
  return events;
510
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
+ */
511
776
  handleCtrlC() {
512
777
  // Handle Ctrl+C double-press logic
513
778
  const now = Date.now();
@@ -522,14 +787,17 @@ export class NewRepl {
522
787
  }
523
788
  this.notification.setMessage("");
524
789
  this.tui.requestRender();
525
- 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
+ }
526
794
  this.stop(true);
527
795
  process.exit(0);
528
796
  }
529
797
  else {
530
798
  // First Ctrl+C - clear the editor and show notification
531
799
  this.clearEditor();
532
- this.notification.setMessage("Press Ctrl+C again to exit");
800
+ this.notification.setMessage("Press Ctrl+C again to exit", 1000);
533
801
  this.tui.requestRender();
534
802
  this.lastSigintTime = now;
535
803
  // Clear notification after threshold if no second Ctrl+C
@@ -545,6 +813,10 @@ export class NewRepl {
545
813
  }, DoublePressThreshold);
546
814
  }
547
815
  }
816
+ /**
817
+ * Stops the REPL, cleaning up timers, animations,
818
+ * and the TUI. Optionally triggers the exit callback.
819
+ */
548
820
  stop(showExitMessage = false) {
549
821
  this.notification.setMessage("");
550
822
  // Clear any pending notification timer
@@ -553,7 +825,11 @@ export class NewRepl {
553
825
  this.exitNotificationTimer = undefined;
554
826
  }
555
827
  if (showExitMessage && this.onExitCallback) {
556
- 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
+ }
557
833
  }
558
834
  if (this.loadingAnimation) {
559
835
  this.loadingAnimation.stop();
@@ -564,4 +840,41 @@ export class NewRepl {
564
840
  this.isInitialized = false;
565
841
  }
566
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
+ }
567
880
  }