@kenkaiiii/ggcoder 4.3.219 → 4.3.221

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 (376) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +53 -22
  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 +4 -0
  47. package/dist/core/session-manager.d.ts.map +1 -1
  48. package/dist/core/session-manager.js +20 -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 +13 -6
  102. package/dist/ui/App.d.ts.map +1 -1
  103. package/dist/ui/App.js +617 -926
  104. package/dist/ui/App.js.map +1 -1
  105. package/dist/ui/app-items.d.ts +8 -1
  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/SessionSummary.d.ts +5 -0
  188. package/dist/ui/components/SessionSummary.d.ts.map +1 -0
  189. package/dist/ui/components/SessionSummary.js +32 -0
  190. package/dist/ui/components/SessionSummary.js.map +1 -0
  191. package/dist/ui/components/StreamingArea.js +1 -1
  192. package/dist/ui/components/StreamingArea.js.map +1 -1
  193. package/dist/ui/components/SubAgentPanel.d.ts +2 -1
  194. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  195. package/dist/ui/components/SubAgentPanel.js +2 -2
  196. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  197. package/dist/ui/components/ToolExecution.d.ts +2 -0
  198. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  199. package/dist/ui/components/ToolExecution.js +13 -11
  200. package/dist/ui/components/ToolExecution.js.map +1 -1
  201. package/dist/ui/components/ToolGroupExecution.d.ts +2 -1
  202. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
  203. package/dist/ui/components/ToolGroupExecution.js +5 -3
  204. package/dist/ui/components/ToolGroupExecution.js.map +1 -1
  205. package/dist/ui/duration-format.d.ts +2 -0
  206. package/dist/ui/duration-format.d.ts.map +1 -0
  207. package/dist/ui/duration-format.js +9 -0
  208. package/dist/ui/duration-format.js.map +1 -0
  209. package/dist/ui/duration-summary.d.ts +2 -0
  210. package/dist/ui/duration-summary.d.ts.map +1 -0
  211. package/dist/ui/duration-summary.js +66 -0
  212. package/dist/ui/duration-summary.js.map +1 -0
  213. package/dist/ui/error-item.d.ts +8 -0
  214. package/dist/ui/error-item.d.ts.map +1 -0
  215. package/dist/ui/error-item.js +32 -0
  216. package/dist/ui/error-item.js.map +1 -0
  217. package/dist/ui/footer-status-layout.test.js +4 -3
  218. package/dist/ui/footer-status-layout.test.js.map +1 -1
  219. package/dist/ui/goal-events.d.ts +1 -0
  220. package/dist/ui/goal-events.d.ts.map +1 -1
  221. package/dist/ui/goal-events.js +2 -1
  222. package/dist/ui/goal-events.js.map +1 -1
  223. package/dist/ui/goal-events.test.js +16 -0
  224. package/dist/ui/goal-events.test.js.map +1 -1
  225. package/dist/ui/goal-lifecycle-orchestration.test.js +105 -18
  226. package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
  227. package/dist/ui/goal-progress.d.ts +1 -1
  228. package/dist/ui/goal-progress.d.ts.map +1 -1
  229. package/dist/ui/goal-progress.js +11 -13
  230. package/dist/ui/goal-progress.js.map +1 -1
  231. package/dist/ui/goal-run-helpers.d.ts +16 -0
  232. package/dist/ui/goal-run-helpers.d.ts.map +1 -0
  233. package/dist/ui/goal-run-helpers.js +61 -0
  234. package/dist/ui/goal-run-helpers.js.map +1 -0
  235. package/dist/ui/goal-status-bar.test.js +8 -6
  236. package/dist/ui/goal-status-bar.test.js.map +1 -1
  237. package/dist/ui/hooks/useChatLayoutMeasurements.d.ts +38 -0
  238. package/dist/ui/hooks/useChatLayoutMeasurements.d.ts.map +1 -0
  239. package/dist/ui/hooks/useChatLayoutMeasurements.js +71 -0
  240. package/dist/ui/hooks/useChatLayoutMeasurements.js.map +1 -0
  241. package/dist/ui/hooks/useGoalPickerController.d.ts +22 -0
  242. package/dist/ui/hooks/useGoalPickerController.d.ts.map +1 -0
  243. package/dist/ui/hooks/useGoalPickerController.js +35 -0
  244. package/dist/ui/hooks/useGoalPickerController.js.map +1 -0
  245. package/dist/ui/hooks/useTaskPickerController.d.ts +19 -0
  246. package/dist/ui/hooks/useTaskPickerController.d.ts.map +1 -0
  247. package/dist/ui/hooks/useTaskPickerController.js +41 -0
  248. package/dist/ui/hooks/useTaskPickerController.js.map +1 -0
  249. package/dist/ui/hooks/useTranscriptHistory.d.ts +34 -0
  250. package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -0
  251. package/dist/ui/hooks/useTranscriptHistory.js +96 -0
  252. package/dist/ui/hooks/useTranscriptHistory.js.map +1 -0
  253. package/dist/ui/layout-decisions.d.ts +3 -12
  254. package/dist/ui/layout-decisions.d.ts.map +1 -1
  255. package/dist/ui/layout-decisions.js +7 -65
  256. package/dist/ui/layout-decisions.js.map +1 -1
  257. package/dist/ui/prompt-routing.d.ts.map +1 -1
  258. package/dist/ui/prompt-routing.js +36 -2
  259. package/dist/ui/prompt-routing.js.map +1 -1
  260. package/dist/ui/prompt-routing.test.d.ts +2 -0
  261. package/dist/ui/prompt-routing.test.d.ts.map +1 -0
  262. package/dist/ui/prompt-routing.test.js +48 -0
  263. package/dist/ui/prompt-routing.test.js.map +1 -0
  264. package/dist/ui/render.d.ts +11 -6
  265. package/dist/ui/render.d.ts.map +1 -1
  266. package/dist/ui/render.js +8 -2
  267. package/dist/ui/render.js.map +1 -1
  268. package/dist/ui/session-summary.d.ts +63 -0
  269. package/dist/ui/session-summary.d.ts.map +1 -0
  270. package/dist/ui/session-summary.js +81 -0
  271. package/dist/ui/session-summary.js.map +1 -0
  272. package/dist/ui/slash-command-images.test.js +3 -2
  273. package/dist/ui/slash-command-images.test.js.map +1 -1
  274. package/dist/ui/submit-prompt-command.d.ts +40 -0
  275. package/dist/ui/submit-prompt-command.d.ts.map +1 -0
  276. package/dist/ui/submit-prompt-command.js +92 -0
  277. package/dist/ui/submit-prompt-command.js.map +1 -0
  278. package/dist/ui/submit-slash-commands.d.ts +13 -0
  279. package/dist/ui/submit-slash-commands.d.ts.map +1 -0
  280. package/dist/ui/submit-slash-commands.js +36 -0
  281. package/dist/ui/submit-slash-commands.js.map +1 -0
  282. package/dist/ui/terminal-history-format.d.ts +1 -0
  283. package/dist/ui/terminal-history-format.d.ts.map +1 -1
  284. package/dist/ui/terminal-history-format.js +2 -1
  285. package/dist/ui/terminal-history-format.js.map +1 -1
  286. package/dist/ui/terminal-history-spacing.d.ts +1 -2
  287. package/dist/ui/terminal-history-spacing.d.ts.map +1 -1
  288. package/dist/ui/terminal-history-spacing.js +1 -28
  289. package/dist/ui/terminal-history-spacing.js.map +1 -1
  290. package/dist/ui/terminal-history-status-renderers.d.ts +2 -2
  291. package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
  292. package/dist/ui/terminal-history-status-renderers.js +36 -17
  293. package/dist/ui/terminal-history-status-renderers.js.map +1 -1
  294. package/dist/ui/terminal-history.d.ts.map +1 -1
  295. package/dist/ui/terminal-history.js +93 -24
  296. package/dist/ui/terminal-history.js.map +1 -1
  297. package/dist/ui/terminal-history.test.js +2 -1
  298. package/dist/ui/terminal-history.test.js.map +1 -1
  299. package/dist/ui/tool-group-summary.d.ts +2 -2
  300. package/dist/ui/tool-group-summary.d.ts.map +1 -1
  301. package/dist/ui/tool-group-summary.js +18 -18
  302. package/dist/ui/tool-group-summary.js.map +1 -1
  303. package/dist/ui/transcript/GoalRows.d.ts +10 -0
  304. package/dist/ui/transcript/GoalRows.d.ts.map +1 -0
  305. package/dist/ui/transcript/GoalRows.js +35 -0
  306. package/dist/ui/transcript/GoalRows.js.map +1 -0
  307. package/dist/ui/transcript/MiscRows.d.ts +23 -0
  308. package/dist/ui/transcript/MiscRows.d.ts.map +1 -0
  309. package/dist/ui/transcript/MiscRows.js +41 -0
  310. package/dist/ui/transcript/MiscRows.js.map +1 -0
  311. package/dist/ui/transcript/StatusRow.d.ts +14 -0
  312. package/dist/ui/transcript/StatusRow.d.ts.map +1 -0
  313. package/dist/ui/transcript/StatusRow.js +14 -0
  314. package/dist/ui/transcript/StatusRow.js.map +1 -0
  315. package/dist/ui/transcript/ToolRows.d.ts +20 -0
  316. package/dist/ui/transcript/ToolRows.d.ts.map +1 -0
  317. package/dist/ui/transcript/ToolRows.js +25 -0
  318. package/dist/ui/transcript/ToolRows.js.map +1 -0
  319. package/dist/ui/transcript/TranscriptItemFrame.d.ts +8 -0
  320. package/dist/ui/transcript/TranscriptItemFrame.d.ts.map +1 -0
  321. package/dist/ui/transcript/TranscriptItemFrame.js +9 -0
  322. package/dist/ui/transcript/TranscriptItemFrame.js.map +1 -0
  323. package/dist/ui/transcript/TranscriptRenderer.d.ts +22 -0
  324. package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -0
  325. package/dist/ui/transcript/TranscriptRenderer.js +87 -0
  326. package/dist/ui/transcript/TranscriptRenderer.js.map +1 -0
  327. package/dist/ui/transcript/presentation.d.ts +76 -0
  328. package/dist/ui/transcript/presentation.d.ts.map +1 -0
  329. package/dist/ui/transcript/presentation.js +109 -0
  330. package/dist/ui/transcript/presentation.js.map +1 -0
  331. package/dist/ui/transcript/spacing.d.ts +29 -0
  332. package/dist/ui/transcript/spacing.d.ts.map +1 -0
  333. package/dist/ui/transcript/spacing.js +91 -0
  334. package/dist/ui/transcript/spacing.js.map +1 -0
  335. package/dist/ui/transcript/spacing.test.d.ts +2 -0
  336. package/dist/ui/transcript/spacing.test.d.ts.map +1 -0
  337. package/dist/ui/transcript/spacing.test.js +21 -0
  338. package/dist/ui/transcript/spacing.test.js.map +1 -0
  339. package/dist/ui/transcript/tool-presentation.d.ts +12 -0
  340. package/dist/ui/transcript/tool-presentation.d.ts.map +1 -0
  341. package/dist/ui/transcript/tool-presentation.js +55 -0
  342. package/dist/ui/transcript/tool-presentation.js.map +1 -0
  343. package/dist/ui/tui-history-parity.test.js +42 -2
  344. package/dist/ui/tui-history-parity.test.js.map +1 -1
  345. package/dist/utils/plan-steps.d.ts.map +1 -1
  346. package/dist/utils/plan-steps.js +5 -1
  347. package/dist/utils/plan-steps.js.map +1 -1
  348. package/dist/utils/plan-steps.test.d.ts +2 -0
  349. package/dist/utils/plan-steps.test.d.ts.map +1 -0
  350. package/dist/utils/plan-steps.test.js +16 -0
  351. package/dist/utils/plan-steps.test.js.map +1 -0
  352. package/package.json +7 -7
  353. package/dist/core/repomap-budget.d.ts +0 -7
  354. package/dist/core/repomap-budget.d.ts.map +0 -1
  355. package/dist/core/repomap-budget.js +0 -10
  356. package/dist/core/repomap-budget.js.map +0 -1
  357. package/dist/core/repomap-budget.test.d.ts +0 -2
  358. package/dist/core/repomap-budget.test.d.ts.map +0 -1
  359. package/dist/core/repomap-budget.test.js +0 -26
  360. package/dist/core/repomap-budget.test.js.map +0 -1
  361. package/dist/core/repomap-context.d.ts +0 -11
  362. package/dist/core/repomap-context.d.ts.map +0 -1
  363. package/dist/core/repomap-context.js +0 -68
  364. package/dist/core/repomap-context.js.map +0 -1
  365. package/dist/core/repomap-context.test.d.ts +0 -2
  366. package/dist/core/repomap-context.test.d.ts.map +0 -1
  367. package/dist/core/repomap-context.test.js +0 -47
  368. package/dist/core/repomap-context.test.js.map +0 -1
  369. package/dist/core/repomap.d.ts +0 -74
  370. package/dist/core/repomap.d.ts.map +0 -1
  371. package/dist/core/repomap.js +0 -906
  372. package/dist/core/repomap.js.map +0 -1
  373. package/dist/core/repomap.test.d.ts +0 -2
  374. package/dist/core/repomap.test.d.ts.map +0 -1
  375. package/dist/core/repomap.test.js +0 -494
  376. package/dist/core/repomap.test.js.map +0 -1
package/dist/ui/App.js CHANGED
@@ -1,41 +1,26 @@
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 { SessionSummaryDisplay } from "./components/SessionSummary.js";
19
+ import { reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntry, syncGoalStatusEntries, } from "./components/GoalStatusBar.js";
34
20
  import { useTheme, useSetTheme } from "./theme/theme.js";
35
21
  import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
36
22
  import { getGitBranch } from "../utils/git.js";
37
23
  import { getModel, getContextWindow } from "../core/model-registry.js";
38
- import { BLACK_CIRCLE } from "./constants/figures.js";
39
24
  import { SessionManager } from "../core/session-manager.js";
40
25
  import { appendMessagesToSession as appendSessionMessages, createCompactedSessionCheckpoint, } from "../core/session-compaction.js";
41
26
  import { log } from "../core/logger.js";
@@ -48,65 +33,40 @@ import { PROMPT_COMMANDS, getPromptCommand } from "../core/prompt-commands.js";
48
33
  import { isFirstTimeSetup, markSetupAudited, getAnnouncedLanguages, markLanguagesAnnounced, } from "../core/setup-history.js";
49
34
  import { loadCustomCommands } from "../core/custom-commands.js";
50
35
  import { buildSystemPrompt } from "../system-prompt.js";
51
- import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
36
+ import { detectLanguages } from "../core/language-detector.js";
52
37
  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
38
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
57
39
  import { getMCPServers } from "../core/mcp/index.js";
58
40
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
59
41
  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";
42
+ import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
43
+ import { getNextPendingTask, markTaskInProgress } from "../core/tasks-store.js";
62
44
  import { canCompleteGoalRun, decideGoalNextAction } from "../core/goal-controller.js";
63
45
  import { runGoalPrerequisiteChecks } from "../core/goal-prerequisites.js";
64
46
  import { runGoalVerifierCommand } from "../core/goal-verifier.js";
47
+ import { checkGoalWorktreeIntegration, isGoalWorktreeDirtyError } from "../core/goal-worktree.js";
65
48
  import { listGoalWorkers, startGoalWorker, stopGoalWorker, subscribeGoalWorkerCompletions, } from "../core/goal-worker.js";
66
49
  import { formatGoalVerifierCompletionEvent, formatGoalWorkerCompletionEvent, isGoalSyntheticEvent, parseGoalSyntheticEvent, } from "./goal-events.js";
67
- import { buildUserContentWithAttachments, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
50
+ import { buildUserContentWithAttachments } from "./prompt-routing.js";
51
+ import { submitPromptCommand } from "./submit-prompt-command.js";
52
+ import { handleUiSlashCommand } from "./submit-slash-commands.js";
68
53
  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";
54
+ import { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, formatGoalWorkerFinishedTitle, getGoalContinuationChoiceKey, goalTerminalProgressId, routeGoalSyntheticEvent, summarizeGoalCompletion, } from "./goal-progress.js";
55
+ import { getDoneFlushDecision, nextGoalModeAfterAgentDone, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
56
+ import { isTranscriptSpacingItem } from "./transcript/spacing.js";
57
+ import { renderTranscriptItem } from "./transcript/TranscriptRenderer.js";
58
+ import { formatDuration } from "./duration-format.js";
59
+ import { pickDurationVerb } from "./duration-summary.js";
60
+ import { toErrorItem } from "./error-item.js";
61
+ import { addLinesChanged, buildSessionSummary, createSessionStats, recordServerToolCall, recordToolEnd, recordTurnEnd, } from "./session-summary.js";
62
+ import { buildGoalDirtyWorktreePauseRun, buildGoalDirtyWorktreeUserPrompt, buildGoalTaskPromptWithReferences, buildGoalUserPauseRun, goalDirtyWorktreeInfoText, goalRunNeedsExplicitContinuationAfterWorker, goalTaskProgress, shouldKeepGoalRunTrackedAfterDecision, shouldRunGoalTaskInMainCheckout, } from "./goal-run-helpers.js";
71
63
  import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, removeItemsWithIds, uniqueItemsById, } from "./item-helpers.js";
72
64
  export { buildGoalSetupPromptFromPlanner, buildUserContentWithAttachments, collectAssistantTextSince, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
73
65
  export { getNextThinkingLevel } from "./thinking-level.js";
74
66
  export { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, getGoalContinuationChoiceKey, routeGoalSyntheticEvent, truncateGoalProgressText, } from "./goal-progress.js";
75
67
  export { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalActivationPaneTransition, getGoalSetupFinishedPaneTransition, getGoalSetupPaneTransitionAfterRun, getScrollStabilizationDecision, getStaticHistoryKey, hasParagraphBreakLiveUserMessage, isTallLiveUserMessage, nextGoalModeAfterAgentDone, shouldHideHistoryForOverlayView, shouldHideStaticItemsForOverlayView, shouldResetUIForGoalSetupPaneTransition, shouldStabilizeOverlayPaneRerender, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
76
68
  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
- }
69
+ export { buildGoalDirtyWorktreePauseRun, buildGoalDirtyWorktreeUserPrompt, buildGoalUserPauseRun, goalDirtyWorktreeInfoText, goalRunNeedsExplicitContinuationAfterWorker, shouldKeepGoalRunTrackedAfterDecision, shouldRunGoalTaskInMainCheckout, } from "./goal-run-helpers.js";
110
70
  /** Tools that get aggregated into a single compact group when possible. */
111
71
  const AGGREGATABLE_TOOLS = new Set([
112
72
  "read",
@@ -118,114 +78,6 @@ const AGGREGATABLE_TOOLS = new Set([
118
78
  "mcp__kencode-search__discoverRepos",
119
79
  ]);
120
80
  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
81
  // ── App Component ──────────────────────────────────────────
230
82
  export function App(props) {
231
83
  const theme = useTheme();
@@ -235,7 +87,9 @@ export function App(props) {
235
87
  // Hoisted before terminal title hook so it can reference them
236
88
  const [lastUserMessage, setLastUserMessage] = useState("");
237
89
  const [exitPending, setExitPending] = useState(false);
90
+ const [quittingSummary, setQuittingSummary] = useState(null);
238
91
  const [goalMode, setGoalMode] = useState(props.sessionStore?.goalMode ?? props.goalModeRef?.current ?? "off");
92
+ const [planMode, setPlanMode] = useState(props.sessionStore?.planMode ?? props.planModeRef?.current ?? false);
239
93
  // Terminal title — updated later after agentLoop is created
240
94
  // (hoisted here so the hook is always called in the same order)
241
95
  const [titleRunning, setTitleRunning] = useState(false);
@@ -279,10 +133,6 @@ export function App(props) {
279
133
  const goalContinuationRecentChoicesRef = useRef(new Map());
280
134
  const startGoalRunRef = useRef(() => { });
281
135
  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
136
  const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
287
137
  const startTaskRef = useRef(() => { });
288
138
  const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
@@ -290,6 +140,11 @@ export function App(props) {
290
140
  const startPixelFixRef = useRef(() => { });
291
141
  const cwdRef = useRef(props.cwd);
292
142
  const [displayedCwd, setDisplayedCwd] = useState(props.cwd);
143
+ const taskPicker = useTaskPickerController({
144
+ displayedCwd,
145
+ onStartTask: (title, prompt, taskId) => startTaskRef.current(title, prompt, taskId),
146
+ onRunAllTasksChange: setRunAllTasks,
147
+ });
293
148
  const [doneStatus, setDoneStatus] = useState(props.sessionStore?.doneStatus ?? null);
294
149
  // Suppress "done" status when a plan overlay is about to open
295
150
  const planOverlayPendingRef = useRef(false);
@@ -302,12 +157,6 @@ export function App(props) {
302
157
  const [thinkingLevel, setThinkingLevel] = useState(props.thinking);
303
158
  const [renderMarkdown, setRenderMarkdown] = useState(true);
304
159
  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
160
  const [planAutoExpand, setPlanAutoExpand] = useState(props.sessionStore?.planAutoExpand ?? false);
312
161
  const [goalAutoExpand, setGoalAutoExpand] = useState(props.sessionStore?.goalAutoExpand ?? false);
313
162
  const goalAutoExpandRef = useRef(props.sessionStore?.goalAutoExpand ?? false);
@@ -315,6 +164,7 @@ export function App(props) {
315
164
  const planStepsRef = useRef(props.sessionStore?.planSteps ?? []);
316
165
  const [planSteps, setPlanSteps] = useState(props.sessionStore?.planSteps ?? []);
317
166
  const goalModeStateRef = useRef(goalMode);
167
+ const planModeStateRef = useRef(planMode);
318
168
  // Stuck-guard for the plan-continuation follow-up nudge. Tracks how many
319
169
  // times we've nudged the agent to continue the same step. Reset whenever a
320
170
  // new [DONE:n] marker advances progress (see onTurnText). Caps at 2 nudges
@@ -333,6 +183,8 @@ export function App(props) {
333
183
  const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
334
184
  const sessionPathRef = useRef(props.sessionStore?.sessionPath ?? props.sessionPath);
335
185
  const persistedIndexRef = useRef(messagesRef.current.length);
186
+ const sessionStatsRef = useRef(props.sessionStore?.sessionStats ??
187
+ createSessionStats({ sessionId: props.sessionStore?.sessionId ?? props.sessionId }));
336
188
  /** Last actual API-reported input token count (from turn_end). */
337
189
  const lastActualTokensRef = useRef(0);
338
190
  /** Timestamp (ms) when lastActualTokensRef was last updated by turn_end. */
@@ -384,58 +236,24 @@ export function App(props) {
384
236
  });
385
237
  }, [props.sessionStore]);
386
238
  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 = {
239
+ const { pendingHistoryFlushRef, streamedAssistantFlushRef, queueFlush, finalizeSubmittedUserItem, clearPendingHistory, } = useTranscriptHistory({
240
+ terminalHistoryPrinter: props.terminalHistoryPrinter,
241
+ terminalHistoryContext: {
397
242
  theme,
398
243
  columns,
399
244
  version: props.version,
400
245
  model: currentModel,
401
246
  provider: currentProvider,
402
247
  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: "",
248
+ },
249
+ writeStdout,
250
+ sessionPathRef,
251
+ sessionManagerRef,
252
+ sessionStore,
253
+ history,
254
+ setHistory,
255
+ setLiveItems,
417
256
  });
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
257
  // Mirror runtime state choices (model/provider/thinking) into renderApp's
440
258
  // closure so unmount/remount preserves them.
441
259
  const onRuntimeStateChange = props.onRuntimeStateChange;
@@ -455,28 +273,6 @@ export function App(props) {
455
273
  thinking: thinkingLevel,
456
274
  });
457
275
  }, [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
276
  // Mirror session state into renderApp's closure so resetUI() can re-seed
481
277
  // the conversation on remount. Each panel that previously did a bare ANSI
482
278
  // screen clear (overlay open/close, plan accept/reject, /clear)
@@ -522,6 +318,14 @@ export function App(props) {
522
318
  if (sessionStore)
523
319
  sessionStore.goalMode = goalMode;
524
320
  }, [goalMode, sessionStore]);
321
+ useEffect(() => {
322
+ if (sessionStore)
323
+ sessionStore.planMode = planMode;
324
+ }, [planMode, sessionStore]);
325
+ useEffect(() => {
326
+ if (sessionStore)
327
+ sessionStore.sessionStats = sessionStatsRef.current;
328
+ }, [sessionStore]);
525
329
  // pendingAction is consumed via a useEffect AFTER agentLoop is created
526
330
  // — see below where useAgentLoop is set up.
527
331
  const pendingActionConsumedRef = useRef(false);
@@ -593,6 +397,11 @@ export function App(props) {
593
397
  props.goalModeRef.current = goalMode;
594
398
  }
595
399
  }, [goalMode, props.goalModeRef]);
400
+ useEffect(() => {
401
+ planModeStateRef.current = planMode;
402
+ if (props.planModeRef)
403
+ props.planModeRef.current = planMode;
404
+ }, [planMode, props.planModeRef]);
596
405
  const setActiveGoalReferences = useCallback((references) => {
597
406
  if (props.goalReferencesRef)
598
407
  props.goalReferencesRef.current = references;
@@ -601,7 +410,7 @@ export function App(props) {
601
410
  const approvedPlanPath = options?.clearApprovedPlan
602
411
  ? undefined
603
412
  : (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);
413
+ 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
414
  }, [props.skills]);
606
415
  const replaceSystemPrompt = useCallback(async (options) => {
607
416
  const newPrompt = await rebuildSystemPrompt(options);
@@ -642,6 +451,15 @@ export function App(props) {
642
451
  setGoalMode(nextMode);
643
452
  await replaceSystemPrompt({ ...options, goalMode: nextMode });
644
453
  }, [props.goalModeRef, props.sessionStore, replaceSystemPrompt]);
454
+ const setPlanModeAndPrompt = useCallback(async (nextMode) => {
455
+ planModeStateRef.current = nextMode;
456
+ if (props.planModeRef)
457
+ props.planModeRef.current = nextMode;
458
+ if (props.sessionStore)
459
+ props.sessionStore.planMode = nextMode;
460
+ setPlanMode(nextMode);
461
+ await replaceSystemPrompt({ planMode: nextMode });
462
+ }, [props.planModeRef, props.sessionStore, replaceSystemPrompt]);
645
463
  const clearGoalModeIfIdle = useCallback(() => {
646
464
  setTimeout(() => {
647
465
  if (goalModeStateRef.current === "off")
@@ -756,9 +574,11 @@ export function App(props) {
756
574
  messages: compactedMessages,
757
575
  });
758
576
  sessionPathRef.current = session.path;
577
+ sessionStatsRef.current.sessionId = session.id;
759
578
  persistedIndexRef.current = compactedMessages.length;
760
579
  if (sessionStore) {
761
580
  sessionStore.sessionPath = session.path;
581
+ sessionStore.sessionId = session.id;
762
582
  sessionStore.messages = [...compactedMessages];
763
583
  }
764
584
  log("INFO", "compaction", "Persisted compacted session checkpoint", { path: session.path });
@@ -773,6 +593,7 @@ export function App(props) {
773
593
  if (sessionStore) {
774
594
  sessionStore.messages = [...allMsgs];
775
595
  sessionStore.sessionPath = sp;
596
+ sessionStore.sessionId = sessionStatsRef.current.sessionId;
776
597
  }
777
598
  }, [appendMessagesToSession, sessionStore]);
778
599
  /**
@@ -892,103 +713,46 @@ export function App(props) {
892
713
  contextWindowOptions,
893
714
  props.authStorage,
894
715
  ]);
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
716
  /**
944
717
  * transformContext callback for the agent loop.
945
718
  * Called before each LLM call and on context overflow.
946
- * Compacts persistent chat only, then injects the dynamic repo map transiently.
947
719
  */
948
720
  const transformContext = useCallback(async (messages, options) => {
949
- const stripped = stripRepoMapMessages(messages);
950
721
  const settings = settingsRef.current;
951
722
  const autoCompact = settings?.get("autoCompact") ?? true;
952
723
  const threshold = settings?.get("compactThreshold") ?? 0.8;
953
724
  // Force-compact on context overflow regardless of settings
954
725
  if (options?.force) {
955
- const result = await compactConversation(stripped);
956
- if (result !== stripped) {
726
+ const result = await compactConversation(messages);
727
+ if (result !== messages) {
957
728
  messagesRef.current = result;
958
729
  await persistCompactedSession(result);
959
730
  }
960
731
  lastCompactionTimeRef.current = Date.now();
961
- return injectRepoMapContext(result);
732
+ return result;
962
733
  }
963
734
  if (!autoCompact)
964
- return injectRepoMapContext(stripped);
735
+ return messages;
965
736
  // Time-based cooldown: skip if compaction ran within the last 30 seconds
966
737
  if (Date.now() - lastCompactionTimeRef.current < 30_000) {
967
738
  log("INFO", "compaction", `Skipping compaction — cooldown active`);
968
- return injectRepoMapContext(stripped);
739
+ return messages;
969
740
  }
970
741
  const contextWindow = getContextWindow(currentModel, contextWindowOptions);
971
742
  const reserveTokens = getCompactionReserveTokens(props.maxTokens);
972
743
  const tokensFresh = lastActualTokensTimestampRef.current > lastCompactionTimeRef.current;
973
744
  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) {
745
+ if (shouldCompact(messages, contextWindow, threshold, actualTokens, reserveTokens)) {
746
+ const result = await compactConversation(messages);
747
+ if (result !== messages) {
977
748
  messagesRef.current = result;
978
749
  await persistCompactedSession(result);
979
750
  }
980
751
  lastCompactionTimeRef.current = Date.now();
981
- return injectRepoMapContext(result);
752
+ return result;
982
753
  }
983
- return injectRepoMapContext(stripped);
984
- }, [
985
- currentModel,
986
- compactConversation,
987
- contextWindowOptions,
988
- injectRepoMapContext,
989
- persistCompactedSession,
990
- stripRepoMapMessages,
991
- ]);
754
+ return messages;
755
+ }, [currentModel, compactConversation, contextWindowOptions, persistCompactedSession]);
992
756
  // ── Background task bar state (external store) ──────────
993
757
  const { bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, } = useTaskBarStore();
994
758
  useTaskBarPolling(props.processManager);
@@ -1029,7 +793,6 @@ export function App(props) {
1029
793
  transformContext,
1030
794
  }, {
1031
795
  onComplete: useCallback(() => {
1032
- messagesRef.current = stripRepoMapMessages(messagesRef.current);
1033
796
  persistNewMessages();
1034
797
  // Auto-clear plan progress and approved plan when all steps are completed
1035
798
  const steps = planStepsRef.current;
@@ -1082,7 +845,6 @@ export function App(props) {
1082
845
  }
1083
846
  }, [
1084
847
  persistNewMessages,
1085
- stripRepoMapMessages,
1086
848
  props.cwd,
1087
849
  props.skills,
1088
850
  currentProvider,
@@ -1301,6 +1063,14 @@ export function App(props) {
1301
1063
  });
1302
1064
  }, []),
1303
1065
  onToolEnd: useCallback((toolCallId, name, result, isError, durationMs, details) => {
1066
+ recordToolEnd(sessionStatsRef.current, name, isError, durationMs);
1067
+ if (name === "edit" && !isError) {
1068
+ const diff = details?.diff ?? result;
1069
+ addLinesChanged(sessionStatsRef.current, {
1070
+ added: (diff.match(/^\+[^+]/gm) ?? []).length,
1071
+ removed: (diff.match(/^-[^-]/gm) ?? []).length,
1072
+ });
1073
+ }
1304
1074
  // Language-pack detection — gated on `write`/`bash` inside the
1305
1075
  // helper; cheap to call unconditionally. Fire-and-forget; the next
1306
1076
  // LLM turn picks up the swapped system prompt automatically.
@@ -1408,6 +1178,7 @@ export function App(props) {
1408
1178
  }
1409
1179
  }, []),
1410
1180
  onServerToolCall: useCallback((id, name, input, stream) => {
1181
+ recordServerToolCall(sessionStatsRef.current);
1411
1182
  log("INFO", "server_tool", `Server tool call: ${name}`, { id });
1412
1183
  const startedAt = Date.now();
1413
1184
  const animateUntil = startedAt + RUNNING_INDICATOR_ANIMATION_MS;
@@ -1481,6 +1252,7 @@ export function App(props) {
1481
1252
  });
1482
1253
  }, [queueFlush]),
1483
1254
  onTurnEnd: useCallback((turn, stopReason, usage) => {
1255
+ recordTurnEnd(sessionStatsRef.current, usage);
1484
1256
  log("INFO", "turn", `Turn ${turn} ended`, {
1485
1257
  stopReason,
1486
1258
  inputTokens: String(usage.inputTokens),
@@ -1790,6 +1562,24 @@ export function App(props) {
1790
1562
  return () => clearTimeout(timer);
1791
1563
  }
1792
1564
  }, [agentLoop.isRunning, sessionStore, props.resetUI]);
1565
+ const showSessionSummaryAndExit = useCallback(() => {
1566
+ const summary = buildSessionSummary({
1567
+ stats: sessionStatsRef.current,
1568
+ provider: currentProvider,
1569
+ model: currentModel,
1570
+ cwd: displayedCwd,
1571
+ footer: sessionStatsRef.current.sessionId
1572
+ ? `To resume this session: ggcoder --resume ${sessionStatsRef.current.sessionId}`
1573
+ : undefined,
1574
+ });
1575
+ setDoneStatus(null);
1576
+ setExitPending(false);
1577
+ setOverlay(null);
1578
+ setLiveItems([]);
1579
+ setQuittingSummary(summary);
1580
+ writeStdout("\x1b[2J\x1b[3J\x1b[H");
1581
+ setTimeout(() => process.exit(0), 150);
1582
+ }, [currentModel, currentProvider, displayedCwd, writeStdout]);
1793
1583
  // Consume pending post-remount work once on mount. Set by resetUI options
1794
1584
  // for paths that remount AND immediately drive work (plan accept/reject,
1795
1585
  // pixel fix, Goal approval). The work survives the unmount because
@@ -1847,227 +1637,101 @@ export function App(props) {
1847
1637
  // has not grown.
1848
1638
  await applyLanguageDetectionRef.current("input");
1849
1639
  }
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) {
1640
+ if (await handleUiSlashCommand(trimmed, {
1641
+ openModelSelector: () => setOverlay("model"),
1642
+ compactConversation: async () => {
1643
+ const ac = new AbortController();
1644
+ compactionAbortRef.current = ac;
1645
+ const compacted = await compactConversation(messagesRef.current, ac.signal);
1646
+ if (!ac.signal.aborted && compacted !== messagesRef.current) {
1647
+ messagesRef.current = compacted;
1648
+ await persistCompactedSession(compacted);
1649
+ }
1650
+ if (compactionAbortRef.current === ac)
1651
+ compactionAbortRef.current = null;
1652
+ },
1653
+ quit: showSessionSummaryAndExit,
1654
+ clearSession: () => {
1655
+ if (props.resetUI) {
1656
+ void (async () => {
1657
+ const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1658
+ props.resetUI?.({
1659
+ wipeSession: true,
1660
+ messages: [{ role: "system", content: newPrompt }],
1661
+ });
1662
+ })();
1663
+ return;
1664
+ }
1665
+ clearPendingHistory();
1666
+ setHistory([{ kind: "banner", id: "banner" }]);
1667
+ setLiveItems([]);
1668
+ setDoneStatus(null);
1669
+ approvedPlanPathRef.current = undefined;
1670
+ planStepsRef.current = [];
1671
+ setPlanSteps([]);
1879
1672
  void (async () => {
1880
1673
  const newPrompt = await rebuildSystemPrompt({ clearApprovedPlan: true });
1881
- props.resetUI?.({
1882
- wipeSession: true,
1883
- messages: [{ role: "system", content: newPrompt }],
1884
- });
1674
+ messagesRef.current = [{ role: "system", content: newPrompt }];
1675
+ persistedIndexRef.current = messagesRef.current.length;
1885
1676
  })();
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);
1677
+ agentLoop.reset();
1678
+ setSessionTitle(undefined);
1679
+ sessionTitleGeneratedRef.current = false;
1680
+ setLiveItems([{ kind: "info", text: "Session cleared.", id: getId() }]);
1681
+ },
1682
+ openThemeSelector: () => setOverlay("theme"),
1683
+ toggleMarkdown: () => {
1684
+ setRenderMarkdown((prev) => {
1685
+ const next = !prev;
1686
+ setLiveItems([
1687
+ {
1688
+ kind: "info",
1689
+ text: next ? "Rendered markdown mode." : "Raw markdown mode.",
1690
+ id: getId(),
1691
+ },
1692
+ ]);
1693
+ return next;
1694
+ });
1695
+ },
1696
+ clearApprovedPlan: () => {
1697
+ approvedPlanPathRef.current = undefined;
1698
+ planStepsRef.current = [];
1699
+ setPlanSteps([]);
1700
+ void replaceSystemPrompt({ clearApprovedPlan: true });
1701
+ setLiveItems([{ kind: "plan_event", event: "dismissed", id: getId() }]);
1702
+ },
1703
+ openGoalsPicker: () => {
1704
+ taskPicker.close();
1705
+ goalPicker.openPicker();
1706
+ },
1707
+ })) {
1985
1708
  return;
1986
1709
  }
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();
1710
+ if (await submitPromptCommand({
1711
+ trimmed,
1712
+ inputImages,
1713
+ cwd: props.cwd,
1714
+ currentModel,
1715
+ customCommands,
1716
+ messagesRef,
1717
+ goalSetupPanePendingRef,
1718
+ goalModeStateRef,
1719
+ goalAutoExpandRef,
1720
+ setActiveGoalReferences,
1721
+ setLastUserMessage,
1722
+ setDoneStatus,
1723
+ finalizeSubmittedUserItem,
1724
+ setGoalModeAndPrompt,
1725
+ runAgent: (content) => agentLoop.run(content),
1726
+ appendGoalAgentTransition,
1727
+ setLiveItems,
1728
+ getId,
1729
+ setGoalAutoExpand,
1730
+ setPlanAutoExpand,
1731
+ closeTaskPicker: taskPicker.close,
1732
+ openGoalPicker: goalPicker.openPicker,
1733
+ reloadCustomCommands,
1734
+ })) {
2071
1735
  return;
2072
1736
  }
2073
1737
  // Check slash commands
@@ -2149,14 +1813,13 @@ export function App(props) {
2149
1813
  props.resetUI,
2150
1814
  props.sessionStore,
2151
1815
  rebuildSystemPrompt,
2152
- refreshRepoMap,
1816
+ showSessionSummaryAndExit,
2153
1817
  reloadCustomCommands,
2154
1818
  replaceSystemPrompt,
2155
1819
  setActiveGoalReferences,
2156
1820
  setGoalModeAndPrompt,
2157
- stripRepoMapMessages,
2158
1821
  ]);
2159
- const handleDoubleExit = useDoublePress(setExitPending, () => process.exit(0));
1822
+ const handleDoubleExit = useDoublePress(setExitPending, showSessionSummaryAndExit);
2160
1823
  const handleAbort = useCallback(() => {
2161
1824
  if (agentLoop.isRunning) {
2162
1825
  agentLoop.clearQueue();
@@ -2343,115 +2006,21 @@ export function App(props) {
2343
2006
  },
2344
2007
  ];
2345
2008
  }, [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
- };
2009
+ const renderItem = (item, index, items) => renderTranscriptItem({
2010
+ item,
2011
+ index,
2012
+ items,
2013
+ pendingHistoryFlushLastItem: pendingHistoryFlushRef.current.at(-1),
2014
+ historyLastItem: history.at(-1),
2015
+ version: props.version,
2016
+ currentModel,
2017
+ currentProvider,
2018
+ displayedCwd,
2019
+ columns,
2020
+ theme,
2021
+ renderMarkdown,
2022
+ measuredLiveAreaRows,
2023
+ });
2455
2024
  const openOverlay = useCallback((kind) => {
2456
2025
  if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2457
2026
  props.sessionStore.overlay = kind;
@@ -2536,6 +2105,10 @@ export function App(props) {
2536
2105
  return;
2537
2106
  }
2538
2107
  const decision = decideGoalNextAction(latestRun);
2108
+ if (!shouldKeepGoalRunTrackedAfterDecision(decision)) {
2109
+ runningGoalIdsRef.current.delete(runId);
2110
+ clearGoalModeIfIdle();
2111
+ }
2539
2112
  if (decision.kind === "wait")
2540
2113
  return;
2541
2114
  const choiceKey = getGoalContinuationChoiceKey({ runId: latestRun.id, decision });
@@ -2640,23 +2213,25 @@ export function App(props) {
2640
2213
  workerId: completion.worker.id,
2641
2214
  status: completion.status,
2642
2215
  });
2216
+ const taskProgress = goalTaskProgress(run, run.tasks.find((task) => task.id === completion.worker.goalTaskId));
2643
2217
  upsertGoalStatusEntry({
2644
2218
  runId: run.id,
2645
- label: taskTitle,
2219
+ label: run.title,
2646
2220
  phase: completion.status === "done" ? "reviewing" : "failed",
2647
2221
  startedAt: Date.now(),
2648
2222
  detail: completion.status === "done" ? "reviewing result" : "task failed",
2649
2223
  workerId: completion.worker.id,
2650
2224
  goalNumber: goalNumberForRun(run.id),
2225
+ ...taskProgress,
2651
2226
  });
2652
2227
  runGoalSyntheticEvent(eventText);
2653
2228
  void (async () => {
2654
- if (listGoalWorkers(completion.worker.cwd).some((worker) => worker.status === "running"))
2229
+ if (listGoalWorkers(completion.worker.projectPath).some((worker) => worker.status === "running"))
2655
2230
  return;
2656
2231
  if (activeVerifierRunIdsRef.current.size > 0)
2657
2232
  return;
2658
- const runs = await loadGoalRuns(completion.worker.cwd);
2659
- const queued = runs.find((item) => item.continueRequestedAt && !goalHasBlockingPrerequisites(item));
2233
+ const runs = await loadGoalRuns(completion.worker.projectPath);
2234
+ const queued = runs.find((item) => goalRunNeedsExplicitContinuationAfterWorker(item));
2660
2235
  if (queued)
2661
2236
  setTimeout(() => continueGoalRun(queued.id), 750);
2662
2237
  })().catch((err) => log("ERROR", "goal", err instanceof Error ? err.message : String(err)));
@@ -2670,7 +2245,7 @@ export function App(props) {
2670
2245
  useEffect(() => {
2671
2246
  return subscribeGoalWorkerCompletions((completion) => {
2672
2247
  void (async () => {
2673
- const latestRun = (await loadGoalRuns(completion.worker.cwd)).find((item) => item.id === completion.worker.goalRunId) ?? null;
2248
+ const latestRun = (await loadGoalRuns(completion.worker.projectPath)).find((item) => item.id === completion.worker.goalRunId) ?? null;
2674
2249
  if (!latestRun) {
2675
2250
  log("WARN", "goal", `Worker completion for unknown Goal ${completion.worker.goalRunId}`);
2676
2251
  return;
@@ -2724,6 +2299,9 @@ export function App(props) {
2724
2299
  }
2725
2300
  const decision = decideGoalNextAction(checkedRun);
2726
2301
  await appendGoalDecision(props.cwd, checkedRun.id, decision);
2302
+ if (!shouldKeepGoalRunTrackedAfterDecision(decision)) {
2303
+ runningGoalIdsRef.current.delete(checkedRun.id);
2304
+ }
2727
2305
  if (decision.kind === "terminal") {
2728
2306
  const terminalProgress = formatGoalTerminalProgress(checkedRun);
2729
2307
  if (terminalProgress) {
@@ -2741,8 +2319,10 @@ export function App(props) {
2741
2319
  phase: "worker_started",
2742
2320
  title: decision.workerId
2743
2321
  ? `Goal working: ${checkedRun.title}`
2744
- : `Goal active: ${checkedRun.title}`,
2745
- detail: decision.reason,
2322
+ : `Goal needs orchestration: ${checkedRun.title}`,
2323
+ detail: decision.workerId
2324
+ ? decision.reason
2325
+ : `${decision.reason} Asking the orchestrator to unblock or revise the Goal plan.`,
2746
2326
  workerId: decision.workerId,
2747
2327
  });
2748
2328
  upsertGoalStatusEntry({
@@ -2754,6 +2334,14 @@ export function App(props) {
2754
2334
  workerId: decision.workerId,
2755
2335
  goalNumber: goalNumberForRun(checkedRun.id),
2756
2336
  });
2337
+ if (!decision.workerId) {
2338
+ const eventText = `Goal continuation is waiting with no active worker for Goal ${checkedRun.id} (${checkedRun.title}).\n` +
2339
+ `Reason: ${decision.reason}\n\n` +
2340
+ `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.`;
2341
+ setLastUserMessage("");
2342
+ setDoneStatus(null);
2343
+ await agentLoop.run(eventText);
2344
+ }
2757
2345
  return;
2758
2346
  }
2759
2347
  if (decision.kind === "complete") {
@@ -2775,14 +2363,38 @@ export function App(props) {
2775
2363
  return;
2776
2364
  }
2777
2365
  if (decision.kind === "create_task") {
2366
+ const latestRunBeforeCreate = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ?? checkedRun;
2367
+ const existingSameTitleTask = latestRunBeforeCreate.tasks.find((item) => item.title === decision.title);
2368
+ if (existingSameTitleTask) {
2369
+ const runWithExistingTask = await upsertGoalRun(props.cwd, {
2370
+ ...latestRunBeforeCreate,
2371
+ status: "ready",
2372
+ });
2373
+ appendGoalProgress({
2374
+ kind: "goal_progress",
2375
+ phase: "continuing",
2376
+ title: `Goal task already exists: ${decision.title}`,
2377
+ detail: "Reusing the existing Goal task instead of creating a duplicate.",
2378
+ status: "ready",
2379
+ });
2380
+ startGoalRunRef.current(runWithExistingTask);
2381
+ return;
2382
+ }
2778
2383
  await updateGoalTask(props.cwd, checkedRun.id, `auto-${Date.now()}`, {
2779
2384
  title: decision.title,
2780
2385
  prompt: decision.prompt,
2781
2386
  status: "pending",
2782
2387
  });
2783
2388
  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);
2389
+ const runWithTask = await upsertGoalRun(props.cwd, { ...latestRun, status: "ready" });
2390
+ appendGoalProgress({
2391
+ kind: "goal_progress",
2392
+ phase: "continuing",
2393
+ title: `Goal task created: ${decision.title}`,
2394
+ detail: "Starting the new Goal task now.",
2395
+ status: "ready",
2396
+ });
2397
+ startGoalRunRef.current(runWithTask);
2786
2398
  return;
2787
2399
  }
2788
2400
  if (decision.kind === "blocked") {
@@ -2844,6 +2456,7 @@ export function App(props) {
2844
2456
  goalTaskId: decision.task.id,
2845
2457
  taskTitle: decision.task.title,
2846
2458
  prompt: buildGoalTaskPromptWithReferences(checkedRun, decision.task.prompt),
2459
+ isolateWorktree: shouldRunGoalTaskInMainCheckout(decision.task.title) ? false : undefined,
2847
2460
  });
2848
2461
  const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ??
2849
2462
  runWithAttempt;
@@ -2866,17 +2479,39 @@ export function App(props) {
2866
2479
  });
2867
2480
  upsertGoalStatusEntry({
2868
2481
  runId: checkedRun.id,
2869
- label: decision.task.title,
2482
+ label: checkedRun.title,
2870
2483
  phase: "worker",
2871
2484
  startedAt: Date.now(),
2872
2485
  detail: "background worker running",
2873
2486
  workerId: worker.id,
2874
2487
  goalNumber: goalNumberForRun(checkedRun.id),
2488
+ ...goalTaskProgress(checkedRun, decision.task),
2875
2489
  });
2876
- })().catch((err) => {
2490
+ })().catch(async (err) => {
2877
2491
  clearGoalStatusEntry(run.id);
2878
2492
  clearGoalModeIfIdle();
2879
2493
  log("ERROR", "goal", err instanceof Error ? err.message : String(err));
2494
+ if (isGoalWorktreeDirtyError(err)) {
2495
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
2496
+ const pausedRun = await upsertGoalRun(props.cwd, buildGoalDirtyWorktreePauseRun(latestRun, err));
2497
+ runningGoalIdsRef.current.delete(pausedRun.id);
2498
+ appendGoalProgress({
2499
+ kind: "goal_progress",
2500
+ phase: "terminal",
2501
+ title: `Goal paused: ${pausedRun.title}`,
2502
+ detail: "Working tree has uncommitted changes; waiting for the user to choose commit, stash, or pause.",
2503
+ status: "paused",
2504
+ });
2505
+ setLiveItems((prev) => [
2506
+ ...prev,
2507
+ { kind: "info", text: goalDirtyWorktreeInfoText(), id: getId() },
2508
+ ]);
2509
+ void agentLoop.run(buildGoalDirtyWorktreeUserPrompt(err)).catch((agentErr) => {
2510
+ log("ERROR", "goal", agentErr instanceof Error ? agentErr.message : String(agentErr));
2511
+ setLiveItems((prev) => [...prev, toErrorItem(agentErr, getId(), "Goal")]);
2512
+ });
2513
+ return;
2514
+ }
2880
2515
  setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
2881
2516
  });
2882
2517
  }, [
@@ -2884,6 +2519,7 @@ export function App(props) {
2884
2519
  currentProvider,
2885
2520
  currentModel,
2886
2521
  thinkingLevel,
2522
+ agentLoop,
2887
2523
  appendGoalProgress,
2888
2524
  clearGoalModeIfIdle,
2889
2525
  clearGoalStatusEntry,
@@ -2916,6 +2552,30 @@ export function App(props) {
2916
2552
  clearGoalModeIfIdle();
2917
2553
  return;
2918
2554
  }
2555
+ const integration = await checkGoalWorktreeIntegration(props.cwd, run);
2556
+ if (!integration.ok) {
2557
+ const runWithEvidence = (await appendGoalEvidence(props.cwd, run.id, {
2558
+ kind: "summary",
2559
+ label: "Goal worktree integration required",
2560
+ content: integration.summary,
2561
+ })) ?? run;
2562
+ await upsertGoalRun(props.cwd, {
2563
+ ...runWithEvidence,
2564
+ status: "blocked",
2565
+ blockers: Array.from(new Set([...runWithEvidence.blockers, integration.summary])),
2566
+ });
2567
+ appendGoalProgress({
2568
+ kind: "goal_progress",
2569
+ phase: "terminal",
2570
+ title: `Goal blocked before verifier: ${run.title}`,
2571
+ detail: integration.summary,
2572
+ status: "blocked",
2573
+ });
2574
+ runningGoalIdsRef.current.delete(run.id);
2575
+ clearGoalStatusEntry(run.id);
2576
+ clearGoalModeIfIdle();
2577
+ return;
2578
+ }
2919
2579
  activeVerifierRunIdsRef.current.add(run.id);
2920
2580
  await upsertGoalRun(props.cwd, {
2921
2581
  ...run,
@@ -2940,7 +2600,7 @@ export function App(props) {
2940
2600
  goalNumber: goalNumberForRun(run.id),
2941
2601
  });
2942
2602
  void runGoalVerifierCommand({
2943
- cwd: props.cwd,
2603
+ cwd: run.verifier.cwd ?? props.cwd,
2944
2604
  runId: run.id,
2945
2605
  command: run.verifier.command,
2946
2606
  timeoutMs: verifierTimeoutMs,
@@ -2958,6 +2618,7 @@ export function App(props) {
2958
2618
  ...latestRun.verifier,
2959
2619
  description: latestRun.verifier?.description ?? "Goal verifier",
2960
2620
  command: run.verifier?.command,
2621
+ ...(run.verifier?.cwd ? { cwd: run.verifier.cwd } : {}),
2961
2622
  lastResult: verification,
2962
2623
  },
2963
2624
  ...(status === "pass"
@@ -2987,7 +2648,7 @@ export function App(props) {
2987
2648
  await appendGoalDecision(props.cwd, run.id, {
2988
2649
  kind: `verifier_${status}`,
2989
2650
  reason: `${failureClass}: verifier exited with code ${verification.exitCode ?? 1}.`,
2990
- content: `outputPath=${outputPath ?? ""}; durationMs=${durationMs}`,
2651
+ content: `outputPath=${outputPath ?? ""}; cwd=${run.verifier?.cwd ?? props.cwd}; durationMs=${durationMs}`,
2991
2652
  });
2992
2653
  appendGoalProgress({
2993
2654
  kind: "goal_progress",
@@ -3034,11 +2695,7 @@ export function App(props) {
3034
2695
  if (run.activeWorkerId)
3035
2696
  await stopGoalWorker(run.activeWorkerId);
3036
2697
  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
- });
2698
+ await upsertGoalRun(props.cwd, buildGoalUserPauseRun(latestRun));
3042
2699
  appendGoalProgress({
3043
2700
  kind: "goal_progress",
3044
2701
  phase: "terminal",
@@ -3088,8 +2745,7 @@ export function App(props) {
3088
2745
  })();
3089
2746
  return;
3090
2747
  }
3091
- pendingHistoryFlushRef.current = [];
3092
- props.terminalHistoryPrinter?.clear();
2748
+ clearPendingHistory();
3093
2749
  setHistory([{ kind: "banner", id: "banner" }]);
3094
2750
  setLiveItems([]);
3095
2751
  messagesRef.current = messagesRef.current.slice(0, 1);
@@ -3142,13 +2798,6 @@ export function App(props) {
3142
2798
  log("WARN", "pixel", `chdir failed: ${err.message}`);
3143
2799
  }
3144
2800
  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
2801
  setDisplayedCwd(prep.projectPath);
3153
2802
  let toolsForPixelFix = currentToolsRef.current;
3154
2803
  if (props.rebuildToolsForCwd) {
@@ -3172,8 +2821,7 @@ export function App(props) {
3172
2821
  });
3173
2822
  // Now that the cwd swap is committed, reset chat. Do not clear the
3174
2823
  // terminal here; terminal clear sequences can erase saved scrollback.
3175
- pendingHistoryFlushRef.current = [];
3176
- props.terminalHistoryPrinter?.clear();
2824
+ clearPendingHistory();
3177
2825
  setHistory([{ kind: "banner", id: "banner" }]);
3178
2826
  setLiveItems([]);
3179
2827
  messagesRef.current = messagesRef.current.slice(0, 1);
@@ -3219,60 +2867,26 @@ export function App(props) {
3219
2867
  }, [runAllPixel, props.sessionStore]);
3220
2868
  const isSkillsView = overlay === "skills";
3221
2869
  const isPlanView = overlay === "plan";
3222
- const footerStatusLayout = getFooterStatusLayoutDecision({
2870
+ const { footerStatusLayout, activityVisible, stallStatusVisible, statusSlotVisible, mainControlsRef, measuredLiveAreaRows, } = useChatLayoutMeasurements({
2871
+ rows,
3223
2872
  columns,
3224
2873
  backgroundTaskCount: bgTasks.length,
3225
2874
  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,
2875
+ agentRunning: agentLoop.isRunning,
2876
+ activityPhase: agentLoop.activityPhase,
2877
+ stallError: agentLoop.stallError,
2878
+ doneStatus,
2879
+ currentModel,
2880
+ contextUsed: agentLoop.contextUsed,
3255
2881
  contextWindowOptions,
3256
- cwd: displayedCwd,
2882
+ displayedCwd,
3257
2883
  gitBranch,
3258
2884
  thinkingLevel,
3259
2885
  goalMode,
3260
- });
3261
- const chatControlsLayout = getChatControlsLayoutDecision({
3262
- rows,
3263
- columns,
3264
- agentRunning: agentLoop.isRunning,
3265
- activityVisible,
3266
- doneStatusVisible,
3267
- stallStatusVisible,
3268
2886
  exitPending,
3269
- footerStatusLayout,
3270
2887
  taskBarExpanded,
3271
2888
  goalStatusEntryCount: goalStatusEntries.length,
3272
- footerFitsOnOneLine,
3273
2889
  });
3274
- const stableControlsRows = controlsHeight > 0 ? controlsHeight : chatControlsLayout.controlsRows;
3275
- const measuredLiveAreaRows = Math.max(MIN_LIVE_AREA_ROWS, rows - stableControlsRows - 1);
3276
2890
  const isPixelView = overlay === "pixel";
3277
2891
  const hasLiveAssistantItem = liveItems.some((item) => item.kind === "assistant");
3278
2892
  const rawVisibleStreamingText = goalModeStateRef.current === "planner" || hasLiveAssistantItem ? "" : agentLoop.streamingText;
@@ -3290,7 +2904,7 @@ export function App(props) {
3290
2904
  queueFlush([
3291
2905
  {
3292
2906
  kind: "assistant",
3293
- text: split.flushedText,
2907
+ text: stripDoneMarkers(split.flushedText),
3294
2908
  continuation: streamedAssistantFlushRef.current.flushedChars > 0,
3295
2909
  id: getId(),
3296
2910
  },
@@ -3306,13 +2920,20 @@ export function App(props) {
3306
2920
  text: rawVisibleStreamingText,
3307
2921
  };
3308
2922
  }, [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));
2923
+ const visibleStreamingText = stripDoneMarkers(rawVisibleStreamingText.slice(streamedAssistantFlushRef.current.flushedChars));
3313
2924
  const lastLiveItem = liveItems.at(-1);
3314
2925
  const lastPendingHistoryItem = pendingHistoryFlushRef.current.at(-1);
3315
2926
  const lastHistoryItem = history.at(-1);
2927
+ const previousTranscriptItem = lastPendingHistoryItem ?? lastHistoryItem;
2928
+ const isAwaitingAssistantAfterUser = agentLoop.isRunning &&
2929
+ !hasLiveAssistantItem &&
2930
+ visibleStreamingText.trim().length === 0 &&
2931
+ (lastLiveItem?.kind === "user" || (!lastLiveItem && previousTranscriptItem?.kind === "user"));
2932
+ const shouldReserveStreamingSpacing = agentLoop.isRunning &&
2933
+ !hasLiveAssistantItem &&
2934
+ (visibleStreamingText.trim().length > 0 ||
2935
+ liveItems.some(isTranscriptSpacingItem) ||
2936
+ isAwaitingAssistantAfterUser);
3316
2937
  const shouldTopSpaceStreamingText = shouldTopSpaceStreamingAssistant({
3317
2938
  visibleStreamingText,
3318
2939
  lastLiveItem,
@@ -3328,215 +2949,285 @@ export function App(props) {
3328
2949
  lastPendingHistoryItem,
3329
2950
  lastHistoryItem,
3330
2951
  });
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,
2952
+ const handleCloseRemountableOverlay = () => {
2953
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2954
+ props.sessionStore.overlay = null;
2955
+ props.resetUI();
2956
+ return;
2957
+ }
2958
+ if (props.sessionStore) {
2959
+ props.sessionStore.overlay = null;
2960
+ if (agentLoop.isRunning)
2961
+ props.sessionStore.pendingResetUI = true;
2962
+ }
2963
+ setOverlay(null);
2964
+ };
2965
+ const handleEnterPlanMode = useCallback(async (reason) => {
2966
+ await setPlanModeAndPrompt(true);
2967
+ const detail = reason ? `Plan mode ON — ${reason}` : "Plan mode ON";
2968
+ setLiveItems((prev) => [
2969
+ ...prev,
2970
+ { kind: "plan_transition", text: detail, id: getId(), active: true },
2971
+ ]);
2972
+ }, [setPlanModeAndPrompt]);
2973
+ const handleExitPlanMode = useCallback(async (planPath) => {
2974
+ await setPlanModeAndPrompt(false);
2975
+ planOverlayPendingRef.current = true;
2976
+ setPlanAutoExpand(true);
2977
+ if (props.sessionStore) {
2978
+ props.sessionStore.overlay = "plan";
2979
+ props.sessionStore.planAutoExpand = true;
2980
+ }
2981
+ setOverlay("plan");
2982
+ setLiveItems((prev) => [
2983
+ ...prev,
2984
+ {
2985
+ kind: "plan_transition",
2986
+ text: `Plan ready for review: ${planPath}`,
2987
+ id: getId(),
2988
+ active: false,
2989
+ },
2990
+ ]);
2991
+ return "Plan submitted for user review. Wait for the user to approve, reject, or dismiss it before implementing.";
2992
+ }, [props.sessionStore, setPlanModeAndPrompt]);
2993
+ useEffect(() => {
2994
+ if (!props.planCallbacks)
2995
+ return;
2996
+ props.planCallbacks.onEnterPlan = handleEnterPlanMode;
2997
+ props.planCallbacks.onExitPlan = handleExitPlanMode;
2998
+ }, [handleEnterPlanMode, handleExitPlanMode, props.planCallbacks]);
2999
+ const handleClosePlanOverlay = () => {
3000
+ planOverlayPendingRef.current = false;
3001
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3002
+ props.sessionStore.overlay = null;
3003
+ props.sessionStore.planAutoExpand = false;
3004
+ props.resetUI();
3005
+ return;
3006
+ }
3007
+ if (props.sessionStore) {
3008
+ props.sessionStore.overlay = null;
3009
+ props.sessionStore.planAutoExpand = false;
3010
+ if (agentLoop.isRunning)
3011
+ props.sessionStore.pendingResetUI = true;
3012
+ }
3013
+ setPlanAutoExpand(false);
3014
+ setOverlay(null);
3015
+ };
3016
+ const handlePixelFixOne = (entry) => {
3017
+ setOverlay(null);
3018
+ startPixelFix(entry.errorId);
3019
+ };
3020
+ const handlePixelFixAll = (entries) => {
3021
+ const first = entries.find((entry) => entry.status === "open") ?? entries[0];
3022
+ if (!first)
3023
+ return;
3024
+ setOverlay(null);
3025
+ setRunAllPixel(true);
3026
+ startPixelFix(first.errorId);
3027
+ };
3028
+ const handleApprovePlan = (planPath) => {
3029
+ log("INFO", "plan", "Plan approved — transitioning to implementation", {
3030
+ planPath,
3031
+ });
3032
+ planOverlayPendingRef.current = false;
3033
+ void (async () => {
3034
+ try {
3035
+ // Read plan steps for progress tracking — handed to the new
3036
+ // mount via sessionStore.planSteps below.
3037
+ const planContent = await import("node:fs/promises").then(({ readFile }) => readFile(planPath, "utf-8"));
3038
+ const steps = extractPlanSteps(planContent);
3039
+ // Build the new system prompt with the approved plan baked in.
3040
+ const newPrompt = await rebuildSystemPrompt({
3041
+ approvedPlanPath: planPath,
3387
3042
  });
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.`;
3043
+ // Create a new session file BEFORE remount so the new tree
3044
+ // picks it up via sessionStore.sessionPath.
3045
+ let newSessionPath;
3046
+ const sm = sessionManagerRef.current;
3047
+ if (sm) {
3048
+ const s = await sm.create(props.cwd, currentProvider, currentModel);
3049
+ newSessionPath = s.path;
3050
+ }
3460
3051
  if (props.resetUI && props.sessionStore) {
3052
+ // Clear the overlay so the new mount lands on the chat,
3053
+ // not back inside the plan pane.
3461
3054
  props.sessionStore.overlay = null;
3462
3055
  props.sessionStore.planAutoExpand = false;
3463
- // No wipeSession — keep history and messages so the agent picks
3464
- // up the rejection mid-conversation.
3465
3056
  props.resetUI({
3057
+ wipeSession: true,
3058
+ messages: [{ role: "system", content: newPrompt }],
3059
+ approvedPlanPath: planPath,
3060
+ planSteps: steps,
3061
+ sessionPath: newSessionPath,
3466
3062
  pendingAction: {
3467
- prompt: rejectionMsg,
3468
- planEvent: { event: "rejected", detail: feedback },
3063
+ prompt: "The plan has been approved. Implement it now, following each step in order.",
3064
+ planEvent: { event: "approved" },
3469
3065
  },
3470
3066
  });
3471
3067
  return;
3472
3068
  }
3069
+ // Fallback path (resetUI not wired — tests). Mutate in place.
3070
+ approvedPlanPathRef.current = planPath;
3071
+ planStepsRef.current = steps;
3072
+ setPlanSteps(steps);
3073
+ clearPendingHistory();
3074
+ setHistory([{ kind: "banner", id: "banner" }]);
3075
+ setLiveItems([]);
3473
3076
  setPlanAutoExpand(false);
3474
3077
  setOverlay(null);
3475
- setDoneStatus(null);
3476
- setLiveItems((prev) => [
3477
- ...prev,
3478
- { kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
3078
+ messagesRef.current = [{ role: "system", content: newPrompt }];
3079
+ agentLoop.reset();
3080
+ persistedIndexRef.current = messagesRef.current.length;
3081
+ if (newSessionPath)
3082
+ sessionPathRef.current = newSessionPath;
3083
+ setLiveItems([
3084
+ {
3085
+ kind: "info",
3086
+ text: "Plan approved — starting fresh session for implementation",
3087
+ id: getId(),
3088
+ },
3479
3089
  ]);
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}`;
3090
+ setDoneStatus(null);
3091
+ await agentLoop.run("The plan has been approved. Implement it now, following each step in order.");
3092
+ }
3093
+ catch (err) {
3094
+ const errMsg = err instanceof Error ? err.message : String(err);
3095
+ log("ERROR", "error", errMsg);
3096
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3097
+ }
3098
+ })();
3099
+ };
3100
+ const handleRejectPlan = (planPath, feedback) => {
3101
+ planOverlayPendingRef.current = false;
3102
+ const rejectionMsg = `The plan at ${planPath} was rejected.\n\nFeedback: ${feedback}\n\n` +
3103
+ `Please revise the plan based on this feedback.`;
3104
+ if (props.resetUI && props.sessionStore) {
3105
+ props.sessionStore.overlay = null;
3106
+ props.sessionStore.planAutoExpand = false;
3107
+ // No wipeSession — keep history and messages so the agent picks
3108
+ // up the rejection mid-conversation.
3109
+ props.resetUI({
3110
+ pendingAction: {
3111
+ prompt: rejectionMsg,
3112
+ planEvent: { event: "rejected", detail: feedback },
3113
+ },
3114
+ });
3115
+ return;
3116
+ }
3117
+ setPlanAutoExpand(false);
3118
+ setOverlay(null);
3119
+ setDoneStatus(null);
3120
+ setLiveItems((prev) => [
3121
+ ...prev,
3122
+ { kind: "info", text: `Plan rejected — "${feedback}"`, id: getId() },
3123
+ ]);
3124
+ void agentLoop.run(rejectionMsg).catch((err) => {
3125
+ const errMsg = err instanceof Error ? err.message : String(err);
3126
+ log("ERROR", "error", errMsg);
3127
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3128
+ });
3129
+ };
3130
+ const handleRunGoalFromPicker = (run) => {
3131
+ setDoneStatus(null);
3132
+ appendGoalProgress({
3133
+ kind: "goal_progress",
3134
+ phase: "continuing",
3135
+ title: `Goal run requested: ${run.title}`,
3136
+ detail: "Enter pressed in Ctrl+G; starting the Goal orchestrator.",
3137
+ status: run.status,
3138
+ });
3139
+ log("INFO", "goal", `Goal run requested from Ctrl+G: ${run.title}`, { id: run.id });
3140
+ void (async () => {
3141
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3142
+ const requestedAt = new Date().toISOString();
3143
+ const runWithContinuation = await upsertGoalRun(props.cwd, {
3144
+ ...latestRun,
3145
+ status: latestRun.status === "running" || latestRun.status === "verifying"
3146
+ ? latestRun.status
3147
+ : "ready",
3148
+ continueRequestedAt: requestedAt,
3149
+ blockers: goalHasBlockingPrerequisites(latestRun) ? latestRun.blockers : [],
3150
+ evidence: [
3151
+ ...latestRun.evidence,
3152
+ {
3153
+ id: `goal-rerun-${requestedAt}`,
3154
+ kind: "summary",
3155
+ label: "Goal rerun requested",
3156
+ content: "Continuation requested from Ctrl+G; the orchestrator will choose the next eligible Goal action.",
3157
+ createdAt: requestedAt,
3158
+ },
3159
+ ],
3160
+ });
3161
+ startGoalRun(runWithContinuation);
3162
+ })().catch((err) => {
3163
+ log("ERROR", "goal", err instanceof Error ? err.message : String(err));
3164
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3165
+ });
3166
+ };
3167
+ const handleDeleteGoalSideEffects = async (run) => {
3168
+ runningGoalIdsRef.current.delete(run.id);
3169
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3170
+ if (latestRun.activeWorkerId)
3171
+ await stopGoalWorker(latestRun.activeWorkerId);
3172
+ clearGoalStatusEntry(run.id);
3173
+ clearGoalModeIfIdle();
3174
+ };
3175
+ const handleGoalPickerError = (err) => {
3176
+ log("ERROR", "goal", err instanceof Error ? err.message : String(err));
3177
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3178
+ };
3179
+ const goalPicker = useGoalPickerController({
3180
+ cwd: props.cwd,
3181
+ onRunGoal: handleRunGoalFromPicker,
3182
+ onDeleteGoalSideEffects: handleDeleteGoalSideEffects,
3183
+ onPauseGoal: pauseGoalRun,
3184
+ onError: handleGoalPickerError,
3185
+ });
3186
+ const handleToggleTasks = () => {
3187
+ goalPicker.close();
3188
+ taskPicker.toggle();
3189
+ };
3190
+ const handleToggleGoalPicker = () => {
3191
+ taskPicker.close();
3192
+ goalPicker.toggle();
3193
+ };
3194
+ const fullScreenOverlay = isPixelView
3195
+ ? "pixel"
3196
+ : isSkillsView
3197
+ ? "skills"
3198
+ : isPlanView
3199
+ ? "plan"
3200
+ : null;
3201
+ if (quittingSummary) {
3202
+ return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: _jsx(SessionSummaryDisplay, { summary: quittingSummary }) }));
3203
+ }
3204
+ 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: {
3205
+ onSubmit: handleSubmit,
3206
+ onAbort: handleAbort,
3207
+ inputActive: !taskBarFocused && !overlay,
3208
+ onDownAtEnd: handleFocusTaskBar,
3209
+ onShiftTab: handleToggleThinking,
3210
+ onToggleTasks: handleToggleTasks,
3211
+ onToggleGoal: handleToggleGoalPicker,
3212
+ onToggleSkills: () => openOverlay("skills"),
3213
+ onTogglePixel: () => openOverlay("pixel"),
3214
+ onToggleMarkdown: () => setRenderMarkdown((prev) => !prev),
3215
+ cwd: props.cwd,
3216
+ commands: allCommands,
3217
+ }, taskPicker: {
3218
+ open: taskPicker.open,
3219
+ tasks: taskPicker.tasks,
3220
+ onClose: taskPicker.close,
3221
+ onStart: taskPicker.start,
3222
+ onRunAll: taskPicker.runAll,
3223
+ onDelete: taskPicker.deleteTask,
3224
+ }, goalPicker: {
3225
+ open: goalPicker.open,
3226
+ goals: goalPicker.goals,
3227
+ onClose: goalPicker.close,
3228
+ onRun: goalPicker.run,
3229
+ onDelete: goalPicker.deleteGoal,
3230
+ onPause: goalPicker.pause,
3231
+ }, 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
3232
  }
3542
3233
  //# sourceMappingURL=App.js.map