@prestyj/cli 4.2.15

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