@mragentix/cli 4.2.37

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 (461) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +772 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config.d.ts +16 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +29 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/core/agent-session.d.ts +87 -0
  12. package/dist/core/agent-session.d.ts.map +1 -0
  13. package/dist/core/agent-session.js +498 -0
  14. package/dist/core/agent-session.js.map +1 -0
  15. package/dist/core/agents.d.ts +30 -0
  16. package/dist/core/agents.d.ts.map +1 -0
  17. package/dist/core/agents.js +91 -0
  18. package/dist/core/agents.js.map +1 -0
  19. package/dist/core/auth-storage.d.ts +35 -0
  20. package/dist/core/auth-storage.d.ts.map +1 -0
  21. package/dist/core/auth-storage.js +144 -0
  22. package/dist/core/auth-storage.js.map +1 -0
  23. package/dist/core/auto-update.d.ts +8 -0
  24. package/dist/core/auto-update.d.ts.map +1 -0
  25. package/dist/core/auto-update.js +152 -0
  26. package/dist/core/auto-update.js.map +1 -0
  27. package/dist/core/compaction/compactor.d.ts +69 -0
  28. package/dist/core/compaction/compactor.d.ts.map +1 -0
  29. package/dist/core/compaction/compactor.js +405 -0
  30. package/dist/core/compaction/compactor.js.map +1 -0
  31. package/dist/core/compaction/compactor.test.d.ts +2 -0
  32. package/dist/core/compaction/compactor.test.d.ts.map +1 -0
  33. package/dist/core/compaction/compactor.test.js +461 -0
  34. package/dist/core/compaction/compactor.test.js.map +1 -0
  35. package/dist/core/compaction/token-estimator.d.ts +10 -0
  36. package/dist/core/compaction/token-estimator.d.ts.map +1 -0
  37. package/dist/core/compaction/token-estimator.js +75 -0
  38. package/dist/core/compaction/token-estimator.js.map +1 -0
  39. package/dist/core/compaction/token-estimator.test.d.ts +2 -0
  40. package/dist/core/compaction/token-estimator.test.d.ts.map +1 -0
  41. package/dist/core/compaction/token-estimator.test.js +137 -0
  42. package/dist/core/compaction/token-estimator.test.js.map +1 -0
  43. package/dist/core/custom-commands.d.ts +13 -0
  44. package/dist/core/custom-commands.d.ts.map +1 -0
  45. package/dist/core/custom-commands.js +40 -0
  46. package/dist/core/custom-commands.js.map +1 -0
  47. package/dist/core/event-bus.d.ts +95 -0
  48. package/dist/core/event-bus.d.ts.map +1 -0
  49. package/dist/core/event-bus.js +99 -0
  50. package/dist/core/event-bus.js.map +1 -0
  51. package/dist/core/extensions/loader.d.ts +8 -0
  52. package/dist/core/extensions/loader.d.ts.map +1 -0
  53. package/dist/core/extensions/loader.js +48 -0
  54. package/dist/core/extensions/loader.js.map +1 -0
  55. package/dist/core/extensions/types.d.ts +19 -0
  56. package/dist/core/extensions/types.d.ts.map +1 -0
  57. package/dist/core/extensions/types.js +2 -0
  58. package/dist/core/extensions/types.js.map +1 -0
  59. package/dist/core/file-lock.d.ts +6 -0
  60. package/dist/core/file-lock.d.ts.map +1 -0
  61. package/dist/core/file-lock.js +76 -0
  62. package/dist/core/file-lock.js.map +1 -0
  63. package/dist/core/index.d.ts +14 -0
  64. package/dist/core/index.d.ts.map +1 -0
  65. package/dist/core/index.js +13 -0
  66. package/dist/core/index.js.map +1 -0
  67. package/dist/core/logger.d.ts +26 -0
  68. package/dist/core/logger.d.ts.map +1 -0
  69. package/dist/core/logger.js +132 -0
  70. package/dist/core/logger.js.map +1 -0
  71. package/dist/core/mcp/client.d.ts +9 -0
  72. package/dist/core/mcp/client.d.ts.map +1 -0
  73. package/dist/core/mcp/client.js +126 -0
  74. package/dist/core/mcp/client.js.map +1 -0
  75. package/dist/core/mcp/defaults.d.ts +9 -0
  76. package/dist/core/mcp/defaults.d.ts.map +1 -0
  77. package/dist/core/mcp/defaults.js +47 -0
  78. package/dist/core/mcp/defaults.js.map +1 -0
  79. package/dist/core/mcp/index.d.ts +4 -0
  80. package/dist/core/mcp/index.d.ts.map +1 -0
  81. package/dist/core/mcp/index.js +3 -0
  82. package/dist/core/mcp/index.js.map +1 -0
  83. package/dist/core/mcp/types.d.ts +15 -0
  84. package/dist/core/mcp/types.d.ts.map +1 -0
  85. package/dist/core/mcp/types.js +2 -0
  86. package/dist/core/mcp/types.js.map +1 -0
  87. package/dist/core/model-registry.d.ts +25 -0
  88. package/dist/core/model-registry.d.ts.map +1 -0
  89. package/dist/core/model-registry.js +135 -0
  90. package/dist/core/model-registry.js.map +1 -0
  91. package/dist/core/oauth/anthropic.d.ts +4 -0
  92. package/dist/core/oauth/anthropic.d.ts.map +1 -0
  93. package/dist/core/oauth/anthropic.js +75 -0
  94. package/dist/core/oauth/anthropic.js.map +1 -0
  95. package/dist/core/oauth/openai.d.ts +4 -0
  96. package/dist/core/oauth/openai.d.ts.map +1 -0
  97. package/dist/core/oauth/openai.js +186 -0
  98. package/dist/core/oauth/openai.js.map +1 -0
  99. package/dist/core/oauth/pkce.d.ts +5 -0
  100. package/dist/core/oauth/pkce.d.ts.map +1 -0
  101. package/dist/core/oauth/pkce.js +17 -0
  102. package/dist/core/oauth/pkce.js.map +1 -0
  103. package/dist/core/oauth/types.d.ts +12 -0
  104. package/dist/core/oauth/types.d.ts.map +1 -0
  105. package/dist/core/oauth/types.js +2 -0
  106. package/dist/core/oauth/types.js.map +1 -0
  107. package/dist/core/process-manager.d.ts +30 -0
  108. package/dist/core/process-manager.d.ts.map +1 -0
  109. package/dist/core/process-manager.js +130 -0
  110. package/dist/core/process-manager.js.map +1 -0
  111. package/dist/core/prompt-commands.d.ts +14 -0
  112. package/dist/core/prompt-commands.d.ts.map +1 -0
  113. package/dist/core/prompt-commands.js +496 -0
  114. package/dist/core/prompt-commands.js.map +1 -0
  115. package/dist/core/session-manager.d.ts +112 -0
  116. package/dist/core/session-manager.d.ts.map +1 -0
  117. package/dist/core/session-manager.js +326 -0
  118. package/dist/core/session-manager.js.map +1 -0
  119. package/dist/core/settings-manager.d.ts +43 -0
  120. package/dist/core/settings-manager.d.ts.map +1 -0
  121. package/dist/core/settings-manager.js +64 -0
  122. package/dist/core/settings-manager.js.map +1 -0
  123. package/dist/core/skills.d.ts +23 -0
  124. package/dist/core/skills.d.ts.map +1 -0
  125. package/dist/core/skills.js +89 -0
  126. package/dist/core/skills.js.map +1 -0
  127. package/dist/core/slash-commands.d.ts +35 -0
  128. package/dist/core/slash-commands.d.ts.map +1 -0
  129. package/dist/core/slash-commands.js +183 -0
  130. package/dist/core/slash-commands.js.map +1 -0
  131. package/dist/core/telegram.d.ts +94 -0
  132. package/dist/core/telegram.d.ts.map +1 -0
  133. package/dist/core/telegram.js +227 -0
  134. package/dist/core/telegram.js.map +1 -0
  135. package/dist/index.d.ts +10 -0
  136. package/dist/index.d.ts.map +1 -0
  137. package/dist/index.js +15 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/interactive.d.ts +3 -0
  140. package/dist/interactive.d.ts.map +1 -0
  141. package/dist/interactive.js +173 -0
  142. package/dist/interactive.js.map +1 -0
  143. package/dist/modes/index.d.ts +3 -0
  144. package/dist/modes/index.d.ts.map +1 -0
  145. package/dist/modes/index.js +3 -0
  146. package/dist/modes/index.js.map +1 -0
  147. package/dist/modes/json-mode.d.ts +13 -0
  148. package/dist/modes/json-mode.d.ts.map +1 -0
  149. package/dist/modes/json-mode.js +74 -0
  150. package/dist/modes/json-mode.js.map +1 -0
  151. package/dist/modes/print-mode.d.ts +12 -0
  152. package/dist/modes/print-mode.d.ts.map +1 -0
  153. package/dist/modes/print-mode.js +49 -0
  154. package/dist/modes/print-mode.js.map +1 -0
  155. package/dist/modes/rpc-mode.d.ts +28 -0
  156. package/dist/modes/rpc-mode.d.ts.map +1 -0
  157. package/dist/modes/rpc-mode.js +145 -0
  158. package/dist/modes/rpc-mode.js.map +1 -0
  159. package/dist/modes/serve-mode.d.ts +21 -0
  160. package/dist/modes/serve-mode.d.ts.map +1 -0
  161. package/dist/modes/serve-mode.js +649 -0
  162. package/dist/modes/serve-mode.js.map +1 -0
  163. package/dist/session.d.ts +16 -0
  164. package/dist/session.d.ts.map +1 -0
  165. package/dist/session.js +129 -0
  166. package/dist/session.js.map +1 -0
  167. package/dist/system-prompt.d.ts +6 -0
  168. package/dist/system-prompt.d.ts.map +1 -0
  169. package/dist/system-prompt.js +115 -0
  170. package/dist/system-prompt.js.map +1 -0
  171. package/dist/tools/bash.d.ts +13 -0
  172. package/dist/tools/bash.d.ts.map +1 -0
  173. package/dist/tools/bash.js +165 -0
  174. package/dist/tools/bash.js.map +1 -0
  175. package/dist/tools/edit-diff.d.ts +18 -0
  176. package/dist/tools/edit-diff.d.ts.map +1 -0
  177. package/dist/tools/edit-diff.js +92 -0
  178. package/dist/tools/edit-diff.js.map +1 -0
  179. package/dist/tools/edit.d.ts +11 -0
  180. package/dist/tools/edit.d.ts.map +1 -0
  181. package/dist/tools/edit.js +57 -0
  182. package/dist/tools/edit.js.map +1 -0
  183. package/dist/tools/find.d.ts +9 -0
  184. package/dist/tools/find.d.ts.map +1 -0
  185. package/dist/tools/find.js +59 -0
  186. package/dist/tools/find.js.map +1 -0
  187. package/dist/tools/grep.d.ts +13 -0
  188. package/dist/tools/grep.d.ts.map +1 -0
  189. package/dist/tools/grep.js +121 -0
  190. package/dist/tools/grep.js.map +1 -0
  191. package/dist/tools/index.d.ts +30 -0
  192. package/dist/tools/index.d.ts.map +1 -0
  193. package/dist/tools/index.js +50 -0
  194. package/dist/tools/index.js.map +1 -0
  195. package/dist/tools/ls.d.ts +10 -0
  196. package/dist/tools/ls.d.ts.map +1 -0
  197. package/dist/tools/ls.js +56 -0
  198. package/dist/tools/ls.js.map +1 -0
  199. package/dist/tools/operations.d.ts +39 -0
  200. package/dist/tools/operations.d.ts.map +1 -0
  201. package/dist/tools/operations.js +27 -0
  202. package/dist/tools/operations.js.map +1 -0
  203. package/dist/tools/path-utils.d.ts +7 -0
  204. package/dist/tools/path-utils.d.ts.map +1 -0
  205. package/dist/tools/path-utils.js +27 -0
  206. package/dist/tools/path-utils.js.map +1 -0
  207. package/dist/tools/read.d.ts +12 -0
  208. package/dist/tools/read.d.ts.map +1 -0
  209. package/dist/tools/read.js +113 -0
  210. package/dist/tools/read.js.map +1 -0
  211. package/dist/tools/subagent.d.ts +26 -0
  212. package/dist/tools/subagent.d.ts.map +1 -0
  213. package/dist/tools/subagent.js +210 -0
  214. package/dist/tools/subagent.js.map +1 -0
  215. package/dist/tools/task-output.d.ts +10 -0
  216. package/dist/tools/task-output.d.ts.map +1 -0
  217. package/dist/tools/task-output.js +33 -0
  218. package/dist/tools/task-output.js.map +1 -0
  219. package/dist/tools/task-stop.d.ts +9 -0
  220. package/dist/tools/task-stop.d.ts.map +1 -0
  221. package/dist/tools/task-stop.js +15 -0
  222. package/dist/tools/task-stop.js.map +1 -0
  223. package/dist/tools/tasks.d.ts +16 -0
  224. package/dist/tools/tasks.d.ts.map +1 -0
  225. package/dist/tools/tasks.js +132 -0
  226. package/dist/tools/tasks.js.map +1 -0
  227. package/dist/tools/truncate.d.ts +19 -0
  228. package/dist/tools/truncate.d.ts.map +1 -0
  229. package/dist/tools/truncate.js +59 -0
  230. package/dist/tools/truncate.js.map +1 -0
  231. package/dist/tools/truncate.test.d.ts +2 -0
  232. package/dist/tools/truncate.test.d.ts.map +1 -0
  233. package/dist/tools/truncate.test.js +100 -0
  234. package/dist/tools/truncate.test.js.map +1 -0
  235. package/dist/tools/web-fetch.d.ts +9 -0
  236. package/dist/tools/web-fetch.d.ts.map +1 -0
  237. package/dist/tools/web-fetch.js +97 -0
  238. package/dist/tools/web-fetch.js.map +1 -0
  239. package/dist/tools/write.d.ts +10 -0
  240. package/dist/tools/write.d.ts.map +1 -0
  241. package/dist/tools/write.js +30 -0
  242. package/dist/tools/write.js.map +1 -0
  243. package/dist/tools/write.test.d.ts +2 -0
  244. package/dist/tools/write.test.d.ts.map +1 -0
  245. package/dist/tools/write.test.js +84 -0
  246. package/dist/tools/write.test.js.map +1 -0
  247. package/dist/types.d.ts +36 -0
  248. package/dist/types.d.ts.map +1 -0
  249. package/dist/types.js +2 -0
  250. package/dist/types.js.map +1 -0
  251. package/dist/ui/App.d.ts +148 -0
  252. package/dist/ui/App.d.ts.map +1 -0
  253. package/dist/ui/App.js +1191 -0
  254. package/dist/ui/App.js.map +1 -0
  255. package/dist/ui/components/ActivityIndicator.d.ts +13 -0
  256. package/dist/ui/components/ActivityIndicator.d.ts.map +1 -0
  257. package/dist/ui/components/ActivityIndicator.js +313 -0
  258. package/dist/ui/components/ActivityIndicator.js.map +1 -0
  259. package/dist/ui/components/AnimationContext.d.ts +22 -0
  260. package/dist/ui/components/AnimationContext.d.ts.map +1 -0
  261. package/dist/ui/components/AnimationContext.js +35 -0
  262. package/dist/ui/components/AnimationContext.js.map +1 -0
  263. package/dist/ui/components/AssistantMessage.d.ts +9 -0
  264. package/dist/ui/components/AssistantMessage.d.ts.map +1 -0
  265. package/dist/ui/components/AssistantMessage.js +11 -0
  266. package/dist/ui/components/AssistantMessage.js.map +1 -0
  267. package/dist/ui/components/BackgroundTasksBar.d.ts +15 -0
  268. package/dist/ui/components/BackgroundTasksBar.d.ts.map +1 -0
  269. package/dist/ui/components/BackgroundTasksBar.js +74 -0
  270. package/dist/ui/components/BackgroundTasksBar.js.map +1 -0
  271. package/dist/ui/components/Banner.d.ts +11 -0
  272. package/dist/ui/components/Banner.d.ts.map +1 -0
  273. package/dist/ui/components/Banner.js +55 -0
  274. package/dist/ui/components/Banner.js.map +1 -0
  275. package/dist/ui/components/CompactionNotice.d.ts +10 -0
  276. package/dist/ui/components/CompactionNotice.d.ts.map +1 -0
  277. package/dist/ui/components/CompactionNotice.js +27 -0
  278. package/dist/ui/components/CompactionNotice.js.map +1 -0
  279. package/dist/ui/components/DiffView.d.ts +4 -0
  280. package/dist/ui/components/DiffView.d.ts.map +1 -0
  281. package/dist/ui/components/DiffView.js +20 -0
  282. package/dist/ui/components/DiffView.js.map +1 -0
  283. package/dist/ui/components/Footer.d.ts +10 -0
  284. package/dist/ui/components/Footer.d.ts.map +1 -0
  285. package/dist/ui/components/Footer.js +105 -0
  286. package/dist/ui/components/Footer.js.map +1 -0
  287. package/dist/ui/components/InputArea.d.ts +21 -0
  288. package/dist/ui/components/InputArea.d.ts.map +1 -0
  289. package/dist/ui/components/InputArea.js +465 -0
  290. package/dist/ui/components/InputArea.js.map +1 -0
  291. package/dist/ui/components/Markdown.d.ts +9 -0
  292. package/dist/ui/components/Markdown.d.ts.map +1 -0
  293. package/dist/ui/components/Markdown.js +246 -0
  294. package/dist/ui/components/Markdown.js.map +1 -0
  295. package/dist/ui/components/ModelSelector.d.ts +11 -0
  296. package/dist/ui/components/ModelSelector.d.ts.map +1 -0
  297. package/dist/ui/components/ModelSelector.js +20 -0
  298. package/dist/ui/components/ModelSelector.js.map +1 -0
  299. package/dist/ui/components/Overlay.d.ts +8 -0
  300. package/dist/ui/components/Overlay.d.ts.map +1 -0
  301. package/dist/ui/components/Overlay.js +9 -0
  302. package/dist/ui/components/Overlay.js.map +1 -0
  303. package/dist/ui/components/SelectList.d.ts +14 -0
  304. package/dist/ui/components/SelectList.d.ts.map +1 -0
  305. package/dist/ui/components/SelectList.js +46 -0
  306. package/dist/ui/components/SelectList.js.map +1 -0
  307. package/dist/ui/components/ServerToolExecution.d.ts +17 -0
  308. package/dist/ui/components/ServerToolExecution.d.ts.map +1 -0
  309. package/dist/ui/components/ServerToolExecution.js +26 -0
  310. package/dist/ui/components/ServerToolExecution.js.map +1 -0
  311. package/dist/ui/components/SessionSelector.d.ts +9 -0
  312. package/dist/ui/components/SessionSelector.d.ts.map +1 -0
  313. package/dist/ui/components/SessionSelector.js +13 -0
  314. package/dist/ui/components/SessionSelector.js.map +1 -0
  315. package/dist/ui/components/SettingsSelector.d.ts +9 -0
  316. package/dist/ui/components/SettingsSelector.d.ts.map +1 -0
  317. package/dist/ui/components/SettingsSelector.js +13 -0
  318. package/dist/ui/components/SettingsSelector.js.map +1 -0
  319. package/dist/ui/components/SlashCommandMenu.d.ts +15 -0
  320. package/dist/ui/components/SlashCommandMenu.d.ts.map +1 -0
  321. package/dist/ui/components/SlashCommandMenu.js +32 -0
  322. package/dist/ui/components/SlashCommandMenu.js.map +1 -0
  323. package/dist/ui/components/Spinner.d.ts +4 -0
  324. package/dist/ui/components/Spinner.d.ts.map +1 -0
  325. package/dist/ui/components/Spinner.js +13 -0
  326. package/dist/ui/components/Spinner.js.map +1 -0
  327. package/dist/ui/components/StreamingArea.d.ts +10 -0
  328. package/dist/ui/components/StreamingArea.d.ts.map +1 -0
  329. package/dist/ui/components/StreamingArea.js +58 -0
  330. package/dist/ui/components/StreamingArea.js.map +1 -0
  331. package/dist/ui/components/SubAgentPanel.d.ts +21 -0
  332. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -0
  333. package/dist/ui/components/SubAgentPanel.js +71 -0
  334. package/dist/ui/components/SubAgentPanel.js.map +1 -0
  335. package/dist/ui/components/TaskOverlay.d.ts +10 -0
  336. package/dist/ui/components/TaskOverlay.d.ts.map +1 -0
  337. package/dist/ui/components/TaskOverlay.js +263 -0
  338. package/dist/ui/components/TaskOverlay.js.map +1 -0
  339. package/dist/ui/components/ThinkingBlock.d.ts +11 -0
  340. package/dist/ui/components/ThinkingBlock.d.ts.map +1 -0
  341. package/dist/ui/components/ThinkingBlock.js +43 -0
  342. package/dist/ui/components/ThinkingBlock.js.map +1 -0
  343. package/dist/ui/components/ThinkingIndicator.d.ts +6 -0
  344. package/dist/ui/components/ThinkingIndicator.d.ts.map +1 -0
  345. package/dist/ui/components/ThinkingIndicator.js +144 -0
  346. package/dist/ui/components/ThinkingIndicator.js.map +1 -0
  347. package/dist/ui/components/ToolExecution.d.ts +16 -0
  348. package/dist/ui/components/ToolExecution.d.ts.map +1 -0
  349. package/dist/ui/components/ToolExecution.js +490 -0
  350. package/dist/ui/components/ToolExecution.js.map +1 -0
  351. package/dist/ui/components/ToolGroupExecution.d.ts +7 -0
  352. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -0
  353. package/dist/ui/components/ToolGroupExecution.js +115 -0
  354. package/dist/ui/components/ToolGroupExecution.js.map +1 -0
  355. package/dist/ui/components/UserMessage.d.ts +7 -0
  356. package/dist/ui/components/UserMessage.d.ts.map +1 -0
  357. package/dist/ui/components/UserMessage.js +16 -0
  358. package/dist/ui/components/UserMessage.js.map +1 -0
  359. package/dist/ui/components/index.d.ts +15 -0
  360. package/dist/ui/components/index.d.ts.map +1 -0
  361. package/dist/ui/components/index.js +15 -0
  362. package/dist/ui/components/index.js.map +1 -0
  363. package/dist/ui/hooks/useAgentLoop.d.ts +69 -0
  364. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -0
  365. package/dist/ui/hooks/useAgentLoop.js +483 -0
  366. package/dist/ui/hooks/useAgentLoop.js.map +1 -0
  367. package/dist/ui/hooks/useSessionManager.d.ts +13 -0
  368. package/dist/ui/hooks/useSessionManager.d.ts.map +1 -0
  369. package/dist/ui/hooks/useSessionManager.js +43 -0
  370. package/dist/ui/hooks/useSessionManager.js.map +1 -0
  371. package/dist/ui/hooks/useSlashCommands.d.ts +7 -0
  372. package/dist/ui/hooks/useSlashCommands.d.ts.map +1 -0
  373. package/dist/ui/hooks/useSlashCommands.js +11 -0
  374. package/dist/ui/hooks/useSlashCommands.js.map +1 -0
  375. package/dist/ui/hooks/useTerminalSize.d.ts +20 -0
  376. package/dist/ui/hooks/useTerminalSize.d.ts.map +1 -0
  377. package/dist/ui/hooks/useTerminalSize.js +55 -0
  378. package/dist/ui/hooks/useTerminalSize.js.map +1 -0
  379. package/dist/ui/hooks/useTerminalTitle.d.ts +3 -0
  380. package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -0
  381. package/dist/ui/hooks/useTerminalTitle.js +41 -0
  382. package/dist/ui/hooks/useTerminalTitle.js.map +1 -0
  383. package/dist/ui/live-item-flush.d.ts +59 -0
  384. package/dist/ui/live-item-flush.d.ts.map +1 -0
  385. package/dist/ui/live-item-flush.js +135 -0
  386. package/dist/ui/live-item-flush.js.map +1 -0
  387. package/dist/ui/live-item-flush.test.d.ts +2 -0
  388. package/dist/ui/live-item-flush.test.d.ts.map +1 -0
  389. package/dist/ui/live-item-flush.test.js +307 -0
  390. package/dist/ui/live-item-flush.test.js.map +1 -0
  391. package/dist/ui/login.d.ts +3 -0
  392. package/dist/ui/login.d.ts.map +1 -0
  393. package/dist/ui/login.js +117 -0
  394. package/dist/ui/login.js.map +1 -0
  395. package/dist/ui/render.d.ts +38 -0
  396. package/dist/ui/render.d.ts.map +1 -0
  397. package/dist/ui/render.js +72 -0
  398. package/dist/ui/render.js.map +1 -0
  399. package/dist/ui/sessions.d.ts +2 -0
  400. package/dist/ui/sessions.d.ts.map +1 -0
  401. package/dist/ui/sessions.js +208 -0
  402. package/dist/ui/sessions.js.map +1 -0
  403. package/dist/ui/spinner-frames.d.ts +3 -0
  404. package/dist/ui/spinner-frames.d.ts.map +1 -0
  405. package/dist/ui/spinner-frames.js +7 -0
  406. package/dist/ui/spinner-frames.js.map +1 -0
  407. package/dist/ui/theme/dark.json +24 -0
  408. package/dist/ui/theme/detect-theme.d.ts +12 -0
  409. package/dist/ui/theme/detect-theme.d.ts.map +1 -0
  410. package/dist/ui/theme/detect-theme.js +152 -0
  411. package/dist/ui/theme/detect-theme.js.map +1 -0
  412. package/dist/ui/theme/light.json +24 -0
  413. package/dist/ui/theme/theme.d.ts +29 -0
  414. package/dist/ui/theme/theme.d.ts.map +1 -0
  415. package/dist/ui/theme/theme.js +11 -0
  416. package/dist/ui/theme/theme.js.map +1 -0
  417. package/dist/ui/utils/highlight.d.ts +8 -0
  418. package/dist/ui/utils/highlight.d.ts.map +1 -0
  419. package/dist/ui/utils/highlight.js +49 -0
  420. package/dist/ui/utils/highlight.js.map +1 -0
  421. package/dist/ui/utils/table-text.d.ts +25 -0
  422. package/dist/ui/utils/table-text.d.ts.map +1 -0
  423. package/dist/ui/utils/table-text.js +78 -0
  424. package/dist/ui/utils/table-text.js.map +1 -0
  425. package/dist/ui/utils/table-text.test.d.ts +2 -0
  426. package/dist/ui/utils/table-text.test.d.ts.map +1 -0
  427. package/dist/ui/utils/table-text.test.js +202 -0
  428. package/dist/ui/utils/table-text.test.js.map +1 -0
  429. package/dist/utils/error-handler.d.ts +5 -0
  430. package/dist/utils/error-handler.d.ts.map +1 -0
  431. package/dist/utils/error-handler.js +120 -0
  432. package/dist/utils/error-handler.js.map +1 -0
  433. package/dist/utils/format.d.ts +21 -0
  434. package/dist/utils/format.d.ts.map +1 -0
  435. package/dist/utils/format.js +120 -0
  436. package/dist/utils/format.js.map +1 -0
  437. package/dist/utils/git.d.ts +2 -0
  438. package/dist/utils/git.d.ts.map +1 -0
  439. package/dist/utils/git.js +13 -0
  440. package/dist/utils/git.js.map +1 -0
  441. package/dist/utils/image.d.ts +30 -0
  442. package/dist/utils/image.d.ts.map +1 -0
  443. package/dist/utils/image.js +231 -0
  444. package/dist/utils/image.js.map +1 -0
  445. package/dist/utils/markdown.d.ts +6 -0
  446. package/dist/utils/markdown.d.ts.map +1 -0
  447. package/dist/utils/markdown.js +25 -0
  448. package/dist/utils/markdown.js.map +1 -0
  449. package/dist/utils/process.d.ts +6 -0
  450. package/dist/utils/process.d.ts.map +1 -0
  451. package/dist/utils/process.js +19 -0
  452. package/dist/utils/process.js.map +1 -0
  453. package/dist/utils/shell.d.ts +3 -0
  454. package/dist/utils/shell.d.ts.map +1 -0
  455. package/dist/utils/shell.js +8 -0
  456. package/dist/utils/shell.js.map +1 -0
  457. package/dist/utils/sound.d.ts +2 -0
  458. package/dist/utils/sound.d.ts.map +1 -0
  459. package/dist/utils/sound.js +11 -0
  460. package/dist/utils/sound.js.map +1 -0
  461. package/package.json +56 -0
package/dist/ui/App.js ADDED
@@ -0,0 +1,1191 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useCallback, useEffect, useMemo } from "react";
3
+ import { Box, Text, Static, useStdout } from "ink";
4
+ import { useTerminalSize } from "./hooks/useTerminalSize.js";
5
+ import crypto, { createHash } from "node:crypto";
6
+ import { readFileSync, writeFileSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { playNotificationSound } from "../utils/sound.js";
10
+ import { extractImagePaths } from "../utils/image.js";
11
+ import { useAgentLoop } from "./hooks/useAgentLoop.js";
12
+ import { UserMessage } from "./components/UserMessage.js";
13
+ import { AssistantMessage } from "./components/AssistantMessage.js";
14
+ import { ToolExecution } from "./components/ToolExecution.js";
15
+ import { ToolGroupExecution } from "./components/ToolGroupExecution.js";
16
+ import { ServerToolExecution } from "./components/ServerToolExecution.js";
17
+ import { SubAgentPanel } from "./components/SubAgentPanel.js";
18
+ import { CompactionSpinner, CompactionDone } from "./components/CompactionNotice.js";
19
+ import { StreamingArea } from "./components/StreamingArea.js";
20
+ import { ActivityIndicator } from "./components/ActivityIndicator.js";
21
+ import { InputArea } from "./components/InputArea.js";
22
+ import { Footer } from "./components/Footer.js";
23
+ import { Banner } from "./components/Banner.js";
24
+ import { ModelSelector } from "./components/ModelSelector.js";
25
+ import { TaskOverlay } from "./components/TaskOverlay.js";
26
+ import { BackgroundTasksBar } from "./components/BackgroundTasksBar.js";
27
+ import { useTheme } from "./theme/theme.js";
28
+ import { useAnimationTick, deriveFrame } from "./components/AnimationContext.js";
29
+ import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
30
+ import { getGitBranch } from "../utils/git.js";
31
+ import { getModel, getContextWindow } from "../core/model-registry.js";
32
+ import { SessionManager } from "../core/session-manager.js";
33
+ import { log } from "../core/logger.js";
34
+ import { SettingsManager } from "../core/settings-manager.js";
35
+ import { shouldCompact, compact } from "../core/compaction/compactor.js";
36
+ import { estimateConversationTokens } from "../core/compaction/token-estimator.js";
37
+ import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
38
+ import { loadCustomCommands } from "../core/custom-commands.js";
39
+ import { getMCPServers } from "../core/mcp/index.js";
40
+ import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd } from "./live-item-flush.js";
41
+ // ── Provider Error Hints ──────────────────────────────────
42
+ /** Detect provider-side errors and return a user-facing hint. */
43
+ function getProviderErrorHint(message) {
44
+ const lower = message.toLowerCase();
45
+ if (lower.includes("overloaded") || lower.includes("engine_overloaded")) {
46
+ return "This is a provider-side issue — their servers are under heavy load. Try again in a moment.";
47
+ }
48
+ if (lower.includes("insufficient balance") ||
49
+ lower.includes("no resource package") ||
50
+ lower.includes("quota exceeded") ||
51
+ lower.includes("recharge")) {
52
+ return "The provider reports a billing or quota issue. Check your account balance or resource package.";
53
+ }
54
+ if (lower.includes("rate limit") ||
55
+ lower.includes("too many requests") ||
56
+ lower.includes("429")) {
57
+ return "You've hit the provider's rate limit. Wait a moment before retrying.";
58
+ }
59
+ if (lower.includes("502") || lower.includes("bad gateway")) {
60
+ return "The provider returned a server error. This is not a mragentix issue — try again shortly.";
61
+ }
62
+ if (lower.includes("503") || lower.includes("service unavailable")) {
63
+ return "The provider's service is temporarily unavailable. Try again in a moment.";
64
+ }
65
+ if (lower.includes("timeout") || lower.includes("timed out")) {
66
+ return "The request to the provider timed out. Their servers may be slow — try again.";
67
+ }
68
+ if (lower.includes("500") && lower.includes("internal server error")) {
69
+ return "The provider experienced an internal error. This is not a mragentix issue.";
70
+ }
71
+ if (lower.includes("does not recognize the requested model") ||
72
+ (lower.includes("model") &&
73
+ (lower.includes("not exist") || lower.includes("not found") || lower.includes("no access")))) {
74
+ return "Use /model to switch to a different model, or check that your account has access to the requested model.";
75
+ }
76
+ return null;
77
+ }
78
+ /** Tools that get aggregated into a single compact group when concurrent. */
79
+ const AGGREGATABLE_TOOLS = new Set(["read", "grep", "find", "ls"]);
80
+ /**
81
+ * Cap memory by replacing old items with tiny tombstones. Ink's <Static>
82
+ * tracks rendered items by array length — the array must never shrink, but
83
+ * we can swap out heavy objects for lightweight `{ kind: "tombstone", id }`
84
+ * entries so GC can reclaim the original data.
85
+ */
86
+ const MAX_LIVE_HISTORY = 200;
87
+ function compactHistory(items) {
88
+ if (items.length <= MAX_LIVE_HISTORY)
89
+ return items;
90
+ const cutoff = items.length - MAX_LIVE_HISTORY;
91
+ const compacted = new Array(items.length);
92
+ for (let i = 0; i < cutoff; i++) {
93
+ const it = items[i];
94
+ compacted[i] = it.kind === "tombstone" ? it : { kind: "tombstone", id: it.id };
95
+ }
96
+ for (let i = cutoff; i < items.length; i++) {
97
+ compacted[i] = items[i];
98
+ }
99
+ return compacted;
100
+ }
101
+ // flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
102
+ // ── Duration summary ─────────────────────────────────────
103
+ function formatDuration(ms) {
104
+ const totalSec = Math.round(ms / 1000);
105
+ if (totalSec < 60)
106
+ return `${totalSec}s`;
107
+ const min = Math.floor(totalSec / 60);
108
+ const sec = totalSec % 60;
109
+ return sec > 0 ? `${min}m ${sec}s` : `${min}m`;
110
+ }
111
+ function pickDurationVerb(toolsUsed) {
112
+ const has = (name) => toolsUsed.includes(name);
113
+ const hasAny = (...names) => names.some(has);
114
+ const writing = has("edit") || has("write");
115
+ const reading = has("read") || has("grep") || has("find") || has("ls");
116
+ // Multi-tool combos (most specific first)
117
+ if (has("subagent") && writing)
118
+ return "Orchestrated changes for";
119
+ if (has("subagent"))
120
+ return "Delegated work for";
121
+ if (has("web-fetch") && writing)
122
+ return "Researched & coded for";
123
+ if (has("web-fetch") && reading)
124
+ return "Researched for";
125
+ if (has("web-fetch"))
126
+ return "Fetched the web for";
127
+ if (has("bash") && writing)
128
+ return "Built & ran for";
129
+ if (has("edit") && has("write"))
130
+ return "Crafted code for";
131
+ if (has("edit") && has("bash"))
132
+ return "Refactored & tested for";
133
+ if (has("edit") && reading)
134
+ return "Refactored for";
135
+ if (has("edit"))
136
+ return "Refactored for";
137
+ if (has("write") && has("bash"))
138
+ return "Wrote & ran for";
139
+ if (has("write") && reading)
140
+ return "Wrote code for";
141
+ if (has("write"))
142
+ return "Wrote code for";
143
+ if (has("bash") && has("grep"))
144
+ return "Hacked away for";
145
+ if (has("bash") && reading)
146
+ return "Ran & investigated for";
147
+ if (has("bash"))
148
+ return "Executed commands for";
149
+ if (hasAny("tasks", "task-output", "task-stop"))
150
+ return "Managed tasks for";
151
+ if (has("grep") && has("read"))
152
+ return "Investigated for";
153
+ if (has("grep") && has("find"))
154
+ return "Scoured the codebase for";
155
+ if (has("grep"))
156
+ return "Searched for";
157
+ if (has("read") && has("find"))
158
+ return "Explored for";
159
+ if (has("read"))
160
+ return "Studied the code for";
161
+ if (has("find") || has("ls"))
162
+ return "Browsed files for";
163
+ // No tools used — pure text response
164
+ const phrases = [
165
+ "Pondered for",
166
+ "Thought for",
167
+ "Reasoned for",
168
+ "Mulled it over for",
169
+ "Noodled on it for",
170
+ "Brewed up a response in",
171
+ "Cooked up an answer in",
172
+ "Worked out a reply in",
173
+ "Channeled wisdom for",
174
+ "Conjured a response in",
175
+ ];
176
+ return phrases[Math.floor(Math.random() * phrases.length)];
177
+ }
178
+ // ── Animated thinking border ────────────────────────────────
179
+ const THINKING_BORDER_COLORS = ["#60a5fa", "#818cf8", "#a78bfa", "#818cf8", "#60a5fa"];
180
+ // ── Task count helper ───────────────────────────────────────
181
+ function getTaskCount(cwd) {
182
+ try {
183
+ const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
184
+ const data = readFileSync(join(homedir(), ".mragentix-tasks", "projects", hash, "tasks.json"), "utf-8");
185
+ const tasks = JSON.parse(data);
186
+ return tasks.filter((t) => t.status !== "done").length;
187
+ }
188
+ catch {
189
+ return 0;
190
+ }
191
+ }
192
+ function getNextPendingTask(cwd) {
193
+ try {
194
+ const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
195
+ const data = readFileSync(join(homedir(), ".mragentix-tasks", "projects", hash, "tasks.json"), "utf-8");
196
+ const tasks = JSON.parse(data);
197
+ const pending = tasks.find((t) => t.status === "pending");
198
+ if (!pending)
199
+ return null;
200
+ return {
201
+ id: pending.id,
202
+ title: pending.title,
203
+ prompt: pending.prompt || pending.text || pending.title,
204
+ };
205
+ }
206
+ catch {
207
+ return null;
208
+ }
209
+ }
210
+ function markTaskInProgress(cwd, taskId) {
211
+ try {
212
+ const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
213
+ const filePath = join(homedir(), ".mragentix-tasks", "projects", hash, "tasks.json");
214
+ const data = readFileSync(filePath, "utf-8");
215
+ const tasks = JSON.parse(data);
216
+ const updated = tasks.map((t) => (t.id === taskId ? { ...t, status: "in-progress" } : t));
217
+ writeFileSync(filePath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
218
+ }
219
+ catch {
220
+ // ignore
221
+ }
222
+ }
223
+ // ── App Component ──────────────────────────────────────────
224
+ export function App(props) {
225
+ const theme = useTheme();
226
+ const { stdout } = useStdout();
227
+ const { resizeKey } = useTerminalSize();
228
+ // Terminal title — updated later after agentLoop is created
229
+ // (hoisted here so the hook is always called in the same order)
230
+ const [titlePhase, setTitlePhase] = useState("idle");
231
+ const [titleRunning, setTitleRunning] = useState(false);
232
+ useTerminalTitle(titlePhase, titleRunning);
233
+ // Items scrolled into Static (history). For restored sessions, skip the
234
+ // banner and add restored items via useEffect so Ink's <Static> treats them
235
+ // as incremental additions (large initial arrays can race with Static's
236
+ // internal useLayoutEffect and get dropped before being flushed).
237
+ const isRestoredSession = props.initialHistory && props.initialHistory.length > 0;
238
+ const [history, setHistory] = useState(isRestoredSession ? [] : [{ kind: "banner", id: "banner" }]);
239
+ const restoredRef = useRef(false);
240
+ useEffect(() => {
241
+ if (isRestoredSession && !restoredRef.current) {
242
+ restoredRef.current = true;
243
+ setHistory((prev) => compactHistory([...prev, ...trimFlushedItems(props.initialHistory)]));
244
+ }
245
+ }, [isRestoredSession, props.initialHistory]);
246
+ // Items from the current/last turn — rendered in the live area so they stay visible
247
+ const [liveItems, setLiveItems] = useState([]);
248
+ const [overlay, setOverlay] = useState(null);
249
+ const [taskCount, setTaskCount] = useState(() => getTaskCount(props.cwd));
250
+ const [runAllTasks, setRunAllTasks] = useState(false);
251
+ const runAllTasksRef = useRef(false);
252
+ const startTaskRef = useRef(() => { });
253
+ const cwdRef = useRef(props.cwd);
254
+ const [staticKey, setStaticKey] = useState(0);
255
+ const [lastUserMessage, setLastUserMessage] = useState("");
256
+ const [doneStatus, setDoneStatus] = useState(null);
257
+ const [gitBranch, setGitBranch] = useState(null);
258
+ const [currentModel, setCurrentModel] = useState(props.model);
259
+ const [currentProvider, setCurrentProvider] = useState(props.provider);
260
+ const [currentTools, setCurrentTools] = useState(props.tools);
261
+ const [thinkingEnabled, setThinkingEnabled] = useState(!!props.thinking);
262
+ const messagesRef = useRef(props.messages);
263
+ const nextIdRef = useRef(0);
264
+ const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
265
+ const sessionPathRef = useRef(props.sessionPath);
266
+ const persistedIndexRef = useRef(messagesRef.current.length);
267
+ const getId = () => String(nextIdRef.current++);
268
+ // Two-phase flush: items waiting to be moved to Static history after the
269
+ // live area has been cleared and Ink has committed the smaller output.
270
+ const pendingFlushRef = useRef([]);
271
+ // Derive credentials for the current provider
272
+ const currentCreds = props.credentialsByProvider?.[currentProvider];
273
+ const activeApiKey = currentCreds?.accessToken ?? props.apiKey;
274
+ const activeAccountId = currentCreds?.accountId ?? props.accountId;
275
+ // Load git branch
276
+ useEffect(() => {
277
+ getGitBranch(props.cwd).then(setGitBranch);
278
+ }, [props.cwd]);
279
+ // Load custom commands from .mragentix/commands/
280
+ const [customCommands, setCustomCommands] = useState([]);
281
+ const reloadCustomCommands = useCallback(() => {
282
+ loadCustomCommands(props.cwd).then(setCustomCommands);
283
+ }, [props.cwd]);
284
+ useEffect(() => {
285
+ reloadCustomCommands();
286
+ }, [reloadCustomCommands]);
287
+ const persistNewMessages = useCallback(async () => {
288
+ const sm = sessionManagerRef.current;
289
+ const sp = sessionPathRef.current;
290
+ if (!sm || !sp)
291
+ return;
292
+ const allMsgs = messagesRef.current;
293
+ for (let i = persistedIndexRef.current; i < allMsgs.length; i++) {
294
+ const msg = allMsgs[i];
295
+ if (msg.role === "system")
296
+ continue;
297
+ const entry = {
298
+ type: "message",
299
+ id: crypto.randomUUID(),
300
+ parentId: null,
301
+ timestamp: new Date().toISOString(),
302
+ message: msg,
303
+ };
304
+ await sm.appendEntry(sp, entry);
305
+ }
306
+ persistedIndexRef.current = allMsgs.length;
307
+ }, []);
308
+ // ── Compaction ─────────────────────────────────────────
309
+ // Load settings for auto-compaction
310
+ const settingsRef = useRef(null);
311
+ useEffect(() => {
312
+ if (props.settingsFile) {
313
+ const sm = new SettingsManager(props.settingsFile);
314
+ sm.load().then(() => {
315
+ settingsRef.current = sm;
316
+ });
317
+ }
318
+ }, [props.settingsFile]);
319
+ const compactConversation = useCallback(async (messages) => {
320
+ const contextWindow = getContextWindow(currentModel);
321
+ const tokensBefore = estimateConversationTokens(messages);
322
+ const spinId = getId();
323
+ log("INFO", "compaction", `Running compaction`, {
324
+ messages: String(messages.length),
325
+ estimatedTokens: String(tokensBefore),
326
+ contextWindow: String(contextWindow),
327
+ });
328
+ // Show animated spinner
329
+ setLiveItems((prev) => [...prev, { kind: "compacting", id: spinId }]);
330
+ try {
331
+ // Resolve fresh credentials for compaction too
332
+ let compactApiKey = activeApiKey;
333
+ if (props.authStorage) {
334
+ const creds = await props.authStorage.resolveCredentials(currentProvider);
335
+ compactApiKey = creds.accessToken;
336
+ }
337
+ const result = await compact(messages, {
338
+ provider: currentProvider,
339
+ model: currentModel,
340
+ apiKey: compactApiKey,
341
+ contextWindow,
342
+ signal: undefined,
343
+ });
344
+ // Replace spinner with completed notice
345
+ setLiveItems((prev) => prev.map((item) => item.id === spinId
346
+ ? {
347
+ kind: "compacted",
348
+ originalCount: result.result.originalCount,
349
+ newCount: result.result.newCount,
350
+ tokensBefore: result.result.tokensBeforeEstimate,
351
+ tokensAfter: result.result.tokensAfterEstimate,
352
+ id: spinId,
353
+ }
354
+ : item));
355
+ return result.messages;
356
+ }
357
+ catch (err) {
358
+ const msg = err instanceof Error ? err.message : String(err);
359
+ log("ERROR", "compaction", `Compaction failed: ${msg}`);
360
+ // Replace spinner with error
361
+ setLiveItems((prev) => prev.map((item) => item.id === spinId
362
+ ? { kind: "error", message: `Compaction failed: ${msg}`, id: spinId }
363
+ : item));
364
+ return messages; // Return unchanged on failure
365
+ }
366
+ }, [currentModel, currentProvider, activeApiKey]);
367
+ /**
368
+ * transformContext callback for the agent loop.
369
+ * Called before each LLM call and on context overflow.
370
+ * Checks if auto-compaction is needed and runs it.
371
+ */
372
+ const transformContext = useCallback(async (messages, options) => {
373
+ const settings = settingsRef.current;
374
+ const autoCompact = settings?.get("autoCompact") ?? true;
375
+ const threshold = settings?.get("compactThreshold") ?? 0.8;
376
+ // Force-compact on context overflow regardless of settings
377
+ if (options?.force) {
378
+ return compactConversation(messages);
379
+ }
380
+ if (!autoCompact)
381
+ return messages;
382
+ const contextWindow = getContextWindow(currentModel);
383
+ if (shouldCompact(messages, contextWindow, threshold)) {
384
+ return compactConversation(messages);
385
+ }
386
+ return messages;
387
+ }, [currentModel, compactConversation]);
388
+ // ── Background task bar state ───────────────────────────
389
+ const [bgTasks, setBgTasks] = useState([]);
390
+ const [taskBarFocused, setTaskBarFocused] = useState(false);
391
+ const [taskBarExpanded, setTaskBarExpanded] = useState(false);
392
+ const [selectedTaskIndex, setSelectedTaskIndex] = useState(0);
393
+ // Poll ProcessManager every 2s for running tasks
394
+ useEffect(() => {
395
+ if (!props.processManager)
396
+ return;
397
+ const pm = props.processManager;
398
+ const poll = () => {
399
+ const running = pm.list().filter((p) => p.exitCode === null);
400
+ setBgTasks(running);
401
+ };
402
+ poll();
403
+ const interval = setInterval(poll, 2000);
404
+ return () => clearInterval(interval);
405
+ }, [props.processManager]);
406
+ // Auto-exit task panel when all tasks gone
407
+ useEffect(() => {
408
+ if (bgTasks.length === 0) {
409
+ setTaskBarFocused(false);
410
+ setTaskBarExpanded(false);
411
+ }
412
+ // Clamp selected index
413
+ const maxIdx = Math.min(bgTasks.length, 5) - 1;
414
+ if (selectedTaskIndex > maxIdx && maxIdx >= 0) {
415
+ setSelectedTaskIndex(maxIdx);
416
+ }
417
+ }, [bgTasks.length, selectedTaskIndex]);
418
+ const handleFocusTaskBar = useCallback(() => {
419
+ if (bgTasks.length > 0) {
420
+ setTaskBarFocused(true);
421
+ }
422
+ }, [bgTasks.length]);
423
+ const handleTaskBarExit = useCallback(() => {
424
+ setTaskBarFocused(false);
425
+ setTaskBarExpanded(false);
426
+ }, []);
427
+ const handleTaskBarExpand = useCallback(() => {
428
+ setTaskBarExpanded(true);
429
+ setSelectedTaskIndex(0);
430
+ }, []);
431
+ const handleTaskBarCollapse = useCallback(() => {
432
+ setTaskBarExpanded(false);
433
+ }, []);
434
+ const handleTaskKill = useCallback((id) => {
435
+ props.processManager?.stop(id);
436
+ }, [props.processManager]);
437
+ const handleTaskNavigate = useCallback((index) => {
438
+ setSelectedTaskIndex(index);
439
+ }, []);
440
+ // Resolve fresh OAuth credentials before each agent loop run.
441
+ // Falls back to the static props when authStorage is not available.
442
+ const resolveCredentials = useCallback(async () => {
443
+ if (props.authStorage) {
444
+ const creds = await props.authStorage.resolveCredentials(currentProvider);
445
+ return { apiKey: creds.accessToken, accountId: creds.accountId };
446
+ }
447
+ return { apiKey: activeApiKey, accountId: activeAccountId };
448
+ }, [props.authStorage, currentProvider, activeApiKey, activeAccountId]);
449
+ const agentLoop = useAgentLoop(messagesRef, {
450
+ provider: currentProvider,
451
+ model: currentModel,
452
+ tools: currentTools,
453
+ webSearch: props.webSearch,
454
+ maxTokens: props.maxTokens,
455
+ thinking: thinkingEnabled ? (props.thinking ?? "medium") : undefined,
456
+ apiKey: activeApiKey,
457
+ baseUrl: props.baseUrl,
458
+ accountId: activeAccountId,
459
+ resolveCredentials,
460
+ transformContext,
461
+ }, {
462
+ onComplete: useCallback(() => {
463
+ persistNewMessages();
464
+ }, [persistNewMessages]),
465
+ onTurnText: useCallback((text, thinking, thinkingMs) => {
466
+ // Flush all completed items from the previous turn to Static history.
467
+ // This keeps liveItems bounded per-turn, preventing Ink's live area from
468
+ // growing unbounded, which makes Ink's live-area re-renders expensive.
469
+ setLiveItems((prev) => {
470
+ const flushed = flushOnTurnText(prev);
471
+ if (flushed.length > 0) {
472
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
473
+ }
474
+ return [{ kind: "assistant", text, thinking, thinkingMs, id: getId() }];
475
+ });
476
+ }, []),
477
+ onToolStart: useCallback((toolCallId, name, args) => {
478
+ log("INFO", "tool", `Tool call started: ${name}`, { id: toolCallId });
479
+ if (name === "subagent") {
480
+ // Create or update the sub-agent group item
481
+ const newAgent = {
482
+ toolCallId,
483
+ task: String(args.task ?? ""),
484
+ agentName: String(args.agent ?? "default"),
485
+ status: "running",
486
+ toolUseCount: 0,
487
+ tokenUsage: { input: 0, output: 0 },
488
+ };
489
+ setLiveItems((prev) => {
490
+ const groupIdx = prev.findIndex((item) => item.kind === "subagent_group");
491
+ if (groupIdx !== -1) {
492
+ const group = prev[groupIdx];
493
+ const next = [...prev];
494
+ next[groupIdx] = {
495
+ ...group,
496
+ agents: [...group.agents, newAgent],
497
+ };
498
+ return next;
499
+ }
500
+ return [...prev, { kind: "subagent_group", agents: [newAgent], id: getId() }];
501
+ });
502
+ }
503
+ else if (AGGREGATABLE_TOOLS.has(name)) {
504
+ // Group concurrent read-only tools into a single compact item
505
+ setLiveItems((prev) => {
506
+ // Find an active tool group (has at least one running tool)
507
+ const groupIdx = prev.findIndex((item) => item.kind === "tool_group" &&
508
+ item.tools.some((t) => t.status === "running"));
509
+ if (groupIdx !== -1) {
510
+ const group = prev[groupIdx];
511
+ const next = [...prev];
512
+ next[groupIdx] = {
513
+ ...group,
514
+ tools: [...group.tools, { toolCallId, name, args, status: "running" }],
515
+ };
516
+ return next;
517
+ }
518
+ return [
519
+ ...prev,
520
+ {
521
+ kind: "tool_group",
522
+ tools: [{ toolCallId, name, args, status: "running" }],
523
+ id: getId(),
524
+ },
525
+ ];
526
+ });
527
+ }
528
+ else {
529
+ setLiveItems((prev) => [
530
+ ...prev,
531
+ { kind: "tool_start", toolCallId, name, args, id: getId() },
532
+ ]);
533
+ }
534
+ }, []),
535
+ onToolUpdate: useCallback((toolCallId, update) => {
536
+ setLiveItems((prev) => {
537
+ const groupIdx = prev.findIndex((item) => item.kind === "subagent_group");
538
+ if (groupIdx === -1)
539
+ return prev;
540
+ const group = prev[groupIdx];
541
+ const agentIdx = group.agents.findIndex((a) => a.toolCallId === toolCallId);
542
+ if (agentIdx === -1)
543
+ return prev;
544
+ const saUpdate = update;
545
+ const updatedAgents = [...group.agents];
546
+ updatedAgents[agentIdx] = {
547
+ ...updatedAgents[agentIdx],
548
+ toolUseCount: saUpdate.toolUseCount,
549
+ tokenUsage: { ...saUpdate.tokenUsage },
550
+ currentActivity: saUpdate.currentActivity,
551
+ };
552
+ const next = [...prev];
553
+ next[groupIdx] = { ...group, agents: updatedAgents };
554
+ return next;
555
+ });
556
+ }, []),
557
+ onToolEnd: useCallback((toolCallId, name, result, isError, durationMs, details) => {
558
+ const level = isError ? "ERROR" : "INFO";
559
+ log(level, "tool", `Tool call ended: ${name}`, {
560
+ id: toolCallId,
561
+ duration: `${durationMs}ms`,
562
+ isError: String(isError),
563
+ });
564
+ if (name === "subagent") {
565
+ setLiveItems((prev) => {
566
+ const groupIdx = prev.findIndex((item) => item.kind === "subagent_group");
567
+ if (groupIdx === -1)
568
+ return prev;
569
+ const group = prev[groupIdx];
570
+ const agentIdx = group.agents.findIndex((a) => a.toolCallId === toolCallId);
571
+ if (agentIdx === -1)
572
+ return prev;
573
+ const saDetails = details;
574
+ const updatedAgents = [...group.agents];
575
+ updatedAgents[agentIdx] = {
576
+ ...updatedAgents[agentIdx],
577
+ status: isError ? "error" : "done",
578
+ result,
579
+ durationMs: saDetails?.durationMs ?? durationMs,
580
+ toolUseCount: saDetails?.toolUseCount ?? updatedAgents[agentIdx].toolUseCount,
581
+ tokenUsage: saDetails?.tokenUsage ?? updatedAgents[agentIdx].tokenUsage,
582
+ };
583
+ const next = [...prev];
584
+ next[groupIdx] = { ...group, agents: updatedAgents };
585
+ return next;
586
+ });
587
+ }
588
+ else {
589
+ setLiveItems((prev) => {
590
+ // Check if this tool is in a tool_group
591
+ const groupIdx = prev.findIndex((item) => item.kind === "tool_group" &&
592
+ item.tools.some((t) => t.toolCallId === toolCallId));
593
+ if (groupIdx !== -1) {
594
+ const group = prev[groupIdx];
595
+ const next = [...prev];
596
+ next[groupIdx] = {
597
+ ...group,
598
+ tools: group.tools.map((t) => t.toolCallId === toolCallId
599
+ ? { ...t, status: "done", result, isError }
600
+ : t),
601
+ };
602
+ return next;
603
+ }
604
+ // Find the matching tool_start and replace it with tool_done
605
+ const startIdx = prev.findIndex((item) => item.kind === "tool_start" && item.toolCallId === toolCallId);
606
+ if (startIdx !== -1) {
607
+ const startItem = prev[startIdx];
608
+ const doneItem = {
609
+ kind: "tool_done",
610
+ name,
611
+ args: startItem.args,
612
+ result,
613
+ isError,
614
+ durationMs,
615
+ id: startItem.id,
616
+ };
617
+ const next = [...prev];
618
+ next[startIdx] = doneItem;
619
+ return next;
620
+ }
621
+ // Fallback: just append
622
+ return [
623
+ ...prev,
624
+ { kind: "tool_done", name, args: {}, result, isError, durationMs, id: getId() },
625
+ ];
626
+ });
627
+ }
628
+ }, []),
629
+ onServerToolCall: useCallback((id, name, input) => {
630
+ log("INFO", "server_tool", `Server tool call: ${name}`, { id });
631
+ setLiveItems((prev) => [
632
+ ...prev,
633
+ {
634
+ kind: "server_tool_start",
635
+ serverToolCallId: id,
636
+ name,
637
+ input,
638
+ startedAt: Date.now(),
639
+ id: getId(),
640
+ },
641
+ ]);
642
+ }, []),
643
+ onServerToolResult: useCallback((toolUseId, resultType, data) => {
644
+ log("INFO", "server_tool", `Server tool result`, { toolUseId, resultType });
645
+ setLiveItems((prev) => {
646
+ const startIdx = prev.findIndex((item) => item.kind === "server_tool_start" && item.serverToolCallId === toolUseId);
647
+ if (startIdx !== -1) {
648
+ const startItem = prev[startIdx];
649
+ const doneItem = {
650
+ kind: "server_tool_done",
651
+ name: startItem.name,
652
+ input: startItem.input,
653
+ resultType,
654
+ data,
655
+ durationMs: Date.now() - startItem.startedAt,
656
+ id: startItem.id,
657
+ };
658
+ const next = [...prev];
659
+ next[startIdx] = doneItem;
660
+ return next;
661
+ }
662
+ return [
663
+ ...prev,
664
+ {
665
+ kind: "server_tool_done",
666
+ name: "unknown",
667
+ input: {},
668
+ resultType,
669
+ data,
670
+ durationMs: 0,
671
+ id: getId(),
672
+ },
673
+ ];
674
+ });
675
+ }, []),
676
+ onTurnEnd: useCallback((turn, stopReason, usage) => {
677
+ log("INFO", "turn", `Turn ${turn} ended`, {
678
+ stopReason,
679
+ inputTokens: String(usage.inputTokens),
680
+ outputTokens: String(usage.outputTokens),
681
+ ...(usage.cacheRead != null && { cacheRead: String(usage.cacheRead) }),
682
+ ...(usage.cacheWrite != null && { cacheWrite: String(usage.cacheWrite) }),
683
+ });
684
+ // For tool-only turns (no text), flush completed items to Static so
685
+ // liveItems doesn't grow unbounded across consecutive tool-only turns.
686
+ setLiveItems((prev) => {
687
+ const { flushed, remaining } = flushOnTurnEnd(prev, stopReason);
688
+ if (flushed.length > 0) {
689
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(flushed)]));
690
+ }
691
+ return remaining;
692
+ });
693
+ }, []),
694
+ onDone: useCallback((durationMs, toolsUsed) => {
695
+ log("INFO", "agent", `Agent done`, {
696
+ duration: `${durationMs}ms`,
697
+ toolsUsed: toolsUsed.join(",") || "none",
698
+ });
699
+ setDoneStatus({ durationMs, toolsUsed, verb: pickDurationVerb(toolsUsed) });
700
+ playNotificationSound();
701
+ // Two-phase flush to avoid Ink text clipping.
702
+ // Phase 1 (here): clear the live area so Ink commits a render with
703
+ // the smaller output and updates its internal line counter.
704
+ // Phase 2 (useEffect below): push items to Static history in a
705
+ // separate render cycle so the Static write never coincides with
706
+ // a live-area height change in the same frame.
707
+ setLiveItems((prev) => {
708
+ if (prev.length > 0) {
709
+ pendingFlushRef.current = prev;
710
+ }
711
+ return [];
712
+ });
713
+ // Run-all: auto-start next pending task after a short delay
714
+ // (allow the two-phase flush to complete first)
715
+ if (runAllTasksRef.current) {
716
+ setTimeout(() => {
717
+ const cwd = cwdRef.current;
718
+ const next = getNextPendingTask(cwd);
719
+ if (next) {
720
+ markTaskInProgress(cwd, next.id);
721
+ startTaskRef.current(next.title, next.prompt, next.id);
722
+ }
723
+ else {
724
+ setRunAllTasks(false);
725
+ log("INFO", "tasks", "Run-all complete — no more pending tasks");
726
+ }
727
+ }, 500);
728
+ }
729
+ }, []),
730
+ onAborted: useCallback(() => {
731
+ log("WARN", "agent", "Agent run aborted by user");
732
+ setRunAllTasks(false);
733
+ setLiveItems((prev) => {
734
+ const next = prev.map((item) => {
735
+ if (item.kind === "subagent_group")
736
+ return { ...item, aborted: true };
737
+ // Convert running tools to stopped state so spinners stop
738
+ if (item.kind === "tool_start") {
739
+ return {
740
+ kind: "tool_done",
741
+ name: item.name,
742
+ args: item.args,
743
+ result: "Stopped.",
744
+ isError: true,
745
+ durationMs: 0,
746
+ id: item.id,
747
+ };
748
+ }
749
+ if (item.kind === "server_tool_start") {
750
+ return {
751
+ kind: "server_tool_done",
752
+ name: item.name,
753
+ input: item.input,
754
+ resultType: "aborted",
755
+ data: null,
756
+ durationMs: 0,
757
+ id: item.id,
758
+ };
759
+ }
760
+ if (item.kind === "tool_group") {
761
+ const tools = item.tools.map((t) => t.status === "running"
762
+ ? { ...t, status: "done", result: "Stopped.", isError: true }
763
+ : t);
764
+ return { ...item, tools };
765
+ }
766
+ // Remove compaction spinner (compaction can't complete after abort)
767
+ if (item.kind === "compacting") {
768
+ return { kind: "tombstone", id: item.id };
769
+ }
770
+ return item;
771
+ });
772
+ return [...next, { kind: "info", text: "Request was stopped.", id: getId() }];
773
+ });
774
+ }, []),
775
+ });
776
+ // Phase 2 of the two-phase flush: after onDone clears liveItems (phase 1)
777
+ // and Ink renders the smaller live area (updating its internal line
778
+ // counter), this effect pushes the stashed items into Static history.
779
+ // Because the Static write happens in a SEPARATE render cycle from the
780
+ // live-area shrink, Ink's log-update never needs to erase the old tall
781
+ // live area AND write Static content in the same frame — avoiding the
782
+ // cursor-math mismatch that caused text clipping.
783
+ useEffect(() => {
784
+ if (pendingFlushRef.current.length > 0) {
785
+ const items = pendingFlushRef.current;
786
+ pendingFlushRef.current = [];
787
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(items)]));
788
+ }
789
+ });
790
+ // Sync terminal title with agent loop state
791
+ useEffect(() => {
792
+ setTitlePhase(agentLoop.activityPhase);
793
+ setTitleRunning(agentLoop.isRunning);
794
+ }, [agentLoop.activityPhase, agentLoop.isRunning]);
795
+ // Animated thinking border — derived from global animation tick
796
+ const animTick = useAnimationTick();
797
+ const thinkingBorderFrame = agentLoop.activityPhase === "thinking"
798
+ ? deriveFrame(animTick, 1000, THINKING_BORDER_COLORS.length)
799
+ : 0;
800
+ const handleSubmit = useCallback(async (input, inputImages = [], pasteInfo) => {
801
+ const trimmed = input.trim();
802
+ if (trimmed.startsWith("/")) {
803
+ log("INFO", "command", `Slash command: ${trimmed}`);
804
+ }
805
+ else {
806
+ const truncated = trimmed.length > 100 ? trimmed.slice(0, 100) + "..." : trimmed;
807
+ log("INFO", "input", `User input: ${truncated}${inputImages.length > 0 ? ` (+${inputImages.length} image${inputImages.length > 1 ? "s" : ""})` : ""}`);
808
+ }
809
+ // Handle /model directly — open inline selector
810
+ if (trimmed === "/model" || trimmed === "/m") {
811
+ setOverlay("model");
812
+ return;
813
+ }
814
+ // Handle /compact — compact conversation
815
+ if (trimmed === "/compact" || trimmed === "/c") {
816
+ const compacted = await compactConversation(messagesRef.current);
817
+ if (compacted !== messagesRef.current) {
818
+ messagesRef.current = compacted;
819
+ persistedIndexRef.current = 0; // Re-persist after compaction
820
+ }
821
+ return;
822
+ }
823
+ // Handle /quit — exit the agent
824
+ if (trimmed === "/quit" || trimmed === "/q" || trimmed === "/exit") {
825
+ process.exit(0);
826
+ }
827
+ // Handle /clear — reset session and clear terminal
828
+ if (trimmed === "/clear") {
829
+ // Clear terminal screen + scrollback — needed because Ink's <Static>
830
+ // writes directly to stdout and can't be removed by clearing React state
831
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
832
+ setHistory([{ kind: "banner", id: "banner" }]);
833
+ setLiveItems([]);
834
+ setDoneStatus(null);
835
+ messagesRef.current = messagesRef.current.slice(0, 1); // keep system prompt
836
+ agentLoop.reset();
837
+ setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
838
+ return;
839
+ }
840
+ // Handle prompt-template commands (built-in + custom from .mragentix/commands/)
841
+ if (trimmed.startsWith("/")) {
842
+ const parts = trimmed.slice(1).split(" ");
843
+ const cmdName = parts[0];
844
+ const cmdArgs = parts.slice(1).join(" ").trim();
845
+ const builtinCmd = getPromptCommand(cmdName);
846
+ const customCmd = !builtinCmd ? customCommands.find((c) => c.name === cmdName) : undefined;
847
+ const promptText = builtinCmd?.prompt ?? customCmd?.prompt;
848
+ if (promptText) {
849
+ log("INFO", "command", `Prompt command: /${cmdName}${cmdArgs ? ` (args: ${cmdArgs})` : ""}`);
850
+ // Move live items into history before starting
851
+ setLiveItems((prev) => {
852
+ if (prev.length > 0) {
853
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
854
+ }
855
+ return [];
856
+ });
857
+ // Show the command name as the user message
858
+ const userItem = { kind: "user", text: trimmed, id: getId() };
859
+ setLastUserMessage(trimmed);
860
+ setDoneStatus(null);
861
+ setLiveItems([userItem]);
862
+ // Send the full prompt to the agent, with user args appended if provided
863
+ const fullPrompt = cmdArgs
864
+ ? `${promptText}\n\n## User Instructions\n\n${cmdArgs}`
865
+ : promptText;
866
+ try {
867
+ await agentLoop.run(fullPrompt);
868
+ }
869
+ catch (err) {
870
+ const msg = err instanceof Error ? err.message : String(err);
871
+ log("ERROR", "error", msg);
872
+ const isAbort = msg.includes("aborted") || msg.includes("abort");
873
+ setLiveItems((prev) => [
874
+ ...prev,
875
+ isAbort
876
+ ? { kind: "info", text: "Request was stopped.", id: getId() }
877
+ : { kind: "error", message: msg, id: getId() },
878
+ ]);
879
+ }
880
+ // Reload custom commands in case a setup command created new ones
881
+ reloadCustomCommands();
882
+ return;
883
+ }
884
+ }
885
+ // Check slash commands
886
+ if (props.onSlashCommand && input.startsWith("/")) {
887
+ const result = await props.onSlashCommand(input);
888
+ if (result !== null) {
889
+ setLiveItems((prev) => [...prev, { kind: "info", text: result, id: getId() }]);
890
+ return;
891
+ }
892
+ }
893
+ // Move any remaining live items into history (Static) before starting new turn
894
+ setLiveItems((prev) => {
895
+ if (prev.length > 0) {
896
+ setHistory((h) => compactHistory([...h, ...trimFlushedItems(prev)]));
897
+ }
898
+ return [];
899
+ });
900
+ // Build display text — strip image paths, show badges instead
901
+ const hasImages = inputImages.length > 0;
902
+ let displayText = input;
903
+ if (hasImages) {
904
+ const { cleanText } = await extractImagePaths(input, props.cwd);
905
+ displayText = cleanText;
906
+ }
907
+ const userItem = {
908
+ kind: "user",
909
+ text: displayText,
910
+ imageCount: hasImages ? inputImages.length : undefined,
911
+ pasteInfo,
912
+ id: getId(),
913
+ };
914
+ setLastUserMessage(input);
915
+ setDoneStatus(null);
916
+ setLiveItems([userItem]);
917
+ // Build user content — plain string or content array with images
918
+ const modelInfo = getModel(currentModel);
919
+ const modelSupportsImages = modelInfo?.supportsImages ?? true;
920
+ let userContent;
921
+ if (hasImages) {
922
+ const parts = [];
923
+ if (trimmed) {
924
+ parts.push({ type: "text", text: trimmed });
925
+ }
926
+ for (const img of inputImages) {
927
+ if (img.kind === "text") {
928
+ parts.push({
929
+ type: "text",
930
+ text: `<file name="${img.fileName}">\n${img.data}\n</file>`,
931
+ });
932
+ }
933
+ else if (modelSupportsImages) {
934
+ parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
935
+ }
936
+ else {
937
+ // GLM models: save image to temp file and instruct model to use vision MCP tool
938
+ const ext = img.mediaType.split("/")[1] ?? "png";
939
+ const tmpPath = `/tmp/mragentix-img-${Date.now()}.${ext}`;
940
+ try {
941
+ writeFileSync(tmpPath, Buffer.from(img.data, "base64"));
942
+ parts.push({
943
+ type: "text",
944
+ text: `[User attached an image saved at: ${tmpPath} — use the image_analysis tool to view and analyze it]`,
945
+ });
946
+ }
947
+ catch {
948
+ parts.push({
949
+ type: "text",
950
+ text: `[User attached an image but it could not be saved for analysis]`,
951
+ });
952
+ }
953
+ }
954
+ }
955
+ // If only text parts remain after stripping images, simplify to plain string
956
+ userContent = parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts;
957
+ }
958
+ else {
959
+ userContent = input;
960
+ }
961
+ // Run agent
962
+ try {
963
+ await agentLoop.run(userContent);
964
+ }
965
+ catch (err) {
966
+ const msg = err instanceof Error ? err.message : String(err);
967
+ log("ERROR", "error", msg);
968
+ const isAbort = msg.includes("aborted") || msg.includes("abort");
969
+ setLiveItems((prev) => [
970
+ ...prev,
971
+ isAbort
972
+ ? { kind: "info", text: "Request was stopped.", id: getId() }
973
+ : { kind: "error", message: msg, id: getId() },
974
+ ]);
975
+ }
976
+ }, [agentLoop, props.onSlashCommand, compactConversation]);
977
+ const handleAbort = useCallback(() => {
978
+ if (agentLoop.isRunning) {
979
+ agentLoop.abort();
980
+ }
981
+ else {
982
+ process.exit(0);
983
+ }
984
+ }, [agentLoop]);
985
+ const handleToggleThinking = useCallback(() => {
986
+ setThinkingEnabled((prev) => {
987
+ const next = !prev;
988
+ log("INFO", "thinking", `Thinking ${next ? "enabled" : "disabled"}`);
989
+ setLiveItems((items) => [
990
+ ...items,
991
+ { kind: "info", text: `Thinking ${next ? "on" : "off"}`, id: getId() },
992
+ ]);
993
+ if (props.settingsFile) {
994
+ const sm = new SettingsManager(props.settingsFile);
995
+ sm.load().then(() => sm.set("thinkingEnabled", next));
996
+ }
997
+ return next;
998
+ });
999
+ }, [props.settingsFile]);
1000
+ const handleModelSelect = useCallback((value) => {
1001
+ setOverlay(null);
1002
+ const colonIdx = value.indexOf(":");
1003
+ if (colonIdx === -1)
1004
+ return;
1005
+ const newProvider = value.slice(0, colonIdx);
1006
+ const newModelId = value.slice(colonIdx + 1);
1007
+ log("INFO", "model", `Model changed`, { provider: newProvider, model: newModelId });
1008
+ // Reconnect MCP servers when provider changes
1009
+ setCurrentProvider((prevProvider) => {
1010
+ if (newProvider !== prevProvider && props.mcpManager) {
1011
+ void (async () => {
1012
+ // Disconnect old MCP servers
1013
+ await props.mcpManager.dispose();
1014
+ // Remove old MCP tools, connect new ones
1015
+ let apiKey;
1016
+ if (newProvider === "glm" && props.authStorage) {
1017
+ try {
1018
+ const glmCreds = await props.authStorage.resolveCredentials("glm");
1019
+ apiKey = glmCreds.accessToken;
1020
+ }
1021
+ catch {
1022
+ // GLM not configured — skip Z.AI MCP servers
1023
+ }
1024
+ }
1025
+ else if (newProvider === "glm") {
1026
+ apiKey = props.credentialsByProvider?.["glm"]?.accessToken;
1027
+ }
1028
+ try {
1029
+ const mcpTools = await props.mcpManager.connectAll(getMCPServers(newProvider, apiKey));
1030
+ setCurrentTools((prev) => [
1031
+ ...prev.filter((t) => !t.name.startsWith("mcp__")),
1032
+ ...mcpTools,
1033
+ ]);
1034
+ log("INFO", "mcp", `MCP servers reconnected for provider ${newProvider}`);
1035
+ }
1036
+ catch (err) {
1037
+ log("WARN", "mcp", `MCP reconnection failed: ${err instanceof Error ? err.message : String(err)}`);
1038
+ // Still remove old MCP tools even if reconnection fails
1039
+ setCurrentTools((prev) => prev.filter((t) => !t.name.startsWith("mcp__")));
1040
+ }
1041
+ })();
1042
+ }
1043
+ return newProvider;
1044
+ });
1045
+ setCurrentModel(newModelId);
1046
+ const modelInfo = getModel(newModelId);
1047
+ const displayName = modelInfo?.name ?? newModelId;
1048
+ setLiveItems((prev) => [
1049
+ ...prev,
1050
+ { kind: "info", text: `Switched to ${displayName}`, id: getId() },
1051
+ ]);
1052
+ // Persist model selection for next CLI launch
1053
+ if (props.settingsFile) {
1054
+ const sm = new SettingsManager(props.settingsFile);
1055
+ sm.load().then(async () => {
1056
+ await sm.set("defaultProvider", newProvider);
1057
+ await sm.set("defaultModel", newModelId);
1058
+ });
1059
+ }
1060
+ }, [props.settingsFile, props.mcpManager, props.credentialsByProvider, props.authStorage]);
1061
+ // All available slash commands for the command palette
1062
+ const allCommands = useMemo(() => [
1063
+ { name: "model", aliases: ["m"], description: "Switch model" },
1064
+ { name: "compact", aliases: ["c"], description: "Compact conversation" },
1065
+ { name: "clear", aliases: [], description: "Clear session and terminal" },
1066
+ { name: "quit", aliases: ["q", "exit"], description: "Exit the agent" },
1067
+ ...PROMPT_COMMANDS.map((cmd) => ({
1068
+ name: cmd.name,
1069
+ aliases: cmd.aliases,
1070
+ description: cmd.description,
1071
+ })),
1072
+ ...customCommands.map((cmd) => ({
1073
+ name: cmd.name,
1074
+ aliases: [],
1075
+ description: cmd.description,
1076
+ })),
1077
+ ], [customCommands]);
1078
+ const renderItem = (item) => {
1079
+ switch (item.kind) {
1080
+ case "tombstone":
1081
+ return null;
1082
+ case "banner":
1083
+ return (_jsx(Banner, { version: props.version, model: props.model, provider: props.provider, cwd: props.cwd, taskCount: taskCount }, item.id));
1084
+ case "user":
1085
+ return (_jsx(UserMessage, { text: item.text, imageCount: item.imageCount, pasteInfo: item.pasteInfo }, item.id));
1086
+ case "task":
1087
+ return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.success, bold: true, children: "▶ " }), _jsx(Text, { color: theme.textDim, children: "Task: " }), _jsx(Text, { color: theme.success, children: item.title })] }) }, item.id));
1088
+ case "assistant":
1089
+ return (_jsx(AssistantMessage, { text: item.text, thinking: item.thinking, thinkingMs: item.thinkingMs, showThinking: props.showThinking }, item.id));
1090
+ case "tool_start":
1091
+ return _jsx(ToolExecution, { status: "running", name: item.name, args: item.args }, item.id);
1092
+ case "tool_done":
1093
+ return (_jsx(ToolExecution, { status: "done", name: item.name, args: item.args, result: item.result, isError: item.isError }, item.id));
1094
+ case "tool_group":
1095
+ return _jsx(ToolGroupExecution, { tools: item.tools }, item.id);
1096
+ case "server_tool_start":
1097
+ return (_jsx(ServerToolExecution, { status: "running", name: item.name, input: item.input, startedAt: item.startedAt }, item.id));
1098
+ case "server_tool_done":
1099
+ return (_jsx(ServerToolExecution, { status: "done", name: item.name, input: item.input, durationMs: item.durationMs, resultType: item.resultType }, item.id));
1100
+ case "error": {
1101
+ const providerHint = getProviderErrorHint(item.message);
1102
+ return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: theme.error, children: ["✗ ", item.message] }), providerHint && (_jsxs(Text, { color: theme.textDim, children: [" Hint: ", providerHint] }))] }, item.id));
1103
+ }
1104
+ case "info":
1105
+ return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textDim, children: item.text }) }, item.id));
1106
+ case "compacting":
1107
+ return _jsx(CompactionSpinner, {}, item.id);
1108
+ case "compacted":
1109
+ return (_jsx(CompactionDone, { originalCount: item.originalCount, newCount: item.newCount, tokensBefore: item.tokensBefore, tokensAfter: item.tokensAfter }, item.id));
1110
+ case "duration":
1111
+ return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.textDim, children: ["✻ ", item.verb, " ", formatDuration(item.durationMs)] }) }, item.id));
1112
+ case "subagent_group":
1113
+ return _jsx(SubAgentPanel, { agents: item.agents, aborted: item.aborted }, item.id);
1114
+ }
1115
+ };
1116
+ // ── Start a task (shared by manual "work on it" and run-all) ──
1117
+ const startTask = useCallback((title, prompt, taskId) => {
1118
+ setTaskCount(getTaskCount(props.cwd));
1119
+ // Reset to a fresh session before sending the task
1120
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1121
+ setHistory([{ kind: "banner", id: "banner" }]);
1122
+ setLiveItems([]);
1123
+ messagesRef.current = messagesRef.current.slice(0, 1);
1124
+ agentLoop.reset();
1125
+ persistedIndexRef.current = messagesRef.current.length;
1126
+ const sm = sessionManagerRef.current;
1127
+ if (sm) {
1128
+ void sm.create(props.cwd, currentProvider, currentModel).then((s) => {
1129
+ sessionPathRef.current = s.path;
1130
+ log("INFO", "tasks", "New session for task", { path: s.path });
1131
+ });
1132
+ }
1133
+ // Inject completion instruction so the agent marks the task done
1134
+ const shortId = taskId.slice(0, 8);
1135
+ const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
1136
+ `tasks({ action: "done", id: "${shortId}" })`;
1137
+ const fullPrompt = prompt + completionHint;
1138
+ // Show the short title in the TUI, but send the full prompt to the agent
1139
+ const taskItem = { kind: "task", title, id: getId() };
1140
+ setLastUserMessage(title);
1141
+ setDoneStatus(null);
1142
+ setLiveItems([taskItem]);
1143
+ void (async () => {
1144
+ try {
1145
+ await agentLoop.run(fullPrompt);
1146
+ }
1147
+ catch (err) {
1148
+ const msg = err instanceof Error ? err.message : String(err);
1149
+ log("ERROR", "error", msg);
1150
+ const isAbort = msg.includes("aborted") || msg.includes("abort");
1151
+ setLiveItems((prev) => [
1152
+ ...prev,
1153
+ isAbort
1154
+ ? { kind: "info", text: "Request was stopped.", id: getId() }
1155
+ : { kind: "error", message: msg, id: getId() },
1156
+ ]);
1157
+ // Stop run-all if a task errors
1158
+ setRunAllTasks(false);
1159
+ }
1160
+ })();
1161
+ }, [props.cwd, stdout, agentLoop, currentProvider, currentModel]);
1162
+ // Keep refs in sync for access from stale closures (onDone)
1163
+ startTaskRef.current = startTask;
1164
+ useEffect(() => {
1165
+ runAllTasksRef.current = runAllTasks;
1166
+ }, [runAllTasks]);
1167
+ const isTaskView = overlay === "tasks";
1168
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: isTaskView ? [] : history, style: { width: "100%" }, children: (item) => (_jsx(Box, { flexDirection: "column", paddingRight: 1, children: renderItem(item) }, item.id)) }, `${resizeKey}-${staticKey}`), isTaskView ? (_jsx(TaskOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, onClose: () => {
1169
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1170
+ setTaskCount(getTaskCount(props.cwd));
1171
+ setStaticKey((k) => k + 1);
1172
+ setOverlay(null);
1173
+ }, onWorkOnTask: (title, prompt, taskId) => {
1174
+ setOverlay(null);
1175
+ startTask(title, prompt, taskId);
1176
+ }, onRunAllTasks: () => {
1177
+ setOverlay(null);
1178
+ setRunAllTasks(true);
1179
+ const next = getNextPendingTask(props.cwd);
1180
+ if (next) {
1181
+ markTaskInProgress(props.cwd, next.id);
1182
+ startTask(next.title, next.prompt, next.id);
1183
+ }
1184
+ } })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingRight: 1, children: [liveItems.map((item) => renderItem(item)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: agentLoop.streamingText, streamingThinking: agentLoop.streamingThinking, showThinking: props.showThinking, thinkingMs: agentLoop.thinkingMs })] }), agentLoop.isRunning && agentLoop.activityPhase !== "idle" ? (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: agentLoop.activityPhase === "thinking"
1185
+ ? THINKING_BORDER_COLORS[thinkingBorderFrame]
1186
+ : "transparent", paddingLeft: 1, paddingRight: 1, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, tokenEstimate: agentLoop.streamedTokenEstimate, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name) }) })) : (doneStatus && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
1187
+ stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1188
+ setOverlay("tasks");
1189
+ }, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, cwd: props.cwd, gitBranch: gitBranch, thinkingEnabled: thinkingEnabled })), bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate }))] }))] }));
1190
+ }
1191
+ //# sourceMappingURL=App.js.map