@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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -22
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +0 -7
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +0 -23
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -98
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/continue-replay-inventory.test.js +1 -1
- package/dist/core/continue-replay-inventory.test.js.map +1 -1
- package/dist/core/goal-controller.d.ts +2 -0
- package/dist/core/goal-controller.d.ts.map +1 -1
- package/dist/core/goal-controller.js +152 -8
- package/dist/core/goal-controller.js.map +1 -1
- package/dist/core/goal-controller.test.js +232 -3
- package/dist/core/goal-controller.test.js.map +1 -1
- package/dist/core/goal-overhead-harness.d.ts +33 -0
- package/dist/core/goal-overhead-harness.d.ts.map +1 -0
- package/dist/core/goal-overhead-harness.js +268 -0
- package/dist/core/goal-overhead-harness.js.map +1 -0
- package/dist/core/goal-store.d.ts +1 -0
- package/dist/core/goal-store.d.ts.map +1 -1
- package/dist/core/goal-store.js +43 -3
- package/dist/core/goal-store.js.map +1 -1
- package/dist/core/goal-store.test.js +13 -0
- package/dist/core/goal-store.test.js.map +1 -1
- package/dist/core/goal-worker.d.ts.map +1 -1
- package/dist/core/goal-worker.js +17 -11
- package/dist/core/goal-worker.js.map +1 -1
- package/dist/core/goal-worker.test.js +41 -8
- package/dist/core/goal-worker.test.js.map +1 -1
- package/dist/core/goal-worktree.d.ts +21 -0
- package/dist/core/goal-worktree.d.ts.map +1 -1
- package/dist/core/goal-worktree.js +71 -1
- package/dist/core/goal-worktree.js.map +1 -1
- package/dist/core/goal-worktree.test.js +127 -2
- package/dist/core/goal-worktree.test.js.map +1 -1
- package/dist/core/prompt-commands.js +2 -2
- package/dist/core/prompt-commands.test.js +1 -1
- package/dist/core/prompt-commands.test.js.map +1 -1
- package/dist/core/runtime-mode.d.ts +7 -0
- package/dist/core/runtime-mode.d.ts.map +1 -1
- package/dist/core/runtime-mode.js +6 -0
- package/dist/core/runtime-mode.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +20 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-restore-display.test.js +84 -0
- package/dist/core/session-restore-display.test.js.map +1 -1
- package/dist/core/slash-commands.d.ts +0 -2
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -15
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/system-prompt.d.ts +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +13 -1
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +2 -2
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts +2 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +5 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +16 -3
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/enter-plan.d.ts +8 -0
- package/dist/tools/enter-plan.d.ts.map +1 -0
- package/dist/tools/enter-plan.js +27 -0
- package/dist/tools/enter-plan.js.map +1 -0
- package/dist/tools/exit-plan.d.ts +8 -0
- package/dist/tools/exit-plan.d.ts.map +1 -0
- package/dist/tools/exit-plan.js +35 -0
- package/dist/tools/exit-plan.js.map +1 -0
- package/dist/tools/goals.d.ts +8 -5
- package/dist/tools/goals.d.ts.map +1 -1
- package/dist/tools/goals.js +124 -49
- package/dist/tools/goals.js.map +1 -1
- package/dist/tools/goals.test.js +356 -11
- package/dist/tools/goals.test.js.map +1 -1
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +15 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/plan-mode.test.js +63 -24
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts.map +1 -1
- package/dist/tools/prompt-hints.js +4 -0
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/subagent.d.ts +3 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +5 -2
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/write.d.ts +2 -0
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +23 -3
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/App.d.ts +13 -6
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +617 -926
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +8 -1
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +1 -1
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/app-state-persistence.test.js +22 -22
- package/dist/ui/app-state-persistence.test.js.map +1 -1
- package/dist/ui/components/AssistantMessage.test.js +7 -7
- package/dist/ui/components/AssistantMessage.test.js.map +1 -1
- package/dist/ui/components/BackgroundTasksBar.d.ts.map +1 -1
- package/dist/ui/components/BackgroundTasksBar.js +6 -6
- package/dist/ui/components/BackgroundTasksBar.js.map +1 -1
- package/dist/ui/components/ChatFooterPane.d.ts +29 -0
- package/dist/ui/components/ChatFooterPane.d.ts.map +1 -0
- package/dist/ui/components/ChatFooterPane.js +16 -0
- package/dist/ui/components/ChatFooterPane.js.map +1 -0
- package/dist/ui/components/ChatInputStack.d.ts +34 -0
- package/dist/ui/components/ChatInputStack.d.ts.map +1 -0
- package/dist/ui/components/ChatInputStack.js +9 -0
- package/dist/ui/components/ChatInputStack.js.map +1 -0
- package/dist/ui/components/ChatLayout.d.ts +23 -0
- package/dist/ui/components/ChatLayout.d.ts.map +1 -0
- package/dist/ui/components/ChatLayout.js +16 -0
- package/dist/ui/components/ChatLayout.js.map +1 -0
- package/dist/ui/components/ChatLivePane.d.ts +18 -0
- package/dist/ui/components/ChatLivePane.d.ts.map +1 -0
- package/dist/ui/components/ChatLivePane.js +8 -0
- package/dist/ui/components/ChatLivePane.js.map +1 -0
- package/dist/ui/components/ChatScreen.d.ts +118 -0
- package/dist/ui/components/ChatScreen.d.ts.map +1 -0
- package/dist/ui/components/ChatScreen.js +14 -0
- package/dist/ui/components/ChatScreen.js.map +1 -0
- package/dist/ui/components/ChatStatusRow.d.ts +34 -0
- package/dist/ui/components/ChatStatusRow.d.ts.map +1 -0
- package/dist/ui/components/ChatStatusRow.js +11 -0
- package/dist/ui/components/ChatStatusRow.js.map +1 -0
- package/dist/ui/components/CompactionNotice.d.ts +4 -2
- package/dist/ui/components/CompactionNotice.d.ts.map +1 -1
- package/dist/ui/components/CompactionNotice.js +4 -4
- package/dist/ui/components/CompactionNotice.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +6 -3
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +14 -4
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/FooterStatusRow.d.ts +20 -0
- package/dist/ui/components/FooterStatusRow.d.ts.map +1 -0
- package/dist/ui/components/FooterStatusRow.js +10 -0
- package/dist/ui/components/FooterStatusRow.js.map +1 -0
- package/dist/ui/components/FullScreenOverlayRouter.d.ts +19 -0
- package/dist/ui/components/FullScreenOverlayRouter.d.ts.map +1 -0
- package/dist/ui/components/FullScreenOverlayRouter.js +18 -0
- package/dist/ui/components/FullScreenOverlayRouter.js.map +1 -0
- package/dist/ui/components/GoalOverlay.d.ts +2 -1
- package/dist/ui/components/GoalOverlay.d.ts.map +1 -1
- package/dist/ui/components/GoalOverlay.js +11 -6
- package/dist/ui/components/GoalOverlay.js.map +1 -1
- package/dist/ui/components/GoalStatusBar.d.ts +2 -0
- package/dist/ui/components/GoalStatusBar.d.ts.map +1 -1
- package/dist/ui/components/GoalStatusBar.js +27 -11
- package/dist/ui/components/GoalStatusBar.js.map +1 -1
- package/dist/ui/components/GoalStatusBar.test.d.ts +2 -0
- package/dist/ui/components/GoalStatusBar.test.d.ts.map +1 -0
- package/dist/ui/components/GoalStatusBar.test.js +17 -0
- package/dist/ui/components/GoalStatusBar.test.js.map +1 -0
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +6 -5
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/PlanOverlay.d.ts +7 -0
- package/dist/ui/components/PlanOverlay.d.ts.map +1 -1
- package/dist/ui/components/PlanOverlay.js +16 -2
- package/dist/ui/components/PlanOverlay.js.map +1 -1
- package/dist/ui/components/PlanOverlay.test.d.ts +2 -0
- package/dist/ui/components/PlanOverlay.test.d.ts.map +1 -0
- package/dist/ui/components/PlanOverlay.test.js +24 -0
- package/dist/ui/components/PlanOverlay.test.js.map +1 -0
- package/dist/ui/components/QueueIndicator.d.ts +9 -0
- package/dist/ui/components/QueueIndicator.d.ts.map +1 -0
- package/dist/ui/components/QueueIndicator.js +9 -0
- package/dist/ui/components/QueueIndicator.js.map +1 -0
- package/dist/ui/components/ServerToolExecution.d.ts +2 -0
- package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ServerToolExecution.js +3 -2
- package/dist/ui/components/ServerToolExecution.js.map +1 -1
- package/dist/ui/components/SessionSummary.d.ts +5 -0
- package/dist/ui/components/SessionSummary.d.ts.map +1 -0
- package/dist/ui/components/SessionSummary.js +32 -0
- package/dist/ui/components/SessionSummary.js.map +1 -0
- package/dist/ui/components/StreamingArea.js +1 -1
- package/dist/ui/components/StreamingArea.js.map +1 -1
- package/dist/ui/components/SubAgentPanel.d.ts +2 -1
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +2 -2
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts +2 -0
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +13 -11
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/components/ToolGroupExecution.d.ts +2 -1
- package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolGroupExecution.js +5 -3
- package/dist/ui/components/ToolGroupExecution.js.map +1 -1
- package/dist/ui/duration-format.d.ts +2 -0
- package/dist/ui/duration-format.d.ts.map +1 -0
- package/dist/ui/duration-format.js +9 -0
- package/dist/ui/duration-format.js.map +1 -0
- package/dist/ui/duration-summary.d.ts +2 -0
- package/dist/ui/duration-summary.d.ts.map +1 -0
- package/dist/ui/duration-summary.js +66 -0
- package/dist/ui/duration-summary.js.map +1 -0
- package/dist/ui/error-item.d.ts +8 -0
- package/dist/ui/error-item.d.ts.map +1 -0
- package/dist/ui/error-item.js +32 -0
- package/dist/ui/error-item.js.map +1 -0
- package/dist/ui/footer-status-layout.test.js +4 -3
- package/dist/ui/footer-status-layout.test.js.map +1 -1
- package/dist/ui/goal-events.d.ts +1 -0
- package/dist/ui/goal-events.d.ts.map +1 -1
- package/dist/ui/goal-events.js +2 -1
- package/dist/ui/goal-events.js.map +1 -1
- package/dist/ui/goal-events.test.js +16 -0
- package/dist/ui/goal-events.test.js.map +1 -1
- package/dist/ui/goal-lifecycle-orchestration.test.js +105 -18
- package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
- package/dist/ui/goal-progress.d.ts +1 -1
- package/dist/ui/goal-progress.d.ts.map +1 -1
- package/dist/ui/goal-progress.js +11 -13
- package/dist/ui/goal-progress.js.map +1 -1
- package/dist/ui/goal-run-helpers.d.ts +16 -0
- package/dist/ui/goal-run-helpers.d.ts.map +1 -0
- package/dist/ui/goal-run-helpers.js +61 -0
- package/dist/ui/goal-run-helpers.js.map +1 -0
- package/dist/ui/goal-status-bar.test.js +8 -6
- package/dist/ui/goal-status-bar.test.js.map +1 -1
- package/dist/ui/hooks/useChatLayoutMeasurements.d.ts +38 -0
- package/dist/ui/hooks/useChatLayoutMeasurements.d.ts.map +1 -0
- package/dist/ui/hooks/useChatLayoutMeasurements.js +71 -0
- package/dist/ui/hooks/useChatLayoutMeasurements.js.map +1 -0
- package/dist/ui/hooks/useGoalPickerController.d.ts +22 -0
- package/dist/ui/hooks/useGoalPickerController.d.ts.map +1 -0
- package/dist/ui/hooks/useGoalPickerController.js +35 -0
- package/dist/ui/hooks/useGoalPickerController.js.map +1 -0
- package/dist/ui/hooks/useTaskPickerController.d.ts +19 -0
- package/dist/ui/hooks/useTaskPickerController.d.ts.map +1 -0
- package/dist/ui/hooks/useTaskPickerController.js +41 -0
- package/dist/ui/hooks/useTaskPickerController.js.map +1 -0
- package/dist/ui/hooks/useTranscriptHistory.d.ts +34 -0
- package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -0
- package/dist/ui/hooks/useTranscriptHistory.js +96 -0
- package/dist/ui/hooks/useTranscriptHistory.js.map +1 -0
- package/dist/ui/layout-decisions.d.ts +3 -12
- package/dist/ui/layout-decisions.d.ts.map +1 -1
- package/dist/ui/layout-decisions.js +7 -65
- package/dist/ui/layout-decisions.js.map +1 -1
- package/dist/ui/prompt-routing.d.ts.map +1 -1
- package/dist/ui/prompt-routing.js +36 -2
- package/dist/ui/prompt-routing.js.map +1 -1
- package/dist/ui/prompt-routing.test.d.ts +2 -0
- package/dist/ui/prompt-routing.test.d.ts.map +1 -0
- package/dist/ui/prompt-routing.test.js +48 -0
- package/dist/ui/prompt-routing.test.js.map +1 -0
- package/dist/ui/render.d.ts +11 -6
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +8 -2
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/session-summary.d.ts +63 -0
- package/dist/ui/session-summary.d.ts.map +1 -0
- package/dist/ui/session-summary.js +81 -0
- package/dist/ui/session-summary.js.map +1 -0
- package/dist/ui/slash-command-images.test.js +3 -2
- package/dist/ui/slash-command-images.test.js.map +1 -1
- package/dist/ui/submit-prompt-command.d.ts +40 -0
- package/dist/ui/submit-prompt-command.d.ts.map +1 -0
- package/dist/ui/submit-prompt-command.js +92 -0
- package/dist/ui/submit-prompt-command.js.map +1 -0
- package/dist/ui/submit-slash-commands.d.ts +13 -0
- package/dist/ui/submit-slash-commands.d.ts.map +1 -0
- package/dist/ui/submit-slash-commands.js +36 -0
- package/dist/ui/submit-slash-commands.js.map +1 -0
- package/dist/ui/terminal-history-format.d.ts +1 -0
- package/dist/ui/terminal-history-format.d.ts.map +1 -1
- package/dist/ui/terminal-history-format.js +2 -1
- package/dist/ui/terminal-history-format.js.map +1 -1
- package/dist/ui/terminal-history-spacing.d.ts +1 -2
- package/dist/ui/terminal-history-spacing.d.ts.map +1 -1
- package/dist/ui/terminal-history-spacing.js +1 -28
- package/dist/ui/terminal-history-spacing.js.map +1 -1
- package/dist/ui/terminal-history-status-renderers.d.ts +2 -2
- package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
- package/dist/ui/terminal-history-status-renderers.js +36 -17
- package/dist/ui/terminal-history-status-renderers.js.map +1 -1
- package/dist/ui/terminal-history.d.ts.map +1 -1
- package/dist/ui/terminal-history.js +93 -24
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/terminal-history.test.js +2 -1
- package/dist/ui/terminal-history.test.js.map +1 -1
- package/dist/ui/tool-group-summary.d.ts +2 -2
- package/dist/ui/tool-group-summary.d.ts.map +1 -1
- package/dist/ui/tool-group-summary.js +18 -18
- package/dist/ui/tool-group-summary.js.map +1 -1
- package/dist/ui/transcript/GoalRows.d.ts +10 -0
- package/dist/ui/transcript/GoalRows.d.ts.map +1 -0
- package/dist/ui/transcript/GoalRows.js +35 -0
- package/dist/ui/transcript/GoalRows.js.map +1 -0
- package/dist/ui/transcript/MiscRows.d.ts +23 -0
- package/dist/ui/transcript/MiscRows.d.ts.map +1 -0
- package/dist/ui/transcript/MiscRows.js +41 -0
- package/dist/ui/transcript/MiscRows.js.map +1 -0
- package/dist/ui/transcript/StatusRow.d.ts +14 -0
- package/dist/ui/transcript/StatusRow.d.ts.map +1 -0
- package/dist/ui/transcript/StatusRow.js +14 -0
- package/dist/ui/transcript/StatusRow.js.map +1 -0
- package/dist/ui/transcript/ToolRows.d.ts +20 -0
- package/dist/ui/transcript/ToolRows.d.ts.map +1 -0
- package/dist/ui/transcript/ToolRows.js +25 -0
- package/dist/ui/transcript/ToolRows.js.map +1 -0
- package/dist/ui/transcript/TranscriptItemFrame.d.ts +8 -0
- package/dist/ui/transcript/TranscriptItemFrame.d.ts.map +1 -0
- package/dist/ui/transcript/TranscriptItemFrame.js +9 -0
- package/dist/ui/transcript/TranscriptItemFrame.js.map +1 -0
- package/dist/ui/transcript/TranscriptRenderer.d.ts +22 -0
- package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -0
- package/dist/ui/transcript/TranscriptRenderer.js +87 -0
- package/dist/ui/transcript/TranscriptRenderer.js.map +1 -0
- package/dist/ui/transcript/presentation.d.ts +76 -0
- package/dist/ui/transcript/presentation.d.ts.map +1 -0
- package/dist/ui/transcript/presentation.js +109 -0
- package/dist/ui/transcript/presentation.js.map +1 -0
- package/dist/ui/transcript/spacing.d.ts +29 -0
- package/dist/ui/transcript/spacing.d.ts.map +1 -0
- package/dist/ui/transcript/spacing.js +91 -0
- package/dist/ui/transcript/spacing.js.map +1 -0
- package/dist/ui/transcript/spacing.test.d.ts +2 -0
- package/dist/ui/transcript/spacing.test.d.ts.map +1 -0
- package/dist/ui/transcript/spacing.test.js +21 -0
- package/dist/ui/transcript/spacing.test.js.map +1 -0
- package/dist/ui/transcript/tool-presentation.d.ts +12 -0
- package/dist/ui/transcript/tool-presentation.d.ts.map +1 -0
- package/dist/ui/transcript/tool-presentation.js +55 -0
- package/dist/ui/transcript/tool-presentation.js.map +1 -0
- package/dist/ui/tui-history-parity.test.js +42 -2
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/dist/utils/plan-steps.d.ts.map +1 -1
- package/dist/utils/plan-steps.js +5 -1
- package/dist/utils/plan-steps.js.map +1 -1
- package/dist/utils/plan-steps.test.d.ts +2 -0
- package/dist/utils/plan-steps.test.d.ts.map +1 -0
- package/dist/utils/plan-steps.test.js +16 -0
- package/dist/utils/plan-steps.test.js.map +1 -0
- package/package.json +7 -7
- package/dist/core/repomap-budget.d.ts +0 -7
- package/dist/core/repomap-budget.d.ts.map +0 -1
- package/dist/core/repomap-budget.js +0 -10
- package/dist/core/repomap-budget.js.map +0 -1
- package/dist/core/repomap-budget.test.d.ts +0 -2
- package/dist/core/repomap-budget.test.d.ts.map +0 -1
- package/dist/core/repomap-budget.test.js +0 -26
- package/dist/core/repomap-budget.test.js.map +0 -1
- package/dist/core/repomap-context.d.ts +0 -11
- package/dist/core/repomap-context.d.ts.map +0 -1
- package/dist/core/repomap-context.js +0 -68
- package/dist/core/repomap-context.js.map +0 -1
- package/dist/core/repomap-context.test.d.ts +0 -2
- package/dist/core/repomap-context.test.d.ts.map +0 -1
- package/dist/core/repomap-context.test.js +0 -47
- package/dist/core/repomap-context.test.js.map +0 -1
- package/dist/core/repomap.d.ts +0 -74
- package/dist/core/repomap.d.ts.map +0 -1
- package/dist/core/repomap.js +0 -906
- package/dist/core/repomap.js.map +0 -1
- package/dist/core/repomap.test.d.ts +0 -2
- package/dist/core/repomap.test.d.ts.map +0 -1
- package/dist/core/repomap.test.js +0 -494
- package/dist/core/repomap.test.js.map +0 -1
package/dist/ui/App.js
CHANGED
|
@@ -1,41 +1,26 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useRef, useCallback, useEffect, useMemo } from "react";
|
|
3
|
-
import { Box,
|
|
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 {
|
|
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 {
|
|
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 {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
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
|
|
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,
|
|
61
|
-
import { getNextPendingTask,
|
|
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
|
|
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,
|
|
70
|
-
import {
|
|
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
|
-
|
|
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
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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,
|
|
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(
|
|
956
|
-
if (result !==
|
|
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
|
|
732
|
+
return result;
|
|
962
733
|
}
|
|
963
734
|
if (!autoCompact)
|
|
964
|
-
return
|
|
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
|
|
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(
|
|
975
|
-
const result = await compactConversation(
|
|
976
|
-
if (result !==
|
|
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
|
|
752
|
+
return result;
|
|
982
753
|
}
|
|
983
|
-
return
|
|
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
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
-
|
|
1882
|
-
|
|
1883
|
-
messages: [{ role: "system", content: newPrompt }],
|
|
1884
|
-
});
|
|
1674
|
+
messagesRef.current = [{ role: "system", content: newPrompt }];
|
|
1675
|
+
persistedIndexRef.current = messagesRef.current.length;
|
|
1885
1676
|
})();
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
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
|
-
|
|
1816
|
+
showSessionSummaryAndExit,
|
|
2153
1817
|
reloadCustomCommands,
|
|
2154
1818
|
replaceSystemPrompt,
|
|
2155
1819
|
setActiveGoalReferences,
|
|
2156
1820
|
setGoalModeAndPrompt,
|
|
2157
|
-
stripRepoMapMessages,
|
|
2158
1821
|
]);
|
|
2159
|
-
const handleDoubleExit = useDoublePress(setExitPending,
|
|
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
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
2659
|
-
const queued = runs.find((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.
|
|
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
|
|
2745
|
-
detail: decision.
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2870
|
+
const { footerStatusLayout, activityVisible, stallStatusVisible, statusSlotVisible, mainControlsRef, measuredLiveAreaRows, } = useChatLayoutMeasurements({
|
|
2871
|
+
rows,
|
|
3223
2872
|
columns,
|
|
3224
2873
|
backgroundTaskCount: bgTasks.length,
|
|
3225
2874
|
updatePending,
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
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
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
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:
|
|
3468
|
-
planEvent: { event: "
|
|
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
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
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
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
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
|