@kenkaiiii/ggcoder 4.3.219 → 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 (368) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +39 -20
  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 +1 -0
  23. package/dist/core/goal-store.d.ts.map +1 -1
  24. package/dist/core/goal-store.js +43 -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/system-prompt.d.ts +1 -1
  57. package/dist/system-prompt.d.ts.map +1 -1
  58. package/dist/system-prompt.js +13 -1
  59. package/dist/system-prompt.js.map +1 -1
  60. package/dist/system-prompt.test.js +2 -2
  61. package/dist/system-prompt.test.js.map +1 -1
  62. package/dist/tools/bash.d.ts +2 -0
  63. package/dist/tools/bash.d.ts.map +1 -1
  64. package/dist/tools/bash.js +5 -2
  65. package/dist/tools/bash.js.map +1 -1
  66. package/dist/tools/edit.d.ts +2 -0
  67. package/dist/tools/edit.d.ts.map +1 -1
  68. package/dist/tools/edit.js +16 -3
  69. package/dist/tools/edit.js.map +1 -1
  70. package/dist/tools/enter-plan.d.ts +8 -0
  71. package/dist/tools/enter-plan.d.ts.map +1 -0
  72. package/dist/tools/enter-plan.js +27 -0
  73. package/dist/tools/enter-plan.js.map +1 -0
  74. package/dist/tools/exit-plan.d.ts +8 -0
  75. package/dist/tools/exit-plan.d.ts.map +1 -0
  76. package/dist/tools/exit-plan.js +35 -0
  77. package/dist/tools/exit-plan.js.map +1 -0
  78. package/dist/tools/goals.d.ts +8 -5
  79. package/dist/tools/goals.d.ts.map +1 -1
  80. package/dist/tools/goals.js +124 -49
  81. package/dist/tools/goals.js.map +1 -1
  82. package/dist/tools/goals.test.js +356 -11
  83. package/dist/tools/goals.test.js.map +1 -1
  84. package/dist/tools/index.d.ts +10 -0
  85. package/dist/tools/index.d.ts.map +1 -1
  86. package/dist/tools/index.js +15 -4
  87. package/dist/tools/index.js.map +1 -1
  88. package/dist/tools/plan-mode.test.js +63 -24
  89. package/dist/tools/plan-mode.test.js.map +1 -1
  90. package/dist/tools/prompt-hints.d.ts.map +1 -1
  91. package/dist/tools/prompt-hints.js +4 -0
  92. package/dist/tools/prompt-hints.js.map +1 -1
  93. package/dist/tools/subagent.d.ts +3 -1
  94. package/dist/tools/subagent.d.ts.map +1 -1
  95. package/dist/tools/subagent.js +5 -2
  96. package/dist/tools/subagent.js.map +1 -1
  97. package/dist/tools/write.d.ts +2 -0
  98. package/dist/tools/write.d.ts.map +1 -1
  99. package/dist/tools/write.js +23 -3
  100. package/dist/tools/write.js.map +1 -1
  101. package/dist/ui/App.d.ts +9 -6
  102. package/dist/ui/App.d.ts.map +1 -1
  103. package/dist/ui/App.js +572 -925
  104. package/dist/ui/App.js.map +1 -1
  105. package/dist/ui/app-items.d.ts +1 -0
  106. package/dist/ui/app-items.d.ts.map +1 -1
  107. package/dist/ui/app-items.js +1 -1
  108. package/dist/ui/app-items.js.map +1 -1
  109. package/dist/ui/app-state-persistence.test.js +22 -22
  110. package/dist/ui/app-state-persistence.test.js.map +1 -1
  111. package/dist/ui/components/AssistantMessage.test.js +7 -7
  112. package/dist/ui/components/AssistantMessage.test.js.map +1 -1
  113. package/dist/ui/components/BackgroundTasksBar.d.ts.map +1 -1
  114. package/dist/ui/components/BackgroundTasksBar.js +6 -6
  115. package/dist/ui/components/BackgroundTasksBar.js.map +1 -1
  116. package/dist/ui/components/ChatFooterPane.d.ts +29 -0
  117. package/dist/ui/components/ChatFooterPane.d.ts.map +1 -0
  118. package/dist/ui/components/ChatFooterPane.js +16 -0
  119. package/dist/ui/components/ChatFooterPane.js.map +1 -0
  120. package/dist/ui/components/ChatInputStack.d.ts +34 -0
  121. package/dist/ui/components/ChatInputStack.d.ts.map +1 -0
  122. package/dist/ui/components/ChatInputStack.js +9 -0
  123. package/dist/ui/components/ChatInputStack.js.map +1 -0
  124. package/dist/ui/components/ChatLayout.d.ts +23 -0
  125. package/dist/ui/components/ChatLayout.d.ts.map +1 -0
  126. package/dist/ui/components/ChatLayout.js +16 -0
  127. package/dist/ui/components/ChatLayout.js.map +1 -0
  128. package/dist/ui/components/ChatLivePane.d.ts +18 -0
  129. package/dist/ui/components/ChatLivePane.d.ts.map +1 -0
  130. package/dist/ui/components/ChatLivePane.js +8 -0
  131. package/dist/ui/components/ChatLivePane.js.map +1 -0
  132. package/dist/ui/components/ChatScreen.d.ts +118 -0
  133. package/dist/ui/components/ChatScreen.d.ts.map +1 -0
  134. package/dist/ui/components/ChatScreen.js +14 -0
  135. package/dist/ui/components/ChatScreen.js.map +1 -0
  136. package/dist/ui/components/ChatStatusRow.d.ts +34 -0
  137. package/dist/ui/components/ChatStatusRow.d.ts.map +1 -0
  138. package/dist/ui/components/ChatStatusRow.js +11 -0
  139. package/dist/ui/components/ChatStatusRow.js.map +1 -0
  140. package/dist/ui/components/CompactionNotice.d.ts +4 -2
  141. package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
  142. package/dist/ui/components/CompactionNotice.js +4 -4
  143. package/dist/ui/components/CompactionNotice.js.map +1 -1
  144. package/dist/ui/components/Footer.d.ts +6 -3
  145. package/dist/ui/components/Footer.d.ts.map +1 -1
  146. package/dist/ui/components/Footer.js +14 -4
  147. package/dist/ui/components/Footer.js.map +1 -1
  148. package/dist/ui/components/FooterStatusRow.d.ts +20 -0
  149. package/dist/ui/components/FooterStatusRow.d.ts.map +1 -0
  150. package/dist/ui/components/FooterStatusRow.js +10 -0
  151. package/dist/ui/components/FooterStatusRow.js.map +1 -0
  152. package/dist/ui/components/FullScreenOverlayRouter.d.ts +19 -0
  153. package/dist/ui/components/FullScreenOverlayRouter.d.ts.map +1 -0
  154. package/dist/ui/components/FullScreenOverlayRouter.js +18 -0
  155. package/dist/ui/components/FullScreenOverlayRouter.js.map +1 -0
  156. package/dist/ui/components/GoalOverlay.d.ts +2 -1
  157. package/dist/ui/components/GoalOverlay.d.ts.map +1 -1
  158. package/dist/ui/components/GoalOverlay.js +11 -6
  159. package/dist/ui/components/GoalOverlay.js.map +1 -1
  160. package/dist/ui/components/GoalStatusBar.d.ts +2 -0
  161. package/dist/ui/components/GoalStatusBar.d.ts.map +1 -1
  162. package/dist/ui/components/GoalStatusBar.js +27 -11
  163. package/dist/ui/components/GoalStatusBar.js.map +1 -1
  164. package/dist/ui/components/GoalStatusBar.test.d.ts +2 -0
  165. package/dist/ui/components/GoalStatusBar.test.d.ts.map +1 -0
  166. package/dist/ui/components/GoalStatusBar.test.js +17 -0
  167. package/dist/ui/components/GoalStatusBar.test.js.map +1 -0
  168. package/dist/ui/components/InputArea.d.ts.map +1 -1
  169. package/dist/ui/components/InputArea.js +6 -5
  170. package/dist/ui/components/InputArea.js.map +1 -1
  171. package/dist/ui/components/PlanOverlay.d.ts +7 -0
  172. package/dist/ui/components/PlanOverlay.d.ts.map +1 -1
  173. package/dist/ui/components/PlanOverlay.js +16 -2
  174. package/dist/ui/components/PlanOverlay.js.map +1 -1
  175. package/dist/ui/components/PlanOverlay.test.d.ts +2 -0
  176. package/dist/ui/components/PlanOverlay.test.d.ts.map +1 -0
  177. package/dist/ui/components/PlanOverlay.test.js +24 -0
  178. package/dist/ui/components/PlanOverlay.test.js.map +1 -0
  179. package/dist/ui/components/QueueIndicator.d.ts +9 -0
  180. package/dist/ui/components/QueueIndicator.d.ts.map +1 -0
  181. package/dist/ui/components/QueueIndicator.js +9 -0
  182. package/dist/ui/components/QueueIndicator.js.map +1 -0
  183. package/dist/ui/components/ServerToolExecution.d.ts +2 -0
  184. package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
  185. package/dist/ui/components/ServerToolExecution.js +3 -2
  186. package/dist/ui/components/ServerToolExecution.js.map +1 -1
  187. package/dist/ui/components/StreamingArea.js +1 -1
  188. package/dist/ui/components/StreamingArea.js.map +1 -1
  189. package/dist/ui/components/SubAgentPanel.d.ts +2 -1
  190. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  191. package/dist/ui/components/SubAgentPanel.js +2 -2
  192. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  193. package/dist/ui/components/ToolExecution.d.ts +2 -0
  194. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  195. package/dist/ui/components/ToolExecution.js +13 -11
  196. package/dist/ui/components/ToolExecution.js.map +1 -1
  197. package/dist/ui/components/ToolGroupExecution.d.ts +2 -1
  198. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
  199. package/dist/ui/components/ToolGroupExecution.js +5 -3
  200. package/dist/ui/components/ToolGroupExecution.js.map +1 -1
  201. package/dist/ui/duration-format.d.ts +2 -0
  202. package/dist/ui/duration-format.d.ts.map +1 -0
  203. package/dist/ui/duration-format.js +9 -0
  204. package/dist/ui/duration-format.js.map +1 -0
  205. package/dist/ui/duration-summary.d.ts +2 -0
  206. package/dist/ui/duration-summary.d.ts.map +1 -0
  207. package/dist/ui/duration-summary.js +66 -0
  208. package/dist/ui/duration-summary.js.map +1 -0
  209. package/dist/ui/error-item.d.ts +8 -0
  210. package/dist/ui/error-item.d.ts.map +1 -0
  211. package/dist/ui/error-item.js +32 -0
  212. package/dist/ui/error-item.js.map +1 -0
  213. package/dist/ui/footer-status-layout.test.js +4 -3
  214. package/dist/ui/footer-status-layout.test.js.map +1 -1
  215. package/dist/ui/goal-events.d.ts +1 -0
  216. package/dist/ui/goal-events.d.ts.map +1 -1
  217. package/dist/ui/goal-events.js +2 -1
  218. package/dist/ui/goal-events.js.map +1 -1
  219. package/dist/ui/goal-events.test.js +16 -0
  220. package/dist/ui/goal-events.test.js.map +1 -1
  221. package/dist/ui/goal-lifecycle-orchestration.test.js +105 -18
  222. package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
  223. package/dist/ui/goal-progress.d.ts +1 -1
  224. package/dist/ui/goal-progress.d.ts.map +1 -1
  225. package/dist/ui/goal-progress.js +11 -13
  226. package/dist/ui/goal-progress.js.map +1 -1
  227. package/dist/ui/goal-run-helpers.d.ts +16 -0
  228. package/dist/ui/goal-run-helpers.d.ts.map +1 -0
  229. package/dist/ui/goal-run-helpers.js +61 -0
  230. package/dist/ui/goal-run-helpers.js.map +1 -0
  231. package/dist/ui/goal-status-bar.test.js +8 -6
  232. package/dist/ui/goal-status-bar.test.js.map +1 -1
  233. package/dist/ui/hooks/useChatLayoutMeasurements.d.ts +38 -0
  234. package/dist/ui/hooks/useChatLayoutMeasurements.d.ts.map +1 -0
  235. package/dist/ui/hooks/useChatLayoutMeasurements.js +71 -0
  236. package/dist/ui/hooks/useChatLayoutMeasurements.js.map +1 -0
  237. package/dist/ui/hooks/useGoalPickerController.d.ts +22 -0
  238. package/dist/ui/hooks/useGoalPickerController.d.ts.map +1 -0
  239. package/dist/ui/hooks/useGoalPickerController.js +35 -0
  240. package/dist/ui/hooks/useGoalPickerController.js.map +1 -0
  241. package/dist/ui/hooks/useTaskPickerController.d.ts +19 -0
  242. package/dist/ui/hooks/useTaskPickerController.d.ts.map +1 -0
  243. package/dist/ui/hooks/useTaskPickerController.js +41 -0
  244. package/dist/ui/hooks/useTaskPickerController.js.map +1 -0
  245. package/dist/ui/hooks/useTranscriptHistory.d.ts +34 -0
  246. package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -0
  247. package/dist/ui/hooks/useTranscriptHistory.js +96 -0
  248. package/dist/ui/hooks/useTranscriptHistory.js.map +1 -0
  249. package/dist/ui/layout-decisions.d.ts +3 -12
  250. package/dist/ui/layout-decisions.d.ts.map +1 -1
  251. package/dist/ui/layout-decisions.js +7 -65
  252. package/dist/ui/layout-decisions.js.map +1 -1
  253. package/dist/ui/prompt-routing.d.ts.map +1 -1
  254. package/dist/ui/prompt-routing.js +36 -2
  255. package/dist/ui/prompt-routing.js.map +1 -1
  256. package/dist/ui/prompt-routing.test.d.ts +2 -0
  257. package/dist/ui/prompt-routing.test.d.ts.map +1 -0
  258. package/dist/ui/prompt-routing.test.js +48 -0
  259. package/dist/ui/prompt-routing.test.js.map +1 -0
  260. package/dist/ui/render.d.ts +9 -6
  261. package/dist/ui/render.d.ts.map +1 -1
  262. package/dist/ui/render.js +3 -2
  263. package/dist/ui/render.js.map +1 -1
  264. package/dist/ui/slash-command-images.test.js +3 -2
  265. package/dist/ui/slash-command-images.test.js.map +1 -1
  266. package/dist/ui/submit-prompt-command.d.ts +40 -0
  267. package/dist/ui/submit-prompt-command.d.ts.map +1 -0
  268. package/dist/ui/submit-prompt-command.js +92 -0
  269. package/dist/ui/submit-prompt-command.js.map +1 -0
  270. package/dist/ui/submit-slash-commands.d.ts +13 -0
  271. package/dist/ui/submit-slash-commands.d.ts.map +1 -0
  272. package/dist/ui/submit-slash-commands.js +36 -0
  273. package/dist/ui/submit-slash-commands.js.map +1 -0
  274. package/dist/ui/terminal-history-format.d.ts +1 -0
  275. package/dist/ui/terminal-history-format.d.ts.map +1 -1
  276. package/dist/ui/terminal-history-format.js +2 -1
  277. package/dist/ui/terminal-history-format.js.map +1 -1
  278. package/dist/ui/terminal-history-spacing.d.ts +1 -2
  279. package/dist/ui/terminal-history-spacing.d.ts.map +1 -1
  280. package/dist/ui/terminal-history-spacing.js +1 -28
  281. package/dist/ui/terminal-history-spacing.js.map +1 -1
  282. package/dist/ui/terminal-history-status-renderers.d.ts +2 -2
  283. package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
  284. package/dist/ui/terminal-history-status-renderers.js +36 -17
  285. package/dist/ui/terminal-history-status-renderers.js.map +1 -1
  286. package/dist/ui/terminal-history.d.ts.map +1 -1
  287. package/dist/ui/terminal-history.js +55 -24
  288. package/dist/ui/terminal-history.js.map +1 -1
  289. package/dist/ui/terminal-history.test.js +2 -1
  290. package/dist/ui/terminal-history.test.js.map +1 -1
  291. package/dist/ui/tool-group-summary.d.ts +2 -2
  292. package/dist/ui/tool-group-summary.d.ts.map +1 -1
  293. package/dist/ui/tool-group-summary.js +18 -18
  294. package/dist/ui/tool-group-summary.js.map +1 -1
  295. package/dist/ui/transcript/GoalRows.d.ts +10 -0
  296. package/dist/ui/transcript/GoalRows.d.ts.map +1 -0
  297. package/dist/ui/transcript/GoalRows.js +35 -0
  298. package/dist/ui/transcript/GoalRows.js.map +1 -0
  299. package/dist/ui/transcript/MiscRows.d.ts +23 -0
  300. package/dist/ui/transcript/MiscRows.d.ts.map +1 -0
  301. package/dist/ui/transcript/MiscRows.js +41 -0
  302. package/dist/ui/transcript/MiscRows.js.map +1 -0
  303. package/dist/ui/transcript/StatusRow.d.ts +14 -0
  304. package/dist/ui/transcript/StatusRow.d.ts.map +1 -0
  305. package/dist/ui/transcript/StatusRow.js +14 -0
  306. package/dist/ui/transcript/StatusRow.js.map +1 -0
  307. package/dist/ui/transcript/ToolRows.d.ts +20 -0
  308. package/dist/ui/transcript/ToolRows.d.ts.map +1 -0
  309. package/dist/ui/transcript/ToolRows.js +25 -0
  310. package/dist/ui/transcript/ToolRows.js.map +1 -0
  311. package/dist/ui/transcript/TranscriptItemFrame.d.ts +8 -0
  312. package/dist/ui/transcript/TranscriptItemFrame.d.ts.map +1 -0
  313. package/dist/ui/transcript/TranscriptItemFrame.js +9 -0
  314. package/dist/ui/transcript/TranscriptItemFrame.js.map +1 -0
  315. package/dist/ui/transcript/TranscriptRenderer.d.ts +22 -0
  316. package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -0
  317. package/dist/ui/transcript/TranscriptRenderer.js +84 -0
  318. package/dist/ui/transcript/TranscriptRenderer.js.map +1 -0
  319. package/dist/ui/transcript/presentation.d.ts +76 -0
  320. package/dist/ui/transcript/presentation.d.ts.map +1 -0
  321. package/dist/ui/transcript/presentation.js +109 -0
  322. package/dist/ui/transcript/presentation.js.map +1 -0
  323. package/dist/ui/transcript/spacing.d.ts +29 -0
  324. package/dist/ui/transcript/spacing.d.ts.map +1 -0
  325. package/dist/ui/transcript/spacing.js +91 -0
  326. package/dist/ui/transcript/spacing.js.map +1 -0
  327. package/dist/ui/transcript/spacing.test.d.ts +2 -0
  328. package/dist/ui/transcript/spacing.test.d.ts.map +1 -0
  329. package/dist/ui/transcript/spacing.test.js +21 -0
  330. package/dist/ui/transcript/spacing.test.js.map +1 -0
  331. package/dist/ui/transcript/tool-presentation.d.ts +12 -0
  332. package/dist/ui/transcript/tool-presentation.d.ts.map +1 -0
  333. package/dist/ui/transcript/tool-presentation.js +55 -0
  334. package/dist/ui/transcript/tool-presentation.js.map +1 -0
  335. package/dist/ui/tui-history-parity.test.js +3 -2
  336. package/dist/ui/tui-history-parity.test.js.map +1 -1
  337. package/dist/utils/plan-steps.d.ts.map +1 -1
  338. package/dist/utils/plan-steps.js +5 -1
  339. package/dist/utils/plan-steps.js.map +1 -1
  340. package/dist/utils/plan-steps.test.d.ts +2 -0
  341. package/dist/utils/plan-steps.test.d.ts.map +1 -0
  342. package/dist/utils/plan-steps.test.js +16 -0
  343. package/dist/utils/plan-steps.test.js.map +1 -0
  344. package/package.json +6 -6
  345. package/dist/core/repomap-budget.d.ts +0 -7
  346. package/dist/core/repomap-budget.d.ts.map +0 -1
  347. package/dist/core/repomap-budget.js +0 -10
  348. package/dist/core/repomap-budget.js.map +0 -1
  349. package/dist/core/repomap-budget.test.d.ts +0 -2
  350. package/dist/core/repomap-budget.test.d.ts.map +0 -1
  351. package/dist/core/repomap-budget.test.js +0 -26
  352. package/dist/core/repomap-budget.test.js.map +0 -1
  353. package/dist/core/repomap-context.d.ts +0 -11
  354. package/dist/core/repomap-context.d.ts.map +0 -1
  355. package/dist/core/repomap-context.js +0 -68
  356. package/dist/core/repomap-context.js.map +0 -1
  357. package/dist/core/repomap-context.test.d.ts +0 -2
  358. package/dist/core/repomap-context.test.d.ts.map +0 -1
  359. package/dist/core/repomap-context.test.js +0 -47
  360. package/dist/core/repomap-context.test.js.map +0 -1
  361. package/dist/core/repomap.d.ts +0 -74
  362. package/dist/core/repomap.d.ts.map +0 -1
  363. package/dist/core/repomap.js +0 -906
  364. package/dist/core/repomap.js.map +0 -1
  365. package/dist/core/repomap.test.d.ts +0 -2
  366. package/dist/core/repomap.test.d.ts.map +0 -1
  367. package/dist/core/repomap.test.js +0 -494
  368. package/dist/core/repomap.test.js.map +0 -1
package/dist/ui/App.js CHANGED
@@ -1,41 +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 { PixelOverlay } from "./components/PixelOverlay.js";
31
- import { SkillsOverlay } from "./components/SkillsOverlay.js";
32
- import { ThemeSelector } from "./components/ThemeSelector.js";
33
- 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";
34
19
  import { useTheme, useSetTheme } from "./theme/theme.js";
35
20
  import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
36
21
  import { getGitBranch } from "../utils/git.js";
37
22
  import { getModel, getContextWindow } from "../core/model-registry.js";
38
- import { BLACK_CIRCLE } from "./constants/figures.js";
39
23
  import { SessionManager } from "../core/session-manager.js";
40
24
  import { appendMessagesToSession as appendSessionMessages, createCompactedSessionCheckpoint, } from "../core/session-compaction.js";
41
25
  import { log } from "../core/logger.js";
@@ -48,65 +32,39 @@ import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
48
32
  import { isFirstTimeSetup, markSetupAudited, getAnnouncedLanguages, markLanguagesAnnounced, } from "../core/setup-history.js";
49
33
  import { loadCustomCommands } from "../core/custom-commands.js";
50
34
  import { buildSystemPrompt } from "../system-prompt.js";
51
- import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
35
+ import { detectLanguages } from "../core/language-detector.js";
52
36
  import { detectVerifyCommands } from "../core/verify-commands.js";
53
- import { buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
54
- import { getRepoMapBudgetForContext } from "../core/repomap-budget.js";
55
- import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMessages, } from "../core/repomap-context.js";
56
37
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
57
38
  import { getMCPServers } from "../core/mcp/index.js";
58
39
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
59
40
  import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
60
- import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, loadGoalRunsSync, reconcileActiveGoalRuns, saveGoalRunsSync, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
61
- import { getNextPendingTask, loadTasksSync, markTaskInProgress, saveTasksSync, } from "../core/tasks-store.js";
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, getGoalSetupPaneTransitionAfterRun, isAgentSpacingItem, MIN_LIVE_AREA_ROWS, nextGoalModeAfterAgentDone, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.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";
71
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);
@@ -279,10 +130,6 @@ export function App(props) {
279
130
  const goalContinuationRecentChoicesRef = useRef(new Map());
280
131
  const startGoalRunRef = useRef(() => { });
281
132
  const [runAllTasks, setRunAllTasks] = useState(props.sessionStore?.runAllTasks ?? false);
282
- const [taskPickerOpen, setTaskPickerOpen] = useState(false);
283
- const [taskPickerTasks, setTaskPickerTasks] = useState(() => loadTasksSync(props.cwd));
284
- const [goalPickerOpen, setGoalPickerOpen] = useState(false);
285
- const [goalPickerGoals, setGoalPickerGoals] = useState(() => loadGoalRunsSync(props.cwd));
286
133
  const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
287
134
  const startTaskRef = useRef(() => { });
288
135
  const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
@@ -290,6 +137,11 @@ export function App(props) {
290
137
  const startPixelFixRef = useRef(() => { });
291
138
  const cwdRef = useRef(props.cwd);
292
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
+ });
293
145
  const [doneStatus, setDoneStatus] = useState(props.sessionStore?.doneStatus ?? null);
294
146
  // Suppress "done" status when a plan overlay is about to open
295
147
  const planOverlayPendingRef = useRef(false);
@@ -302,12 +154,6 @@ export function App(props) {
302
154
  const [thinkingLevel, setThinkingLevel] = useState(props.thinking);
303
155
  const [renderMarkdown, setRenderMarkdown] = useState(true);
304
156
  const messagesRef = useRef(props.sessionStore?.messages ?? props.messages);
305
- const repoMapInjectionEnabledRef = useRef(true);
306
- const repoMapDirtyRef = useRef(true);
307
- const repoMapMarkdownRef = useRef("");
308
- const repoMapSnapshotRef = useRef(undefined);
309
- const repoMapChangedCountRef = useRef(0);
310
- const repoMapCacheRef = useRef(createRepoMapCache());
311
157
  const [planAutoExpand, setPlanAutoExpand] = useState(props.sessionStore?.planAutoExpand ?? false);
312
158
  const [goalAutoExpand, setGoalAutoExpand] = useState(props.sessionStore?.goalAutoExpand ?? false);
313
159
  const goalAutoExpandRef = useRef(props.sessionStore?.goalAutoExpand ?? false);
@@ -315,6 +161,7 @@ export function App(props) {
315
161
  const planStepsRef = useRef(props.sessionStore?.planSteps ?? []);
316
162
  const [planSteps, setPlanSteps] = useState(props.sessionStore?.planSteps ?? []);
317
163
  const goalModeStateRef = useRef(goalMode);
164
+ const planModeStateRef = useRef(planMode);
318
165
  // Stuck-guard for the plan-continuation follow-up nudge. Tracks how many
319
166
  // times we've nudged the agent to continue the same step. Reset whenever a
320
167
  // new [DONE:n] marker advances progress (see onTurnText). Caps at 2 nudges
@@ -384,58 +231,24 @@ export function App(props) {
384
231
  });
385
232
  }, [props.sessionStore]);
386
233
  const sessionStore = props.sessionStore;
387
- const terminalHistoryContextRef = useRef({
388
- theme,
389
- columns,
390
- version: props.version,
391
- model: currentModel,
392
- provider: currentProvider,
393
- cwd: displayedCwd,
394
- });
395
- useEffect(() => {
396
- terminalHistoryContextRef.current = {
234
+ const { pendingHistoryFlushRef, streamedAssistantFlushRef, queueFlush, finalizeSubmittedUserItem, clearPendingHistory, } = useTranscriptHistory({
235
+ terminalHistoryPrinter: props.terminalHistoryPrinter,
236
+ terminalHistoryContext: {
397
237
  theme,
398
238
  columns,
399
239
  version: props.version,
400
240
  model: currentModel,
401
241
  provider: currentProvider,
402
242
  cwd: displayedCwd,
403
- };
404
- }, [theme, columns, props.version, currentModel, currentProvider, displayedCwd]);
405
- const printHistoryItems = useCallback((items, options) => {
406
- if (!props.terminalHistoryPrinter || items.length === 0)
407
- return;
408
- props.terminalHistoryPrinter.print(items, terminalHistoryContextRef.current, {
409
- ...options,
410
- write: writeStdout,
411
- });
412
- }, [props.terminalHistoryPrinter, writeStdout]);
413
- const pendingHistoryFlushRef = useRef([]);
414
- const streamedAssistantFlushRef = useRef({
415
- flushedChars: 0,
416
- text: "",
243
+ },
244
+ writeStdout,
245
+ sessionPathRef,
246
+ sessionManagerRef,
247
+ sessionStore,
248
+ history,
249
+ setHistory,
250
+ setLiveItems,
417
251
  });
418
- const [historyFlushGeneration, setHistoryFlushGeneration] = useState(0);
419
- const queueFlush = useCallback((items) => {
420
- const flushed = trimFlushedItems(items);
421
- if (flushed.length === 0)
422
- return;
423
- pendingHistoryFlushRef.current = [...pendingHistoryFlushRef.current, ...flushed];
424
- if (sessionStore) {
425
- const queuedIds = new Set(items.map((item) => item.id));
426
- sessionStore.liveItems = removeItemsWithIds(uniqueItemsById(sessionStore.liveItems ?? []), queuedIds);
427
- }
428
- setHistoryFlushGeneration((generation) => generation + 1);
429
- }, [sessionStore]);
430
- const finalizeSubmittedUserItem = useCallback((item) => {
431
- streamedAssistantFlushRef.current = { flushedChars: 0, text: "" };
432
- setLiveItems((prev) => {
433
- if (prev.length > 0)
434
- queueFlush(prev);
435
- queueFlush([item]);
436
- return [];
437
- });
438
- }, [queueFlush]);
439
252
  // Mirror runtime state choices (model/provider/thinking) into renderApp's
440
253
  // closure so unmount/remount preserves them.
441
254
  const onRuntimeStateChange = props.onRuntimeStateChange;
@@ -455,28 +268,6 @@ export function App(props) {
455
268
  thinking: thinkingLevel,
456
269
  });
457
270
  }, [thinkingLevel, onRuntimeStateChange]);
458
- useEffect(() => {
459
- printHistoryItems(history);
460
- }, [history, printHistoryItems]);
461
- useEffect(() => {
462
- const flushed = pendingHistoryFlushRef.current;
463
- if (flushed.length === 0)
464
- return;
465
- pendingHistoryFlushRef.current = [];
466
- printHistoryItems(flushed);
467
- const flushedIds = new Set(flushed.map((item) => item.id));
468
- setLiveItems((prev) => prev.filter((item) => !flushedIds.has(item.id)));
469
- setHistory((prev) => {
470
- const existingIds = new Set(prev.map((item) => item.id));
471
- const nextItems = flushed.filter((item) => !existingIds.has(item.id));
472
- if (nextItems.length === 0)
473
- return prev;
474
- const next = compactHistory([...prev, ...nextItems]);
475
- if (sessionStore)
476
- sessionStore.history = next;
477
- return next;
478
- });
479
- }, [historyFlushGeneration, printHistoryItems, sessionStore]);
480
271
  // Mirror session state into renderApp's closure so resetUI() can re-seed
481
272
  // the conversation on remount. Each panel that previously did a bare ANSI
482
273
  // screen clear (overlay open/close, plan accept/reject, /clear)
@@ -522,6 +313,10 @@ export function App(props) {
522
313
  if (sessionStore)
523
314
  sessionStore.goalMode = goalMode;
524
315
  }, [goalMode, sessionStore]);
316
+ useEffect(() => {
317
+ if (sessionStore)
318
+ sessionStore.planMode = planMode;
319
+ }, [planMode, sessionStore]);
525
320
  // pendingAction is consumed via a useEffect AFTER agentLoop is created
526
321
  // — see below where useAgentLoop is set up.
527
322
  const pendingActionConsumedRef = useRef(false);
@@ -593,6 +388,11 @@ export function App(props) {
593
388
  props.goalModeRef.current = goalMode;
594
389
  }
595
390
  }, [goalMode, props.goalModeRef]);
391
+ useEffect(() => {
392
+ planModeStateRef.current = planMode;
393
+ if (props.planModeRef)
394
+ props.planModeRef.current = planMode;
395
+ }, [planMode, props.planModeRef]);
596
396
  const setActiveGoalReferences = useCallback((references) => {
597
397
  if (props.goalReferencesRef)
598
398
  props.goalReferencesRef.current = references;
@@ -601,7 +401,7 @@ export function App(props) {
601
401
  const approvedPlanPath = options?.clearApprovedPlan
602
402
  ? undefined
603
403
  : (options?.approvedPlanPath ?? approvedPlanPathRef.current);
604
- 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);
605
405
  }, [props.skills]);
606
406
  const replaceSystemPrompt = useCallback(async (options) => {
607
407
  const newPrompt = await rebuildSystemPrompt(options);
@@ -642,6 +442,15 @@ export function App(props) {
642
442
  setGoalMode(nextMode);
643
443
  await replaceSystemPrompt({ ...options, goalMode: nextMode });
644
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]);
645
454
  const clearGoalModeIfIdle = useCallback(() => {
646
455
  setTimeout(() => {
647
456
  if (goalModeStateRef.current === "off")
@@ -892,103 +701,46 @@ export function App(props) {
892
701
  contextWindowOptions,
893
702
  props.authStorage,
894
703
  ]);
895
- const getRepoMapSignalCount = useCallback(() => {
896
- return ((props.repoMapChangedFilesRef?.current.size ?? 0) +
897
- (props.repoMapReadFilesRef?.current.size ?? 0));
898
- }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef]);
899
- const getRepoMapBudget = useCallback(() => {
900
- return getRepoMapBudgetForContext({
901
- messages: messagesRef.current,
902
- readFileCount: props.repoMapReadFilesRef?.current.size ?? 0,
903
- });
904
- }, [props.repoMapReadFilesRef]);
905
- const refreshRepoMap = useCallback(async (latestUserPrompt) => {
906
- const rendered = await buildRepoMap({
907
- cwd: cwdRef.current,
908
- maxChars: getRepoMapBudget(),
909
- changedFiles: [...(props.repoMapChangedFilesRef?.current ?? new Set())],
910
- readFiles: [...(props.repoMapReadFilesRef?.current ?? new Set())],
911
- focusTerms: latestUserPrompt ? [latestUserPrompt] : [],
912
- cache: repoMapCacheRef.current,
913
- });
914
- repoMapMarkdownRef.current = rendered.markdown;
915
- repoMapSnapshotRef.current = rendered.snapshot;
916
- repoMapChangedCountRef.current = getRepoMapSignalCount();
917
- repoMapDirtyRef.current = false;
918
- return rendered.markdown;
919
- }, [
920
- getRepoMapBudget,
921
- getRepoMapSignalCount,
922
- props.repoMapChangedFilesRef,
923
- props.repoMapReadFilesRef,
924
- ]);
925
- const stripRepoMapMessages = useCallback((messages) => {
926
- return stripRepoMapContextMessages(messages);
927
- }, []);
928
- const injectRepoMapContext = useCallback(async (messages) => {
929
- if (!repoMapInjectionEnabledRef.current)
930
- return stripRepoMapMessages(messages);
931
- const stripped = stripRepoMapMessages(messages);
932
- const latestUserPrompt = getLatestUserText(stripped);
933
- const signalCount = getRepoMapSignalCount();
934
- if (signalCount !== repoMapChangedCountRef.current)
935
- repoMapDirtyRef.current = true;
936
- if (repoMapDirtyRef.current || !repoMapMarkdownRef.current) {
937
- await refreshRepoMap(latestUserPrompt);
938
- }
939
- if (!repoMapMarkdownRef.current)
940
- return stripped;
941
- return injectRepoMapContextMessages(stripped, repoMapMarkdownRef.current);
942
- }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef, refreshRepoMap, stripRepoMapMessages]);
943
704
  /**
944
705
  * transformContext callback for the agent loop.
945
706
  * Called before each LLM call and on context overflow.
946
- * Compacts persistent chat only, then injects the dynamic repo map transiently.
947
707
  */
948
708
  const transformContext = useCallback(async (messages, options) => {
949
- const stripped = stripRepoMapMessages(messages);
950
709
  const settings = settingsRef.current;
951
710
  const autoCompact = settings?.get("autoCompact") ?? true;
952
711
  const threshold = settings?.get("compactThreshold") ?? 0.8;
953
712
  // Force-compact on context overflow regardless of settings
954
713
  if (options?.force) {
955
- const result = await compactConversation(stripped);
956
- if (result !== stripped) {
714
+ const result = await compactConversation(messages);
715
+ if (result !== messages) {
957
716
  messagesRef.current = result;
958
717
  await persistCompactedSession(result);
959
718
  }
960
719
  lastCompactionTimeRef.current = Date.now();
961
- return injectRepoMapContext(result);
720
+ return result;
962
721
  }
963
722
  if (!autoCompact)
964
- return injectRepoMapContext(stripped);
723
+ return messages;
965
724
  // Time-based cooldown: skip if compaction ran within the last 30 seconds
966
725
  if (Date.now() - lastCompactionTimeRef.current < 30_000) {
967
726
  log("INFO", "compaction", `Skipping compaction — cooldown active`);
968
- return injectRepoMapContext(stripped);
727
+ return messages;
969
728
  }
970
729
  const contextWindow = getContextWindow(currentModel, contextWindowOptions);
971
730
  const reserveTokens = getCompactionReserveTokens(props.maxTokens);
972
731
  const tokensFresh = lastActualTokensTimestampRef.current > lastCompactionTimeRef.current;
973
732
  const actualTokens = lastActualTokensRef.current > 0 && tokensFresh ? lastActualTokensRef.current : undefined;
974
- if (shouldCompact(stripped, contextWindow, threshold, actualTokens, reserveTokens)) {
975
- const result = await compactConversation(stripped);
976
- if (result !== stripped) {
733
+ if (shouldCompact(messages, contextWindow, threshold, actualTokens, reserveTokens)) {
734
+ const result = await compactConversation(messages);
735
+ if (result !== messages) {
977
736
  messagesRef.current = result;
978
737
  await persistCompactedSession(result);
979
738
  }
980
739
  lastCompactionTimeRef.current = Date.now();
981
- return injectRepoMapContext(result);
740
+ return result;
982
741
  }
983
- return injectRepoMapContext(stripped);
984
- }, [
985
- currentModel,
986
- compactConversation,
987
- contextWindowOptions,
988
- injectRepoMapContext,
989
- persistCompactedSession,
990
- stripRepoMapMessages,
991
- ]);
742
+ return messages;
743
+ }, [currentModel, compactConversation, contextWindowOptions, persistCompactedSession]);
992
744
  // ── Background task bar state (external store) ──────────
993
745
  const { bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, } = useTaskBarStore();
994
746
  useTaskBarPolling(props.processManager);
@@ -1029,7 +781,6 @@ export function App(props) {
1029
781
  transformContext,
1030
782
  }, {
1031
783
  onComplete: useCallback(() => {
1032
- messagesRef.current = stripRepoMapMessages(messagesRef.current);
1033
784
  persistNewMessages();
1034
785
  // Auto-clear plan progress and approved plan when all steps are completed
1035
786
  const steps = planStepsRef.current;
@@ -1082,7 +833,6 @@ export function App(props) {
1082
833
  }
1083
834
  }, [
1084
835
  persistNewMessages,
1085
- stripRepoMapMessages,
1086
836
  props.cwd,
1087
837
  props.skills,
1088
838
  currentProvider,
@@ -1847,227 +1597,101 @@ export function App(props) {
1847
1597
  // has not grown.
1848
1598
  await applyLanguageDetectionRef.current("input");
1849
1599
  }
1850
- // Handle /model directly — open inline selector
1851
- if (trimmed === "/model" || trimmed === "/m" || trimmed === "/models") {
1852
- setOverlay("model");
1853
- return;
1854
- }
1855
- // Handle /compact compact conversation
1856
- if (trimmed === "/compact" || trimmed === "/c") {
1857
- const ac = new AbortController();
1858
- compactionAbortRef.current = ac;
1859
- const compacted = await compactConversation(messagesRef.current, ac.signal);
1860
- if (!ac.signal.aborted && compacted !== messagesRef.current) {
1861
- messagesRef.current = compacted;
1862
- await persistCompactedSession(compacted);
1863
- }
1864
- if (compactionAbortRef.current === ac)
1865
- compactionAbortRef.current = null;
1866
- return;
1867
- }
1868
- // Handle /quit — exit the agent
1869
- if (trimmed === "/quit" || trimmed === "/q" || trimmed === "/exit") {
1870
- process.exit(0);
1871
- }
1872
- // Handle /clear — tear down the entire Ink instance and rebuild fresh.
1873
- // Avoid direct ANSI terminal clears here; they can erase scrollback.
1874
- // Runtime state (model, provider, thinking) survives via renderApp's
1875
- // closure-held `runtimeState`, mirrored from React state via the
1876
- // useEffects above.
1877
- if (trimmed === "/clear") {
1878
- 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([]);
1879
1632
  void (async () => {
1880
1633
  const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1881
- props.resetUI?.({
1882
- wipeSession: true,
1883
- messages: [{ role: "system", content: newPrompt }],
1884
- });
1634
+ messagesRef.current = [{ role: "system", content: newPrompt }];
1635
+ persistedIndexRef.current = messagesRef.current.length;
1885
1636
  })();
1886
- return;
1887
- }
1888
- // Fallback path (resetUI not wired — e.g. tests). Best-effort: clear
1889
- // React state in place without touching terminal scrollback.
1890
- pendingHistoryFlushRef.current = [];
1891
- props.terminalHistoryPrinter?.clear();
1892
- setHistory([{ kind: "banner", id: "banner" }]);
1893
- setLiveItems([]);
1894
- setDoneStatus(null);
1895
- approvedPlanPathRef.current = undefined;
1896
- planStepsRef.current = [];
1897
- setPlanSteps([]);
1898
- void (async () => {
1899
- const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1900
- messagesRef.current = [{ role: "system", content: newPrompt }];
1901
- persistedIndexRef.current = messagesRef.current.length;
1902
- })();
1903
- agentLoop.reset();
1904
- setSessionTitle(undefined);
1905
- sessionTitleGeneratedRef.current = false;
1906
- setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
1907
- return;
1908
- }
1909
- // Handle /theme open theme selector overlay
1910
- if (trimmed === "/theme" || trimmed === "/t") {
1911
- setOverlay("theme");
1912
- return;
1913
- }
1914
- // Handle /markdown — Gemini-style rendered/raw markdown toggle
1915
- if (trimmed === "/markdown" || trimmed === "/md") {
1916
- setRenderMarkdown((prev) => {
1917
- const next = !prev;
1918
- setLiveItems([
1919
- {
1920
- kind: "info",
1921
- text: next ? "Rendered markdown mode." : "Raw markdown mode.",
1922
- id: getId(),
1923
- },
1924
- ]);
1925
- return next;
1926
- });
1927
- return;
1928
- }
1929
- // Handle /clearplan — dismiss the approved plan
1930
- if (trimmed === "/clearplan") {
1931
- approvedPlanPathRef.current = undefined;
1932
- planStepsRef.current = [];
1933
- setPlanSteps([]);
1934
- // Rebuild system prompt without the plan
1935
- void replaceSystemPrompt({ clearApprovedPlan: true });
1936
- setLiveItems([{ kind: "plan_event", event: "dismissed", id: getId() }]);
1937
- return;
1938
- }
1939
- // Handle /map — show, refresh, or toggle dynamic repo map injection
1940
- if (trimmed === "/map" ||
1941
- trimmed === "/map refresh" ||
1942
- trimmed === "/map on" ||
1943
- trimmed === "/map off") {
1944
- const action = trimmed.slice("/map".length).trim();
1945
- if (action === "on") {
1946
- repoMapInjectionEnabledRef.current = true;
1947
- repoMapDirtyRef.current = true;
1948
- setLiveItems((prev) => [
1949
- ...prev,
1950
- { kind: "info", text: "Dynamic repo map injection is on.", id: getId() },
1951
- ]);
1952
- return;
1953
- }
1954
- if (action === "off") {
1955
- repoMapInjectionEnabledRef.current = false;
1956
- messagesRef.current = stripRepoMapMessages(messagesRef.current);
1957
- setLiveItems((prev) => [
1958
- ...prev,
1959
- {
1960
- kind: "info",
1961
- text: "Dynamic repo map injection is off for this session.",
1962
- id: getId(),
1963
- },
1964
- ]);
1965
- return;
1966
- }
1967
- if (action === "refresh")
1968
- repoMapDirtyRef.current = true;
1969
- const markdown = await refreshRepoMap(getLatestUserText(messagesRef.current));
1970
- setLiveItems((prev) => [
1971
- ...prev,
1972
- {
1973
- kind: "info",
1974
- text: formatRepoMapCommandOutput(repoMapInjectionEnabledRef.current, markdown, action === "refresh"),
1975
- id: getId(),
1976
- },
1977
- ]);
1978
- return;
1979
- }
1980
- // Handle /goals — open the input-area goal picker.
1981
- if (trimmed === "/goals") {
1982
- setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
1983
- setTaskPickerOpen(false);
1984
- setGoalPickerOpen(true);
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
+ })) {
1985
1668
  return;
1986
1669
  }
1987
- // Handle prompt-template commands (built-in + custom from .gg/commands/)
1988
- const promptCommandRoute = routePromptCommandInput(trimmed, PROMPT_COMMANDS, customCommands);
1989
- if (promptCommandRoute) {
1990
- const { cmdName, cmdArgs, fullPrompt } = promptCommandRoute;
1991
- log("INFO", "command", `Prompt command: /${cmdName}${cmdArgs ? ` (args: ${cmdArgs})` : ""}`);
1992
- const hasImages = inputImages.length > 0;
1993
- const isGoalSetupCommand = isGoalPromptCommandName(cmdName);
1994
- let promptForAgent = fullPrompt;
1995
- if (isGoalSetupCommand) {
1996
- const referenceContext = await buildGoalReferenceContext({
1997
- cwd: props.cwd,
1998
- originalGoalPrompt: fullPrompt,
1999
- attachments: inputImages,
2000
- });
2001
- setActiveGoalReferences(referenceContext.references);
2002
- promptForAgent = referenceContext.promptSection
2003
- ? `${fullPrompt}\n\n${referenceContext.promptSection}`
2004
- : fullPrompt;
2005
- }
2006
- const modelInfo = getModel(currentModel);
2007
- const modelSupportsImages = modelInfo?.supportsImages ?? true;
2008
- const userContent = buildUserContentWithAttachments(promptForAgent, inputImages, modelSupportsImages);
2009
- // Show the typed command as the user message
2010
- const userItem = {
2011
- kind: "user",
2012
- text: trimmed,
2013
- imageCount: hasImages ? inputImages.length : undefined,
2014
- id: getId(),
2015
- };
2016
- setLastUserMessage(trimmed);
2017
- setDoneStatus(null);
2018
- finalizeSubmittedUserItem(userItem);
2019
- // Send the full prompt to the agent, with user args appended if provided
2020
- try {
2021
- if (isGoalSetupCommand) {
2022
- goalSetupPanePendingRef.current = true;
2023
- await runGoalPromptSetupSequence({
2024
- userContent,
2025
- fullPrompt: promptForAgent,
2026
- messagesRef,
2027
- setGoalModeAndPrompt,
2028
- runAgent: (content) => agentLoop.run(content),
2029
- onStage: appendGoalAgentTransition,
2030
- });
2031
- }
2032
- else {
2033
- await agentLoop.run(userContent);
2034
- }
2035
- }
2036
- catch (err) {
2037
- const msg = err instanceof Error ? err.message : String(err);
2038
- log("ERROR", "error", msg);
2039
- const isAbort = msg.includes("aborted") || msg.includes("abort");
2040
- if (isGoalSetupCommand)
2041
- goalSetupPanePendingRef.current = false;
2042
- setLiveItems((prev) => [
2043
- ...prev,
2044
- isAbort
2045
- ? { kind: "stopped", text: "Request was stopped.", id: getId() }
2046
- : toErrorItem(err, getId()),
2047
- ]);
2048
- }
2049
- finally {
2050
- if (isGoalSetupCommand) {
2051
- setActiveGoalReferences(undefined);
2052
- const paneTransition = getGoalSetupPaneTransitionAfterRun({
2053
- isGoalSetupCommand,
2054
- setupPanePending: goalSetupPanePendingRef.current,
2055
- });
2056
- goalSetupPanePendingRef.current = false;
2057
- if (goalModeStateRef.current !== "off") {
2058
- await setGoalModeAndPrompt("off");
2059
- }
2060
- if (paneTransition) {
2061
- goalAutoExpandRef.current = false;
2062
- setGoalAutoExpand(false);
2063
- setPlanAutoExpand(false);
2064
- setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
2065
- setGoalPickerOpen(true);
2066
- }
2067
- }
2068
- }
2069
- // Reload custom commands in case a setup command created new ones
2070
- 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
+ })) {
2071
1695
  return;
2072
1696
  }
2073
1697
  // Check slash commands
@@ -2149,12 +1773,10 @@ export function App(props) {
2149
1773
  props.resetUI,
2150
1774
  props.sessionStore,
2151
1775
  rebuildSystemPrompt,
2152
- refreshRepoMap,
2153
1776
  reloadCustomCommands,
2154
1777
  replaceSystemPrompt,
2155
1778
  setActiveGoalReferences,
2156
1779
  setGoalModeAndPrompt,
2157
- stripRepoMapMessages,
2158
1780
  ]);
2159
1781
  const handleDoubleExit = useDoublePress(setExitPending, () => process.exit(0));
2160
1782
  const handleAbort = useCallback(() => {
@@ -2343,115 +1965,21 @@ export function App(props) {
2343
1965
  },
2344
1966
  ];
2345
1967
  }, [customCommands]);
2346
- const normalizeStatusText = (text) => text.replace(/\\n/g, "\n").replace(/^\n+|\n+$/g, "");
2347
- 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));
2348
- const renderItem = (item, index, items) => {
2349
- const previousLiveItem = index > 0 ? items[index - 1] : undefined;
2350
- const shouldTopSpacePrintedBoundary = shouldTopSpaceAfterPrintedAgentBoundary({
2351
- currentKind: item.kind,
2352
- previousLiveItem,
2353
- lastPendingHistoryItem: pendingHistoryFlushRef.current.at(-1),
2354
- lastHistoryItem: history.at(-1),
2355
- });
2356
- const assistantMarginTop = item.kind === "assistant" &&
2357
- (shouldTopSpacePrintedBoundary ||
2358
- shouldTopSpaceAssistantAfterToolBoundary({
2359
- text: item.text,
2360
- previousLiveItem,
2361
- lastPendingHistoryItem: pendingHistoryFlushRef.current.at(-1),
2362
- lastHistoryItem: history.at(-1),
2363
- }))
2364
- ? 1
2365
- : 0;
2366
- const withPrintedBoundarySpacing = (node) => shouldTopSpacePrintedBoundary ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: node }, `${item.id}-printed-boundary`)) : (node);
2367
- switch (item.kind) {
2368
- case "tombstone":
2369
- return null;
2370
- case "banner":
2371
- return (_jsx(Banner, { version: props.version, model: currentModel, provider: currentProvider, cwd: displayedCwd }, item.id));
2372
- case "user":
2373
- return (_jsx(UserMessage, { text: item.text, imageCount: item.imageCount, pasteInfo: item.pasteInfo }, item.id));
2374
- case "goal":
2375
- 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));
2376
- case "goal_progress": {
2377
- const color = goalProgressColor(item, theme);
2378
- const loaderStatus = goalProgressLoaderStatus(item);
2379
- const hasBody = !!item.detail ||
2380
- (item.summaryRows !== undefined && item.summaryRows.length > 0) ||
2381
- (item.summarySections !== undefined && item.summarySections.length > 0);
2382
- const headerContentWidth = Math.max(10, columns - 3);
2383
- 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));
2384
- }
2385
- case "style_pack": {
2386
- const names = item.added.map((id) => LANGUAGE_DISPLAY_NAMES[id]);
2387
- const headerLabel = item.added.length > 1 ? "STYLE PACKS ACTIVE" : "STYLE PACK ACTIVE";
2388
- 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));
2389
- }
2390
- case "setup_hint":
2391
- 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));
2392
- case "assistant":
2393
- return (_jsx(AssistantMessage, { text: item.text, thinking: item.thinking, thinkingMs: item.thinkingMs, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, marginTop: assistantMarginTop }, item.id));
2394
- case "tool_start":
2395
- return withPrintedBoundarySpacing(_jsx(ToolExecution, { status: "running", name: item.name, args: item.args, progressOutput: item.progressOutput, animateUntil: item.animateUntil }, item.id));
2396
- case "tool_done":
2397
- return withPrintedBoundarySpacing(_jsx(ToolExecution, { status: "done", name: item.name, args: item.args, result: item.result, isError: item.isError, details: item.details }, item.id));
2398
- case "tool_group":
2399
- return withPrintedBoundarySpacing(_jsx(ToolGroupExecution, { tools: item.tools }, item.id));
2400
- case "server_tool_start":
2401
- return withPrintedBoundarySpacing(_jsx(ServerToolExecution, { status: "running", name: item.name, input: item.input, startedAt: item.startedAt, animateUntil: item.animateUntil }, item.id));
2402
- case "server_tool_done":
2403
- return withPrintedBoundarySpacing(_jsx(ServerToolExecution, { status: "done", name: item.name, input: item.input, durationMs: item.durationMs, resultType: item.resultType }, item.id));
2404
- case "error": {
2405
- const showMessage = item.message && item.message !== item.headline;
2406
- 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));
2407
- }
2408
- case "info":
2409
- return renderStatusMessage(item.id, "○ ", item.text, theme.commandColor, { muted: true });
2410
- case "update_notice":
2411
- 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));
2412
- case "plan_transition":
2413
- return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
2414
- case "goal_agent_transition":
2415
- return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
2416
- case "task":
2417
- return withPrintedBoundarySpacing(renderStatusMessage(item.id, "▸ ", _jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textDim, children: "Task: " }), _jsx(Text, { color: theme.commandColor, bold: true, children: item.title })] }), 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;
@@ -2536,6 +2064,10 @@ export function App(props) {
2536
2064
  return;
2537
2065
  }
2538
2066
  const decision = decideGoalNextAction(latestRun);
2067
+ if (!shouldKeepGoalRunTrackedAfterDecision(decision)) {
2068
+ runningGoalIdsRef.current.delete(runId);
2069
+ clearGoalModeIfIdle();
2070
+ }
2539
2071
  if (decision.kind === "wait")
2540
2072
  return;
2541
2073
  const choiceKey = getGoalContinuationChoiceKey({ runId: latestRun.id, decision });
@@ -2640,23 +2172,25 @@ export function App(props) {
2640
2172
  workerId: completion.worker.id,
2641
2173
  status: completion.status,
2642
2174
  });
2175
+ const taskProgress = goalTaskProgress(run, run.tasks.find((task) => task.id === completion.worker.goalTaskId));
2643
2176
  upsertGoalStatusEntry({
2644
2177
  runId: run.id,
2645
- label: taskTitle,
2178
+ label: run.title,
2646
2179
  phase: completion.status === "done" ? "reviewing" : "failed",
2647
2180
  startedAt: Date.now(),
2648
2181
  detail: completion.status === "done" ? "reviewing result" : "task failed",
2649
2182
  workerId: completion.worker.id,
2650
2183
  goalNumber: goalNumberForRun(run.id),
2184
+ ...taskProgress,
2651
2185
  });
2652
2186
  runGoalSyntheticEvent(eventText);
2653
2187
  void (async () => {
2654
- if (listGoalWorkers(completion.worker.cwd).some((worker) => worker.status === "running"))
2188
+ if (listGoalWorkers(completion.worker.projectPath).some((worker) => worker.status === "running"))
2655
2189
  return;
2656
2190
  if (activeVerifierRunIdsRef.current.size > 0)
2657
2191
  return;
2658
- const runs = await loadGoalRuns(completion.worker.cwd);
2659
- 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));
2660
2194
  if (queued)
2661
2195
  setTimeout(() => continueGoalRun(queued.id), 750);
2662
2196
  })().catch((err) => log("ERROR", "goal", err instanceof Error ? err.message : String(err)));
@@ -2670,7 +2204,7 @@ export function App(props) {
2670
2204
  useEffect(() => {
2671
2205
  return subscribeGoalWorkerCompletions((completion) => {
2672
2206
  void (async () => {
2673
- 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;
2674
2208
  if (!latestRun) {
2675
2209
  log("WARN", "goal", `Worker completion for unknown Goal ${completion.worker.goalRunId}`);
2676
2210
  return;
@@ -2724,6 +2258,9 @@ export function App(props) {
2724
2258
  }
2725
2259
  const decision = decideGoalNextAction(checkedRun);
2726
2260
  await appendGoalDecision(props.cwd, checkedRun.id, decision);
2261
+ if (!shouldKeepGoalRunTrackedAfterDecision(decision)) {
2262
+ runningGoalIdsRef.current.delete(checkedRun.id);
2263
+ }
2727
2264
  if (decision.kind === "terminal") {
2728
2265
  const terminalProgress = formatGoalTerminalProgress(checkedRun);
2729
2266
  if (terminalProgress) {
@@ -2741,8 +2278,10 @@ export function App(props) {
2741
2278
  phase: "worker_started",
2742
2279
  title: decision.workerId
2743
2280
  ? `Goal working: ${checkedRun.title}`
2744
- : `Goal active: ${checkedRun.title}`,
2745
- 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.`,
2746
2285
  workerId: decision.workerId,
2747
2286
  });
2748
2287
  upsertGoalStatusEntry({
@@ -2754,6 +2293,14 @@ export function App(props) {
2754
2293
  workerId: decision.workerId,
2755
2294
  goalNumber: goalNumberForRun(checkedRun.id),
2756
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
+ }
2757
2304
  return;
2758
2305
  }
2759
2306
  if (decision.kind === "complete") {
@@ -2775,14 +2322,38 @@ export function App(props) {
2775
2322
  return;
2776
2323
  }
2777
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
+ }
2778
2342
  await updateGoalTask(props.cwd, checkedRun.id, `auto-${Date.now()}`, {
2779
2343
  title: decision.title,
2780
2344
  prompt: decision.prompt,
2781
2345
  status: "pending",
2782
2346
  });
2783
2347
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ?? checkedRun;
2784
- await upsertGoalRun(props.cwd, { ...latestRun, status: "ready" });
2785
- 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);
2786
2357
  return;
2787
2358
  }
2788
2359
  if (decision.kind === "blocked") {
@@ -2844,6 +2415,7 @@ export function App(props) {
2844
2415
  goalTaskId: decision.task.id,
2845
2416
  taskTitle: decision.task.title,
2846
2417
  prompt: buildGoalTaskPromptWithReferences(checkedRun, decision.task.prompt),
2418
+ isolateWorktree: shouldRunGoalTaskInMainCheckout(decision.task.title) ? false : undefined,
2847
2419
  });
2848
2420
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ??
2849
2421
  runWithAttempt;
@@ -2866,17 +2438,39 @@ export function App(props) {
2866
2438
  });
2867
2439
  upsertGoalStatusEntry({
2868
2440
  runId: checkedRun.id,
2869
- label: decision.task.title,
2441
+ label: checkedRun.title,
2870
2442
  phase: "worker",
2871
2443
  startedAt: Date.now(),
2872
2444
  detail: "background worker running",
2873
2445
  workerId: worker.id,
2874
2446
  goalNumber: goalNumberForRun(checkedRun.id),
2447
+ ...goalTaskProgress(checkedRun, decision.task),
2875
2448
  });
2876
- })().catch((err) => {
2449
+ })().catch(async (err) => {
2877
2450
  clearGoalStatusEntry(run.id);
2878
2451
  clearGoalModeIfIdle();
2879
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
+ }
2880
2474
  setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
2881
2475
  });
2882
2476
  }, [
@@ -2884,6 +2478,7 @@ export function App(props) {
2884
2478
  currentProvider,
2885
2479
  currentModel,
2886
2480
  thinkingLevel,
2481
+ agentLoop,
2887
2482
  appendGoalProgress,
2888
2483
  clearGoalModeIfIdle,
2889
2484
  clearGoalStatusEntry,
@@ -2916,6 +2511,30 @@ export function App(props) {
2916
2511
  clearGoalModeIfIdle();
2917
2512
  return;
2918
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
+ }
2919
2538
  activeVerifierRunIdsRef.current.add(run.id);
2920
2539
  await upsertGoalRun(props.cwd, {
2921
2540
  ...run,
@@ -2940,7 +2559,7 @@ export function App(props) {
2940
2559
  goalNumber: goalNumberForRun(run.id),
2941
2560
  });
2942
2561
  void runGoalVerifierCommand({
2943
- cwd: props.cwd,
2562
+ cwd: run.verifier.cwd ?? props.cwd,
2944
2563
  runId: run.id,
2945
2564
  command: run.verifier.command,
2946
2565
  timeoutMs: verifierTimeoutMs,
@@ -2958,6 +2577,7 @@ export function App(props) {
2958
2577
  ...latestRun.verifier,
2959
2578
  description: latestRun.verifier?.description ?? "Goal verifier",
2960
2579
  command: run.verifier?.command,
2580
+ ...(run.verifier?.cwd ? { cwd: run.verifier.cwd } : {}),
2961
2581
  lastResult: verification,
2962
2582
  },
2963
2583
  ...(status === "pass"
@@ -2987,7 +2607,7 @@ export function App(props) {
2987
2607
  await appendGoalDecision(props.cwd, run.id, {
2988
2608
  kind: `verifier_${status}`,
2989
2609
  reason: `${failureClass}: verifier exited with code ${verification.exitCode ?? 1}.`,
2990
- content: `outputPath=${outputPath ?? ""}; durationMs=${durationMs}`,
2610
+ content: `outputPath=${outputPath ?? ""}; cwd=${run.verifier?.cwd ?? props.cwd}; durationMs=${durationMs}`,
2991
2611
  });
2992
2612
  appendGoalProgress({
2993
2613
  kind: "goal_progress",
@@ -3034,11 +2654,7 @@ export function App(props) {
3034
2654
  if (run.activeWorkerId)
3035
2655
  await stopGoalWorker(run.activeWorkerId);
3036
2656
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3037
- await upsertGoalRun(props.cwd, {
3038
- ...latestRun,
3039
- status: "paused",
3040
- activeWorkerId: undefined,
3041
- });
2657
+ await upsertGoalRun(props.cwd, buildGoalUserPauseRun(latestRun));
3042
2658
  appendGoalProgress({
3043
2659
  kind: "goal_progress",
3044
2660
  phase: "terminal",
@@ -3088,8 +2704,7 @@ export function App(props) {
3088
2704
  })();
3089
2705
  return;
3090
2706
  }
3091
- pendingHistoryFlushRef.current = [];
3092
- props.terminalHistoryPrinter?.clear();
2707
+ clearPendingHistory();
3093
2708
  setHistory([{ kind: "banner", id: "banner" }]);
3094
2709
  setLiveItems([]);
3095
2710
  messagesRef.current = messagesRef.current.slice(0, 1);
@@ -3142,13 +2757,6 @@ export function App(props) {
3142
2757
  log("WARN", "pixel", `chdir failed: ${err.message}`);
3143
2758
  }
3144
2759
  cwdRef.current = prep.projectPath;
3145
- repoMapDirtyRef.current = true;
3146
- repoMapMarkdownRef.current = "";
3147
- repoMapSnapshotRef.current = undefined;
3148
- repoMapChangedCountRef.current = 0;
3149
- repoMapCacheRef.current = createRepoMapCache();
3150
- props.repoMapChangedFilesRef?.current.clear();
3151
- props.repoMapReadFilesRef?.current.clear();
3152
2760
  setDisplayedCwd(prep.projectPath);
3153
2761
  let toolsForPixelFix = currentToolsRef.current;
3154
2762
  if (props.rebuildToolsForCwd) {
@@ -3172,8 +2780,7 @@ export function App(props) {
3172
2780
  });
3173
2781
  // Now that the cwd swap is committed, reset chat. Do not clear the
3174
2782
  // terminal here; terminal clear sequences can erase saved scrollback.
3175
- pendingHistoryFlushRef.current = [];
3176
- props.terminalHistoryPrinter?.clear();
2783
+ clearPendingHistory();
3177
2784
  setHistory([{ kind: "banner", id: "banner" }]);
3178
2785
  setLiveItems([]);
3179
2786
  messagesRef.current = messagesRef.current.slice(0, 1);
@@ -3219,60 +2826,26 @@ export function App(props) {
3219
2826
  }, [runAllPixel, props.sessionStore]);
3220
2827
  const isSkillsView = overlay === "skills";
3221
2828
  const isPlanView = overlay === "plan";
3222
- const footerStatusLayout = getFooterStatusLayoutDecision({
2829
+ const { footerStatusLayout, activityVisible, stallStatusVisible, statusSlotVisible, mainControlsRef, measuredLiveAreaRows, } = useChatLayoutMeasurements({
2830
+ rows,
3223
2831
  columns,
3224
2832
  backgroundTaskCount: bgTasks.length,
3225
2833
  updatePending,
3226
- });
3227
- const activityVisible = agentLoop.isRunning && agentLoop.activityPhase !== "idle";
3228
- const stallStatusVisible = !activityVisible && !!agentLoop.stallError;
3229
- const doneStatusVisible = !activityVisible && !stallStatusVisible && !!doneStatus && !agentLoop.isRunning;
3230
- const statusSlotVisible = activityVisible || stallStatusVisible || doneStatusVisible;
3231
- const [controlsHeight, setControlsHeight] = useState(0);
3232
- const controlsObserverRef = useRef(null);
3233
- const mainControlsRef = useCallback((node) => {
3234
- if (controlsObserverRef.current) {
3235
- controlsObserverRef.current.disconnect();
3236
- controlsObserverRef.current = null;
3237
- }
3238
- if (!node || typeof ResizeObserver === "undefined")
3239
- return;
3240
- const observer = new ResizeObserver((entries) => {
3241
- const entry = entries[0];
3242
- if (!entry)
3243
- return;
3244
- const roundedHeight = Math.round(entry.contentRect.height);
3245
- setControlsHeight((prev) => (roundedHeight !== prev ? roundedHeight : prev));
3246
- });
3247
- observer.observe(node);
3248
- controlsObserverRef.current = observer;
3249
- }, []);
3250
- useEffect(() => () => controlsObserverRef.current?.disconnect(), []);
3251
- const footerFitsOnOneLine = doesFooterFitOnOneLine({
3252
- columns,
3253
- model: currentModel,
3254
- tokensIn: agentLoop.contextUsed,
2834
+ agentRunning: agentLoop.isRunning,
2835
+ activityPhase: agentLoop.activityPhase,
2836
+ stallError: agentLoop.stallError,
2837
+ doneStatus,
2838
+ currentModel,
2839
+ contextUsed: agentLoop.contextUsed,
3255
2840
  contextWindowOptions,
3256
- cwd: displayedCwd,
2841
+ displayedCwd,
3257
2842
  gitBranch,
3258
2843
  thinkingLevel,
3259
2844
  goalMode,
3260
- });
3261
- const chatControlsLayout = getChatControlsLayoutDecision({
3262
- rows,
3263
- columns,
3264
- agentRunning: agentLoop.isRunning,
3265
- activityVisible,
3266
- doneStatusVisible,
3267
- stallStatusVisible,
3268
2845
  exitPending,
3269
- footerStatusLayout,
3270
2846
  taskBarExpanded,
3271
2847
  goalStatusEntryCount: goalStatusEntries.length,
3272
- footerFitsOnOneLine,
3273
2848
  });
3274
- const stableControlsRows = controlsHeight > 0 ? controlsHeight : chatControlsLayout.controlsRows;
3275
- const measuredLiveAreaRows = Math.max(MIN_LIVE_AREA_ROWS, rows - stableControlsRows - 1);
3276
2849
  const isPixelView = overlay === "pixel";
3277
2850
  const hasLiveAssistantItem = liveItems.some((item) => item.kind === "assistant");
3278
2851
  const rawVisibleStreamingText = goalModeStateRef.current === "planner" || hasLiveAssistantItem ? "" : agentLoop.streamingText;
@@ -3290,7 +2863,7 @@ export function App(props) {
3290
2863
  queueFlush([
3291
2864
  {
3292
2865
  kind: "assistant",
3293
- text: split.flushedText,
2866
+ text: stripDoneMarkers(split.flushedText),
3294
2867
  continuation: streamedAssistantFlushRef.current.flushedChars > 0,
3295
2868
  id: getId(),
3296
2869
  },
@@ -3306,13 +2879,20 @@ export function App(props) {
3306
2879
  text: rawVisibleStreamingText,
3307
2880
  };
3308
2881
  }, [rawVisibleStreamingText, queueFlush]);
3309
- const visibleStreamingText = rawVisibleStreamingText.slice(streamedAssistantFlushRef.current.flushedChars);
3310
- const shouldReserveStreamingSpacing = agentLoop.isRunning &&
3311
- !hasLiveAssistantItem &&
3312
- (visibleStreamingText.trim().length > 0 || liveItems.some(isAgentSpacingItem));
2882
+ const visibleStreamingText = stripDoneMarkers(rawVisibleStreamingText.slice(streamedAssistantFlushRef.current.flushedChars));
3313
2883
  const lastLiveItem = liveItems.at(-1);
3314
2884
  const lastPendingHistoryItem = pendingHistoryFlushRef.current.at(-1);
3315
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);
3316
2896
  const shouldTopSpaceStreamingText = shouldTopSpaceStreamingAssistant({
3317
2897
  visibleStreamingText,
3318
2898
  lastLiveItem,
@@ -3328,215 +2908,282 @@ export function App(props) {
3328
2908
  lastPendingHistoryItem,
3329
2909
  lastHistoryItem,
3330
2910
  });
3331
- return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
3332
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3333
- props.sessionStore.overlay = null;
3334
- props.resetUI();
3335
- }
3336
- else {
3337
- if (props.sessionStore) {
3338
- props.sessionStore.overlay = null;
3339
- if (agentLoop.isRunning)
3340
- props.sessionStore.pendingResetUI = true;
3341
- }
3342
- setOverlay(null);
3343
- }
3344
- }, onFixOne: (entry) => {
3345
- setOverlay(null);
3346
- startPixelFix(entry.errorId);
3347
- }, onFixAll: (entries) => {
3348
- const first = entries.find((e) => e.status === "open") ?? entries[0];
3349
- if (!first)
3350
- return;
3351
- setOverlay(null);
3352
- setRunAllPixel(true);
3353
- startPixelFix(first.errorId);
3354
- } })) : isSkillsView ? (_jsx(SkillsOverlay, { cwd: props.cwd, onClose: () => {
3355
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3356
- props.sessionStore.overlay = null;
3357
- props.resetUI();
3358
- }
3359
- else {
3360
- if (props.sessionStore) {
3361
- props.sessionStore.overlay = null;
3362
- if (agentLoop.isRunning)
3363
- props.sessionStore.pendingResetUI = true;
3364
- }
3365
- setOverlay(null);
3366
- }
3367
- } })) : isPlanView ? (_jsx(PlanOverlay, { cwd: props.cwd, autoExpandNewest: planAutoExpand, onClose: () => {
3368
- planOverlayPendingRef.current = false;
3369
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3370
- props.sessionStore.overlay = null;
3371
- props.sessionStore.planAutoExpand = false;
3372
- props.resetUI();
3373
- }
3374
- else {
3375
- if (props.sessionStore) {
3376
- props.sessionStore.overlay = null;
3377
- props.sessionStore.planAutoExpand = false;
3378
- if (agentLoop.isRunning)
3379
- props.sessionStore.pendingResetUI = true;
3380
- }
3381
- setPlanAutoExpand(false);
3382
- setOverlay(null);
3383
- }
3384
- }, onApprove: (planPath) => {
3385
- log("INFO", "plan", "Plan approved — transitioning to implementation", {
3386
- 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,
3387
3001
  });
3388
- planOverlayPendingRef.current = false;
3389
- void (async () => {
3390
- try {
3391
- // Read plan steps for progress tracking — handed to the new
3392
- // mount via sessionStore.planSteps below.
3393
- const planContent = await import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8"));
3394
- const steps = extractPlanSteps(planContent);
3395
- // Build the new system prompt with the approved plan baked in.
3396
- const newPrompt = await rebuildSystemPrompt({
3397
- approvedPlanPath: planPath,
3398
- });
3399
- // Create a new session file BEFORE remount so the new tree
3400
- // picks it up via sessionStore.sessionPath.
3401
- let newSessionPath;
3402
- const sm = sessionManagerRef.current;
3403
- if (sm) {
3404
- const s = await sm.create(props.cwd, currentProvider, currentModel);
3405
- newSessionPath = s.path;
3406
- }
3407
- if (props.resetUI && props.sessionStore) {
3408
- // Clear the overlay so the new mount lands on the chat,
3409
- // not back inside the plan pane.
3410
- props.sessionStore.overlay = null;
3411
- props.sessionStore.planAutoExpand = false;
3412
- props.resetUI({
3413
- wipeSession: true,
3414
- messages: [{ role: "system", content: newPrompt }],
3415
- approvedPlanPath: planPath,
3416
- planSteps: steps,
3417
- sessionPath: newSessionPath,
3418
- pendingAction: {
3419
- prompt: "The plan has been approved. Implement it now, following each step in order.",
3420
- planEvent: { event: "approved" },
3421
- },
3422
- });
3423
- return;
3424
- }
3425
- // Fallback path (resetUI not wired — tests). Mutate in place.
3426
- approvedPlanPathRef.current = planPath;
3427
- planStepsRef.current = steps;
3428
- setPlanSteps(steps);
3429
- pendingHistoryFlushRef.current = [];
3430
- props.terminalHistoryPrinter?.clear();
3431
- setHistory([{ kind: "banner", id: "banner" }]);
3432
- setLiveItems([]);
3433
- setPlanAutoExpand(false);
3434
- setOverlay(null);
3435
- messagesRef.current = [{ role: "system", content: newPrompt }];
3436
- agentLoop.reset();
3437
- persistedIndexRef.current = messagesRef.current.length;
3438
- if (newSessionPath)
3439
- sessionPathRef.current = newSessionPath;
3440
- setLiveItems([
3441
- {
3442
- kind: "info",
3443
- text: "Plan approved — starting fresh session for implementation",
3444
- id: getId(),
3445
- },
3446
- ]);
3447
- setDoneStatus(null);
3448
- await agentLoop.run("The plan has been approved. Implement it now, following each step in order.");
3449
- }
3450
- catch (err) {
3451
- const errMsg = err instanceof Error ? err.message : String(err);
3452
- log("ERROR", "error", errMsg);
3453
- setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3454
- }
3455
- })();
3456
- }, onReject: (planPath, feedback) => {
3457
- planOverlayPendingRef.current = false;
3458
- const rejectionMsg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
3459
- `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
+ }
3460
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.
3461
3013
  props.sessionStore.overlay = null;
3462
3014
  props.sessionStore.planAutoExpand = false;
3463
- // No wipeSession — keep history and messages so the agent picks
3464
- // up the rejection mid-conversation.
3465
3015
  props.resetUI({
3016
+ wipeSession: true,
3017
+ messages: [{ role: "system", content: newPrompt }],
3018
+ approvedPlanPath: planPath,
3019
+ planSteps: steps,
3020
+ sessionPath: newSessionPath,
3466
3021
  pendingAction: {
3467
- prompt: rejectionMsg,
3468
- planEvent: { event: "rejected", detail: feedback },
3022
+ prompt: "The plan has been approved. Implement it now, following each step in order.",
3023
+ planEvent: { event: "approved" },
3469
3024
  },
3470
3025
  });
3471
3026
  return;
3472
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([]);
3473
3035
  setPlanAutoExpand(false);
3474
3036
  setOverlay(null);
3475
- setDoneStatus(null);
3476
- setLiveItems((prev) => [
3477
- ...prev,
3478
- { 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
+ },
3479
3048
  ]);
3480
- void agentLoop.run(rejectionMsg).catch((err) => {
3481
- const errMsg = err instanceof Error ? err.message : String(err);
3482
- log("ERROR", "error", errMsg);
3483
- setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3484
- });
3485
- } })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [uniqueItemsById(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, onToggleTasks: () => {
3486
- setGoalPickerOpen(false);
3487
- setTaskPickerTasks(loadTasksSync(displayedCwd));
3488
- setTaskPickerOpen((open) => !open);
3489
- }, taskPickerOpen: taskPickerOpen, tasks: taskPickerTasks, onCloseTaskPicker: () => setTaskPickerOpen(false), onStartTask: (task) => {
3490
- setTaskPickerOpen(false);
3491
- markTaskInProgress(displayedCwd, task.id);
3492
- setTaskPickerTasks(loadTasksSync(displayedCwd));
3493
- startTask(task.title, task.prompt, task.id);
3494
- }, onRunAllTasks: (task) => {
3495
- setTaskPickerOpen(false);
3496
- setRunAllTasks(true);
3497
- const selected = task
3498
- ? {
3499
- id: task.id,
3500
- title: task.title,
3501
- prompt: task.prompt || task.text || task.title,
3502
- }
3503
- : getNextPendingTask(displayedCwd);
3504
- if (selected) {
3505
- markTaskInProgress(displayedCwd, selected.id);
3506
- setTaskPickerTasks(loadTasksSync(displayedCwd));
3507
- startTask(selected.title, selected.prompt, selected.id);
3508
- }
3509
- }, onDeleteTask: (task) => {
3510
- const nextTasks = loadTasksSync(displayedCwd).filter((candidate) => candidate.id !== task.id);
3511
- saveTasksSync(displayedCwd, nextTasks);
3512
- setTaskPickerTasks(nextTasks);
3513
- }, goalPickerOpen: goalPickerOpen, goals: goalPickerGoals, onCloseGoalPicker: () => setGoalPickerOpen(false), onRunGoal: (run) => {
3514
- setGoalPickerOpen(false);
3515
- startGoalRun(run);
3516
- }, onDeleteGoal: (run) => {
3517
- const nextGoals = loadGoalRunsSync(displayedCwd).filter((candidate) => candidate.id !== run.id);
3518
- saveGoalRunsSync(displayedCwd, nextGoals);
3519
- setGoalPickerGoals(nextGoals);
3520
- }, onPauseGoal: (run) => {
3521
- setGoalPickerOpen(false);
3522
- pauseGoalRun(run);
3523
- }, onToggleGoal: () => {
3524
- setTaskPickerOpen(false);
3525
- setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
3526
- setGoalPickerOpen((open) => !open);
3527
- }, onToggleSkills: () => {
3528
- openOverlay("skills");
3529
- }, onTogglePixel: () => {
3530
- openOverlay("pixel");
3531
- }, onToggleMarkdown: () => {
3532
- setRenderMarkdown((prev) => !prev);
3533
- }, 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" }) }))] }))] })] })) }));
3534
- }
3535
- function formatRepoMapCommandOutput(enabled, markdown, refreshed) {
3536
- const status = enabled ? "on" : "off";
3537
- const prefix = refreshed
3538
- ? `Dynamic repo map refreshed · injection: ${status}`
3539
- : `Dynamic repo map · injection: ${status}`;
3540
- 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 })) }));
3541
3188
  }
3542
3189
  //# sourceMappingURL=App.js.map