@kenkaiiii/ggcoder 4.3.218 → 4.3.220

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 (393) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +41 -21
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/agent-session-compaction.test.js +0 -7
  5. package/dist/core/agent-session-compaction.test.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +0 -23
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +4 -98
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/continue-replay-inventory.test.js +1 -1
  11. package/dist/core/continue-replay-inventory.test.js.map +1 -1
  12. package/dist/core/goal-controller.d.ts +2 -0
  13. package/dist/core/goal-controller.d.ts.map +1 -1
  14. package/dist/core/goal-controller.js +152 -8
  15. package/dist/core/goal-controller.js.map +1 -1
  16. package/dist/core/goal-controller.test.js +232 -3
  17. package/dist/core/goal-controller.test.js.map +1 -1
  18. package/dist/core/goal-overhead-harness.d.ts +33 -0
  19. package/dist/core/goal-overhead-harness.d.ts.map +1 -0
  20. package/dist/core/goal-overhead-harness.js +268 -0
  21. package/dist/core/goal-overhead-harness.js.map +1 -0
  22. package/dist/core/goal-store.d.ts +3 -0
  23. package/dist/core/goal-store.d.ts.map +1 -1
  24. package/dist/core/goal-store.js +64 -3
  25. package/dist/core/goal-store.js.map +1 -1
  26. package/dist/core/goal-store.test.js +13 -0
  27. package/dist/core/goal-store.test.js.map +1 -1
  28. package/dist/core/goal-worker.d.ts.map +1 -1
  29. package/dist/core/goal-worker.js +17 -11
  30. package/dist/core/goal-worker.js.map +1 -1
  31. package/dist/core/goal-worker.test.js +41 -8
  32. package/dist/core/goal-worker.test.js.map +1 -1
  33. package/dist/core/goal-worktree.d.ts +21 -0
  34. package/dist/core/goal-worktree.d.ts.map +1 -1
  35. package/dist/core/goal-worktree.js +71 -1
  36. package/dist/core/goal-worktree.js.map +1 -1
  37. package/dist/core/goal-worktree.test.js +127 -2
  38. package/dist/core/goal-worktree.test.js.map +1 -1
  39. package/dist/core/prompt-commands.js +2 -2
  40. package/dist/core/prompt-commands.test.js +1 -1
  41. package/dist/core/prompt-commands.test.js.map +1 -1
  42. package/dist/core/runtime-mode.d.ts +7 -0
  43. package/dist/core/runtime-mode.d.ts.map +1 -1
  44. package/dist/core/runtime-mode.js +6 -0
  45. package/dist/core/runtime-mode.js.map +1 -1
  46. package/dist/core/session-manager.d.ts +3 -0
  47. package/dist/core/session-manager.d.ts.map +1 -1
  48. package/dist/core/session-manager.js +16 -0
  49. package/dist/core/session-manager.js.map +1 -1
  50. package/dist/core/session-restore-display.test.js +84 -0
  51. package/dist/core/session-restore-display.test.js.map +1 -1
  52. package/dist/core/slash-commands.d.ts +0 -2
  53. package/dist/core/slash-commands.d.ts.map +1 -1
  54. package/dist/core/slash-commands.js +0 -15
  55. package/dist/core/slash-commands.js.map +1 -1
  56. package/dist/core/tasks-store.d.ts +24 -0
  57. package/dist/core/tasks-store.d.ts.map +1 -0
  58. package/dist/core/tasks-store.js +81 -0
  59. package/dist/core/tasks-store.js.map +1 -0
  60. package/dist/system-prompt.d.ts +1 -1
  61. package/dist/system-prompt.d.ts.map +1 -1
  62. package/dist/system-prompt.js +13 -1
  63. package/dist/system-prompt.js.map +1 -1
  64. package/dist/system-prompt.test.js +2 -2
  65. package/dist/system-prompt.test.js.map +1 -1
  66. package/dist/tools/bash.d.ts +2 -0
  67. package/dist/tools/bash.d.ts.map +1 -1
  68. package/dist/tools/bash.js +5 -2
  69. package/dist/tools/bash.js.map +1 -1
  70. package/dist/tools/edit.d.ts +2 -0
  71. package/dist/tools/edit.d.ts.map +1 -1
  72. package/dist/tools/edit.js +16 -3
  73. package/dist/tools/edit.js.map +1 -1
  74. package/dist/tools/enter-plan.d.ts +8 -0
  75. package/dist/tools/enter-plan.d.ts.map +1 -0
  76. package/dist/tools/enter-plan.js +27 -0
  77. package/dist/tools/enter-plan.js.map +1 -0
  78. package/dist/tools/exit-plan.d.ts +8 -0
  79. package/dist/tools/exit-plan.d.ts.map +1 -0
  80. package/dist/tools/exit-plan.js +35 -0
  81. package/dist/tools/exit-plan.js.map +1 -0
  82. package/dist/tools/goals.d.ts +8 -5
  83. package/dist/tools/goals.d.ts.map +1 -1
  84. package/dist/tools/goals.js +124 -49
  85. package/dist/tools/goals.js.map +1 -1
  86. package/dist/tools/goals.test.js +356 -11
  87. package/dist/tools/goals.test.js.map +1 -1
  88. package/dist/tools/index.d.ts +11 -0
  89. package/dist/tools/index.d.ts.map +1 -1
  90. package/dist/tools/index.js +18 -4
  91. package/dist/tools/index.js.map +1 -1
  92. package/dist/tools/plan-mode.test.js +63 -24
  93. package/dist/tools/plan-mode.test.js.map +1 -1
  94. package/dist/tools/prompt-hints.d.ts.map +1 -1
  95. package/dist/tools/prompt-hints.js +4 -0
  96. package/dist/tools/prompt-hints.js.map +1 -1
  97. package/dist/tools/subagent.d.ts +3 -1
  98. package/dist/tools/subagent.d.ts.map +1 -1
  99. package/dist/tools/subagent.js +5 -2
  100. package/dist/tools/subagent.js.map +1 -1
  101. package/dist/tools/tasks.d.ts +16 -0
  102. package/dist/tools/tasks.d.ts.map +1 -0
  103. package/dist/tools/tasks.js +93 -0
  104. package/dist/tools/tasks.js.map +1 -0
  105. package/dist/tools/write.d.ts +2 -0
  106. package/dist/tools/write.d.ts.map +1 -1
  107. package/dist/tools/write.js +23 -3
  108. package/dist/tools/write.js.map +1 -1
  109. package/dist/ui/App.d.ts +10 -6
  110. package/dist/ui/App.d.ts.map +1 -1
  111. package/dist/ui/App.js +660 -987
  112. package/dist/ui/App.js.map +1 -1
  113. package/dist/ui/app-items.d.ts +7 -1
  114. package/dist/ui/app-items.d.ts.map +1 -1
  115. package/dist/ui/app-items.js +1 -1
  116. package/dist/ui/app-items.js.map +1 -1
  117. package/dist/ui/app-state-persistence.test.js +41 -23
  118. package/dist/ui/app-state-persistence.test.js.map +1 -1
  119. package/dist/ui/chat-layout-pinning.test.js +10 -0
  120. package/dist/ui/chat-layout-pinning.test.js.map +1 -1
  121. package/dist/ui/components/AssistantMessage.test.js +7 -7
  122. package/dist/ui/components/AssistantMessage.test.js.map +1 -1
  123. package/dist/ui/components/BackgroundTasksBar.d.ts.map +1 -1
  124. package/dist/ui/components/BackgroundTasksBar.js +6 -6
  125. package/dist/ui/components/BackgroundTasksBar.js.map +1 -1
  126. package/dist/ui/components/Banner.js +2 -2
  127. package/dist/ui/components/Banner.js.map +1 -1
  128. package/dist/ui/components/ChatFooterPane.d.ts +29 -0
  129. package/dist/ui/components/ChatFooterPane.d.ts.map +1 -0
  130. package/dist/ui/components/ChatFooterPane.js +16 -0
  131. package/dist/ui/components/ChatFooterPane.js.map +1 -0
  132. package/dist/ui/components/ChatInputStack.d.ts +34 -0
  133. package/dist/ui/components/ChatInputStack.d.ts.map +1 -0
  134. package/dist/ui/components/ChatInputStack.js +9 -0
  135. package/dist/ui/components/ChatInputStack.js.map +1 -0
  136. package/dist/ui/components/ChatLayout.d.ts +23 -0
  137. package/dist/ui/components/ChatLayout.d.ts.map +1 -0
  138. package/dist/ui/components/ChatLayout.js +16 -0
  139. package/dist/ui/components/ChatLayout.js.map +1 -0
  140. package/dist/ui/components/ChatLivePane.d.ts +18 -0
  141. package/dist/ui/components/ChatLivePane.d.ts.map +1 -0
  142. package/dist/ui/components/ChatLivePane.js +8 -0
  143. package/dist/ui/components/ChatLivePane.js.map +1 -0
  144. package/dist/ui/components/ChatScreen.d.ts +118 -0
  145. package/dist/ui/components/ChatScreen.d.ts.map +1 -0
  146. package/dist/ui/components/ChatScreen.js +14 -0
  147. package/dist/ui/components/ChatScreen.js.map +1 -0
  148. package/dist/ui/components/ChatStatusRow.d.ts +34 -0
  149. package/dist/ui/components/ChatStatusRow.d.ts.map +1 -0
  150. package/dist/ui/components/ChatStatusRow.js +11 -0
  151. package/dist/ui/components/ChatStatusRow.js.map +1 -0
  152. package/dist/ui/components/CompactionNotice.d.ts +4 -2
  153. package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
  154. package/dist/ui/components/CompactionNotice.js +4 -4
  155. package/dist/ui/components/CompactionNotice.js.map +1 -1
  156. package/dist/ui/components/Footer.d.ts +6 -3
  157. package/dist/ui/components/Footer.d.ts.map +1 -1
  158. package/dist/ui/components/Footer.js +14 -4
  159. package/dist/ui/components/Footer.js.map +1 -1
  160. package/dist/ui/components/FooterStatusRow.d.ts +20 -0
  161. package/dist/ui/components/FooterStatusRow.d.ts.map +1 -0
  162. package/dist/ui/components/FooterStatusRow.js +10 -0
  163. package/dist/ui/components/FooterStatusRow.js.map +1 -0
  164. package/dist/ui/components/FullScreenOverlayRouter.d.ts +19 -0
  165. package/dist/ui/components/FullScreenOverlayRouter.d.ts.map +1 -0
  166. package/dist/ui/components/FullScreenOverlayRouter.js +18 -0
  167. package/dist/ui/components/FullScreenOverlayRouter.js.map +1 -0
  168. package/dist/ui/components/GoalOverlay.d.ts +2 -1
  169. package/dist/ui/components/GoalOverlay.d.ts.map +1 -1
  170. package/dist/ui/components/GoalOverlay.js +11 -6
  171. package/dist/ui/components/GoalOverlay.js.map +1 -1
  172. package/dist/ui/components/GoalPickerMenu.d.ts +9 -0
  173. package/dist/ui/components/GoalPickerMenu.d.ts.map +1 -0
  174. package/dist/ui/components/GoalPickerMenu.js +37 -0
  175. package/dist/ui/components/GoalPickerMenu.js.map +1 -0
  176. package/dist/ui/components/GoalStatusBar.d.ts +2 -0
  177. package/dist/ui/components/GoalStatusBar.d.ts.map +1 -1
  178. package/dist/ui/components/GoalStatusBar.js +27 -11
  179. package/dist/ui/components/GoalStatusBar.js.map +1 -1
  180. package/dist/ui/components/GoalStatusBar.test.d.ts +2 -0
  181. package/dist/ui/components/GoalStatusBar.test.d.ts.map +1 -0
  182. package/dist/ui/components/GoalStatusBar.test.js +17 -0
  183. package/dist/ui/components/GoalStatusBar.test.js.map +1 -0
  184. package/dist/ui/components/InputArea.d.ts +16 -1
  185. package/dist/ui/components/InputArea.d.ts.map +1 -1
  186. package/dist/ui/components/InputArea.js +95 -6
  187. package/dist/ui/components/InputArea.js.map +1 -1
  188. package/dist/ui/components/PlanOverlay.d.ts +7 -0
  189. package/dist/ui/components/PlanOverlay.d.ts.map +1 -1
  190. package/dist/ui/components/PlanOverlay.js +16 -2
  191. package/dist/ui/components/PlanOverlay.js.map +1 -1
  192. package/dist/ui/components/PlanOverlay.test.d.ts +2 -0
  193. package/dist/ui/components/PlanOverlay.test.d.ts.map +1 -0
  194. package/dist/ui/components/PlanOverlay.test.js +24 -0
  195. package/dist/ui/components/PlanOverlay.test.js.map +1 -0
  196. package/dist/ui/components/QueueIndicator.d.ts +9 -0
  197. package/dist/ui/components/QueueIndicator.d.ts.map +1 -0
  198. package/dist/ui/components/QueueIndicator.js +9 -0
  199. package/dist/ui/components/QueueIndicator.js.map +1 -0
  200. package/dist/ui/components/ServerToolExecution.d.ts +2 -0
  201. package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
  202. package/dist/ui/components/ServerToolExecution.js +3 -2
  203. package/dist/ui/components/ServerToolExecution.js.map +1 -1
  204. package/dist/ui/components/StreamingArea.js +1 -1
  205. package/dist/ui/components/StreamingArea.js.map +1 -1
  206. package/dist/ui/components/SubAgentPanel.d.ts +2 -1
  207. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  208. package/dist/ui/components/SubAgentPanel.js +2 -2
  209. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  210. package/dist/ui/components/TaskPickerMenu.d.ts +9 -0
  211. package/dist/ui/components/TaskPickerMenu.d.ts.map +1 -0
  212. package/dist/ui/components/TaskPickerMenu.js +33 -0
  213. package/dist/ui/components/TaskPickerMenu.js.map +1 -0
  214. package/dist/ui/components/ToolExecution.d.ts +2 -0
  215. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  216. package/dist/ui/components/ToolExecution.js +13 -11
  217. package/dist/ui/components/ToolExecution.js.map +1 -1
  218. package/dist/ui/components/ToolGroupExecution.d.ts +2 -1
  219. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
  220. package/dist/ui/components/ToolGroupExecution.js +5 -3
  221. package/dist/ui/components/ToolGroupExecution.js.map +1 -1
  222. package/dist/ui/duration-format.d.ts +2 -0
  223. package/dist/ui/duration-format.d.ts.map +1 -0
  224. package/dist/ui/duration-format.js +9 -0
  225. package/dist/ui/duration-format.js.map +1 -0
  226. package/dist/ui/duration-summary.d.ts +2 -0
  227. package/dist/ui/duration-summary.d.ts.map +1 -0
  228. package/dist/ui/duration-summary.js +66 -0
  229. package/dist/ui/duration-summary.js.map +1 -0
  230. package/dist/ui/error-item.d.ts +8 -0
  231. package/dist/ui/error-item.d.ts.map +1 -0
  232. package/dist/ui/error-item.js +32 -0
  233. package/dist/ui/error-item.js.map +1 -0
  234. package/dist/ui/footer-status-layout.test.js +4 -3
  235. package/dist/ui/footer-status-layout.test.js.map +1 -1
  236. package/dist/ui/goal-events.d.ts +1 -0
  237. package/dist/ui/goal-events.d.ts.map +1 -1
  238. package/dist/ui/goal-events.js +2 -1
  239. package/dist/ui/goal-events.js.map +1 -1
  240. package/dist/ui/goal-events.test.js +16 -0
  241. package/dist/ui/goal-events.test.js.map +1 -1
  242. package/dist/ui/goal-lifecycle-orchestration.test.js +105 -18
  243. package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
  244. package/dist/ui/goal-progress.d.ts +1 -1
  245. package/dist/ui/goal-progress.d.ts.map +1 -1
  246. package/dist/ui/goal-progress.js +11 -13
  247. package/dist/ui/goal-progress.js.map +1 -1
  248. package/dist/ui/goal-run-helpers.d.ts +16 -0
  249. package/dist/ui/goal-run-helpers.d.ts.map +1 -0
  250. package/dist/ui/goal-run-helpers.js +61 -0
  251. package/dist/ui/goal-run-helpers.js.map +1 -0
  252. package/dist/ui/goal-status-bar.test.js +8 -6
  253. package/dist/ui/goal-status-bar.test.js.map +1 -1
  254. package/dist/ui/hooks/useChatLayoutMeasurements.d.ts +38 -0
  255. package/dist/ui/hooks/useChatLayoutMeasurements.d.ts.map +1 -0
  256. package/dist/ui/hooks/useChatLayoutMeasurements.js +71 -0
  257. package/dist/ui/hooks/useChatLayoutMeasurements.js.map +1 -0
  258. package/dist/ui/hooks/useGoalPickerController.d.ts +22 -0
  259. package/dist/ui/hooks/useGoalPickerController.d.ts.map +1 -0
  260. package/dist/ui/hooks/useGoalPickerController.js +35 -0
  261. package/dist/ui/hooks/useGoalPickerController.js.map +1 -0
  262. package/dist/ui/hooks/useTaskPickerController.d.ts +19 -0
  263. package/dist/ui/hooks/useTaskPickerController.d.ts.map +1 -0
  264. package/dist/ui/hooks/useTaskPickerController.js +41 -0
  265. package/dist/ui/hooks/useTaskPickerController.js.map +1 -0
  266. package/dist/ui/hooks/useTranscriptHistory.d.ts +34 -0
  267. package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -0
  268. package/dist/ui/hooks/useTranscriptHistory.js +96 -0
  269. package/dist/ui/hooks/useTranscriptHistory.js.map +1 -0
  270. package/dist/ui/item-helpers.d.ts +2 -0
  271. package/dist/ui/item-helpers.d.ts.map +1 -1
  272. package/dist/ui/item-helpers.js +16 -0
  273. package/dist/ui/item-helpers.js.map +1 -1
  274. package/dist/ui/layout-decisions.d.ts +3 -12
  275. package/dist/ui/layout-decisions.d.ts.map +1 -1
  276. package/dist/ui/layout-decisions.js +7 -64
  277. package/dist/ui/layout-decisions.js.map +1 -1
  278. package/dist/ui/prompt-routing.d.ts.map +1 -1
  279. package/dist/ui/prompt-routing.js +36 -2
  280. package/dist/ui/prompt-routing.js.map +1 -1
  281. package/dist/ui/prompt-routing.test.d.ts +2 -0
  282. package/dist/ui/prompt-routing.test.d.ts.map +1 -0
  283. package/dist/ui/prompt-routing.test.js +48 -0
  284. package/dist/ui/prompt-routing.test.js.map +1 -0
  285. package/dist/ui/render.d.ts +9 -6
  286. package/dist/ui/render.d.ts.map +1 -1
  287. package/dist/ui/render.js +3 -2
  288. package/dist/ui/render.js.map +1 -1
  289. package/dist/ui/slash-command-images.test.js +3 -2
  290. package/dist/ui/slash-command-images.test.js.map +1 -1
  291. package/dist/ui/submit-prompt-command.d.ts +40 -0
  292. package/dist/ui/submit-prompt-command.d.ts.map +1 -0
  293. package/dist/ui/submit-prompt-command.js +92 -0
  294. package/dist/ui/submit-prompt-command.js.map +1 -0
  295. package/dist/ui/submit-slash-commands.d.ts +13 -0
  296. package/dist/ui/submit-slash-commands.d.ts.map +1 -0
  297. package/dist/ui/submit-slash-commands.js +36 -0
  298. package/dist/ui/submit-slash-commands.js.map +1 -0
  299. package/dist/ui/terminal-history-format.d.ts +1 -0
  300. package/dist/ui/terminal-history-format.d.ts.map +1 -1
  301. package/dist/ui/terminal-history-format.js +2 -1
  302. package/dist/ui/terminal-history-format.js.map +1 -1
  303. package/dist/ui/terminal-history-spacing.d.ts +1 -2
  304. package/dist/ui/terminal-history-spacing.d.ts.map +1 -1
  305. package/dist/ui/terminal-history-spacing.js +1 -27
  306. package/dist/ui/terminal-history-spacing.js.map +1 -1
  307. package/dist/ui/terminal-history-status-renderers.d.ts +2 -2
  308. package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
  309. package/dist/ui/terminal-history-status-renderers.js +36 -17
  310. package/dist/ui/terminal-history-status-renderers.js.map +1 -1
  311. package/dist/ui/terminal-history.d.ts.map +1 -1
  312. package/dist/ui/terminal-history.js +59 -25
  313. package/dist/ui/terminal-history.js.map +1 -1
  314. package/dist/ui/terminal-history.test.js +2 -1
  315. package/dist/ui/terminal-history.test.js.map +1 -1
  316. package/dist/ui/tool-group-summary.d.ts +2 -2
  317. package/dist/ui/tool-group-summary.d.ts.map +1 -1
  318. package/dist/ui/tool-group-summary.js +18 -18
  319. package/dist/ui/tool-group-summary.js.map +1 -1
  320. package/dist/ui/transcript/GoalRows.d.ts +10 -0
  321. package/dist/ui/transcript/GoalRows.d.ts.map +1 -0
  322. package/dist/ui/transcript/GoalRows.js +35 -0
  323. package/dist/ui/transcript/GoalRows.js.map +1 -0
  324. package/dist/ui/transcript/MiscRows.d.ts +23 -0
  325. package/dist/ui/transcript/MiscRows.d.ts.map +1 -0
  326. package/dist/ui/transcript/MiscRows.js +41 -0
  327. package/dist/ui/transcript/MiscRows.js.map +1 -0
  328. package/dist/ui/transcript/StatusRow.d.ts +14 -0
  329. package/dist/ui/transcript/StatusRow.d.ts.map +1 -0
  330. package/dist/ui/transcript/StatusRow.js +14 -0
  331. package/dist/ui/transcript/StatusRow.js.map +1 -0
  332. package/dist/ui/transcript/ToolRows.d.ts +20 -0
  333. package/dist/ui/transcript/ToolRows.d.ts.map +1 -0
  334. package/dist/ui/transcript/ToolRows.js +25 -0
  335. package/dist/ui/transcript/ToolRows.js.map +1 -0
  336. package/dist/ui/transcript/TranscriptItemFrame.d.ts +8 -0
  337. package/dist/ui/transcript/TranscriptItemFrame.d.ts.map +1 -0
  338. package/dist/ui/transcript/TranscriptItemFrame.js +9 -0
  339. package/dist/ui/transcript/TranscriptItemFrame.js.map +1 -0
  340. package/dist/ui/transcript/TranscriptRenderer.d.ts +22 -0
  341. package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -0
  342. package/dist/ui/transcript/TranscriptRenderer.js +84 -0
  343. package/dist/ui/transcript/TranscriptRenderer.js.map +1 -0
  344. package/dist/ui/transcript/presentation.d.ts +76 -0
  345. package/dist/ui/transcript/presentation.d.ts.map +1 -0
  346. package/dist/ui/transcript/presentation.js +109 -0
  347. package/dist/ui/transcript/presentation.js.map +1 -0
  348. package/dist/ui/transcript/spacing.d.ts +29 -0
  349. package/dist/ui/transcript/spacing.d.ts.map +1 -0
  350. package/dist/ui/transcript/spacing.js +91 -0
  351. package/dist/ui/transcript/spacing.js.map +1 -0
  352. package/dist/ui/transcript/spacing.test.d.ts +2 -0
  353. package/dist/ui/transcript/spacing.test.d.ts.map +1 -0
  354. package/dist/ui/transcript/spacing.test.js +21 -0
  355. package/dist/ui/transcript/spacing.test.js.map +1 -0
  356. package/dist/ui/transcript/tool-presentation.d.ts +12 -0
  357. package/dist/ui/transcript/tool-presentation.d.ts.map +1 -0
  358. package/dist/ui/transcript/tool-presentation.js +55 -0
  359. package/dist/ui/transcript/tool-presentation.js.map +1 -0
  360. package/dist/ui/tui-history-parity.test.js +6 -2
  361. package/dist/ui/tui-history-parity.test.js.map +1 -1
  362. package/dist/utils/plan-steps.d.ts.map +1 -1
  363. package/dist/utils/plan-steps.js +5 -1
  364. package/dist/utils/plan-steps.js.map +1 -1
  365. package/dist/utils/plan-steps.test.d.ts +2 -0
  366. package/dist/utils/plan-steps.test.d.ts.map +1 -0
  367. package/dist/utils/plan-steps.test.js +16 -0
  368. package/dist/utils/plan-steps.test.js.map +1 -0
  369. package/package.json +6 -6
  370. package/dist/core/repomap-budget.d.ts +0 -7
  371. package/dist/core/repomap-budget.d.ts.map +0 -1
  372. package/dist/core/repomap-budget.js +0 -10
  373. package/dist/core/repomap-budget.js.map +0 -1
  374. package/dist/core/repomap-budget.test.d.ts +0 -2
  375. package/dist/core/repomap-budget.test.d.ts.map +0 -1
  376. package/dist/core/repomap-budget.test.js +0 -26
  377. package/dist/core/repomap-budget.test.js.map +0 -1
  378. package/dist/core/repomap-context.d.ts +0 -11
  379. package/dist/core/repomap-context.d.ts.map +0 -1
  380. package/dist/core/repomap-context.js +0 -68
  381. package/dist/core/repomap-context.js.map +0 -1
  382. package/dist/core/repomap-context.test.d.ts +0 -2
  383. package/dist/core/repomap-context.test.d.ts.map +0 -1
  384. package/dist/core/repomap-context.test.js +0 -47
  385. package/dist/core/repomap-context.test.js.map +0 -1
  386. package/dist/core/repomap.d.ts +0 -74
  387. package/dist/core/repomap.d.ts.map +0 -1
  388. package/dist/core/repomap.js +0 -906
  389. package/dist/core/repomap.js.map +0 -1
  390. package/dist/core/repomap.test.d.ts +0 -2
  391. package/dist/core/repomap.test.d.ts.map +0 -1
  392. package/dist/core/repomap.test.js +0 -494
  393. package/dist/core/repomap.test.js.map +0 -1
package/dist/ui/App.js CHANGED
@@ -1,42 +1,25 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useState, useRef, useCallback, useEffect, useMemo } from "react";
3
- import { Box, Text, useStdout } from "ink";
3
+ import { Box, useStdout } from "ink";
4
4
  import { useTerminalSize } from "./hooks/useTerminalSize.js";
5
+ import { useChatLayoutMeasurements } from "./hooks/useChatLayoutMeasurements.js";
6
+ import { useTaskPickerController } from "./hooks/useTaskPickerController.js";
7
+ import { useGoalPickerController } from "./hooks/useGoalPickerController.js";
5
8
  import { useDoublePress } from "./hooks/useDoublePress.js";
6
9
  import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTaskBar, collapseTaskBar, navigateTaskBar, killTask, } from "./stores/taskbar-store.js";
7
10
  import { playNotificationSound } from "../utils/sound.js";
8
- import { formatError, } from "@kenkaiiii/gg-ai";
11
+ import {} from "@kenkaiiii/gg-ai";
9
12
  import { extractImagePaths } from "../utils/image.js";
10
- import { buildGoalReferenceContext, formatGoalReferencesForPrompt, } from "../core/goal-references.js";
11
13
  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 { ToolUseLoader } from "./components/ToolUseLoader.js";
16
- import { ToolGroupExecution } from "./components/ToolGroupExecution.js";
17
- import { ServerToolExecution } from "./components/ServerToolExecution.js";
18
- import { MessageResponse } from "./components/MessageResponse.js";
19
- import { SubAgentPanel } from "./components/SubAgentPanel.js";
20
- import { CompactionSpinner, CompactionDone } from "./components/CompactionNotice.js";
14
+ import { useTranscriptHistory } from "./hooks/useTranscriptHistory.js";
21
15
  import { createWebSearchTool } from "../tools/web-search.js";
22
- import { StreamingArea } from "./components/StreamingArea.js";
23
- import { ActivityIndicator } from "./components/ActivityIndicator.js";
24
- import { InputArea } from "./components/InputArea.js";
25
- import { Footer, doesFooterFitOnOneLine } from "./components/Footer.js";
26
- import { GoalStatusBar, reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntry, syncGoalStatusEntries, } from "./components/GoalStatusBar.js";
27
- import { Banner } from "./components/Banner.js";
28
- import { PlanOverlay } from "./components/PlanOverlay.js";
29
- import { ModelSelector } from "./components/ModelSelector.js";
30
- import { GoalOverlay } from "./components/GoalOverlay.js";
31
- import { PixelOverlay } from "./components/PixelOverlay.js";
32
- import { SkillsOverlay } from "./components/SkillsOverlay.js";
33
- import { ThemeSelector } from "./components/ThemeSelector.js";
34
- import { BackgroundTasksBar, getFooterStatusLayoutDecision, } from "./components/BackgroundTasksBar.js";
16
+ import { ChatScreen } from "./components/ChatScreen.js";
17
+ import { FullScreenOverlayRouter } from "./components/FullScreenOverlayRouter.js";
18
+ import { reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntry, syncGoalStatusEntries, } from "./components/GoalStatusBar.js";
35
19
  import { useTheme, useSetTheme } from "./theme/theme.js";
36
20
  import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
37
21
  import { getGitBranch } from "../utils/git.js";
38
22
  import { getModel, getContextWindow } from "../core/model-registry.js";
39
- import { BLACK_CIRCLE } from "./constants/figures.js";
40
23
  import { SessionManager } from "../core/session-manager.js";
41
24
  import { appendMessagesToSession as appendSessionMessages, createCompactedSessionCheckpoint, } from "../core/session-compaction.js";
42
25
  import { log } from "../core/logger.js";
@@ -49,64 +32,39 @@ import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
49
32
  import { isFirstTimeSetup, markSetupAudited, getAnnouncedLanguages, markLanguagesAnnounced, } from "../core/setup-history.js";
50
33
  import { loadCustomCommands } from "../core/custom-commands.js";
51
34
  import { buildSystemPrompt } from "../system-prompt.js";
52
- import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
35
+ import { detectLanguages } from "../core/language-detector.js";
53
36
  import { detectVerifyCommands } from "../core/verify-commands.js";
54
- import { buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
55
- import { getRepoMapBudgetForContext } from "../core/repomap-budget.js";
56
- import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMessages, } from "../core/repomap-context.js";
57
37
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
58
38
  import { getMCPServers } from "../core/mcp/index.js";
59
39
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
60
40
  import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
61
41
  import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
42
+ import { getNextPendingTask, markTaskInProgress } from "../core/tasks-store.js";
62
43
  import { canCompleteGoalRun, decideGoalNextAction } from "../core/goal-controller.js";
63
44
  import { runGoalPrerequisiteChecks } from "../core/goal-prerequisites.js";
64
45
  import { runGoalVerifierCommand } from "../core/goal-verifier.js";
46
+ import { checkGoalWorktreeIntegration, isGoalWorktreeDirtyError } from "../core/goal-worktree.js";
65
47
  import { listGoalWorkers, startGoalWorker, stopGoalWorker, subscribeGoalWorkerCompletions, } from "../core/goal-worker.js";
66
48
  import { formatGoalVerifierCompletionEvent, formatGoalWorkerCompletionEvent, isGoalSyntheticEvent, parseGoalSyntheticEvent, } from "./goal-events.js";
67
- import { buildUserContentWithAttachments, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
49
+ import { buildUserContentWithAttachments } from "./prompt-routing.js";
50
+ import { submitPromptCommand } from "./submit-prompt-command.js";
51
+ import { handleUiSlashCommand } from "./submit-slash-commands.js";
68
52
  import { getNextThinkingLevel, isThinkingLevelSupported } from "./thinking-level.js";
69
- import { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, formatGoalWorkerFinishedTitle, getGoalContinuationChoiceKey, goalTerminalProgressId, routeGoalSyntheticEvent, summarizeGoalCompletion, truncateGoalProgressText, } from "./goal-progress.js";
70
- import { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalActivationPaneTransition, getGoalSetupFinishedPaneTransition, getGoalSetupPaneTransitionAfterRun, isAgentSpacingItem, MIN_LIVE_AREA_ROWS, nextGoalModeAfterAgentDone, shouldResetUIForGoalSetupPaneTransition, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
71
- import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, } from "./item-helpers.js";
53
+ import { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, formatGoalWorkerFinishedTitle, getGoalContinuationChoiceKey, goalTerminalProgressId, routeGoalSyntheticEvent, summarizeGoalCompletion, } from "./goal-progress.js";
54
+ import { getDoneFlushDecision, nextGoalModeAfterAgentDone, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
55
+ import { isTranscriptSpacingItem } from "./transcript/spacing.js";
56
+ import { renderTranscriptItem } from "./transcript/TranscriptRenderer.js";
57
+ import { formatDuration } from "./duration-format.js";
58
+ import { pickDurationVerb } from "./duration-summary.js";
59
+ import { toErrorItem } from "./error-item.js";
60
+ import { buildGoalDirtyWorktreePauseRun, buildGoalDirtyWorktreeUserPrompt, buildGoalTaskPromptWithReferences, buildGoalUserPauseRun, goalDirtyWorktreeInfoText, goalRunNeedsExplicitContinuationAfterWorker, goalTaskProgress, shouldKeepGoalRunTrackedAfterDecision, shouldRunGoalTaskInMainCheckout, } from "./goal-run-helpers.js";
61
+ import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, removeItemsWithIds, uniqueItemsById, } from "./item-helpers.js";
72
62
  export { buildGoalSetupPromptFromPlanner, buildUserContentWithAttachments, collectAssistantTextSince, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
73
63
  export { getNextThinkingLevel } from "./thinking-level.js";
74
64
  export { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, getGoalContinuationChoiceKey, routeGoalSyntheticEvent, truncateGoalProgressText, } from "./goal-progress.js";
75
65
  export { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalActivationPaneTransition, getGoalSetupFinishedPaneTransition, getGoalSetupPaneTransitionAfterRun, getScrollStabilizationDecision, getStaticHistoryKey, hasParagraphBreakLiveUserMessage, isTallLiveUserMessage, nextGoalModeAfterAgentDone, shouldHideHistoryForOverlayView, shouldHideStaticItemsForOverlayView, shouldResetUIForGoalSetupPaneTransition, shouldStabilizeOverlayPaneRerender, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
76
66
  export { getNextGeneratedItemId, isActiveItem, partitionCompleted, pinStreamingTextBeforeToolBoundary, } from "./item-helpers.js";
77
- /** Where ggcoder bugs should be reported. Surfaced in the guidance line. */
78
- const GGCODER_BUG_REPORT_URL = "github.com/kenkaiiii/gg-framework/issues";
79
- /**
80
- * Build an ErrorItem from any thrown value. Centralises headline / message /
81
- * guidance extraction so every error answers the same question for the user:
82
- * "Should I retry, or is this a ggcoder bug to report?"
83
- */
84
- function toErrorItem(err, id, contextPrefix) {
85
- const f = formatError(err);
86
- const headline = contextPrefix ? `${contextPrefix} — ${f.headline}` : f.headline;
87
- // For ggcoder bugs, swap the generic "see /help" guidance for an actual URL
88
- // so users have a clear place to send the report.
89
- const guidance = f.source === "ggcoder"
90
- ? `This looks like a ggcoder bug — please send it to the dev at ${GGCODER_BUG_REPORT_URL}.`
91
- : f.guidance;
92
- // Mirror every user-visible error into ~/.gg/debug.log so reports can be
93
- // diagnosed even after the terminal scrollback is gone.
94
- log("ERROR", "ui-error", headline, {
95
- source: f.source,
96
- message: f.message,
97
- ...(f.provider ? { provider: f.provider } : {}),
98
- ...(f.statusCode != null ? { statusCode: String(f.statusCode) } : {}),
99
- ...(f.requestId ? { requestId: f.requestId } : {}),
100
- ...(err instanceof Error && err.stack ? { stack: err.stack } : {}),
101
- });
102
- return {
103
- kind: "error",
104
- headline,
105
- message: f.message,
106
- guidance,
107
- id,
108
- };
109
- }
67
+ export { buildGoalDirtyWorktreePauseRun, buildGoalDirtyWorktreeUserPrompt, buildGoalUserPauseRun, goalDirtyWorktreeInfoText, goalRunNeedsExplicitContinuationAfterWorker, shouldKeepGoalRunTrackedAfterDecision, shouldRunGoalTaskInMainCheckout, } from "./goal-run-helpers.js";
110
68
  /** Tools that get aggregated into a single compact group when possible. */
111
69
  const AGGREGATABLE_TOOLS = new Set([
112
70
  "read",
@@ -118,114 +76,6 @@ const AGGREGATABLE_TOOLS = new Set([
118
76
  "mcp__kencode-search__discoverRepos",
119
77
  ]);
120
78
  const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
121
- function buildGoalTaskPromptWithReferences(run, taskPrompt) {
122
- if (taskPrompt.includes("## Goal References (MANDATORY)"))
123
- return taskPrompt;
124
- const references = formatGoalReferencesForPrompt(run.references ?? []);
125
- return references ? `${references}\n\n${taskPrompt}` : taskPrompt;
126
- }
127
- function goalProgressLoaderStatus(item) {
128
- if (item.status === "failed" || item.status === "fail" || item.status === "blocked") {
129
- return "error";
130
- }
131
- if (item.phase === "worker_finished" ||
132
- item.phase === "verifier_finished" ||
133
- item.phase === "terminal") {
134
- return "done";
135
- }
136
- return "running";
137
- }
138
- function goalProgressColor(item, theme) {
139
- const isError = item.status === "failed" || item.status === "fail" || item.status === "blocked";
140
- if (isError)
141
- return theme.error;
142
- if (item.phase === "worker_finished" || item.phase === "terminal")
143
- return theme.success;
144
- if (item.phase === "verifier_finished" || item.phase === "verifier_started")
145
- return theme.accent;
146
- if (item.phase === "orchestrator_reviewing" || item.phase === "orchestrator_working") {
147
- return theme.secondary;
148
- }
149
- if (item.phase === "continuing")
150
- return theme.warning;
151
- return theme.primary;
152
- }
153
- // ── Duration summary ─────────────────────────────────────
154
- function formatDuration(ms) {
155
- const totalSec = Math.round(ms / 1000);
156
- if (totalSec < 60)
157
- return `${totalSec}s`;
158
- const min = Math.floor(totalSec / 60);
159
- const sec = totalSec % 60;
160
- return sec > 0 ? `${min}m ${sec}s` : `${min}m`;
161
- }
162
- function pickDurationVerb(toolsUsed) {
163
- const has = (name) => toolsUsed.includes(name);
164
- const hasAny = (...names) => names.some(has);
165
- const writing = has("edit") || has("write");
166
- const reading = has("read") || has("grep") || has("find") || has("ls");
167
- // Multi-tool combos (most specific first)
168
- if (has("subagent") && writing)
169
- return "Orchestrated changes for";
170
- if (has("subagent"))
171
- return "Delegated work for";
172
- if (has("web-fetch") && writing)
173
- return "Researched & coded for";
174
- if (has("web-fetch") && reading)
175
- return "Researched for";
176
- if (has("web-fetch"))
177
- return "Fetched the web for";
178
- if (has("bash") && writing)
179
- return "Built & ran for";
180
- if (has("edit") && has("write"))
181
- return "Crafted code for";
182
- if (has("edit") && has("bash"))
183
- return "Refactored & tested for";
184
- if (has("edit") && reading)
185
- return "Refactored for";
186
- if (has("edit"))
187
- return "Refactored for";
188
- if (has("write") && has("bash"))
189
- return "Wrote & ran for";
190
- if (has("write") && reading)
191
- return "Wrote code for";
192
- if (has("write"))
193
- return "Wrote code for";
194
- if (has("bash") && has("grep"))
195
- return "Hacked away for";
196
- if (has("bash") && reading)
197
- return "Ran & investigated for";
198
- if (has("bash"))
199
- return "Executed commands for";
200
- if (hasAny("task-output", "task-stop"))
201
- return "Managed background processes for";
202
- if (has("grep") && has("read"))
203
- return "Investigated for";
204
- if (has("grep") && has("find"))
205
- return "Scoured the codebase for";
206
- if (has("grep"))
207
- return "Searched for";
208
- if (has("read") && has("find"))
209
- return "Explored for";
210
- if (has("read"))
211
- return "Studied the code for";
212
- if (has("find") || has("ls"))
213
- return "Browsed files for";
214
- // No tools used — pure text response
215
- const phrases = [
216
- "Pondered for",
217
- "Thought for",
218
- "Reasoned for",
219
- "Mulled it over for",
220
- "Noodled on it for",
221
- "Brewed up a response in",
222
- "Cooked up an answer in",
223
- "Worked out a reply in",
224
- "Channeled wisdom for",
225
- "Conjured a response in",
226
- ];
227
- return phrases[Math.floor(Math.random() * phrases.length)];
228
- }
229
79
  // ── App Component ──────────────────────────────────────────
230
80
  export function App(props) {
231
81
  const theme = useTheme();
@@ -236,6 +86,7 @@ export function App(props) {
236
86
  const [lastUserMessage, setLastUserMessage] = useState("");
237
87
  const [exitPending, setExitPending] = useState(false);
238
88
  const [goalMode, setGoalMode] = useState(props.sessionStore?.goalMode ?? props.goalModeRef?.current ?? "off");
89
+ const [planMode, setPlanMode] = useState(props.sessionStore?.planMode ?? props.planModeRef?.current ?? false);
239
90
  // Terminal title — updated later after agentLoop is created
240
91
  // (hoisted here so the hook is always called in the same order)
241
92
  const [titleRunning, setTitleRunning] = useState(false);
@@ -261,7 +112,11 @@ export function App(props) {
261
112
  // Items from the current/last turn — rendered in the live area so they stay visible.
262
113
  // Seed from sessionStore so Goal progress/completion rows and other live output
263
114
  // survive pane/overlay/resize remounts before they are finalized.
264
- const [liveItems, setLiveItems] = useState(() => props.sessionStore?.liveItems ?? []);
115
+ const [liveItems, setLiveItems] = useState(() => {
116
+ const restoredLiveItems = uniqueItemsById(props.sessionStore?.liveItems ?? []);
117
+ const restoredHistoryIds = new Set(history.map((item) => item.id));
118
+ return removeItemsWithIds(restoredLiveItems, restoredHistoryIds);
119
+ });
265
120
  // overlay seeded from sessionStore (lives across remount). Falls back to
266
121
  // props.initialOverlay (CLI launched with one), then null.
267
122
  const [overlay, setOverlay] = useState(props.sessionStore?.overlay ?? props.initialOverlay ?? null);
@@ -274,11 +129,19 @@ export function App(props) {
274
129
  const goalContinuationFlightsRef = useRef(new Set());
275
130
  const goalContinuationRecentChoicesRef = useRef(new Map());
276
131
  const startGoalRunRef = useRef(() => { });
132
+ const [runAllTasks, setRunAllTasks] = useState(props.sessionStore?.runAllTasks ?? false);
133
+ const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
134
+ const startTaskRef = useRef(() => { });
277
135
  const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
278
136
  const currentPixelFixRef = useRef(null);
279
137
  const startPixelFixRef = useRef(() => { });
280
138
  const cwdRef = useRef(props.cwd);
281
139
  const [displayedCwd, setDisplayedCwd] = useState(props.cwd);
140
+ const taskPicker = useTaskPickerController({
141
+ displayedCwd,
142
+ onStartTask: (title, prompt, taskId) => startTaskRef.current(title, prompt, taskId),
143
+ onRunAllTasksChange: setRunAllTasks,
144
+ });
282
145
  const [doneStatus, setDoneStatus] = useState(props.sessionStore?.doneStatus ?? null);
283
146
  // Suppress "done" status when a plan overlay is about to open
284
147
  const planOverlayPendingRef = useRef(false);
@@ -291,12 +154,6 @@ export function App(props) {
291
154
  const [thinkingLevel, setThinkingLevel] = useState(props.thinking);
292
155
  const [renderMarkdown, setRenderMarkdown] = useState(true);
293
156
  const messagesRef = useRef(props.sessionStore?.messages ?? props.messages);
294
- const repoMapInjectionEnabledRef = useRef(true);
295
- const repoMapDirtyRef = useRef(true);
296
- const repoMapMarkdownRef = useRef("");
297
- const repoMapSnapshotRef = useRef(undefined);
298
- const repoMapChangedCountRef = useRef(0);
299
- const repoMapCacheRef = useRef(createRepoMapCache());
300
157
  const [planAutoExpand, setPlanAutoExpand] = useState(props.sessionStore?.planAutoExpand ?? false);
301
158
  const [goalAutoExpand, setGoalAutoExpand] = useState(props.sessionStore?.goalAutoExpand ?? false);
302
159
  const goalAutoExpandRef = useRef(props.sessionStore?.goalAutoExpand ?? false);
@@ -304,6 +161,7 @@ export function App(props) {
304
161
  const planStepsRef = useRef(props.sessionStore?.planSteps ?? []);
305
162
  const [planSteps, setPlanSteps] = useState(props.sessionStore?.planSteps ?? []);
306
163
  const goalModeStateRef = useRef(goalMode);
164
+ const planModeStateRef = useRef(planMode);
307
165
  // Stuck-guard for the plan-continuation follow-up nudge. Tracks how many
308
166
  // times we've nudged the agent to continue the same step. Reset whenever a
309
167
  // new [DONE:n] marker advances progress (see onTurnText). Caps at 2 nudges
@@ -373,58 +231,24 @@ export function App(props) {
373
231
  });
374
232
  }, [props.sessionStore]);
375
233
  const sessionStore = props.sessionStore;
376
- const terminalHistoryContextRef = useRef({
377
- theme,
378
- columns,
379
- version: props.version,
380
- model: currentModel,
381
- provider: currentProvider,
382
- cwd: displayedCwd,
383
- });
384
- useEffect(() => {
385
- terminalHistoryContextRef.current = {
234
+ const { pendingHistoryFlushRef, streamedAssistantFlushRef, queueFlush, finalizeSubmittedUserItem, clearPendingHistory, } = useTranscriptHistory({
235
+ terminalHistoryPrinter: props.terminalHistoryPrinter,
236
+ terminalHistoryContext: {
386
237
  theme,
387
238
  columns,
388
239
  version: props.version,
389
240
  model: currentModel,
390
241
  provider: currentProvider,
391
242
  cwd: displayedCwd,
392
- };
393
- }, [theme, columns, props.version, currentModel, currentProvider, displayedCwd]);
394
- const printHistoryItems = useCallback((items, options) => {
395
- if (!props.terminalHistoryPrinter || items.length === 0)
396
- return;
397
- props.terminalHistoryPrinter.print(items, terminalHistoryContextRef.current, {
398
- ...options,
399
- write: writeStdout,
400
- });
401
- }, [props.terminalHistoryPrinter, writeStdout]);
402
- const pendingHistoryFlushRef = useRef([]);
403
- const streamedAssistantFlushRef = useRef({
404
- flushedChars: 0,
405
- text: "",
243
+ },
244
+ writeStdout,
245
+ sessionPathRef,
246
+ sessionManagerRef,
247
+ sessionStore,
248
+ history,
249
+ setHistory,
250
+ setLiveItems,
406
251
  });
407
- const [historyFlushGeneration, setHistoryFlushGeneration] = useState(0);
408
- const queueFlush = useCallback((items) => {
409
- const flushed = trimFlushedItems(items);
410
- if (flushed.length === 0)
411
- return;
412
- pendingHistoryFlushRef.current = [...pendingHistoryFlushRef.current, ...flushed];
413
- if (sessionStore) {
414
- const queuedIds = new Set(items.map((item) => item.id));
415
- sessionStore.liveItems = (sessionStore.liveItems ?? []).filter((item) => !queuedIds.has(item.id));
416
- }
417
- setHistoryFlushGeneration((generation) => generation + 1);
418
- }, [sessionStore]);
419
- const finalizeSubmittedUserItem = useCallback((item) => {
420
- streamedAssistantFlushRef.current = { flushedChars: 0, text: "" };
421
- setLiveItems((prev) => {
422
- if (prev.length > 0)
423
- queueFlush(prev);
424
- queueFlush([item]);
425
- return [];
426
- });
427
- }, [queueFlush]);
428
252
  // Mirror runtime state choices (model/provider/thinking) into renderApp's
429
253
  // closure so unmount/remount preserves them.
430
254
  const onRuntimeStateChange = props.onRuntimeStateChange;
@@ -444,28 +268,6 @@ export function App(props) {
444
268
  thinking: thinkingLevel,
445
269
  });
446
270
  }, [thinkingLevel, onRuntimeStateChange]);
447
- useEffect(() => {
448
- printHistoryItems(history);
449
- }, [history, printHistoryItems]);
450
- useEffect(() => {
451
- const flushed = pendingHistoryFlushRef.current;
452
- if (flushed.length === 0)
453
- return;
454
- pendingHistoryFlushRef.current = [];
455
- printHistoryItems(flushed);
456
- const flushedIds = new Set(flushed.map((item) => item.id));
457
- setLiveItems((prev) => prev.filter((item) => !flushedIds.has(item.id)));
458
- setHistory((prev) => {
459
- const existingIds = new Set(prev.map((item) => item.id));
460
- const nextItems = flushed.filter((item) => !existingIds.has(item.id));
461
- if (nextItems.length === 0)
462
- return prev;
463
- const next = compactHistory([...prev, ...nextItems]);
464
- if (sessionStore)
465
- sessionStore.history = next;
466
- return next;
467
- });
468
- }, [historyFlushGeneration, printHistoryItems, sessionStore]);
469
271
  // Mirror session state into renderApp's closure so resetUI() can re-seed
470
272
  // the conversation on remount. Each panel that previously did a bare ANSI
471
273
  // screen clear (overlay open/close, plan accept/reject, /clear)
@@ -477,8 +279,10 @@ export function App(props) {
477
279
  sessionStore.history = history;
478
280
  }, [history, sessionStore]);
479
281
  useEffect(() => {
480
- if (sessionStore)
481
- sessionStore.liveItems = liveItems;
282
+ if (!sessionStore)
283
+ return;
284
+ const historyIds = new Set(historyRef.current.map((item) => item.id));
285
+ sessionStore.liveItems = removeItemsWithIds(uniqueItemsById(liveItems), historyIds);
482
286
  }, [liveItems, sessionStore]);
483
287
  useEffect(() => {
484
288
  if (sessionStore)
@@ -509,6 +313,10 @@ export function App(props) {
509
313
  if (sessionStore)
510
314
  sessionStore.goalMode = goalMode;
511
315
  }, [goalMode, sessionStore]);
316
+ useEffect(() => {
317
+ if (sessionStore)
318
+ sessionStore.planMode = planMode;
319
+ }, [planMode, sessionStore]);
512
320
  // pendingAction is consumed via a useEffect AFTER agentLoop is created
513
321
  // — see below where useAgentLoop is set up.
514
322
  const pendingActionConsumedRef = useRef(false);
@@ -580,6 +388,11 @@ export function App(props) {
580
388
  props.goalModeRef.current = goalMode;
581
389
  }
582
390
  }, [goalMode, props.goalModeRef]);
391
+ useEffect(() => {
392
+ planModeStateRef.current = planMode;
393
+ if (props.planModeRef)
394
+ props.planModeRef.current = planMode;
395
+ }, [planMode, props.planModeRef]);
583
396
  const setActiveGoalReferences = useCallback((references) => {
584
397
  if (props.goalReferencesRef)
585
398
  props.goalReferencesRef.current = references;
@@ -588,7 +401,7 @@ export function App(props) {
588
401
  const approvedPlanPath = options?.clearApprovedPlan
589
402
  ? undefined
590
403
  : (options?.approvedPlanPath ?? approvedPlanPathRef.current);
591
- return buildSystemPrompt(options?.cwd ?? cwdRef.current, props.skills, false, approvedPlanPath, (options?.tools ?? currentToolsRef.current).map((tool) => tool.name), options?.activeLanguages ?? injectedLanguagesRef.current, options?.goalMode ?? goalModeStateRef.current);
404
+ return buildSystemPrompt(options?.cwd ?? cwdRef.current, props.skills, options?.planMode ?? planModeStateRef.current, approvedPlanPath, (options?.tools ?? currentToolsRef.current).map((tool) => tool.name), options?.activeLanguages ?? injectedLanguagesRef.current, options?.goalMode ?? goalModeStateRef.current);
592
405
  }, [props.skills]);
593
406
  const replaceSystemPrompt = useCallback(async (options) => {
594
407
  const newPrompt = await rebuildSystemPrompt(options);
@@ -629,6 +442,15 @@ export function App(props) {
629
442
  setGoalMode(nextMode);
630
443
  await replaceSystemPrompt({ ...options, goalMode: nextMode });
631
444
  }, [props.goalModeRef, props.sessionStore, replaceSystemPrompt]);
445
+ const setPlanModeAndPrompt = useCallback(async (nextMode) => {
446
+ planModeStateRef.current = nextMode;
447
+ if (props.planModeRef)
448
+ props.planModeRef.current = nextMode;
449
+ if (props.sessionStore)
450
+ props.sessionStore.planMode = nextMode;
451
+ setPlanMode(nextMode);
452
+ await replaceSystemPrompt({ planMode: nextMode });
453
+ }, [props.planModeRef, props.sessionStore, replaceSystemPrompt]);
632
454
  const clearGoalModeIfIdle = useCallback(() => {
633
455
  setTimeout(() => {
634
456
  if (goalModeStateRef.current === "off")
@@ -879,103 +701,46 @@ export function App(props) {
879
701
  contextWindowOptions,
880
702
  props.authStorage,
881
703
  ]);
882
- const getRepoMapSignalCount = useCallback(() => {
883
- return ((props.repoMapChangedFilesRef?.current.size ?? 0) +
884
- (props.repoMapReadFilesRef?.current.size ?? 0));
885
- }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef]);
886
- const getRepoMapBudget = useCallback(() => {
887
- return getRepoMapBudgetForContext({
888
- messages: messagesRef.current,
889
- readFileCount: props.repoMapReadFilesRef?.current.size ?? 0,
890
- });
891
- }, [props.repoMapReadFilesRef]);
892
- const refreshRepoMap = useCallback(async (latestUserPrompt) => {
893
- const rendered = await buildRepoMap({
894
- cwd: cwdRef.current,
895
- maxChars: getRepoMapBudget(),
896
- changedFiles: [...(props.repoMapChangedFilesRef?.current ?? new Set())],
897
- readFiles: [...(props.repoMapReadFilesRef?.current ?? new Set())],
898
- focusTerms: latestUserPrompt ? [latestUserPrompt] : [],
899
- cache: repoMapCacheRef.current,
900
- });
901
- repoMapMarkdownRef.current = rendered.markdown;
902
- repoMapSnapshotRef.current = rendered.snapshot;
903
- repoMapChangedCountRef.current = getRepoMapSignalCount();
904
- repoMapDirtyRef.current = false;
905
- return rendered.markdown;
906
- }, [
907
- getRepoMapBudget,
908
- getRepoMapSignalCount,
909
- props.repoMapChangedFilesRef,
910
- props.repoMapReadFilesRef,
911
- ]);
912
- const stripRepoMapMessages = useCallback((messages) => {
913
- return stripRepoMapContextMessages(messages);
914
- }, []);
915
- const injectRepoMapContext = useCallback(async (messages) => {
916
- if (!repoMapInjectionEnabledRef.current)
917
- return stripRepoMapMessages(messages);
918
- const stripped = stripRepoMapMessages(messages);
919
- const latestUserPrompt = getLatestUserText(stripped);
920
- const signalCount = getRepoMapSignalCount();
921
- if (signalCount !== repoMapChangedCountRef.current)
922
- repoMapDirtyRef.current = true;
923
- if (repoMapDirtyRef.current || !repoMapMarkdownRef.current) {
924
- await refreshRepoMap(latestUserPrompt);
925
- }
926
- if (!repoMapMarkdownRef.current)
927
- return stripped;
928
- return injectRepoMapContextMessages(stripped, repoMapMarkdownRef.current);
929
- }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef, refreshRepoMap, stripRepoMapMessages]);
930
704
  /**
931
705
  * transformContext callback for the agent loop.
932
706
  * Called before each LLM call and on context overflow.
933
- * Compacts persistent chat only, then injects the dynamic repo map transiently.
934
707
  */
935
708
  const transformContext = useCallback(async (messages, options) => {
936
- const stripped = stripRepoMapMessages(messages);
937
709
  const settings = settingsRef.current;
938
710
  const autoCompact = settings?.get("autoCompact") ?? true;
939
711
  const threshold = settings?.get("compactThreshold") ?? 0.8;
940
712
  // Force-compact on context overflow regardless of settings
941
713
  if (options?.force) {
942
- const result = await compactConversation(stripped);
943
- if (result !== stripped) {
714
+ const result = await compactConversation(messages);
715
+ if (result !== messages) {
944
716
  messagesRef.current = result;
945
717
  await persistCompactedSession(result);
946
718
  }
947
719
  lastCompactionTimeRef.current = Date.now();
948
- return injectRepoMapContext(result);
720
+ return result;
949
721
  }
950
722
  if (!autoCompact)
951
- return injectRepoMapContext(stripped);
723
+ return messages;
952
724
  // Time-based cooldown: skip if compaction ran within the last 30 seconds
953
725
  if (Date.now() - lastCompactionTimeRef.current < 30_000) {
954
726
  log("INFO", "compaction", `Skipping compaction — cooldown active`);
955
- return injectRepoMapContext(stripped);
727
+ return messages;
956
728
  }
957
729
  const contextWindow = getContextWindow(currentModel, contextWindowOptions);
958
730
  const reserveTokens = getCompactionReserveTokens(props.maxTokens);
959
731
  const tokensFresh = lastActualTokensTimestampRef.current > lastCompactionTimeRef.current;
960
732
  const actualTokens = lastActualTokensRef.current > 0 && tokensFresh ? lastActualTokensRef.current : undefined;
961
- if (shouldCompact(stripped, contextWindow, threshold, actualTokens, reserveTokens)) {
962
- const result = await compactConversation(stripped);
963
- if (result !== stripped) {
733
+ if (shouldCompact(messages, contextWindow, threshold, actualTokens, reserveTokens)) {
734
+ const result = await compactConversation(messages);
735
+ if (result !== messages) {
964
736
  messagesRef.current = result;
965
737
  await persistCompactedSession(result);
966
738
  }
967
739
  lastCompactionTimeRef.current = Date.now();
968
- return injectRepoMapContext(result);
740
+ return result;
969
741
  }
970
- return injectRepoMapContext(stripped);
971
- }, [
972
- currentModel,
973
- compactConversation,
974
- contextWindowOptions,
975
- injectRepoMapContext,
976
- persistCompactedSession,
977
- stripRepoMapMessages,
978
- ]);
742
+ return messages;
743
+ }, [currentModel, compactConversation, contextWindowOptions, persistCompactedSession]);
979
744
  // ── Background task bar state (external store) ──────────
980
745
  const { bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, } = useTaskBarStore();
981
746
  useTaskBarPolling(props.processManager);
@@ -1016,7 +781,6 @@ export function App(props) {
1016
781
  transformContext,
1017
782
  }, {
1018
783
  onComplete: useCallback(() => {
1019
- messagesRef.current = stripRepoMapMessages(messagesRef.current);
1020
784
  persistNewMessages();
1021
785
  // Auto-clear plan progress and approved plan when all steps are completed
1022
786
  const steps = planStepsRef.current;
@@ -1069,7 +833,6 @@ export function App(props) {
1069
833
  }
1070
834
  }, [
1071
835
  persistNewMessages,
1072
- stripRepoMapMessages,
1073
836
  props.cwd,
1074
837
  props.skills,
1075
838
  currentProvider,
@@ -1528,6 +1291,21 @@ export function App(props) {
1528
1291
  if (nextGoalMode !== goalModeStateRef.current) {
1529
1292
  void setGoalModeAndPrompt(nextGoalMode);
1530
1293
  }
1294
+ // Run-all: auto-start next pending task after a short delay.
1295
+ if (runAllTasksRef.current) {
1296
+ setTimeout(() => {
1297
+ const cwd = cwdRef.current;
1298
+ const next = getNextPendingTask(cwd);
1299
+ if (next) {
1300
+ markTaskInProgress(cwd, next.id);
1301
+ startTaskRef.current(next.title, next.prompt, next.id);
1302
+ }
1303
+ else {
1304
+ setRunAllTasks(false);
1305
+ log("INFO", "tasks", "Run-all complete — no more pending tasks");
1306
+ }
1307
+ }, 500);
1308
+ }
1531
1309
  // Goal loop: after the orchestrator handles a worker/verifier event,
1532
1310
  // continue the same Goal automatically until it reaches a terminal state.
1533
1311
  for (const runId of [...runningGoalIdsRef.current]) {
@@ -1819,257 +1597,101 @@ export function App(props) {
1819
1597
  // has not grown.
1820
1598
  await applyLanguageDetectionRef.current("input");
1821
1599
  }
1822
- // Handle /model directly — open inline selector
1823
- if (trimmed === "/model" || trimmed === "/m" || trimmed === "/models") {
1824
- setOverlay("model");
1825
- return;
1826
- }
1827
- // Handle /compact compact conversation
1828
- if (trimmed === "/compact" || trimmed === "/c") {
1829
- const ac = new AbortController();
1830
- compactionAbortRef.current = ac;
1831
- const compacted = await compactConversation(messagesRef.current, ac.signal);
1832
- if (!ac.signal.aborted && compacted !== messagesRef.current) {
1833
- messagesRef.current = compacted;
1834
- await persistCompactedSession(compacted);
1835
- }
1836
- if (compactionAbortRef.current === ac)
1837
- compactionAbortRef.current = null;
1838
- return;
1839
- }
1840
- // Handle /quit — exit the agent
1841
- if (trimmed === "/quit" || trimmed === "/q" || trimmed === "/exit") {
1842
- process.exit(0);
1843
- }
1844
- // Handle /clear — tear down the entire Ink instance and rebuild fresh.
1845
- // Avoid direct ANSI terminal clears here; they can erase scrollback.
1846
- // Runtime state (model, provider, thinking) survives via renderApp's
1847
- // closure-held `runtimeState`, mirrored from React state via the
1848
- // useEffects above.
1849
- if (trimmed === "/clear") {
1850
- if (props.resetUI) {
1600
+ if (await handleUiSlashCommand(trimmed, {
1601
+ openModelSelector: () => setOverlay("model"),
1602
+ compactConversation: async () => {
1603
+ const ac = new AbortController();
1604
+ compactionAbortRef.current = ac;
1605
+ const compacted = await compactConversation(messagesRef.current, ac.signal);
1606
+ if (!ac.signal.aborted && compacted !== messagesRef.current) {
1607
+ messagesRef.current = compacted;
1608
+ await persistCompactedSession(compacted);
1609
+ }
1610
+ if (compactionAbortRef.current === ac)
1611
+ compactionAbortRef.current = null;
1612
+ },
1613
+ quit: () => process.exit(0),
1614
+ clearSession: () => {
1615
+ if (props.resetUI) {
1616
+ void (async () => {
1617
+ const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1618
+ props.resetUI?.({
1619
+ wipeSession: true,
1620
+ messages: [{ role: "system", content: newPrompt }],
1621
+ });
1622
+ })();
1623
+ return;
1624
+ }
1625
+ clearPendingHistory();
1626
+ setHistory([{ kind: "banner", id: "banner" }]);
1627
+ setLiveItems([]);
1628
+ setDoneStatus(null);
1629
+ approvedPlanPathRef.current = undefined;
1630
+ planStepsRef.current = [];
1631
+ setPlanSteps([]);
1851
1632
  void (async () => {
1852
1633
  const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1853
- props.resetUI?.({
1854
- wipeSession: true,
1855
- messages: [{ role: "system", content: newPrompt }],
1856
- });
1634
+ messagesRef.current = [{ role: "system", content: newPrompt }];
1635
+ persistedIndexRef.current = messagesRef.current.length;
1857
1636
  })();
1858
- return;
1859
- }
1860
- // Fallback path (resetUI not wired — e.g. tests). Best-effort: clear
1861
- // React state in place without touching terminal scrollback.
1862
- pendingHistoryFlushRef.current = [];
1863
- props.terminalHistoryPrinter?.clear();
1864
- setHistory([{ kind: "banner", id: "banner" }]);
1865
- setLiveItems([]);
1866
- setDoneStatus(null);
1867
- approvedPlanPathRef.current = undefined;
1868
- planStepsRef.current = [];
1869
- setPlanSteps([]);
1870
- void (async () => {
1871
- const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1872
- messagesRef.current = [{ role: "system", content: newPrompt }];
1873
- persistedIndexRef.current = messagesRef.current.length;
1874
- })();
1875
- agentLoop.reset();
1876
- setSessionTitle(undefined);
1877
- sessionTitleGeneratedRef.current = false;
1878
- setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
1879
- return;
1880
- }
1881
- // Handle /theme open theme selector overlay
1882
- if (trimmed === "/theme" || trimmed === "/t") {
1883
- setOverlay("theme");
1884
- return;
1885
- }
1886
- // Handle /markdown — Gemini-style rendered/raw markdown toggle
1887
- if (trimmed === "/markdown" || trimmed === "/md") {
1888
- setRenderMarkdown((prev) => {
1889
- const next = !prev;
1890
- setLiveItems([
1891
- {
1892
- kind: "info",
1893
- text: next ? "Rendered markdown mode." : "Raw markdown mode.",
1894
- id: getId(),
1895
- },
1896
- ]);
1897
- return next;
1898
- });
1899
- return;
1900
- }
1901
- // Handle /clearplan — dismiss the approved plan
1902
- if (trimmed === "/clearplan") {
1903
- approvedPlanPathRef.current = undefined;
1904
- planStepsRef.current = [];
1905
- setPlanSteps([]);
1906
- // Rebuild system prompt without the plan
1907
- void replaceSystemPrompt({ clearApprovedPlan: true });
1908
- setLiveItems([{ kind: "plan_event", event: "dismissed", id: getId() }]);
1909
- return;
1910
- }
1911
- // Handle /map — show, refresh, or toggle dynamic repo map injection
1912
- if (trimmed === "/map" ||
1913
- trimmed === "/map refresh" ||
1914
- trimmed === "/map on" ||
1915
- trimmed === "/map off") {
1916
- const action = trimmed.slice("/map".length).trim();
1917
- if (action === "on") {
1918
- repoMapInjectionEnabledRef.current = true;
1919
- repoMapDirtyRef.current = true;
1920
- setLiveItems((prev) => [
1921
- ...prev,
1922
- { kind: "info", text: "Dynamic repo map injection is on.", id: getId() },
1923
- ]);
1924
- return;
1925
- }
1926
- if (action === "off") {
1927
- repoMapInjectionEnabledRef.current = false;
1928
- messagesRef.current = stripRepoMapMessages(messagesRef.current);
1929
- setLiveItems((prev) => [
1930
- ...prev,
1931
- {
1932
- kind: "info",
1933
- text: "Dynamic repo map injection is off for this session.",
1934
- id: getId(),
1935
- },
1936
- ]);
1937
- return;
1938
- }
1939
- if (action === "refresh")
1940
- repoMapDirtyRef.current = true;
1941
- const markdown = await refreshRepoMap(getLatestUserText(messagesRef.current));
1942
- setLiveItems((prev) => [
1943
- ...prev,
1944
- {
1945
- kind: "info",
1946
- text: formatRepoMapCommandOutput(repoMapInjectionEnabledRef.current, markdown, action === "refresh"),
1947
- id: getId(),
1948
- },
1949
- ]);
1950
- return;
1951
- }
1952
- // Handle /goals — open goal pane
1953
- if (trimmed === "/goals") {
1954
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
1955
- props.sessionStore.overlay = "goal";
1956
- props.sessionStore.planAutoExpand = false;
1957
- props.sessionStore.goalAutoExpand = false;
1958
- props.resetUI();
1959
- }
1960
- else {
1961
- if (props.sessionStore) {
1962
- props.sessionStore.overlay = "goal";
1963
- props.sessionStore.planAutoExpand = false;
1964
- props.sessionStore.goalAutoExpand = false;
1965
- if (agentLoop.isRunning)
1966
- props.sessionStore.pendingResetUI = true;
1967
- }
1968
- setPlanAutoExpand(false);
1969
- setGoalAutoExpand(false);
1970
- setOverlay("goal");
1971
- }
1637
+ agentLoop.reset();
1638
+ setSessionTitle(undefined);
1639
+ sessionTitleGeneratedRef.current = false;
1640
+ setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
1641
+ },
1642
+ openThemeSelector: () => setOverlay("theme"),
1643
+ toggleMarkdown: () => {
1644
+ setRenderMarkdown((prev) => {
1645
+ const next = !prev;
1646
+ setLiveItems([
1647
+ {
1648
+ kind: "info",
1649
+ text: next ? "Rendered markdown mode." : "Raw markdown mode.",
1650
+ id: getId(),
1651
+ },
1652
+ ]);
1653
+ return next;
1654
+ });
1655
+ },
1656
+ clearApprovedPlan: () => {
1657
+ approvedPlanPathRef.current = undefined;
1658
+ planStepsRef.current = [];
1659
+ setPlanSteps([]);
1660
+ void replaceSystemPrompt({ clearApprovedPlan: true });
1661
+ setLiveItems([{ kind: "plan_event", event: "dismissed", id: getId() }]);
1662
+ },
1663
+ openGoalsPicker: () => {
1664
+ taskPicker.close();
1665
+ goalPicker.openPicker();
1666
+ },
1667
+ })) {
1972
1668
  return;
1973
1669
  }
1974
- // Handle prompt-template commands (built-in + custom from .gg/commands/)
1975
- const promptCommandRoute = routePromptCommandInput(trimmed, PROMPT_COMMANDS, customCommands);
1976
- if (promptCommandRoute) {
1977
- const { cmdName, cmdArgs, fullPrompt } = promptCommandRoute;
1978
- log("INFO", "command", `Prompt command: /${cmdName}${cmdArgs ? ` (args: ${cmdArgs})` : ""}`);
1979
- const hasImages = inputImages.length > 0;
1980
- const isGoalSetupCommand = isGoalPromptCommandName(cmdName);
1981
- let promptForAgent = fullPrompt;
1982
- if (isGoalSetupCommand) {
1983
- const referenceContext = await buildGoalReferenceContext({
1984
- cwd: props.cwd,
1985
- originalGoalPrompt: fullPrompt,
1986
- attachments: inputImages,
1987
- });
1988
- setActiveGoalReferences(referenceContext.references);
1989
- promptForAgent = referenceContext.promptSection
1990
- ? `${fullPrompt}\n\n${referenceContext.promptSection}`
1991
- : fullPrompt;
1992
- }
1993
- const modelInfo = getModel(currentModel);
1994
- const modelSupportsImages = modelInfo?.supportsImages ?? true;
1995
- const userContent = buildUserContentWithAttachments(promptForAgent, inputImages, modelSupportsImages);
1996
- // Show the typed command as the user message
1997
- const userItem = {
1998
- kind: "user",
1999
- text: trimmed,
2000
- imageCount: hasImages ? inputImages.length : undefined,
2001
- id: getId(),
2002
- };
2003
- setLastUserMessage(trimmed);
2004
- setDoneStatus(null);
2005
- finalizeSubmittedUserItem(userItem);
2006
- // Send the full prompt to the agent, with user args appended if provided
2007
- try {
2008
- if (isGoalSetupCommand) {
2009
- goalSetupPanePendingRef.current = true;
2010
- await runGoalPromptSetupSequence({
2011
- userContent,
2012
- fullPrompt: promptForAgent,
2013
- messagesRef,
2014
- setGoalModeAndPrompt,
2015
- runAgent: (content) => agentLoop.run(content),
2016
- onStage: appendGoalAgentTransition,
2017
- });
2018
- }
2019
- else {
2020
- await agentLoop.run(userContent);
2021
- }
2022
- }
2023
- catch (err) {
2024
- const msg = err instanceof Error ? err.message : String(err);
2025
- log("ERROR", "error", msg);
2026
- const isAbort = msg.includes("aborted") || msg.includes("abort");
2027
- if (isGoalSetupCommand)
2028
- goalSetupPanePendingRef.current = false;
2029
- setLiveItems((prev) => [
2030
- ...prev,
2031
- isAbort
2032
- ? { kind: "stopped", text: "Request was stopped.", id: getId() }
2033
- : toErrorItem(err, getId()),
2034
- ]);
2035
- }
2036
- finally {
2037
- if (isGoalSetupCommand) {
2038
- setActiveGoalReferences(undefined);
2039
- const paneTransition = getGoalSetupPaneTransitionAfterRun({
2040
- isGoalSetupCommand,
2041
- setupPanePending: goalSetupPanePendingRef.current,
2042
- });
2043
- goalSetupPanePendingRef.current = false;
2044
- if (goalModeStateRef.current !== "off") {
2045
- await setGoalModeAndPrompt("off");
2046
- }
2047
- if (paneTransition) {
2048
- goalAutoExpandRef.current = paneTransition.goalAutoExpand;
2049
- setTimeout(() => {
2050
- const resetUI = props.resetUI;
2051
- const sessionStore = props.sessionStore;
2052
- if (shouldResetUIForGoalSetupPaneTransition({
2053
- hasResetUI: resetUI !== undefined,
2054
- hasSessionStore: sessionStore !== undefined,
2055
- }) &&
2056
- resetUI &&
2057
- sessionStore) {
2058
- sessionStore.overlay = paneTransition.overlay;
2059
- sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
2060
- sessionStore.planAutoExpand = paneTransition.planAutoExpand;
2061
- resetUI();
2062
- return;
2063
- }
2064
- setGoalAutoExpand(paneTransition.goalAutoExpand);
2065
- setPlanAutoExpand(paneTransition.planAutoExpand);
2066
- setOverlay(paneTransition.overlay);
2067
- }, 300);
2068
- }
2069
- }
2070
- }
2071
- // Reload custom commands in case a setup command created new ones
2072
- reloadCustomCommands();
1670
+ if (await submitPromptCommand({
1671
+ trimmed,
1672
+ inputImages,
1673
+ cwd: props.cwd,
1674
+ currentModel,
1675
+ customCommands,
1676
+ messagesRef,
1677
+ goalSetupPanePendingRef,
1678
+ goalModeStateRef,
1679
+ goalAutoExpandRef,
1680
+ setActiveGoalReferences,
1681
+ setLastUserMessage,
1682
+ setDoneStatus,
1683
+ finalizeSubmittedUserItem,
1684
+ setGoalModeAndPrompt,
1685
+ runAgent: (content) => agentLoop.run(content),
1686
+ appendGoalAgentTransition,
1687
+ setLiveItems,
1688
+ getId,
1689
+ setGoalAutoExpand,
1690
+ setPlanAutoExpand,
1691
+ closeTaskPicker: taskPicker.close,
1692
+ openGoalPicker: goalPicker.openPicker,
1693
+ reloadCustomCommands,
1694
+ })) {
2073
1695
  return;
2074
1696
  }
2075
1697
  // Check slash commands
@@ -2151,12 +1773,10 @@ export function App(props) {
2151
1773
  props.resetUI,
2152
1774
  props.sessionStore,
2153
1775
  rebuildSystemPrompt,
2154
- refreshRepoMap,
2155
1776
  reloadCustomCommands,
2156
1777
  replaceSystemPrompt,
2157
1778
  setActiveGoalReferences,
2158
1779
  setGoalModeAndPrompt,
2159
- stripRepoMapMessages,
2160
1780
  ]);
2161
1781
  const handleDoubleExit = useDoublePress(setExitPending, () => process.exit(0));
2162
1782
  const handleAbort = useCallback(() => {
@@ -2345,113 +1965,21 @@ export function App(props) {
2345
1965
  },
2346
1966
  ];
2347
1967
  }, [customCommands]);
2348
- const normalizeStatusText = (text) => text.replace(/\\n/g, "\n").replace(/^\n+|\n+$/g, "");
2349
- const renderStatusMessage = (key, glyph, content, glyphColor = theme.commandColor, options = {}) => (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: glyphColor, bold: options.bold ?? true, children: glyph }) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsx(Text, { color: options.muted ? theme.textDim : theme.commandColor, bold: options.bold, wrap: "wrap", children: content }) })] }, key));
2350
- const renderItem = (item, index, items) => {
2351
- const previousLiveItem = index > 0 ? items[index - 1] : undefined;
2352
- const shouldTopSpacePrintedBoundary = shouldTopSpaceAfterPrintedAgentBoundary({
2353
- currentKind: item.kind,
2354
- previousLiveItem,
2355
- lastPendingHistoryItem: pendingHistoryFlushRef.current.at(-1),
2356
- lastHistoryItem: history.at(-1),
2357
- });
2358
- const assistantMarginTop = item.kind === "assistant" &&
2359
- (shouldTopSpacePrintedBoundary ||
2360
- shouldTopSpaceAssistantAfterToolBoundary({
2361
- text: item.text,
2362
- previousLiveItem,
2363
- lastPendingHistoryItem: pendingHistoryFlushRef.current.at(-1),
2364
- lastHistoryItem: history.at(-1),
2365
- }))
2366
- ? 1
2367
- : 0;
2368
- const withPrintedBoundarySpacing = (node) => shouldTopSpacePrintedBoundary ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: node }, `${item.id}-printed-boundary`)) : (node);
2369
- switch (item.kind) {
2370
- case "tombstone":
2371
- return null;
2372
- case "banner":
2373
- return (_jsx(Banner, { version: props.version, model: currentModel, provider: currentProvider, cwd: displayedCwd }, item.id));
2374
- case "user":
2375
- return (_jsx(UserMessage, { text: item.text, imageCount: item.imageCount, pasteInfo: item.pasteInfo }, item.id));
2376
- case "goal":
2377
- return (_jsx(Box, { paddingLeft: 1, marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.success, bold: true, children: "▶ " }), _jsx(Text, { color: theme.textDim, children: "Goal: " }), _jsx(Text, { color: theme.success, children: truncateGoalProgressText(item.title) }), item.workerId ? _jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] }) : null] }) }, item.id));
2378
- case "goal_progress": {
2379
- const color = goalProgressColor(item, theme);
2380
- const loaderStatus = goalProgressLoaderStatus(item);
2381
- const hasBody = !!item.detail ||
2382
- (item.summaryRows !== undefined && item.summaryRows.length > 0) ||
2383
- (item.summarySections !== undefined && item.summarySections.length > 0);
2384
- const headerContentWidth = Math.max(10, columns - 3);
2385
- return withPrintedBoundarySpacing(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { status: loaderStatus, staticDisplay: true, color: color }), _jsx(Box, { flexGrow: 1, width: headerContentWidth, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: color, bold: true, children: truncateGoalProgressText(item.title) }), item.workerId ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] })) : null] }) })] }), hasBody ? (_jsx(MessageResponse, { children: _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [item.detail ? (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: truncateGoalProgressText(item.detail) })) : null, item.summaryRows?.map((row) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.textDim, children: row.label.padEnd(12) }), _jsx(Text, { color: theme.text, children: truncateGoalProgressText(row.value) }), row.detail ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 ", truncateGoalProgressText(row.detail)] })) : null] }, row.label))), item.summarySections?.map((section) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: theme.textDim, bold: true, children: section.title }), section.lines.map((line, sectionLineIndex) => (_jsx(Text, { color: theme.text, wrap: "wrap", children: `• ${truncateGoalProgressText(line)}` }, `${section.title}-${sectionLineIndex}`)))] }, section.title)))] }) })) : null] }, item.id));
2386
- }
2387
- case "style_pack": {
2388
- const names = item.added.map((id) => LANGUAGE_DISPLAY_NAMES[id]);
2389
- const headerLabel = item.added.length > 1 ? "STYLE PACKS ACTIVE" : "STYLE PACK ACTIVE";
2390
- return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsxs(Box, { flexShrink: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.language, paddingX: 1, children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.language, bold: true, children: "◆ " }), _jsx(Text, { color: theme.language, bold: true, children: headerLabel })] }), _jsx(Text, { color: theme.text, bold: true, wrap: "wrap", children: names.join(", ") }), item.showSetupHint && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.textMuted, children: "Tip: run " }), _jsx(Text, { color: theme.language, bold: true, children: "/setup" }), _jsx(Text, { color: theme.textMuted, children: " to audit this project against the active pack(s)" })] }) }))] }) }, item.id));
2391
- }
2392
- case "setup_hint":
2393
- return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsxs(Box, { flexShrink: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.language, paddingX: 1, children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.language, bold: true, children: "◆ " }), _jsx(Text, { color: theme.language, bold: true, children: "NO STYLE PACKS DETECTED" })] }), _jsx(Text, { color: theme.textMuted, wrap: "wrap", children: "This directory has no recognized language manifest at its root." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.textMuted, children: "Tip: run " }), _jsx(Text, { color: theme.language, bold: true, children: "/setup" }), _jsx(Text, { color: theme.textMuted, children: " to audit project hygiene or bootstrap a new project from scratch" })] }) })] }) }, item.id));
2394
- case "assistant":
2395
- return (_jsx(AssistantMessage, { text: item.text, thinking: item.thinking, thinkingMs: item.thinkingMs, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, marginTop: assistantMarginTop }, item.id));
2396
- case "tool_start":
2397
- return withPrintedBoundarySpacing(_jsx(ToolExecution, { status: "running", name: item.name, args: item.args, progressOutput: item.progressOutput, animateUntil: item.animateUntil }, item.id));
2398
- case "tool_done":
2399
- return withPrintedBoundarySpacing(_jsx(ToolExecution, { status: "done", name: item.name, args: item.args, result: item.result, isError: item.isError, details: item.details }, item.id));
2400
- case "tool_group":
2401
- return withPrintedBoundarySpacing(_jsx(ToolGroupExecution, { tools: item.tools }, item.id));
2402
- case "server_tool_start":
2403
- return withPrintedBoundarySpacing(_jsx(ServerToolExecution, { status: "running", name: item.name, input: item.input, startedAt: item.startedAt, animateUntil: item.animateUntil }, item.id));
2404
- case "server_tool_done":
2405
- return withPrintedBoundarySpacing(_jsx(ServerToolExecution, { status: "done", name: item.name, input: item.input, durationMs: item.durationMs, resultType: item.resultType }, item.id));
2406
- case "error": {
2407
- const showMessage = item.message && item.message !== item.headline;
2408
- return (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.error, bold: true, children: "✗ " }) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Text, { color: theme.error, wrap: "wrap", children: item.headline }), showMessage && (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: item.message })), _jsx(Text, { color: theme.textDim, wrap: "wrap", children: `→ ${item.guidance}` })] })] }, item.id));
2409
- }
2410
- case "info":
2411
- return renderStatusMessage(item.id, "○ ", item.text, theme.commandColor, { muted: true });
2412
- case "update_notice":
2413
- return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsx(Box, { flexShrink: 1, borderStyle: "round", borderColor: theme.commandColor, paddingX: 1, children: _jsxs(Text, { color: theme.commandColor, bold: true, wrap: "wrap", children: ["✨ ", item.text] }) }) }, item.id));
2414
- case "plan_transition":
2415
- return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
2416
- case "goal_agent_transition":
2417
- return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
2418
- case "model_transition":
2419
- return renderStatusMessage(item.id, "▸ ", _jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textDim, children: "Switched to " }), _jsx(Text, { color: theme.commandColor, bold: true, children: item.modelName })] }), theme.commandColor, { bold: true });
2420
- case "theme_transition":
2421
- return renderStatusMessage(item.id, "◐ ", _jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textDim, children: "Theme switched to " }), _jsx(Text, { color: theme.commandColor, bold: true, children: item.themeName })] }), theme.commandColor, { bold: true });
2422
- case "plan_event": {
2423
- // Plan-domain status changes (approve / reject / dismiss). Use the
2424
- // command accent so transient TUI status rows share one purple voice.
2425
- const label = item.event === "approved"
2426
- ? "Plan approved"
2427
- : item.event === "rejected"
2428
- ? "Plan rejected"
2429
- : "Plan dismissed";
2430
- return renderStatusMessage(item.id, "○ ", _jsxs(_Fragment, { children: [_jsx(Text, { children: label }), item.detail ? _jsx(Text, { color: theme.textDim, children: ` — "${item.detail}"` }) : null] }), theme.commandColor, { bold: true });
2431
- }
2432
- case "stopped":
2433
- // Cancellation / abort acknowledgement (ESC, auto-setup cancel, etc.).
2434
- // Muted dim treatment — this is an ack, not a state change worth a
2435
- // gradient. Glyph `⊘` reads as "stop" without being alarming.
2436
- return renderStatusMessage(item.id, "⊘ ", normalizeStatusText(item.text), theme.commandColor, { bold: true });
2437
- case "step_done":
2438
- return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.success, bold: true, children: "✓ " }), _jsx(Text, { color: theme.success, bold: true, children: `Step ${item.stepNum} done` }), item.description ? (_jsx(Text, { color: theme.textDim, children: ` — ${item.description}` })) : null] }) }, item.id));
2439
- case "queued": {
2440
- const suffix = item.imageCount
2441
- ? ` (+${item.imageCount} image${item.imageCount > 1 ? "s" : ""})`
2442
- : "";
2443
- return withPrintedBoundarySpacing(_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsxs(Text, { color: theme.text, wrap: "wrap", children: [_jsx(Text, { color: theme.textDim, children: "Queued: " }), item.text || "(empty)", suffix] }) })] }, item.id));
2444
- }
2445
- case "compacting":
2446
- return _jsx(CompactionSpinner, { staticDisplay: true }, item.id);
2447
- case "compacted":
2448
- return (_jsx(CompactionDone, { originalCount: item.originalCount, newCount: item.newCount, tokensBefore: item.tokensBefore, tokensAfter: item.tokensAfter }, item.id));
2449
- case "duration":
2450
- return (_jsx(Box, { paddingLeft: 1, marginTop: 1, children: _jsxs(Text, { color: theme.textDim, children: ["✻ ", item.verb, " ", formatDuration(item.durationMs)] }) }, item.id));
2451
- case "subagent_group":
2452
- return withPrintedBoundarySpacing(_jsx(SubAgentPanel, { agents: item.agents, aborted: item.aborted }, item.id));
2453
- }
2454
- };
1968
+ const renderItem = (item, index, items) => renderTranscriptItem({
1969
+ item,
1970
+ index,
1971
+ items,
1972
+ pendingHistoryFlushLastItem: pendingHistoryFlushRef.current.at(-1),
1973
+ historyLastItem: history.at(-1),
1974
+ version: props.version,
1975
+ currentModel,
1976
+ currentProvider,
1977
+ displayedCwd,
1978
+ columns,
1979
+ theme,
1980
+ renderMarkdown,
1981
+ measuredLiveAreaRows,
1982
+ });
2455
1983
  const openOverlay = useCallback((kind) => {
2456
1984
  if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2457
1985
  props.sessionStore.overlay = kind;
@@ -2479,18 +2007,6 @@ export function App(props) {
2479
2007
  setOverlay(kind);
2480
2008
  }
2481
2009
  }, [agentLoop.isRunning, props]);
2482
- const closeOverlay = useCallback(() => {
2483
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2484
- props.sessionStore.overlay = null;
2485
- props.resetUI();
2486
- }
2487
- else {
2488
- if (props.sessionStore) {
2489
- props.sessionStore.overlay = null;
2490
- }
2491
- setOverlay(null);
2492
- }
2493
- }, [agentLoop.isRunning, overlay, props]);
2494
2010
  const runGoalSyntheticEvent = useCallback((eventText) => {
2495
2011
  const eventInfo = parseGoalSyntheticEvent(eventText);
2496
2012
  const detail = eventInfo?.kind === "worker"
@@ -2548,6 +2064,10 @@ export function App(props) {
2548
2064
  return;
2549
2065
  }
2550
2066
  const decision = decideGoalNextAction(latestRun);
2067
+ if (!shouldKeepGoalRunTrackedAfterDecision(decision)) {
2068
+ runningGoalIdsRef.current.delete(runId);
2069
+ clearGoalModeIfIdle();
2070
+ }
2551
2071
  if (decision.kind === "wait")
2552
2072
  return;
2553
2073
  const choiceKey = getGoalContinuationChoiceKey({ runId: latestRun.id, decision });
@@ -2652,23 +2172,25 @@ export function App(props) {
2652
2172
  workerId: completion.worker.id,
2653
2173
  status: completion.status,
2654
2174
  });
2175
+ const taskProgress = goalTaskProgress(run, run.tasks.find((task) => task.id === completion.worker.goalTaskId));
2655
2176
  upsertGoalStatusEntry({
2656
2177
  runId: run.id,
2657
- label: taskTitle,
2178
+ label: run.title,
2658
2179
  phase: completion.status === "done" ? "reviewing" : "failed",
2659
2180
  startedAt: Date.now(),
2660
2181
  detail: completion.status === "done" ? "reviewing result" : "task failed",
2661
2182
  workerId: completion.worker.id,
2662
2183
  goalNumber: goalNumberForRun(run.id),
2184
+ ...taskProgress,
2663
2185
  });
2664
2186
  runGoalSyntheticEvent(eventText);
2665
2187
  void (async () => {
2666
- if (listGoalWorkers(completion.worker.cwd).some((worker) => worker.status === "running"))
2188
+ if (listGoalWorkers(completion.worker.projectPath).some((worker) => worker.status === "running"))
2667
2189
  return;
2668
2190
  if (activeVerifierRunIdsRef.current.size > 0)
2669
2191
  return;
2670
- const runs = await loadGoalRuns(completion.worker.cwd);
2671
- const queued = runs.find((item) => item.continueRequestedAt && !goalHasBlockingPrerequisites(item));
2192
+ const runs = await loadGoalRuns(completion.worker.projectPath);
2193
+ const queued = runs.find((item) => goalRunNeedsExplicitContinuationAfterWorker(item));
2672
2194
  if (queued)
2673
2195
  setTimeout(() => continueGoalRun(queued.id), 750);
2674
2196
  })().catch((err) => log("ERROR", "goal", err instanceof Error ? err.message : String(err)));
@@ -2682,7 +2204,7 @@ export function App(props) {
2682
2204
  useEffect(() => {
2683
2205
  return subscribeGoalWorkerCompletions((completion) => {
2684
2206
  void (async () => {
2685
- const latestRun = (await loadGoalRuns(completion.worker.cwd)).find((item) => item.id === completion.worker.goalRunId) ?? null;
2207
+ const latestRun = (await loadGoalRuns(completion.worker.projectPath)).find((item) => item.id === completion.worker.goalRunId) ?? null;
2686
2208
  if (!latestRun) {
2687
2209
  log("WARN", "goal", `Worker completion for unknown Goal ${completion.worker.goalRunId}`);
2688
2210
  return;
@@ -2736,6 +2258,9 @@ export function App(props) {
2736
2258
  }
2737
2259
  const decision = decideGoalNextAction(checkedRun);
2738
2260
  await appendGoalDecision(props.cwd, checkedRun.id, decision);
2261
+ if (!shouldKeepGoalRunTrackedAfterDecision(decision)) {
2262
+ runningGoalIdsRef.current.delete(checkedRun.id);
2263
+ }
2739
2264
  if (decision.kind === "terminal") {
2740
2265
  const terminalProgress = formatGoalTerminalProgress(checkedRun);
2741
2266
  if (terminalProgress) {
@@ -2753,8 +2278,10 @@ export function App(props) {
2753
2278
  phase: "worker_started",
2754
2279
  title: decision.workerId
2755
2280
  ? `Goal working: ${checkedRun.title}`
2756
- : `Goal active: ${checkedRun.title}`,
2757
- detail: decision.reason,
2281
+ : `Goal needs orchestration: ${checkedRun.title}`,
2282
+ detail: decision.workerId
2283
+ ? decision.reason
2284
+ : `${decision.reason} Asking the orchestrator to unblock or revise the Goal plan.`,
2758
2285
  workerId: decision.workerId,
2759
2286
  });
2760
2287
  upsertGoalStatusEntry({
@@ -2766,6 +2293,14 @@ export function App(props) {
2766
2293
  workerId: decision.workerId,
2767
2294
  goalNumber: goalNumberForRun(checkedRun.id),
2768
2295
  });
2296
+ if (!decision.workerId) {
2297
+ const eventText = `Goal continuation is waiting with no active worker for Goal ${checkedRun.id} (${checkedRun.title}).\n` +
2298
+ `Reason: ${decision.reason}\n\n` +
2299
+ `Inspect the durable Goal state with the goals tool, resolve blocked dependencies by creating or updating concrete worker tasks, and then continue the Goal. If no local/free action can proceed, record an explicit blocker with exact user instructions. Do not stop after only explaining the state.`;
2300
+ setLastUserMessage("");
2301
+ setDoneStatus(null);
2302
+ await agentLoop.run(eventText);
2303
+ }
2769
2304
  return;
2770
2305
  }
2771
2306
  if (decision.kind === "complete") {
@@ -2787,14 +2322,38 @@ export function App(props) {
2787
2322
  return;
2788
2323
  }
2789
2324
  if (decision.kind === "create_task") {
2325
+ const latestRunBeforeCreate = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ?? checkedRun;
2326
+ const existingSameTitleTask = latestRunBeforeCreate.tasks.find((item) => item.title === decision.title);
2327
+ if (existingSameTitleTask) {
2328
+ const runWithExistingTask = await upsertGoalRun(props.cwd, {
2329
+ ...latestRunBeforeCreate,
2330
+ status: "ready",
2331
+ });
2332
+ appendGoalProgress({
2333
+ kind: "goal_progress",
2334
+ phase: "continuing",
2335
+ title: `Goal task already exists: ${decision.title}`,
2336
+ detail: "Reusing the existing Goal task instead of creating a duplicate.",
2337
+ status: "ready",
2338
+ });
2339
+ startGoalRunRef.current(runWithExistingTask);
2340
+ return;
2341
+ }
2790
2342
  await updateGoalTask(props.cwd, checkedRun.id, `auto-${Date.now()}`, {
2791
2343
  title: decision.title,
2792
2344
  prompt: decision.prompt,
2793
2345
  status: "pending",
2794
2346
  });
2795
2347
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ?? checkedRun;
2796
- await upsertGoalRun(props.cwd, { ...latestRun, status: "ready" });
2797
- setTimeout(() => continueGoalRun(checkedRun.id), 250);
2348
+ const runWithTask = await upsertGoalRun(props.cwd, { ...latestRun, status: "ready" });
2349
+ appendGoalProgress({
2350
+ kind: "goal_progress",
2351
+ phase: "continuing",
2352
+ title: `Goal task created: ${decision.title}`,
2353
+ detail: "Starting the new Goal task now.",
2354
+ status: "ready",
2355
+ });
2356
+ startGoalRunRef.current(runWithTask);
2798
2357
  return;
2799
2358
  }
2800
2359
  if (decision.kind === "blocked") {
@@ -2856,6 +2415,7 @@ export function App(props) {
2856
2415
  goalTaskId: decision.task.id,
2857
2416
  taskTitle: decision.task.title,
2858
2417
  prompt: buildGoalTaskPromptWithReferences(checkedRun, decision.task.prompt),
2418
+ isolateWorktree: shouldRunGoalTaskInMainCheckout(decision.task.title) ? false : undefined,
2859
2419
  });
2860
2420
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ??
2861
2421
  runWithAttempt;
@@ -2878,17 +2438,39 @@ export function App(props) {
2878
2438
  });
2879
2439
  upsertGoalStatusEntry({
2880
2440
  runId: checkedRun.id,
2881
- label: decision.task.title,
2441
+ label: checkedRun.title,
2882
2442
  phase: "worker",
2883
2443
  startedAt: Date.now(),
2884
2444
  detail: "background worker running",
2885
2445
  workerId: worker.id,
2886
2446
  goalNumber: goalNumberForRun(checkedRun.id),
2447
+ ...goalTaskProgress(checkedRun, decision.task),
2887
2448
  });
2888
- })().catch((err) => {
2449
+ })().catch(async (err) => {
2889
2450
  clearGoalStatusEntry(run.id);
2890
2451
  clearGoalModeIfIdle();
2891
2452
  log("ERROR", "goal", err instanceof Error ? err.message : String(err));
2453
+ if (isGoalWorktreeDirtyError(err)) {
2454
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
2455
+ const pausedRun = await upsertGoalRun(props.cwd, buildGoalDirtyWorktreePauseRun(latestRun, err));
2456
+ runningGoalIdsRef.current.delete(pausedRun.id);
2457
+ appendGoalProgress({
2458
+ kind: "goal_progress",
2459
+ phase: "terminal",
2460
+ title: `Goal paused: ${pausedRun.title}`,
2461
+ detail: "Working tree has uncommitted changes; waiting for the user to choose commit, stash, or pause.",
2462
+ status: "paused",
2463
+ });
2464
+ setLiveItems((prev) => [
2465
+ ...prev,
2466
+ { kind: "info", text: goalDirtyWorktreeInfoText(), id: getId() },
2467
+ ]);
2468
+ void agentLoop.run(buildGoalDirtyWorktreeUserPrompt(err)).catch((agentErr) => {
2469
+ log("ERROR", "goal", agentErr instanceof Error ? agentErr.message : String(agentErr));
2470
+ setLiveItems((prev) => [...prev, toErrorItem(agentErr, getId(), "Goal")]);
2471
+ });
2472
+ return;
2473
+ }
2892
2474
  setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
2893
2475
  });
2894
2476
  }, [
@@ -2896,6 +2478,7 @@ export function App(props) {
2896
2478
  currentProvider,
2897
2479
  currentModel,
2898
2480
  thinkingLevel,
2481
+ agentLoop,
2899
2482
  appendGoalProgress,
2900
2483
  clearGoalModeIfIdle,
2901
2484
  clearGoalStatusEntry,
@@ -2928,6 +2511,30 @@ export function App(props) {
2928
2511
  clearGoalModeIfIdle();
2929
2512
  return;
2930
2513
  }
2514
+ const integration = await checkGoalWorktreeIntegration(props.cwd, run);
2515
+ if (!integration.ok) {
2516
+ const runWithEvidence = (await appendGoalEvidence(props.cwd, run.id, {
2517
+ kind: "summary",
2518
+ label: "Goal worktree integration required",
2519
+ content: integration.summary,
2520
+ })) ?? run;
2521
+ await upsertGoalRun(props.cwd, {
2522
+ ...runWithEvidence,
2523
+ status: "blocked",
2524
+ blockers: Array.from(new Set([...runWithEvidence.blockers, integration.summary])),
2525
+ });
2526
+ appendGoalProgress({
2527
+ kind: "goal_progress",
2528
+ phase: "terminal",
2529
+ title: `Goal blocked before verifier: ${run.title}`,
2530
+ detail: integration.summary,
2531
+ status: "blocked",
2532
+ });
2533
+ runningGoalIdsRef.current.delete(run.id);
2534
+ clearGoalStatusEntry(run.id);
2535
+ clearGoalModeIfIdle();
2536
+ return;
2537
+ }
2931
2538
  activeVerifierRunIdsRef.current.add(run.id);
2932
2539
  await upsertGoalRun(props.cwd, {
2933
2540
  ...run,
@@ -2952,7 +2559,7 @@ export function App(props) {
2952
2559
  goalNumber: goalNumberForRun(run.id),
2953
2560
  });
2954
2561
  void runGoalVerifierCommand({
2955
- cwd: props.cwd,
2562
+ cwd: run.verifier.cwd ?? props.cwd,
2956
2563
  runId: run.id,
2957
2564
  command: run.verifier.command,
2958
2565
  timeoutMs: verifierTimeoutMs,
@@ -2970,6 +2577,7 @@ export function App(props) {
2970
2577
  ...latestRun.verifier,
2971
2578
  description: latestRun.verifier?.description ?? "Goal verifier",
2972
2579
  command: run.verifier?.command,
2580
+ ...(run.verifier?.cwd ? { cwd: run.verifier.cwd } : {}),
2973
2581
  lastResult: verification,
2974
2582
  },
2975
2583
  ...(status === "pass"
@@ -2999,7 +2607,7 @@ export function App(props) {
2999
2607
  await appendGoalDecision(props.cwd, run.id, {
3000
2608
  kind: `verifier_${status}`,
3001
2609
  reason: `${failureClass}: verifier exited with code ${verification.exitCode ?? 1}.`,
3002
- content: `outputPath=${outputPath ?? ""}; durationMs=${durationMs}`,
2610
+ content: `outputPath=${outputPath ?? ""}; cwd=${run.verifier?.cwd ?? props.cwd}; durationMs=${durationMs}`,
3003
2611
  });
3004
2612
  appendGoalProgress({
3005
2613
  kind: "goal_progress",
@@ -3046,11 +2654,7 @@ export function App(props) {
3046
2654
  if (run.activeWorkerId)
3047
2655
  await stopGoalWorker(run.activeWorkerId);
3048
2656
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3049
- await upsertGoalRun(props.cwd, {
3050
- ...latestRun,
3051
- status: "paused",
3052
- activeWorkerId: undefined,
3053
- });
2657
+ await upsertGoalRun(props.cwd, buildGoalUserPauseRun(latestRun));
3054
2658
  appendGoalProgress({
3055
2659
  kind: "goal_progress",
3056
2660
  phase: "terminal",
@@ -3065,8 +2669,70 @@ export function App(props) {
3065
2669
  setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3066
2670
  });
3067
2671
  }, [appendGoalProgress, clearGoalModeIfIdle, clearGoalStatusEntry, props.cwd]);
2672
+ const startTask = useCallback((title, prompt, taskId) => {
2673
+ const taskCwd = cwdRef.current;
2674
+ const shortId = taskId.slice(0, 8);
2675
+ const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
2676
+ `tasks({ action: "done", id: "${shortId}" })`;
2677
+ const fullPrompt = prompt + completionHint;
2678
+ if (props.resetUI && props.sessionStore) {
2679
+ const sysMsg = messagesRef.current[0];
2680
+ const newMessages = sysMsg && sysMsg.role === "system" ? [sysMsg] : messagesRef.current.slice(0, 1);
2681
+ const taskItem = { kind: "task", title, id: getId() };
2682
+ const sm = sessionManagerRef.current;
2683
+ void (async () => {
2684
+ let newSessionPath;
2685
+ if (sm) {
2686
+ try {
2687
+ const session = await sm.create(taskCwd, currentProvider, currentModel);
2688
+ newSessionPath = session.path;
2689
+ log("INFO", "tasks", "New session for task", { path: session.path });
2690
+ }
2691
+ catch {
2692
+ // Session creation is best-effort.
2693
+ }
2694
+ }
2695
+ if (props.sessionStore)
2696
+ props.sessionStore.overlay = null;
2697
+ props.resetUI?.({
2698
+ wipeSession: true,
2699
+ messages: newMessages,
2700
+ history: [{ kind: "banner", id: "banner" }, taskItem],
2701
+ sessionPath: newSessionPath,
2702
+ pendingAction: { prompt: fullPrompt },
2703
+ });
2704
+ })();
2705
+ return;
2706
+ }
2707
+ clearPendingHistory();
2708
+ setHistory([{ kind: "banner", id: "banner" }]);
2709
+ setLiveItems([]);
2710
+ messagesRef.current = messagesRef.current.slice(0, 1);
2711
+ agentLoop.reset();
2712
+ persistedIndexRef.current = messagesRef.current.length;
2713
+ const sm = sessionManagerRef.current;
2714
+ if (sm) {
2715
+ void sm.create(taskCwd, currentProvider, currentModel).then((session) => {
2716
+ sessionPathRef.current = session.path;
2717
+ log("INFO", "tasks", "New session for task", { path: session.path });
2718
+ });
2719
+ }
2720
+ const taskItem = { kind: "task", title, id: getId() };
2721
+ setLastUserMessage(title);
2722
+ setDoneStatus(null);
2723
+ setLiveItems([taskItem]);
2724
+ void agentLoop.run(fullPrompt).catch((err) => {
2725
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
2726
+ });
2727
+ }, [agentLoop, currentModel, currentProvider, props]);
3068
2728
  // Keep refs in sync for access from stale closures (onDone)
2729
+ startTaskRef.current = startTask;
3069
2730
  startGoalRunRef.current = startGoalRun;
2731
+ useEffect(() => {
2732
+ runAllTasksRef.current = runAllTasks;
2733
+ if (props.sessionStore)
2734
+ props.sessionStore.runAllTasks = runAllTasks;
2735
+ }, [runAllTasks, props.sessionStore]);
3070
2736
  useEffect(() => {
3071
2737
  agentRunningRef.current = agentLoop.isRunning;
3072
2738
  }, [agentLoop.isRunning]);
@@ -3091,13 +2757,6 @@ export function App(props) {
3091
2757
  log("WARN", "pixel", `chdir failed: ${err.message}`);
3092
2758
  }
3093
2759
  cwdRef.current = prep.projectPath;
3094
- repoMapDirtyRef.current = true;
3095
- repoMapMarkdownRef.current = "";
3096
- repoMapSnapshotRef.current = undefined;
3097
- repoMapChangedCountRef.current = 0;
3098
- repoMapCacheRef.current = createRepoMapCache();
3099
- props.repoMapChangedFilesRef?.current.clear();
3100
- props.repoMapReadFilesRef?.current.clear();
3101
2760
  setDisplayedCwd(prep.projectPath);
3102
2761
  let toolsForPixelFix = currentToolsRef.current;
3103
2762
  if (props.rebuildToolsForCwd) {
@@ -3121,8 +2780,7 @@ export function App(props) {
3121
2780
  });
3122
2781
  // Now that the cwd swap is committed, reset chat. Do not clear the
3123
2782
  // terminal here; terminal clear sequences can erase saved scrollback.
3124
- pendingHistoryFlushRef.current = [];
3125
- props.terminalHistoryPrinter?.clear();
2783
+ clearPendingHistory();
3126
2784
  setHistory([{ kind: "banner", id: "banner" }]);
3127
2785
  setLiveItems([]);
3128
2786
  messagesRef.current = messagesRef.current.slice(0, 1);
@@ -3166,63 +2824,28 @@ export function App(props) {
3166
2824
  if (props.sessionStore)
3167
2825
  props.sessionStore.runAllPixel = runAllPixel;
3168
2826
  }, [runAllPixel, props.sessionStore]);
3169
- const isGoalView = overlay === "goal";
3170
2827
  const isSkillsView = overlay === "skills";
3171
2828
  const isPlanView = overlay === "plan";
3172
- const footerStatusLayout = getFooterStatusLayoutDecision({
2829
+ const { footerStatusLayout, activityVisible, stallStatusVisible, statusSlotVisible, mainControlsRef, measuredLiveAreaRows, } = useChatLayoutMeasurements({
2830
+ rows,
3173
2831
  columns,
3174
2832
  backgroundTaskCount: bgTasks.length,
3175
2833
  updatePending,
3176
- });
3177
- const activityVisible = agentLoop.isRunning && agentLoop.activityPhase !== "idle";
3178
- const stallStatusVisible = !activityVisible && !!agentLoop.stallError;
3179
- const doneStatusVisible = !activityVisible && !stallStatusVisible && !!doneStatus && !agentLoop.isRunning;
3180
- const statusSlotVisible = activityVisible || stallStatusVisible || doneStatusVisible;
3181
- const [controlsHeight, setControlsHeight] = useState(0);
3182
- const controlsObserverRef = useRef(null);
3183
- const mainControlsRef = useCallback((node) => {
3184
- if (controlsObserverRef.current) {
3185
- controlsObserverRef.current.disconnect();
3186
- controlsObserverRef.current = null;
3187
- }
3188
- if (!node || typeof ResizeObserver === "undefined")
3189
- return;
3190
- const observer = new ResizeObserver((entries) => {
3191
- const entry = entries[0];
3192
- if (!entry)
3193
- return;
3194
- const roundedHeight = Math.round(entry.contentRect.height);
3195
- setControlsHeight((prev) => (roundedHeight !== prev ? roundedHeight : prev));
3196
- });
3197
- observer.observe(node);
3198
- controlsObserverRef.current = observer;
3199
- }, []);
3200
- useEffect(() => () => controlsObserverRef.current?.disconnect(), []);
3201
- const footerFitsOnOneLine = doesFooterFitOnOneLine({
3202
- columns,
3203
- model: currentModel,
3204
- tokensIn: agentLoop.contextUsed,
2834
+ agentRunning: agentLoop.isRunning,
2835
+ activityPhase: agentLoop.activityPhase,
2836
+ stallError: agentLoop.stallError,
2837
+ doneStatus,
2838
+ currentModel,
2839
+ contextUsed: agentLoop.contextUsed,
3205
2840
  contextWindowOptions,
3206
- cwd: displayedCwd,
2841
+ displayedCwd,
3207
2842
  gitBranch,
3208
2843
  thinkingLevel,
3209
2844
  goalMode,
3210
- });
3211
- const chatControlsLayout = getChatControlsLayoutDecision({
3212
- rows,
3213
- columns,
3214
- agentRunning: agentLoop.isRunning,
3215
- activityVisible,
3216
- doneStatusVisible,
3217
- stallStatusVisible,
3218
2845
  exitPending,
3219
- footerStatusLayout,
3220
2846
  taskBarExpanded,
3221
2847
  goalStatusEntryCount: goalStatusEntries.length,
3222
- footerFitsOnOneLine,
3223
2848
  });
3224
- const stableControlsRows = controlsHeight > 0 ? controlsHeight : chatControlsLayout.controlsRows;
3225
- const measuredLiveAreaRows = Math.max(MIN_LIVE_AREA_ROWS, rows - stableControlsRows - 1);
3226
2849
  const isPixelView = overlay === "pixel";
3227
2850
  const hasLiveAssistantItem = liveItems.some((item) => item.kind === "assistant");
3228
2851
  const rawVisibleStreamingText = goalModeStateRef.current === "planner" || hasLiveAssistantItem ? "" : agentLoop.streamingText;
@@ -3240,7 +2863,7 @@ export function App(props) {
3240
2863
  queueFlush([
3241
2864
  {
3242
2865
  kind: "assistant",
3243
- text: split.flushedText,
2866
+ text: stripDoneMarkers(split.flushedText),
3244
2867
  continuation: streamedAssistantFlushRef.current.flushedChars > 0,
3245
2868
  id: getId(),
3246
2869
  },
@@ -3256,13 +2879,20 @@ export function App(props) {
3256
2879
  text: rawVisibleStreamingText,
3257
2880
  };
3258
2881
  }, [rawVisibleStreamingText, queueFlush]);
3259
- const visibleStreamingText = rawVisibleStreamingText.slice(streamedAssistantFlushRef.current.flushedChars);
3260
- const shouldReserveStreamingSpacing = agentLoop.isRunning &&
3261
- !hasLiveAssistantItem &&
3262
- (visibleStreamingText.trim().length > 0 || liveItems.some(isAgentSpacingItem));
2882
+ const visibleStreamingText = stripDoneMarkers(rawVisibleStreamingText.slice(streamedAssistantFlushRef.current.flushedChars));
3263
2883
  const lastLiveItem = liveItems.at(-1);
3264
2884
  const lastPendingHistoryItem = pendingHistoryFlushRef.current.at(-1);
3265
2885
  const lastHistoryItem = history.at(-1);
2886
+ const previousTranscriptItem = lastPendingHistoryItem ?? lastHistoryItem;
2887
+ const isAwaitingAssistantAfterUser = agentLoop.isRunning &&
2888
+ !hasLiveAssistantItem &&
2889
+ visibleStreamingText.trim().length === 0 &&
2890
+ (lastLiveItem?.kind === "user" || (!lastLiveItem && previousTranscriptItem?.kind === "user"));
2891
+ const shouldReserveStreamingSpacing = agentLoop.isRunning &&
2892
+ !hasLiveAssistantItem &&
2893
+ (visibleStreamingText.trim().length > 0 ||
2894
+ liveItems.some(isTranscriptSpacingItem) ||
2895
+ isAwaitingAssistantAfterUser);
3266
2896
  const shouldTopSpaceStreamingText = shouldTopSpaceStreamingAssistant({
3267
2897
  visibleStreamingText,
3268
2898
  lastLiveItem,
@@ -3278,239 +2908,282 @@ export function App(props) {
3278
2908
  lastPendingHistoryItem,
3279
2909
  lastHistoryItem,
3280
2910
  });
3281
- return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: isGoalView ? (_jsx(GoalOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, autoExpandNewest: goalAutoExpand, onClose: () => {
3282
- goalAutoExpandRef.current = false;
3283
- setGoalAutoExpand(false);
3284
- if (props.sessionStore)
3285
- props.sessionStore.goalAutoExpand = false;
3286
- closeOverlay();
3287
- }, onRunGoal: (run) => {
3288
- const paneTransition = getGoalActivationPaneTransition();
3289
- goalAutoExpandRef.current = paneTransition.goalAutoExpand;
3290
- setGoalAutoExpand(paneTransition.goalAutoExpand);
3291
- setPlanAutoExpand(paneTransition.planAutoExpand);
3292
- if (props.sessionStore) {
3293
- props.sessionStore.overlay = paneTransition.overlay;
3294
- props.sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
3295
- props.sessionStore.planAutoExpand = paneTransition.planAutoExpand;
3296
- }
3297
- if (paneTransition.resetReviewScreen && props.resetUI && props.sessionStore) {
3298
- props.sessionStore.pendingGoalRun = run;
3299
- props.resetUI();
3300
- return;
3301
- }
3302
- setOverlay(paneTransition.overlay);
3303
- startGoalRun(run);
3304
- }, onVerifyGoal: (run) => {
3305
- void verifyGoalRun(run);
3306
- }, onPauseGoal: (run) => {
3307
- pauseGoalRun(run);
3308
- }, onRefineGoal: (run, feedback) => {
3309
- goalAutoExpandRef.current = true;
3310
- setGoalAutoExpand(true);
3311
- void (async () => {
3312
- try {
3313
- await setGoalModeAndPrompt("setup");
3314
- await agentLoop.run(`Refine Goal run ${run.id} (${run.title}) based on this user feedback. Update durable Goal setup only, then stop and reopen the Goal pane for review.\n\nFeedback: ${feedback}`);
3315
- }
3316
- catch (err) {
3317
- log("ERROR", "goal", err instanceof Error ? err.message : String(err));
3318
- setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3319
- }
3320
- finally {
3321
- await setGoalModeAndPrompt("off");
3322
- const paneTransition = getGoalSetupFinishedPaneTransition();
3323
- goalAutoExpandRef.current = paneTransition.goalAutoExpand;
3324
- setTimeout(() => {
3325
- const resetUI = props.resetUI;
3326
- const sessionStore = props.sessionStore;
3327
- if (shouldResetUIForGoalSetupPaneTransition({
3328
- hasResetUI: resetUI !== undefined,
3329
- hasSessionStore: sessionStore !== undefined,
3330
- }) &&
3331
- resetUI &&
3332
- sessionStore) {
3333
- sessionStore.overlay = paneTransition.overlay;
3334
- sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
3335
- sessionStore.planAutoExpand = paneTransition.planAutoExpand;
3336
- resetUI();
3337
- return;
3338
- }
3339
- setGoalAutoExpand(paneTransition.goalAutoExpand);
3340
- setPlanAutoExpand(paneTransition.planAutoExpand);
3341
- setOverlay(paneTransition.overlay);
3342
- }, 300);
3343
- }
3344
- })();
3345
- } })) : isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
3346
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3347
- props.sessionStore.overlay = null;
3348
- props.resetUI();
3349
- }
3350
- else {
3351
- if (props.sessionStore) {
3352
- props.sessionStore.overlay = null;
3353
- if (agentLoop.isRunning)
3354
- props.sessionStore.pendingResetUI = true;
3355
- }
3356
- setOverlay(null);
3357
- }
3358
- }, onFixOne: (entry) => {
3359
- setOverlay(null);
3360
- startPixelFix(entry.errorId);
3361
- }, onFixAll: (entries) => {
3362
- const first = entries.find((e) => e.status === "open") ?? entries[0];
3363
- if (!first)
3364
- return;
3365
- setOverlay(null);
3366
- setRunAllPixel(true);
3367
- startPixelFix(first.errorId);
3368
- } })) : isSkillsView ? (_jsx(SkillsOverlay, { cwd: props.cwd, onClose: () => {
3369
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3370
- props.sessionStore.overlay = null;
3371
- props.resetUI();
3372
- }
3373
- else {
3374
- if (props.sessionStore) {
3375
- props.sessionStore.overlay = null;
3376
- if (agentLoop.isRunning)
3377
- props.sessionStore.pendingResetUI = true;
3378
- }
3379
- setOverlay(null);
3380
- }
3381
- } })) : isPlanView ? (_jsx(PlanOverlay, { cwd: props.cwd, autoExpandNewest: planAutoExpand, onClose: () => {
3382
- planOverlayPendingRef.current = false;
3383
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3384
- props.sessionStore.overlay = null;
3385
- props.sessionStore.planAutoExpand = false;
3386
- props.resetUI();
3387
- }
3388
- else {
3389
- if (props.sessionStore) {
3390
- props.sessionStore.overlay = null;
3391
- props.sessionStore.planAutoExpand = false;
3392
- if (agentLoop.isRunning)
3393
- props.sessionStore.pendingResetUI = true;
3394
- }
3395
- setPlanAutoExpand(false);
3396
- setOverlay(null);
3397
- }
3398
- }, onApprove: (planPath) => {
3399
- log("INFO", "plan", "Plan approved — transitioning to implementation", {
3400
- planPath,
2911
+ const handleCloseRemountableOverlay = () => {
2912
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2913
+ props.sessionStore.overlay = null;
2914
+ props.resetUI();
2915
+ return;
2916
+ }
2917
+ if (props.sessionStore) {
2918
+ props.sessionStore.overlay = null;
2919
+ if (agentLoop.isRunning)
2920
+ props.sessionStore.pendingResetUI = true;
2921
+ }
2922
+ setOverlay(null);
2923
+ };
2924
+ const handleEnterPlanMode = useCallback(async (reason) => {
2925
+ await setPlanModeAndPrompt(true);
2926
+ const detail = reason ? `Plan mode ON — ${reason}` : "Plan mode ON";
2927
+ setLiveItems((prev) => [
2928
+ ...prev,
2929
+ { kind: "plan_transition", text: detail, id: getId(), active: true },
2930
+ ]);
2931
+ }, [setPlanModeAndPrompt]);
2932
+ const handleExitPlanMode = useCallback(async (planPath) => {
2933
+ await setPlanModeAndPrompt(false);
2934
+ planOverlayPendingRef.current = true;
2935
+ setPlanAutoExpand(true);
2936
+ if (props.sessionStore) {
2937
+ props.sessionStore.overlay = "plan";
2938
+ props.sessionStore.planAutoExpand = true;
2939
+ }
2940
+ setOverlay("plan");
2941
+ setLiveItems((prev) => [
2942
+ ...prev,
2943
+ {
2944
+ kind: "plan_transition",
2945
+ text: `Plan ready for review: ${planPath}`,
2946
+ id: getId(),
2947
+ active: false,
2948
+ },
2949
+ ]);
2950
+ return "Plan submitted for user review. Wait for the user to approve, reject, or dismiss it before implementing.";
2951
+ }, [props.sessionStore, setPlanModeAndPrompt]);
2952
+ useEffect(() => {
2953
+ if (!props.planCallbacks)
2954
+ return;
2955
+ props.planCallbacks.onEnterPlan = handleEnterPlanMode;
2956
+ props.planCallbacks.onExitPlan = handleExitPlanMode;
2957
+ }, [handleEnterPlanMode, handleExitPlanMode, props.planCallbacks]);
2958
+ const handleClosePlanOverlay = () => {
2959
+ planOverlayPendingRef.current = false;
2960
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2961
+ props.sessionStore.overlay = null;
2962
+ props.sessionStore.planAutoExpand = false;
2963
+ props.resetUI();
2964
+ return;
2965
+ }
2966
+ if (props.sessionStore) {
2967
+ props.sessionStore.overlay = null;
2968
+ props.sessionStore.planAutoExpand = false;
2969
+ if (agentLoop.isRunning)
2970
+ props.sessionStore.pendingResetUI = true;
2971
+ }
2972
+ setPlanAutoExpand(false);
2973
+ setOverlay(null);
2974
+ };
2975
+ const handlePixelFixOne = (entry) => {
2976
+ setOverlay(null);
2977
+ startPixelFix(entry.errorId);
2978
+ };
2979
+ const handlePixelFixAll = (entries) => {
2980
+ const first = entries.find((entry) => entry.status === "open") ?? entries[0];
2981
+ if (!first)
2982
+ return;
2983
+ setOverlay(null);
2984
+ setRunAllPixel(true);
2985
+ startPixelFix(first.errorId);
2986
+ };
2987
+ const handleApprovePlan = (planPath) => {
2988
+ log("INFO", "plan", "Plan approved — transitioning to implementation", {
2989
+ planPath,
2990
+ });
2991
+ planOverlayPendingRef.current = false;
2992
+ void (async () => {
2993
+ try {
2994
+ // Read plan steps for progress tracking — handed to the new
2995
+ // mount via sessionStore.planSteps below.
2996
+ const planContent = await import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8"));
2997
+ const steps = extractPlanSteps(planContent);
2998
+ // Build the new system prompt with the approved plan baked in.
2999
+ const newPrompt = await rebuildSystemPrompt({
3000
+ approvedPlanPath: planPath,
3401
3001
  });
3402
- planOverlayPendingRef.current = false;
3403
- void (async () => {
3404
- try {
3405
- // Read plan steps for progress tracking — handed to the new
3406
- // mount via sessionStore.planSteps below.
3407
- const planContent = await import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8"));
3408
- const steps = extractPlanSteps(planContent);
3409
- // Build the new system prompt with the approved plan baked in.
3410
- const newPrompt = await rebuildSystemPrompt({
3411
- approvedPlanPath: planPath,
3412
- });
3413
- // Create a new session file BEFORE remount so the new tree
3414
- // picks it up via sessionStore.sessionPath.
3415
- let newSessionPath;
3416
- const sm = sessionManagerRef.current;
3417
- if (sm) {
3418
- const s = await sm.create(props.cwd, currentProvider, currentModel);
3419
- newSessionPath = s.path;
3420
- }
3421
- if (props.resetUI && props.sessionStore) {
3422
- // Clear the overlay so the new mount lands on the chat,
3423
- // not back inside the plan pane.
3424
- props.sessionStore.overlay = null;
3425
- props.sessionStore.planAutoExpand = false;
3426
- props.resetUI({
3427
- wipeSession: true,
3428
- messages: [{ role: "system", content: newPrompt }],
3429
- approvedPlanPath: planPath,
3430
- planSteps: steps,
3431
- sessionPath: newSessionPath,
3432
- pendingAction: {
3433
- prompt: "The plan has been approved. Implement it now, following each step in order.",
3434
- planEvent: { event: "approved" },
3435
- },
3436
- });
3437
- return;
3438
- }
3439
- // Fallback path (resetUI not wired — tests). Mutate in place.
3440
- approvedPlanPathRef.current = planPath;
3441
- planStepsRef.current = steps;
3442
- setPlanSteps(steps);
3443
- pendingHistoryFlushRef.current = [];
3444
- props.terminalHistoryPrinter?.clear();
3445
- setHistory([{ kind: "banner", id: "banner" }]);
3446
- setLiveItems([]);
3447
- setPlanAutoExpand(false);
3448
- setOverlay(null);
3449
- messagesRef.current = [{ role: "system", content: newPrompt }];
3450
- agentLoop.reset();
3451
- persistedIndexRef.current = messagesRef.current.length;
3452
- if (newSessionPath)
3453
- sessionPathRef.current = newSessionPath;
3454
- setLiveItems([
3455
- {
3456
- kind: "info",
3457
- text: "Plan approved — starting fresh session for implementation",
3458
- id: getId(),
3459
- },
3460
- ]);
3461
- setDoneStatus(null);
3462
- await agentLoop.run("The plan has been approved. Implement it now, following each step in order.");
3463
- }
3464
- catch (err) {
3465
- const errMsg = err instanceof Error ? err.message : String(err);
3466
- log("ERROR", "error", errMsg);
3467
- setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3468
- }
3469
- })();
3470
- }, onReject: (planPath, feedback) => {
3471
- planOverlayPendingRef.current = false;
3472
- const rejectionMsg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
3473
- `Please revise the plan based on this feedback.`;
3002
+ // Create a new session file BEFORE remount so the new tree
3003
+ // picks it up via sessionStore.sessionPath.
3004
+ let newSessionPath;
3005
+ const sm = sessionManagerRef.current;
3006
+ if (sm) {
3007
+ const s = await sm.create(props.cwd, currentProvider, currentModel);
3008
+ newSessionPath = s.path;
3009
+ }
3474
3010
  if (props.resetUI && props.sessionStore) {
3011
+ // Clear the overlay so the new mount lands on the chat,
3012
+ // not back inside the plan pane.
3475
3013
  props.sessionStore.overlay = null;
3476
3014
  props.sessionStore.planAutoExpand = false;
3477
- // No wipeSession — keep history and messages so the agent picks
3478
- // up the rejection mid-conversation.
3479
3015
  props.resetUI({
3016
+ wipeSession: true,
3017
+ messages: [{ role: "system", content: newPrompt }],
3018
+ approvedPlanPath: planPath,
3019
+ planSteps: steps,
3020
+ sessionPath: newSessionPath,
3480
3021
  pendingAction: {
3481
- prompt: rejectionMsg,
3482
- planEvent: { event: "rejected", detail: feedback },
3022
+ prompt: "The plan has been approved. Implement it now, following each step in order.",
3023
+ planEvent: { event: "approved" },
3483
3024
  },
3484
3025
  });
3485
3026
  return;
3486
3027
  }
3028
+ // Fallback path (resetUI not wired — tests). Mutate in place.
3029
+ approvedPlanPathRef.current = planPath;
3030
+ planStepsRef.current = steps;
3031
+ setPlanSteps(steps);
3032
+ clearPendingHistory();
3033
+ setHistory([{ kind: "banner", id: "banner" }]);
3034
+ setLiveItems([]);
3487
3035
  setPlanAutoExpand(false);
3488
3036
  setOverlay(null);
3489
- setDoneStatus(null);
3490
- setLiveItems((prev) => [
3491
- ...prev,
3492
- { kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
3037
+ messagesRef.current = [{ role: "system", content: newPrompt }];
3038
+ agentLoop.reset();
3039
+ persistedIndexRef.current = messagesRef.current.length;
3040
+ if (newSessionPath)
3041
+ sessionPathRef.current = newSessionPath;
3042
+ setLiveItems([
3043
+ {
3044
+ kind: "info",
3045
+ text: "Plan approved — starting fresh session for implementation",
3046
+ id: getId(),
3047
+ },
3493
3048
  ]);
3494
- void agentLoop.run(rejectionMsg).catch((err) => {
3495
- const errMsg = err instanceof Error ? err.message : String(err);
3496
- log("ERROR", "error", errMsg);
3497
- setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3498
- });
3499
- } })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [liveItems.map((item, index, items) => renderItem(item, index, items)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, continuation: streamedAssistantFlushRef.current.flushedChars > 0 })] }), _jsxs(Box, { ref: mainControlsRef, flexDirection: "column", flexShrink: 0, flexGrow: 0, children: [hiddenQueuedCount > 0 && (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: shouldTopSpaceQueueIndicator ? 2 : 1, flexShrink: 0, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsxs(Text, { color: theme.textDim, children: [hiddenQueuedCount, " message", hiddenQueuedCount > 1 ? "s" : "", " queued"] })] })), _jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.textDim, width: columns, height: 0 }), _jsx(Box, { paddingLeft: 1, paddingRight: 1, width: columns, children: statusSlotVisible ? (activityVisible ? (_jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: !!thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, staticDisplay: true })) : stallStatusVisible ? (_jsx(Text, { color: theme.warning, wrap: "truncate", children: "⚠ API provider stream interrupted — retries exhausted. Your conversation is preserved." })) : doneStatus ? (_jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." }), !renderMarkdown && (_jsx(Text, { color: theme.warning, children: " · raw markdown mode" }))] }))) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." })] })) })] }), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleGoal: () => {
3500
- openOverlay("goal");
3501
- }, onToggleSkills: () => {
3502
- openOverlay("skills");
3503
- }, onTogglePixel: () => {
3504
- openOverlay("pixel");
3505
- }, onToggleMarkdown: () => {
3506
- setRenderMarkdown((prev) => !prev);
3507
- }, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : overlay === "theme" ? (_jsx(ThemeSelector, { onSelect: handleThemeSelect, onCancel: () => setOverlay(null), currentTheme: theme.name })) : (_jsxs(_Fragment, { children: [_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, contextWindowOptions: contextWindowOptions, cwd: displayedCwd, gitBranch: gitBranch, thinkingLevel: thinkingLevel, goalMode: goalMode, exitPending: exitPending, renderMarkdown: renderMarkdown }), !exitPending && _jsx(GoalStatusBar, { entries: goalStatusEntries })] })), (footerStatusLayout.hasBackgroundTasks || footerStatusLayout.hasUpdateNotice) && (_jsxs(Box, { flexDirection: footerStatusLayout.stack ? "column" : "row", width: columns, children: [footerStatusLayout.hasBackgroundTasks && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate, compact: footerStatusLayout.compactBackgroundTasks })), footerStatusLayout.hasUpdateNotice && (_jsx(Box, { paddingLeft: footerStatusLayout.stack || !footerStatusLayout.hasBackgroundTasks ? 1 : 2, paddingRight: 1, children: _jsx(Text, { color: theme.success, bold: true, wrap: "truncate", children: "\u2728 Update ready \u00B7 restart to apply" }) }))] }))] })] })) }));
3508
- }
3509
- function formatRepoMapCommandOutput(enabled, markdown, refreshed) {
3510
- const status = enabled ? "on" : "off";
3511
- const prefix = refreshed
3512
- ? `Dynamic repo map refreshed · injection: ${status}`
3513
- : `Dynamic repo map · injection: ${status}`;
3514
- return `${prefix}\n\n${markdown}`;
3049
+ setDoneStatus(null);
3050
+ await agentLoop.run("The plan has been approved. Implement it now, following each step in order.");
3051
+ }
3052
+ catch (err) {
3053
+ const errMsg = err instanceof Error ? err.message : String(err);
3054
+ log("ERROR", "error", errMsg);
3055
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3056
+ }
3057
+ })();
3058
+ };
3059
+ const handleRejectPlan = (planPath, feedback) => {
3060
+ planOverlayPendingRef.current = false;
3061
+ const rejectionMsg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
3062
+ `Please revise the plan based on this feedback.`;
3063
+ if (props.resetUI && props.sessionStore) {
3064
+ props.sessionStore.overlay = null;
3065
+ props.sessionStore.planAutoExpand = false;
3066
+ // No wipeSession — keep history and messages so the agent picks
3067
+ // up the rejection mid-conversation.
3068
+ props.resetUI({
3069
+ pendingAction: {
3070
+ prompt: rejectionMsg,
3071
+ planEvent: { event: "rejected", detail: feedback },
3072
+ },
3073
+ });
3074
+ return;
3075
+ }
3076
+ setPlanAutoExpand(false);
3077
+ setOverlay(null);
3078
+ setDoneStatus(null);
3079
+ setLiveItems((prev) => [
3080
+ ...prev,
3081
+ { kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
3082
+ ]);
3083
+ void agentLoop.run(rejectionMsg).catch((err) => {
3084
+ const errMsg = err instanceof Error ? err.message : String(err);
3085
+ log("ERROR", "error", errMsg);
3086
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3087
+ });
3088
+ };
3089
+ const handleRunGoalFromPicker = (run) => {
3090
+ setDoneStatus(null);
3091
+ appendGoalProgress({
3092
+ kind: "goal_progress",
3093
+ phase: "continuing",
3094
+ title: `Goal run requested: ${run.title}`,
3095
+ detail: "Enter pressed in Ctrl+G; starting the Goal orchestrator.",
3096
+ status: run.status,
3097
+ });
3098
+ log("INFO", "goal", `Goal run requested from Ctrl+G: ${run.title}`, { id: run.id });
3099
+ void (async () => {
3100
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3101
+ const requestedAt = new Date().toISOString();
3102
+ const runWithContinuation = await upsertGoalRun(props.cwd, {
3103
+ ...latestRun,
3104
+ status: latestRun.status === "running" || latestRun.status === "verifying"
3105
+ ? latestRun.status
3106
+ : "ready",
3107
+ continueRequestedAt: requestedAt,
3108
+ blockers: goalHasBlockingPrerequisites(latestRun) ? latestRun.blockers : [],
3109
+ evidence: [
3110
+ ...latestRun.evidence,
3111
+ {
3112
+ id: `goal-rerun-${requestedAt}`,
3113
+ kind: "summary",
3114
+ label: "Goal rerun requested",
3115
+ content: "Continuation requested from Ctrl+G; the orchestrator will choose the next eligible Goal action.",
3116
+ createdAt: requestedAt,
3117
+ },
3118
+ ],
3119
+ });
3120
+ startGoalRun(runWithContinuation);
3121
+ })().catch((err) => {
3122
+ log("ERROR", "goal", err instanceof Error ? err.message : String(err));
3123
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3124
+ });
3125
+ };
3126
+ const handleDeleteGoalSideEffects = async (run) => {
3127
+ runningGoalIdsRef.current.delete(run.id);
3128
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3129
+ if (latestRun.activeWorkerId)
3130
+ await stopGoalWorker(latestRun.activeWorkerId);
3131
+ clearGoalStatusEntry(run.id);
3132
+ clearGoalModeIfIdle();
3133
+ };
3134
+ const handleGoalPickerError = (err) => {
3135
+ log("ERROR", "goal", err instanceof Error ? err.message : String(err));
3136
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3137
+ };
3138
+ const goalPicker = useGoalPickerController({
3139
+ cwd: props.cwd,
3140
+ onRunGoal: handleRunGoalFromPicker,
3141
+ onDeleteGoalSideEffects: handleDeleteGoalSideEffects,
3142
+ onPauseGoal: pauseGoalRun,
3143
+ onError: handleGoalPickerError,
3144
+ });
3145
+ const handleToggleTasks = () => {
3146
+ goalPicker.close();
3147
+ taskPicker.toggle();
3148
+ };
3149
+ const handleToggleGoalPicker = () => {
3150
+ taskPicker.close();
3151
+ goalPicker.toggle();
3152
+ };
3153
+ const fullScreenOverlay = isPixelView
3154
+ ? "pixel"
3155
+ : isSkillsView
3156
+ ? "skills"
3157
+ : isPlanView
3158
+ ? "plan"
3159
+ : null;
3160
+ return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: fullScreenOverlay ? (_jsx(FullScreenOverlayRouter, { overlay: fullScreenOverlay, version: props.version, cwd: props.cwd, agentRunning: agentLoop.isRunning, planAutoExpand: planAutoExpand, onClosePixel: handleCloseRemountableOverlay, onPixelFixOne: handlePixelFixOne, onPixelFixAll: handlePixelFixAll, onCloseSkills: handleCloseRemountableOverlay, onClosePlan: handleClosePlanOverlay, onApprovePlan: handleApprovePlan, onRejectPlan: handleRejectPlan })) : (_jsx(ChatScreen, { columns: columns, liveItems: uniqueItemsById(liveItems), renderItem: renderItem, isRunning: agentLoop.isRunning, visibleStreamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveStreamingSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, measuredLiveAreaRows: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, streamingContinuation: streamedAssistantFlushRef.current.flushedChars > 0, controlsRef: mainControlsRef, hiddenQueuedCount: hiddenQueuedCount, queueIndicatorMarginTop: shouldTopSpaceQueueIndicator ? 2 : 1, theme: theme, statusSlotVisible: statusSlotVisible, activityVisible: activityVisible, stallStatusVisible: stallStatusVisible, doneStatus: doneStatus, activityPhase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, isThinking: agentLoop.isThinking, thinkingLevel: thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, lastUserMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, formatDuration: formatDuration, inputControls: {
3161
+ onSubmit: handleSubmit,
3162
+ onAbort: handleAbort,
3163
+ inputActive: !taskBarFocused && !overlay,
3164
+ onDownAtEnd: handleFocusTaskBar,
3165
+ onShiftTab: handleToggleThinking,
3166
+ onToggleTasks: handleToggleTasks,
3167
+ onToggleGoal: handleToggleGoalPicker,
3168
+ onToggleSkills: () => openOverlay("skills"),
3169
+ onTogglePixel: () => openOverlay("pixel"),
3170
+ onToggleMarkdown: () => setRenderMarkdown((prev) => !prev),
3171
+ cwd: props.cwd,
3172
+ commands: allCommands,
3173
+ }, taskPicker: {
3174
+ open: taskPicker.open,
3175
+ tasks: taskPicker.tasks,
3176
+ onClose: taskPicker.close,
3177
+ onStart: taskPicker.start,
3178
+ onRunAll: taskPicker.runAll,
3179
+ onDelete: taskPicker.deleteTask,
3180
+ }, goalPicker: {
3181
+ open: goalPicker.open,
3182
+ goals: goalPicker.goals,
3183
+ onClose: goalPicker.close,
3184
+ onRun: goalPicker.run,
3185
+ onDelete: goalPicker.deleteGoal,
3186
+ onPause: goalPicker.pause,
3187
+ }, overlay: overlay, onModelSelect: handleModelSelect, onModelCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider, onThemeSelect: handleThemeSelect, onThemeCancel: () => setOverlay(null), currentTheme: theme.name, contextUsed: agentLoop.contextUsed, contextWindowOptions: contextWindowOptions, displayedCwd: displayedCwd, gitBranch: gitBranch, goalMode: goalMode, planMode: planMode, exitPending: exitPending, goalStatusEntries: goalStatusEntries, footerStatusLayout: footerStatusLayout, backgroundTasks: bgTasks, taskBarFocused: taskBarFocused, taskBarExpanded: taskBarExpanded, selectedTaskIndex: selectedTaskIndex, onTaskBarExpand: handleTaskBarExpand, onTaskBarCollapse: handleTaskBarCollapse, onTaskKill: handleTaskKill, onTaskBarExit: handleTaskBarExit, onTaskNavigate: handleTaskNavigate })) }));
3515
3188
  }
3516
3189
  //# sourceMappingURL=App.js.map