@kenkaiiii/ggcoder 4.3.217 → 4.3.219

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/cli/command-routing.d.ts +28 -0
  2. package/dist/cli/command-routing.d.ts.map +1 -0
  3. package/dist/cli/command-routing.js +51 -0
  4. package/dist/cli/command-routing.js.map +1 -0
  5. package/dist/cli/command-routing.test.d.ts +2 -0
  6. package/dist/cli/command-routing.test.d.ts.map +1 -0
  7. package/dist/cli/command-routing.test.js +59 -0
  8. package/dist/cli/command-routing.test.js.map +1 -0
  9. package/dist/cli.d.ts +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +48 -104
  12. package/dist/cli.js.map +1 -1
  13. package/dist/core/agent-session-compaction.test.d.ts +2 -0
  14. package/dist/core/agent-session-compaction.test.d.ts.map +1 -0
  15. package/dist/core/agent-session-compaction.test.js +121 -0
  16. package/dist/core/agent-session-compaction.test.js.map +1 -0
  17. package/dist/core/agent-session.d.ts.map +1 -1
  18. package/dist/core/agent-session.js +6 -7
  19. package/dist/core/agent-session.js.map +1 -1
  20. package/dist/core/goal-store.d.ts +11 -0
  21. package/dist/core/goal-store.d.ts.map +1 -1
  22. package/dist/core/goal-store.js +45 -0
  23. package/dist/core/goal-store.js.map +1 -1
  24. package/dist/core/goal-worker.d.ts +7 -0
  25. package/dist/core/goal-worker.d.ts.map +1 -1
  26. package/dist/core/goal-worker.js +62 -20
  27. package/dist/core/goal-worker.js.map +1 -1
  28. package/dist/core/goal-worker.test.js +92 -2
  29. package/dist/core/goal-worker.test.js.map +1 -1
  30. package/dist/core/goal-worktree.d.ts +33 -0
  31. package/dist/core/goal-worktree.d.ts.map +1 -0
  32. package/dist/core/goal-worktree.js +67 -0
  33. package/dist/core/goal-worktree.js.map +1 -0
  34. package/dist/core/goal-worktree.test.d.ts +2 -0
  35. package/dist/core/goal-worktree.test.d.ts.map +1 -0
  36. package/dist/core/goal-worktree.test.js +101 -0
  37. package/dist/core/goal-worktree.test.js.map +1 -0
  38. package/dist/core/repomap-budget.d.ts +7 -0
  39. package/dist/core/repomap-budget.d.ts.map +1 -0
  40. package/dist/core/repomap-budget.js +10 -0
  41. package/dist/core/repomap-budget.js.map +1 -0
  42. package/dist/core/repomap-budget.test.d.ts +2 -0
  43. package/dist/core/repomap-budget.test.d.ts.map +1 -0
  44. package/dist/core/repomap-budget.test.js +26 -0
  45. package/dist/core/repomap-budget.test.js.map +1 -0
  46. package/dist/core/tasks-store.d.ts +24 -0
  47. package/dist/core/tasks-store.d.ts.map +1 -0
  48. package/dist/core/tasks-store.js +81 -0
  49. package/dist/core/tasks-store.js.map +1 -0
  50. package/dist/system-prompt.js +2 -2
  51. package/dist/system-prompt.js.map +1 -1
  52. package/dist/system-prompt.test.js +2 -2
  53. package/dist/system-prompt.test.js.map +1 -1
  54. package/dist/tools/goals.d.ts.map +1 -1
  55. package/dist/tools/goals.js +30 -26
  56. package/dist/tools/goals.js.map +1 -1
  57. package/dist/tools/goals.test.js +47 -1
  58. package/dist/tools/goals.test.js.map +1 -1
  59. package/dist/tools/index.d.ts +1 -0
  60. package/dist/tools/index.d.ts.map +1 -1
  61. package/dist/tools/index.js +3 -0
  62. package/dist/tools/index.js.map +1 -1
  63. package/dist/tools/tasks.d.ts +16 -0
  64. package/dist/tools/tasks.d.ts.map +1 -0
  65. package/dist/tools/tasks.js +93 -0
  66. package/dist/tools/tasks.js.map +1 -0
  67. package/dist/ui/App.d.ts +12 -381
  68. package/dist/ui/App.d.ts.map +1 -1
  69. package/dist/ui/App.js +212 -679
  70. package/dist/ui/App.js.map +1 -1
  71. package/dist/ui/app-items.d.ts +212 -0
  72. package/dist/ui/app-items.d.ts.map +1 -0
  73. package/dist/ui/app-items.js +2 -0
  74. package/dist/ui/app-items.js.map +1 -0
  75. package/dist/ui/app-state-persistence.test.js +22 -1
  76. package/dist/ui/app-state-persistence.test.js.map +1 -1
  77. package/dist/ui/chat-layout-pinning.test.js +12 -1
  78. package/dist/ui/chat-layout-pinning.test.js.map +1 -1
  79. package/dist/ui/components/Banner.js +2 -2
  80. package/dist/ui/components/Banner.js.map +1 -1
  81. package/dist/ui/components/GoalPickerMenu.d.ts +9 -0
  82. package/dist/ui/components/GoalPickerMenu.d.ts.map +1 -0
  83. package/dist/ui/components/GoalPickerMenu.js +37 -0
  84. package/dist/ui/components/GoalPickerMenu.js.map +1 -0
  85. package/dist/ui/components/InputArea.d.ts +16 -1
  86. package/dist/ui/components/InputArea.d.ts.map +1 -1
  87. package/dist/ui/components/InputArea.js +91 -3
  88. package/dist/ui/components/InputArea.js.map +1 -1
  89. package/dist/ui/components/TaskPickerMenu.d.ts +9 -0
  90. package/dist/ui/components/TaskPickerMenu.d.ts.map +1 -0
  91. package/dist/ui/components/TaskPickerMenu.js +33 -0
  92. package/dist/ui/components/TaskPickerMenu.js.map +1 -0
  93. package/dist/ui/components/ToolUseLoader.d.ts +3 -1
  94. package/dist/ui/components/ToolUseLoader.d.ts.map +1 -1
  95. package/dist/ui/components/ToolUseLoader.js +2 -2
  96. package/dist/ui/components/ToolUseLoader.js.map +1 -1
  97. package/dist/ui/goal-events.test.js +2 -1
  98. package/dist/ui/goal-events.test.js.map +1 -1
  99. package/dist/ui/goal-lifecycle-orchestration.test.js +20 -1
  100. package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
  101. package/dist/ui/goal-progress.d.ts +31 -0
  102. package/dist/ui/goal-progress.d.ts.map +1 -0
  103. package/dist/ui/goal-progress.js +152 -0
  104. package/dist/ui/goal-progress.js.map +1 -0
  105. package/dist/ui/item-helpers.d.ts +26 -0
  106. package/dist/ui/item-helpers.d.ts.map +1 -0
  107. package/dist/ui/item-helpers.js +112 -0
  108. package/dist/ui/item-helpers.js.map +1 -0
  109. package/dist/ui/layout-decisions.d.ts +113 -0
  110. package/dist/ui/layout-decisions.d.ts.map +1 -0
  111. package/dist/ui/layout-decisions.js +174 -0
  112. package/dist/ui/layout-decisions.js.map +1 -0
  113. package/dist/ui/prompt-routing.d.ts +29 -0
  114. package/dist/ui/prompt-routing.d.ts.map +1 -0
  115. package/dist/ui/prompt-routing.js +105 -0
  116. package/dist/ui/prompt-routing.js.map +1 -0
  117. package/dist/ui/queued-message.test.js +1 -1
  118. package/dist/ui/queued-message.test.js.map +1 -1
  119. package/dist/ui/render.d.ts +1 -0
  120. package/dist/ui/render.d.ts.map +1 -1
  121. package/dist/ui/render.js +1 -0
  122. package/dist/ui/render.js.map +1 -1
  123. package/dist/ui/scroll-stabilization.test.js +1 -1
  124. package/dist/ui/scroll-stabilization.test.js.map +1 -1
  125. package/dist/ui/slash-command-images.test.js +1 -1
  126. package/dist/ui/slash-command-images.test.js.map +1 -1
  127. package/dist/ui/terminal-history-format.d.ts +41 -0
  128. package/dist/ui/terminal-history-format.d.ts.map +1 -0
  129. package/dist/ui/terminal-history-format.js +131 -0
  130. package/dist/ui/terminal-history-format.js.map +1 -0
  131. package/dist/ui/terminal-history-spacing.d.ts +3 -0
  132. package/dist/ui/terminal-history-spacing.d.ts.map +1 -0
  133. package/dist/ui/terminal-history-spacing.js +29 -0
  134. package/dist/ui/terminal-history-spacing.js.map +1 -0
  135. package/dist/ui/terminal-history-status-renderers.d.ts +16 -0
  136. package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -0
  137. package/dist/ui/terminal-history-status-renderers.js +83 -0
  138. package/dist/ui/terminal-history-status-renderers.js.map +1 -0
  139. package/dist/ui/terminal-history.d.ts.map +1 -1
  140. package/dist/ui/terminal-history.js +23 -236
  141. package/dist/ui/terminal-history.js.map +1 -1
  142. package/dist/ui/terminal-history.test.js +2 -1
  143. package/dist/ui/terminal-history.test.js.map +1 -1
  144. package/dist/ui/thinking-level-cycle.test.js +7 -1
  145. package/dist/ui/thinking-level-cycle.test.js.map +1 -1
  146. package/dist/ui/thinking-level.d.ts +5 -0
  147. package/dist/ui/thinking-level.d.ts.map +1 -0
  148. package/dist/ui/thinking-level.js +30 -0
  149. package/dist/ui/thinking-level.js.map +1 -0
  150. package/dist/ui/tui-history-parity.test.js +7 -3
  151. package/dist/ui/tui-history-parity.test.js.map +1 -1
  152. package/package.json +3 -3
package/dist/ui/App.js CHANGED
@@ -4,7 +4,6 @@ import { Box, Text, useStdout } from "ink";
4
4
  import { useTerminalSize } from "./hooks/useTerminalSize.js";
5
5
  import { useDoublePress } from "./hooks/useDoublePress.js";
6
6
  import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTaskBar, collapseTaskBar, navigateTaskBar, killTask, } from "./stores/taskbar-store.js";
7
- import { writeFileSync } from "node:fs";
8
7
  import { playNotificationSound } from "../utils/sound.js";
9
8
  import { formatError, } from "@kenkaiiii/gg-ai";
10
9
  import { extractImagePaths } from "../utils/image.js";
@@ -28,16 +27,15 @@ import { GoalStatusBar, reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntr
28
27
  import { Banner } from "./components/Banner.js";
29
28
  import { PlanOverlay } from "./components/PlanOverlay.js";
30
29
  import { ModelSelector } from "./components/ModelSelector.js";
31
- import { GoalOverlay } from "./components/GoalOverlay.js";
32
30
  import { PixelOverlay } from "./components/PixelOverlay.js";
33
- import { buildGoalFinalSummarySections, buildGoalSummaryRows, goalPassedDetail, } from "./goal-summary.js";
34
31
  import { SkillsOverlay } from "./components/SkillsOverlay.js";
35
32
  import { ThemeSelector } from "./components/ThemeSelector.js";
36
33
  import { BackgroundTasksBar, getFooterStatusLayoutDecision, } from "./components/BackgroundTasksBar.js";
37
34
  import { useTheme, useSetTheme } from "./theme/theme.js";
38
35
  import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
39
36
  import { getGitBranch } from "../utils/git.js";
40
- import { getModel, getContextWindow, getMaxThinkingLevel } from "../core/model-registry.js";
37
+ import { getModel, getContextWindow } from "../core/model-registry.js";
38
+ import { BLACK_CIRCLE } from "./constants/figures.js";
41
39
  import { SessionManager } from "../core/session-manager.js";
42
40
  import { appendMessagesToSession as appendSessionMessages, createCompactedSessionCheckpoint, } from "../core/session-compaction.js";
43
41
  import { log } from "../core/logger.js";
@@ -52,18 +50,30 @@ import { loadCustomCommands } from "../core/custom-commands.js";
52
50
  import { buildSystemPrompt } from "../system-prompt.js";
53
51
  import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
54
52
  import { detectVerifyCommands } from "../core/verify-commands.js";
55
- import { FOCUSED_REPO_MAP_MAX_CHARS, FIRST_TURN_REPO_MAP_MAX_CHARS, buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
53
+ import { buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
54
+ import { getRepoMapBudgetForContext } from "../core/repomap-budget.js";
56
55
  import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMessages, } from "../core/repomap-context.js";
57
56
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
58
57
  import { getMCPServers } from "../core/mcp/index.js";
59
58
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
60
59
  import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
61
- import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
62
- import { canCompleteGoalRun, decideGoalNextAction, } from "../core/goal-controller.js";
60
+ import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, loadGoalRunsSync, reconcileActiveGoalRuns, saveGoalRunsSync, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
61
+ import { getNextPendingTask, loadTasksSync, markTaskInProgress, saveTasksSync, } from "../core/tasks-store.js";
62
+ import { canCompleteGoalRun, decideGoalNextAction } from "../core/goal-controller.js";
63
63
  import { runGoalPrerequisiteChecks } from "../core/goal-prerequisites.js";
64
64
  import { runGoalVerifierCommand } from "../core/goal-verifier.js";
65
65
  import { listGoalWorkers, startGoalWorker, stopGoalWorker, subscribeGoalWorkerCompletions, } from "../core/goal-worker.js";
66
66
  import { formatGoalVerifierCompletionEvent, formatGoalWorkerCompletionEvent, isGoalSyntheticEvent, parseGoalSyntheticEvent, } from "./goal-events.js";
67
+ import { buildUserContentWithAttachments, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
68
+ import { getNextThinkingLevel, isThinkingLevelSupported } from "./thinking-level.js";
69
+ import { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, formatGoalWorkerFinishedTitle, getGoalContinuationChoiceKey, goalTerminalProgressId, routeGoalSyntheticEvent, summarizeGoalCompletion, truncateGoalProgressText, } from "./goal-progress.js";
70
+ import { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalSetupPaneTransitionAfterRun, isAgentSpacingItem, MIN_LIVE_AREA_ROWS, nextGoalModeAfterAgentDone, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
71
+ import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, removeItemsWithIds, uniqueItemsById, } from "./item-helpers.js";
72
+ export { buildGoalSetupPromptFromPlanner, buildUserContentWithAttachments, collectAssistantTextSince, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
73
+ export { getNextThinkingLevel } from "./thinking-level.js";
74
+ export { appendGoalProgressDraft, completedItemsWithDurableGoalTerminalProgress, formatGoalTerminalProgress, getGoalContinuationChoiceKey, routeGoalSyntheticEvent, truncateGoalProgressText, } from "./goal-progress.js";
75
+ export { getChatControlsLayoutDecision, getDoneFlushDecision, getGoalActivationPaneTransition, getGoalSetupFinishedPaneTransition, getGoalSetupPaneTransitionAfterRun, getScrollStabilizationDecision, getStaticHistoryKey, hasParagraphBreakLiveUserMessage, isTallLiveUserMessage, nextGoalModeAfterAgentDone, shouldHideHistoryForOverlayView, shouldHideStaticItemsForOverlayView, shouldResetUIForGoalSetupPaneTransition, shouldStabilizeOverlayPaneRerender, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
76
+ export { getNextGeneratedItemId, isActiveItem, partitionCompleted, pinStreamingTextBeforeToolBoundary, } from "./item-helpers.js";
67
77
  /** Where ggcoder bugs should be reported. Surfaced in the guidance line. */
68
78
  const GGCODER_BUG_REPORT_URL = "github.com/kenkaiiii/gg-framework/issues";
69
79
  /**
@@ -97,114 +107,6 @@ function toErrorItem(err, id, contextPrefix) {
97
107
  id,
98
108
  };
99
109
  }
100
- export function routePromptCommandInput(input, promptCommands = PROMPT_COMMANDS, customCommands = []) {
101
- const trimmed = input.trim();
102
- if (!trimmed.startsWith("/"))
103
- return null;
104
- const parts = trimmed.slice(1).split(" ");
105
- const cmdName = parts[0];
106
- const cmdArgs = parts.slice(1).join(" ").trim();
107
- const builtinCmd = promptCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
108
- const customCmd = !builtinCmd ? customCommands.find((c) => c.name === cmdName) : undefined;
109
- const promptText = builtinCmd?.prompt ?? customCmd?.prompt;
110
- if (!promptText)
111
- return null;
112
- return {
113
- cmdName,
114
- cmdArgs,
115
- promptText,
116
- fullPrompt: cmdArgs ? `${promptText}\n\n## User Instructions\n\n${cmdArgs}` : promptText,
117
- };
118
- }
119
- const GOAL_PLANNER_OUTPUT_MAX_CHARS = 2400;
120
- function messageTextContent(message) {
121
- if (typeof message.content === "string")
122
- return message.content;
123
- return message.content
124
- .filter((part) => part.type === "text")
125
- .map((part) => part.text)
126
- .join("\n");
127
- }
128
- export function collectAssistantTextSince(messages, startIndex, maxChars = GOAL_PLANNER_OUTPUT_MAX_CHARS) {
129
- const text = messages
130
- .slice(startIndex)
131
- .filter((message) => message.role === "assistant")
132
- .map(messageTextContent)
133
- .join("\n")
134
- .trim();
135
- if (text.length <= maxChars)
136
- return text;
137
- return text.slice(0, maxChars).trimEnd() + "\n[planner output truncated]";
138
- }
139
- export function buildGoalSetupPromptFromPlanner({ originalGoalPrompt, plannerOutput, }) {
140
- const compactPlannerOutput = plannerOutput.trim() || "GOAL_PLAN\nresearch=none\nEND_GOAL_PLAN";
141
- return (`${originalGoalPrompt.trim()}\n\n` +
142
- `## Goal Planner Output\n\n${compactPlannerOutput}\n\n` +
143
- `Use the original objective plus this planner output to create durable Goal setup only. ` +
144
- `Do not redo planner research unless the planner output is unusable.`);
145
- }
146
- export function isGoalPromptCommandName(cmdName) {
147
- return getPromptCommand(cmdName)?.name === "goal";
148
- }
149
- export async function runGoalPromptSetupSequence({ userContent, fullPrompt, messagesRef, setGoalModeAndPrompt, runAgent, onStage, }) {
150
- onStage?.("Planning Goal setup");
151
- await setGoalModeAndPrompt("planner");
152
- const plannerStartIndex = messagesRef.current.length;
153
- await runAgent(userContent);
154
- const plannerOutput = collectAssistantTextSince(messagesRef.current, plannerStartIndex);
155
- const setupPrompt = buildGoalSetupPromptFromPlanner({
156
- originalGoalPrompt: fullPrompt,
157
- plannerOutput,
158
- });
159
- await setGoalModeAndPrompt("setup");
160
- onStage?.("Creating Goal run");
161
- await runAgent(setupPrompt);
162
- }
163
- function buildGoalTaskPromptWithReferences(run, taskPrompt) {
164
- if (taskPrompt.includes("## Goal References (MANDATORY)"))
165
- return taskPrompt;
166
- const references = formatGoalReferencesForPrompt(run.references ?? []);
167
- return references ? `${references}\n\n${taskPrompt}` : taskPrompt;
168
- }
169
- export function buildUserContentWithAttachments(text, inputImages, modelSupportsImages) {
170
- if (inputImages.length === 0)
171
- return text;
172
- const parts = [];
173
- if (text) {
174
- parts.push({ type: "text", text });
175
- }
176
- for (const img of inputImages) {
177
- if (img.kind === "text") {
178
- parts.push({
179
- type: "text",
180
- text: `<file name="${img.fileName}">\n${img.data}\n</file>`,
181
- });
182
- }
183
- else if (modelSupportsImages) {
184
- parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
185
- }
186
- else {
187
- // GLM models: save image to temp file and instruct model to use vision MCP tool
188
- const ext = img.mediaType.split("/")[1] ?? "png";
189
- const tmpPath = `/tmp/ggcoder-img-${Date.now()}.${ext}`;
190
- try {
191
- writeFileSync(tmpPath, Buffer.from(img.data, "base64"));
192
- parts.push({
193
- type: "text",
194
- text: `[User attached an image saved at: ${tmpPath} — use the image_analysis tool to view and analyze it]`,
195
- });
196
- }
197
- catch {
198
- parts.push({
199
- type: "text",
200
- text: `[User attached an image but it could not be saved for analysis]`,
201
- });
202
- }
203
- }
204
- }
205
- // If only text parts remain after stripping images, simplify to plain string
206
- return parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts;
207
- }
208
110
  /** Tools that get aggregated into a single compact group when possible. */
209
111
  const AGGREGATABLE_TOOLS = new Set([
210
112
  "read",
@@ -215,66 +117,17 @@ const AGGREGATABLE_TOOLS = new Set([
215
117
  "mcp__kencode-search__referenceSources",
216
118
  "mcp__kencode-search__discoverRepos",
217
119
  ]);
218
- const OPENAI_GPT_THINKING_LEVELS = ["medium", "high", "xhigh"];
219
- function isOpenAIGptModel(provider, model) {
220
- return provider === "openai" && model.startsWith("gpt-");
221
- }
222
- export function getNextThinkingLevel(provider, model, current) {
223
- if (!isOpenAIGptModel(provider, model)) {
224
- return current ? undefined : getMaxThinkingLevel(model);
225
- }
226
- if (!current)
227
- return "medium";
228
- const index = OPENAI_GPT_THINKING_LEVELS.indexOf(current);
229
- if (index === -1)
230
- return "medium";
231
- return OPENAI_GPT_THINKING_LEVELS[index + 1];
232
- }
233
120
  const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
234
- /**
235
- * Cap memory by replacing old finalized rows with tiny tombstones. The full
236
- * transcript is already printed into terminal scrollback, so the in-memory copy
237
- * only needs enough recent structure to survive remounts and session mirroring.
238
- */
239
- const MAX_LIVE_HISTORY = 200;
240
- function compactHistory(items) {
241
- if (items.length <= MAX_LIVE_HISTORY)
242
- return items;
243
- const cutoff = items.length - MAX_LIVE_HISTORY;
244
- const compacted = new Array(items.length);
245
- for (let i = 0; i < cutoff; i++) {
246
- const it = items[i];
247
- compacted[i] = it.kind === "tombstone" ? it : { kind: "tombstone", id: it.id };
248
- }
249
- for (let i = cutoff; i < items.length; i++) {
250
- compacted[i] = items[i];
251
- }
252
- return compacted;
253
- }
254
- function summarizeGoalCompletion(summary) {
255
- const lines = summary
256
- .split("\n")
257
- .map((line) => line.trim())
258
- .filter((line) => line.length > 0 && line !== "[agent_done]");
259
- const statusLine = lines.find((line) => /^Status:/i.test(line));
260
- const changedLine = lines.find((line) => /^(Changed|Implemented|Fixed|Added|Key findings|Full verifier)/i.test(line));
261
- const verificationLine = lines.find((line) => /^(Verification|Verified|Result):/i.test(line));
262
- return statusLine ?? changedLine ?? verificationLine ?? lines[0];
263
- }
264
- const GOAL_PROGRESS_TEXT_LIMIT = 72;
265
- export function truncateGoalProgressText(text) {
266
- const normalized = text.replace(/\s+/g, " ").trim();
267
- if (normalized.length <= GOAL_PROGRESS_TEXT_LIMIT)
268
- return normalized;
269
- return `${normalized.slice(0, GOAL_PROGRESS_TEXT_LIMIT - 1).trimEnd()}…`;
270
- }
271
- function formatGoalWorkerFinishedTitle(taskTitle, status) {
272
- const prefix = status === "done" ? "Done" : "Failed";
273
- return truncateGoalProgressText(`${prefix}: ${taskTitle}`);
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;
274
126
  }
275
127
  function goalProgressLoaderStatus(item) {
276
- if (item.status === "failed" || item.status === "fail" || item.status === "blocked")
128
+ if (item.status === "failed" || item.status === "fail" || item.status === "blocked") {
277
129
  return "error";
130
+ }
278
131
  if (item.phase === "worker_finished" ||
279
132
  item.phase === "verifier_finished" ||
280
133
  item.phase === "terminal") {
@@ -297,373 +150,6 @@ function goalProgressColor(item, theme) {
297
150
  return theme.warning;
298
151
  return theme.primary;
299
152
  }
300
- function goalTerminalProgressId(run) {
301
- return `goal-terminal-${run.id}`;
302
- }
303
- function goalTerminalRunIdFromItem(item) {
304
- if (item.kind !== "goal_progress" || item.phase !== "terminal")
305
- return undefined;
306
- if (!item.id.startsWith("goal-terminal-"))
307
- return undefined;
308
- return item.id.slice("goal-terminal-".length);
309
- }
310
- function goalProgressMatchesDraft(item, draft) {
311
- return (item.phase === draft.phase &&
312
- item.title === draft.title &&
313
- item.detail === draft.detail &&
314
- item.workerId === draft.workerId &&
315
- item.status === draft.status &&
316
- JSON.stringify(item.summaryRows ?? []) === JSON.stringify(draft.summaryRows ?? []) &&
317
- JSON.stringify(item.summarySections ?? []) === JSON.stringify(draft.summarySections ?? []));
318
- }
319
- export function appendGoalProgressDraft(items, draft, makeId) {
320
- const previous = items.at(-1);
321
- if (previous?.kind === "goal_progress" && goalProgressMatchesDraft(previous, draft)) {
322
- return items;
323
- }
324
- return [...items, { ...draft, id: makeId() }];
325
- }
326
- /**
327
- * Reconcile terminal Goal cards that are already visible in this UI session.
328
- *
329
- * This intentionally does not synthesize missing cards from durable GoalRun
330
- * state. Goal terminal cards are event notifications: they should appear when
331
- * the terminal event happens in the current UI, not whenever a fresh session
332
- * polls old Goal runs from the Goal pane. Callers that just observed a terminal
333
- * event append that card first, then use this helper to tombstone stale older
334
- * cards for the same run.
335
- */
336
- export function getNextGeneratedItemId(items) {
337
- let max = -1;
338
- for (const item of items) {
339
- const raw = item.id.startsWith("ui-") ? item.id.slice(3) : item.id;
340
- const n = Number(raw);
341
- if (Number.isInteger(n) && n >= 0 && n > max)
342
- max = n;
343
- }
344
- return max + 1;
345
- }
346
- export function completedItemsWithDurableGoalTerminalProgress(items, runs) {
347
- const runIds = new Set(runs.map((run) => run.id));
348
- const terminalByRun = new Map(runs
349
- .map((run) => [run.id, formatGoalTerminalProgress(run)])
350
- .filter((entry) => entry[1] !== null));
351
- if (runIds.size === 0)
352
- return items;
353
- let changed = false;
354
- const reconciled = items.map((item, index) => {
355
- const runId = goalTerminalRunIdFromItem(item);
356
- if (!runId || !runIds.has(runId))
357
- return item;
358
- const draft = terminalByRun.get(runId);
359
- if (draft && goalProgressMatchesDraft(item, draft))
360
- return item;
361
- changed = true;
362
- return { kind: "tombstone", id: `tombstone-${item.id}-${index}` };
363
- });
364
- return changed ? reconciled : items;
365
- }
366
- export function formatGoalTerminalProgress(run) {
367
- switch (run.status) {
368
- case "passed":
369
- return {
370
- kind: "goal_progress",
371
- phase: "terminal",
372
- title: `Goal passed: ${run.title}`,
373
- detail: goalPassedDetail(run),
374
- summaryRows: buildGoalSummaryRows(run),
375
- summarySections: buildGoalFinalSummarySections(run),
376
- status: run.status,
377
- };
378
- case "failed":
379
- return {
380
- kind: "goal_progress",
381
- phase: "terminal",
382
- title: `Goal failed: ${run.title}`,
383
- detail: "Auto-continuation stopped. Check Goal tasks for the failing step.",
384
- summaryRows: buildGoalSummaryRows(run),
385
- status: run.status,
386
- };
387
- case "blocked":
388
- return {
389
- kind: "goal_progress",
390
- phase: "terminal",
391
- title: `Goal blocked: ${run.title}`,
392
- detail: goalHasBlockingPrerequisites(run)
393
- ? formatGoalBlockingPrerequisites(run)
394
- : (run.blockers[0] ?? "A prerequisite or missing verifier blocked progress."),
395
- summaryRows: buildGoalSummaryRows(run),
396
- status: run.status,
397
- };
398
- case "paused":
399
- return {
400
- kind: "goal_progress",
401
- phase: "terminal",
402
- title: `Goal paused: ${run.title}`,
403
- detail: run.blockers[0] ?? "Auto-continuation paused.",
404
- summaryRows: buildGoalSummaryRows(run),
405
- status: run.status,
406
- };
407
- case "draft":
408
- case "ready":
409
- case "running":
410
- case "verifying":
411
- return null;
412
- }
413
- }
414
- export function shouldHideHistoryForOverlayView(isOverlayView, _isAgentRunning) {
415
- // Overlay panes are standalone full-screen states. Finalized chat rows are
416
- // printed outside Ink, so overlays should never replay transcript UI behind them.
417
- return isOverlayView;
418
- }
419
- export function shouldStabilizeOverlayPaneRerender({ overlayPane, isAgentRunning, }) {
420
- return isAgentRunning && overlayPane === "goal";
421
- }
422
- export function shouldHideStaticItemsForOverlayView({ shouldHideHistoryForOverlay, stabilizeOverlayPaneRerender: _stabilizeOverlayPaneRerender, }) {
423
- return shouldHideHistoryForOverlay;
424
- }
425
- export function getDoneFlushDecision({ planOverlayPending, goalMode, goalAutoExpand, }) {
426
- return {
427
- showDoneStatus: !(planOverlayPending ||
428
- goalMode === "planner" ||
429
- goalMode === "setup" ||
430
- goalAutoExpand),
431
- flushLiveItems: true,
432
- };
433
- }
434
- export function getGoalSetupFinishedPaneTransition() {
435
- return {
436
- overlay: "goal",
437
- goalAutoExpand: true,
438
- planAutoExpand: false,
439
- suppressDoneStatus: true,
440
- };
441
- }
442
- export function getGoalSetupPaneTransitionAfterRun({ isGoalSetupCommand, setupPanePending, }) {
443
- return isGoalSetupCommand && setupPanePending ? getGoalSetupFinishedPaneTransition() : null;
444
- }
445
- export function shouldResetUIForSetupPaneTransition({ hasResetUI, hasSessionStore, }) {
446
- // Opening a review pane is a full-screen state transition. A bare React state
447
- // flip hides history in the virtual tree, but it does not reset Ink/log-update's
448
- // already-written terminal frame, so the pane can render below prior chat.
449
- return hasResetUI && hasSessionStore;
450
- }
451
- export const shouldResetUIForGoalSetupPaneTransition = shouldResetUIForSetupPaneTransition;
452
- export function getGoalActivationPaneTransition() {
453
- return { overlay: null, goalAutoExpand: false, planAutoExpand: false, resetReviewScreen: true };
454
- }
455
- export function getGoalContinuationChoiceKey({ runId, decision, }) {
456
- switch (decision.kind) {
457
- case "create_task":
458
- return `${runId}:create_task:${decision.title}:${decision.prompt}`;
459
- case "start_worker":
460
- case "pause":
461
- return `${runId}:${decision.kind}:${decision.task.id}:${decision.attempts}`;
462
- case "run_verifier":
463
- return `${runId}:run_verifier:${decision.command}`;
464
- case "blocked":
465
- case "complete":
466
- case "terminal":
467
- case "wait":
468
- return `${runId}:${decision.kind}:${decision.reason}`;
469
- }
470
- }
471
- export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, hasTallLiveUserMessage = false, hasParagraphBreakLiveUserMessage = false, }) {
472
- const shouldPreserveStatic = isUserScrolled || hasTallLiveUserMessage || hasParagraphBreakLiveUserMessage;
473
- const shouldAutoFollow = !(isUserScrolled || hasTallLiveUserMessage);
474
- return {
475
- preserveStatic: shouldPreserveStatic && hasNewOutput,
476
- autoFollow: shouldAutoFollow,
477
- };
478
- }
479
- export function nextGoalModeAfterAgentDone({ currentMode, runningGoalIds, queuedSyntheticEvents, activeContinuationFlights = 0, wasGoalSetupTurn, }) {
480
- if (wasGoalSetupTurn)
481
- return "off";
482
- if (currentMode === "planner" || currentMode === "setup")
483
- return currentMode;
484
- if (queuedSyntheticEvents > 0)
485
- return "coordinator";
486
- if (activeContinuationFlights > 0)
487
- return "coordinator";
488
- if (currentMode === "coordinator" && runningGoalIds > 0)
489
- return "coordinator";
490
- return "off";
491
- }
492
- export function hasParagraphBreakLiveUserMessage(text) {
493
- return /\n[ \t]*\n/.test(text);
494
- }
495
- export function isTallLiveUserMessage(text, rows) {
496
- return text.split("\n").length > Math.max(8, Math.floor(rows * 0.6));
497
- }
498
- export function getStaticHistoryKey({ resizeKey }) {
499
- return `${resizeKey}`;
500
- }
501
- const MIN_LIVE_AREA_ROWS = 3;
502
- const INPUT_AREA_ROWS = 3;
503
- const STATUS_SLOT_ROWS = 2;
504
- const FOOTER_ONE_LINE_ROWS = 1;
505
- const FOOTER_TWO_LINE_ROWS = 2;
506
- const GOAL_STATUS_ROWS = 1;
507
- const COLLAPSED_FOOTER_STATUS_ROWS = 1;
508
- const MAX_EXPANDED_BACKGROUND_TASK_ROWS = 7;
509
- function isAgentSpacingKind(kind) {
510
- return [
511
- "assistant",
512
- "queued",
513
- "goal_progress",
514
- "tool_start",
515
- "tool_done",
516
- "tool_group",
517
- "server_tool_start",
518
- "server_tool_done",
519
- "subagent_group",
520
- "info",
521
- "error",
522
- "stopped",
523
- "plan_transition",
524
- "goal_agent_transition",
525
- "model_transition",
526
- "theme_transition",
527
- "plan_event",
528
- "update_notice",
529
- "compacting",
530
- "compacted",
531
- "style_pack",
532
- "setup_hint",
533
- ].includes(kind);
534
- }
535
- function isToolBoundaryKind(kind) {
536
- return [
537
- "goal_progress",
538
- "tool_start",
539
- "tool_done",
540
- "tool_group",
541
- "server_tool_start",
542
- "server_tool_done",
543
- "subagent_group",
544
- ].includes(kind);
545
- }
546
- function isAgentSpacingItem(item) {
547
- return isAgentSpacingKind(item.kind);
548
- }
549
- export function shouldTopSpaceAfterPrintedAgentBoundary({ currentKind, previousLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
550
- const needsExternalSpacing = isAgentSpacingKind(currentKind);
551
- if (!needsExternalSpacing)
552
- return false;
553
- if (previousLiveItem !== undefined)
554
- return false;
555
- const previousKind = lastPendingHistoryItem?.kind ?? lastHistoryItem?.kind;
556
- return previousKind !== undefined && isAgentSpacingKind(previousKind);
557
- }
558
- export function shouldTopSpaceAssistantAfterToolBoundary({ text, previousLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
559
- if (text.trim().length === 0)
560
- return false;
561
- if (shouldTopSpaceAfterPrintedAgentBoundary({
562
- currentKind: "assistant",
563
- previousLiveItem,
564
- lastPendingHistoryItem,
565
- lastHistoryItem,
566
- })) {
567
- return true;
568
- }
569
- const previousKind = previousLiveItem?.kind;
570
- return previousKind !== undefined && isToolBoundaryKind(previousKind);
571
- }
572
- export function shouldTopSpaceStreamingAssistant({ visibleStreamingText, lastLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
573
- return shouldTopSpaceAssistantAfterToolBoundary({
574
- text: visibleStreamingText,
575
- previousLiveItem: lastLiveItem,
576
- lastPendingHistoryItem,
577
- lastHistoryItem,
578
- });
579
- }
580
- export function getChatControlsLayoutDecision({ rows, agentRunning, activityVisible, doneStatusVisible, stallStatusVisible, exitPending, footerStatusLayout, taskBarExpanded, goalStatusEntryCount, footerFitsOnOneLine, }) {
581
- const statusRows = activityVisible || stallStatusVisible || doneStatusVisible || agentRunning
582
- ? STATUS_SLOT_ROWS
583
- : 0;
584
- const footerRows = exitPending || footerFitsOnOneLine ? FOOTER_ONE_LINE_ROWS : FOOTER_TWO_LINE_ROWS;
585
- const goalRows = !exitPending && goalStatusEntryCount > 0 ? GOAL_STATUS_ROWS : 0;
586
- const footerStatusRows = footerStatusLayout.stack
587
- ? Number(footerStatusLayout.hasBackgroundTasks) + Number(footerStatusLayout.hasUpdateNotice)
588
- : footerStatusLayout.hasBackgroundTasks || footerStatusLayout.hasUpdateNotice
589
- ? COLLAPSED_FOOTER_STATUS_ROWS
590
- : 0;
591
- const expandedTaskRows = taskBarExpanded && footerStatusLayout.hasBackgroundTasks
592
- ? MAX_EXPANDED_BACKGROUND_TASK_ROWS - COLLAPSED_FOOTER_STATUS_ROWS
593
- : 0;
594
- const controlsRows = statusRows + INPUT_AREA_ROWS + footerRows + goalRows + footerStatusRows + expandedTaskRows;
595
- const maxControlsRows = Math.max(1, rows - MIN_LIVE_AREA_ROWS);
596
- const boundedControlsRows = Math.min(controlsRows, maxControlsRows);
597
- return {
598
- controlsRows: boundedControlsRows,
599
- liveAreaRows: Math.max(MIN_LIVE_AREA_ROWS, rows - boundedControlsRows),
600
- };
601
- }
602
- // flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
603
- /** Check whether an item is still active (running spinner, pending result). */
604
- export function isActiveItem(item) {
605
- switch (item.kind) {
606
- case "tool_start":
607
- case "server_tool_start":
608
- case "queued":
609
- case "compacting":
610
- return true;
611
- case "tool_group":
612
- return item.tools.some((t) => t.status === "running");
613
- case "subagent_group":
614
- return item.agents.some((a) => a.status === "running");
615
- default:
616
- return false;
617
- }
618
- }
619
- /**
620
- * Partition live items into completed (flushable to finalized history) and still-active.
621
- * Completed items precede active ones — we flush the longest contiguous prefix
622
- * of completed items to keep ordering stable.
623
- */
624
- export function partitionCompleted(items) {
625
- // Find the first active item — everything before it is safe to flush as a
626
- // single chronological prefix. Splitting assistant text out of that prefix
627
- // lets later tool rows print to scrollback above the message that introduced
628
- // them, so keep the prefix intact.
629
- const firstActiveIdx = items.findIndex(isActiveItem);
630
- if (firstActiveIdx === -1) {
631
- return { flushed: items, remaining: [] };
632
- }
633
- if (firstActiveIdx === 0) {
634
- return { flushed: [], remaining: items };
635
- }
636
- return {
637
- flushed: items.slice(0, firstActiveIdx),
638
- remaining: items.slice(firstActiveIdx),
639
- };
640
- }
641
- function normalizeAssistantText(text) {
642
- return stripDoneMarkers(text).trim();
643
- }
644
- function isReasoningMarkerText(text) {
645
- return /^(?:currentItem\?\.type\s*=+\s*)?["']?reasoning["']?$/u.test(text.trim());
646
- }
647
- function isSameAssistantText(item, text) {
648
- return item.kind === "assistant" && normalizeAssistantText(item.text) === text;
649
- }
650
- export function pinStreamingTextBeforeToolBoundary({ items, visibleStreamingText, thinking, thinkingMs, makeId, }) {
651
- const text = normalizeAssistantText(visibleStreamingText);
652
- if (text.length === 0 || isReasoningMarkerText(text))
653
- return items;
654
- if (items.some((item) => item.kind === "assistant"))
655
- return items;
656
- return [
657
- ...items,
658
- {
659
- kind: "assistant",
660
- text,
661
- thinking: thinking.length > 0 ? thinking : undefined,
662
- thinkingMs: thinking.length > 0 ? thinkingMs : undefined,
663
- id: makeId(),
664
- },
665
- ];
666
- }
667
153
  // ── Duration summary ─────────────────────────────────────
668
154
  function formatDuration(ms) {
669
155
  const totalSec = Math.round(ms / 1000);
@@ -775,7 +261,11 @@ export function App(props) {
775
261
  // Items from the current/last turn — rendered in the live area so they stay visible.
776
262
  // Seed from sessionStore so Goal progress/completion rows and other live output
777
263
  // survive pane/overlay/resize remounts before they are finalized.
778
- const [liveItems, setLiveItems] = useState(() => props.sessionStore?.liveItems ?? []);
264
+ const [liveItems, setLiveItems] = useState(() => {
265
+ const restoredLiveItems = uniqueItemsById(props.sessionStore?.liveItems ?? []);
266
+ const restoredHistoryIds = new Set(history.map((item) => item.id));
267
+ return removeItemsWithIds(restoredLiveItems, restoredHistoryIds);
268
+ });
779
269
  // overlay seeded from sessionStore (lives across remount). Falls back to
780
270
  // props.initialOverlay (CLI launched with one), then null.
781
271
  const [overlay, setOverlay] = useState(props.sessionStore?.overlay ?? props.initialOverlay ?? null);
@@ -788,6 +278,13 @@ export function App(props) {
788
278
  const goalContinuationFlightsRef = useRef(new Set());
789
279
  const goalContinuationRecentChoicesRef = useRef(new Map());
790
280
  const startGoalRunRef = useRef(() => { });
281
+ 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
+ const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
287
+ const startTaskRef = useRef(() => { });
791
288
  const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
792
289
  const currentPixelFixRef = useRef(null);
793
290
  const startPixelFixRef = useRef(() => { });
@@ -926,7 +423,7 @@ export function App(props) {
926
423
  pendingHistoryFlushRef.current = [...pendingHistoryFlushRef.current, ...flushed];
927
424
  if (sessionStore) {
928
425
  const queuedIds = new Set(items.map((item) => item.id));
929
- sessionStore.liveItems = (sessionStore.liveItems ?? []).filter((item) => !queuedIds.has(item.id));
426
+ sessionStore.liveItems = removeItemsWithIds(uniqueItemsById(sessionStore.liveItems ?? []), queuedIds);
930
427
  }
931
428
  setHistoryFlushGeneration((generation) => generation + 1);
932
429
  }, [sessionStore]);
@@ -949,11 +446,8 @@ export function App(props) {
949
446
  onRuntimeStateChange?.({ provider: currentProvider });
950
447
  }, [currentProvider, onRuntimeStateChange]);
951
448
  useEffect(() => {
952
- if (thinkingLevel && !isOpenAIGptModel(currentProvider, currentModel)) {
953
- const maxLevel = getMaxThinkingLevel(currentModel);
954
- if (thinkingLevel !== maxLevel) {
955
- setThinkingLevel(maxLevel);
956
- }
449
+ if (thinkingLevel && !isThinkingLevelSupported(currentProvider, currentModel, thinkingLevel)) {
450
+ setThinkingLevel(getNextThinkingLevel(currentProvider, currentModel, undefined));
957
451
  }
958
452
  }, [currentProvider, currentModel, thinkingLevel]);
959
453
  useEffect(() => {
@@ -994,8 +488,10 @@ export function App(props) {
994
488
  sessionStore.history = history;
995
489
  }, [history, sessionStore]);
996
490
  useEffect(() => {
997
- if (sessionStore)
998
- sessionStore.liveItems = liveItems;
491
+ if (!sessionStore)
492
+ return;
493
+ const historyIds = new Set(historyRef.current.map((item) => item.id));
494
+ sessionStore.liveItems = removeItemsWithIds(uniqueItemsById(liveItems), historyIds);
999
495
  }, [liveItems, sessionStore]);
1000
496
  useEffect(() => {
1001
497
  if (sessionStore)
@@ -1114,6 +610,29 @@ export function App(props) {
1114
610
  }
1115
611
  return newPrompt;
1116
612
  }, [rebuildSystemPrompt]);
613
+ useEffect(() => {
614
+ if (!props.connectInitialMcpTools)
615
+ return;
616
+ let cancelled = false;
617
+ void props
618
+ .connectInitialMcpTools()
619
+ .then((mcpTools) => {
620
+ if (cancelled || mcpTools.length === 0)
621
+ return;
622
+ setCurrentTools((prev) => {
623
+ const next = [...prev.filter((tool) => !tool.name.startsWith("mcp__")), ...mcpTools];
624
+ currentToolsRef.current = next;
625
+ void replaceSystemPrompt({ tools: next });
626
+ return next;
627
+ });
628
+ })
629
+ .catch((err) => {
630
+ log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
631
+ });
632
+ return () => {
633
+ cancelled = true;
634
+ };
635
+ }, [props.connectInitialMcpTools, replaceSystemPrompt]);
1117
636
  const setGoalModeAndPrompt = useCallback(async (nextMode, options) => {
1118
637
  goalModeStateRef.current = nextMode;
1119
638
  if (props.goalModeRef)
@@ -1378,13 +897,10 @@ export function App(props) {
1378
897
  (props.repoMapReadFilesRef?.current.size ?? 0));
1379
898
  }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef]);
1380
899
  const getRepoMapBudget = useCallback(() => {
1381
- const userTurns = messagesRef.current.filter((message) => message.role === "user").length;
1382
- const readCount = props.repoMapReadFilesRef?.current.size ?? 0;
1383
- if (userTurns <= 1 && readCount === 0)
1384
- return FIRST_TURN_REPO_MAP_MAX_CHARS;
1385
- if (readCount > 0)
1386
- return FOCUSED_REPO_MAP_MAX_CHARS;
1387
- return FOCUSED_REPO_MAP_MAX_CHARS + 1000;
900
+ return getRepoMapBudgetForContext({
901
+ messages: messagesRef.current,
902
+ readFileCount: props.repoMapReadFilesRef?.current.size ?? 0,
903
+ });
1388
904
  }, [props.repoMapReadFilesRef]);
1389
905
  const refreshRepoMap = useCallback(async (latestUserPrompt) => {
1390
906
  const rendered = await buildRepoMap({
@@ -2025,6 +1541,21 @@ export function App(props) {
2025
1541
  if (nextGoalMode !== goalModeStateRef.current) {
2026
1542
  void setGoalModeAndPrompt(nextGoalMode);
2027
1543
  }
1544
+ // Run-all: auto-start next pending task after a short delay.
1545
+ if (runAllTasksRef.current) {
1546
+ setTimeout(() => {
1547
+ const cwd = cwdRef.current;
1548
+ const next = getNextPendingTask(cwd);
1549
+ if (next) {
1550
+ markTaskInProgress(cwd, next.id);
1551
+ startTaskRef.current(next.title, next.prompt, next.id);
1552
+ }
1553
+ else {
1554
+ setRunAllTasks(false);
1555
+ log("INFO", "tasks", "Run-all complete — no more pending tasks");
1556
+ }
1557
+ }, 500);
1558
+ }
2028
1559
  // Goal loop: after the orchestrator handles a worker/verifier event,
2029
1560
  // continue the same Goal automatically until it reaches a terminal state.
2030
1561
  for (const runId of [...runningGoalIdsRef.current]) {
@@ -2446,26 +1977,11 @@ export function App(props) {
2446
1977
  ]);
2447
1978
  return;
2448
1979
  }
2449
- // Handle /goals — open goal pane
1980
+ // Handle /goals — open the input-area goal picker.
2450
1981
  if (trimmed === "/goals") {
2451
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2452
- props.sessionStore.overlay = "goal";
2453
- props.sessionStore.planAutoExpand = false;
2454
- props.sessionStore.goalAutoExpand = false;
2455
- props.resetUI();
2456
- }
2457
- else {
2458
- if (props.sessionStore) {
2459
- props.sessionStore.overlay = "goal";
2460
- props.sessionStore.planAutoExpand = false;
2461
- props.sessionStore.goalAutoExpand = false;
2462
- if (agentLoop.isRunning)
2463
- props.sessionStore.pendingResetUI = true;
2464
- }
2465
- setPlanAutoExpand(false);
2466
- setGoalAutoExpand(false);
2467
- setOverlay("goal");
2468
- }
1982
+ setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
1983
+ setTaskPickerOpen(false);
1984
+ setGoalPickerOpen(true);
2469
1985
  return;
2470
1986
  }
2471
1987
  // Handle prompt-template commands (built-in + custom from .gg/commands/)
@@ -2542,26 +2058,11 @@ export function App(props) {
2542
2058
  await setGoalModeAndPrompt("off");
2543
2059
  }
2544
2060
  if (paneTransition) {
2545
- goalAutoExpandRef.current = paneTransition.goalAutoExpand;
2546
- setTimeout(() => {
2547
- const resetUI = props.resetUI;
2548
- const sessionStore = props.sessionStore;
2549
- if (shouldResetUIForGoalSetupPaneTransition({
2550
- hasResetUI: resetUI !== undefined,
2551
- hasSessionStore: sessionStore !== undefined,
2552
- }) &&
2553
- resetUI &&
2554
- sessionStore) {
2555
- sessionStore.overlay = paneTransition.overlay;
2556
- sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
2557
- sessionStore.planAutoExpand = paneTransition.planAutoExpand;
2558
- resetUI();
2559
- return;
2560
- }
2561
- setGoalAutoExpand(paneTransition.goalAutoExpand);
2562
- setPlanAutoExpand(paneTransition.planAutoExpand);
2563
- setOverlay(paneTransition.overlay);
2564
- }, 300);
2061
+ goalAutoExpandRef.current = false;
2062
+ setGoalAutoExpand(false);
2063
+ setPlanAutoExpand(false);
2064
+ setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
2065
+ setGoalPickerOpen(true);
2565
2066
  }
2566
2067
  }
2567
2068
  }
@@ -2879,7 +2380,7 @@ export function App(props) {
2879
2380
  (item.summaryRows !== undefined && item.summaryRows.length > 0) ||
2880
2381
  (item.summarySections !== undefined && item.summarySections.length > 0);
2881
2382
  const headerContentWidth = Math.max(10, columns - 3);
2882
- return withPrintedBoundarySpacing(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { status: loaderStatus, staticDisplay: true }), _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));
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));
2883
2384
  }
2884
2385
  case "style_pack": {
2885
2386
  const names = item.added.map((id) => LANGUAGE_DISPLAY_NAMES[id]);
@@ -2909,9 +2410,11 @@ export function App(props) {
2909
2410
  case "update_notice":
2910
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));
2911
2412
  case "plan_transition":
2912
- return renderStatusMessage(item.id, "● ", normalizeStatusText(item.text), theme.commandColor, { bold: true });
2413
+ return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
2913
2414
  case "goal_agent_transition":
2914
- return renderStatusMessage(item.id, "● ", normalizeStatusText(item.text), theme.commandColor, { bold: true });
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 }));
2915
2418
  case "model_transition":
2916
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 });
2917
2420
  case "theme_transition":
@@ -2976,26 +2479,18 @@ export function App(props) {
2976
2479
  setOverlay(kind);
2977
2480
  }
2978
2481
  }, [agentLoop.isRunning, props]);
2979
- const closeOverlay = useCallback(() => {
2980
- if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2981
- props.sessionStore.overlay = null;
2982
- props.resetUI();
2983
- }
2984
- else {
2985
- if (props.sessionStore) {
2986
- props.sessionStore.overlay = null;
2987
- }
2988
- setOverlay(null);
2989
- }
2990
- }, [agentLoop.isRunning, overlay, props]);
2991
2482
  const runGoalSyntheticEvent = useCallback((eventText) => {
2992
2483
  const eventInfo = parseGoalSyntheticEvent(eventText);
2993
2484
  const detail = eventInfo?.kind === "worker"
2994
2485
  ? `Inspecting worker result${eventInfo.task ? ` for ${eventInfo.task}` : ""}.`
2995
2486
  : `Inspecting verifier result${eventInfo?.status ? ` (${eventInfo.status})` : ""}.`;
2996
- if (agentRunningRef.current) {
2997
- queuedGoalSyntheticEventsRef.current += 1;
2998
- void setGoalModeAndPrompt("coordinator");
2487
+ const route = routeGoalSyntheticEvent({
2488
+ agentRunning: agentRunningRef.current,
2489
+ queuedSyntheticEvents: queuedGoalSyntheticEventsRef.current,
2490
+ });
2491
+ if (route.action === "queue") {
2492
+ queuedGoalSyntheticEventsRef.current = route.nextQueuedSyntheticEvents;
2493
+ void setGoalModeAndPrompt(route.nextGoalMode);
2999
2494
  appendGoalProgress({
3000
2495
  kind: "goal_progress",
3001
2496
  phase: "orchestrator_reviewing",
@@ -3558,8 +3053,71 @@ export function App(props) {
3558
3053
  setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3559
3054
  });
3560
3055
  }, [appendGoalProgress, clearGoalModeIfIdle, clearGoalStatusEntry, props.cwd]);
3056
+ const startTask = useCallback((title, prompt, taskId) => {
3057
+ const taskCwd = cwdRef.current;
3058
+ const shortId = taskId.slice(0, 8);
3059
+ const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
3060
+ `tasks({ action: "done", id: "${shortId}" })`;
3061
+ const fullPrompt = prompt + completionHint;
3062
+ if (props.resetUI && props.sessionStore) {
3063
+ const sysMsg = messagesRef.current[0];
3064
+ const newMessages = sysMsg && sysMsg.role === "system" ? [sysMsg] : messagesRef.current.slice(0, 1);
3065
+ const taskItem = { kind: "task", title, id: getId() };
3066
+ const sm = sessionManagerRef.current;
3067
+ void (async () => {
3068
+ let newSessionPath;
3069
+ if (sm) {
3070
+ try {
3071
+ const session = await sm.create(taskCwd, currentProvider, currentModel);
3072
+ newSessionPath = session.path;
3073
+ log("INFO", "tasks", "New session for task", { path: session.path });
3074
+ }
3075
+ catch {
3076
+ // Session creation is best-effort.
3077
+ }
3078
+ }
3079
+ if (props.sessionStore)
3080
+ props.sessionStore.overlay = null;
3081
+ props.resetUI?.({
3082
+ wipeSession: true,
3083
+ messages: newMessages,
3084
+ history: [{ kind: "banner", id: "banner" }, taskItem],
3085
+ sessionPath: newSessionPath,
3086
+ pendingAction: { prompt: fullPrompt },
3087
+ });
3088
+ })();
3089
+ return;
3090
+ }
3091
+ pendingHistoryFlushRef.current = [];
3092
+ props.terminalHistoryPrinter?.clear();
3093
+ setHistory([{ kind: "banner", id: "banner" }]);
3094
+ setLiveItems([]);
3095
+ messagesRef.current = messagesRef.current.slice(0, 1);
3096
+ agentLoop.reset();
3097
+ persistedIndexRef.current = messagesRef.current.length;
3098
+ const sm = sessionManagerRef.current;
3099
+ if (sm) {
3100
+ void sm.create(taskCwd, currentProvider, currentModel).then((session) => {
3101
+ sessionPathRef.current = session.path;
3102
+ log("INFO", "tasks", "New session for task", { path: session.path });
3103
+ });
3104
+ }
3105
+ const taskItem = { kind: "task", title, id: getId() };
3106
+ setLastUserMessage(title);
3107
+ setDoneStatus(null);
3108
+ setLiveItems([taskItem]);
3109
+ void agentLoop.run(fullPrompt).catch((err) => {
3110
+ setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3111
+ });
3112
+ }, [agentLoop, currentModel, currentProvider, props]);
3561
3113
  // Keep refs in sync for access from stale closures (onDone)
3114
+ startTaskRef.current = startTask;
3562
3115
  startGoalRunRef.current = startGoalRun;
3116
+ useEffect(() => {
3117
+ runAllTasksRef.current = runAllTasks;
3118
+ if (props.sessionStore)
3119
+ props.sessionStore.runAllTasks = runAllTasks;
3120
+ }, [runAllTasks, props.sessionStore]);
3563
3121
  useEffect(() => {
3564
3122
  agentRunningRef.current = agentLoop.isRunning;
3565
3123
  }, [agentLoop.isRunning]);
@@ -3659,7 +3217,6 @@ export function App(props) {
3659
3217
  if (props.sessionStore)
3660
3218
  props.sessionStore.runAllPixel = runAllPixel;
3661
3219
  }, [runAllPixel, props.sessionStore]);
3662
- const isGoalView = overlay === "goal";
3663
3220
  const isSkillsView = overlay === "skills";
3664
3221
  const isPlanView = overlay === "plan";
3665
3222
  const footerStatusLayout = getFooterStatusLayoutDecision({
@@ -3771,71 +3328,7 @@ export function App(props) {
3771
3328
  lastPendingHistoryItem,
3772
3329
  lastHistoryItem,
3773
3330
  });
3774
- return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: isGoalView ? (_jsx(GoalOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, autoExpandNewest: goalAutoExpand, onClose: () => {
3775
- goalAutoExpandRef.current = false;
3776
- setGoalAutoExpand(false);
3777
- if (props.sessionStore)
3778
- props.sessionStore.goalAutoExpand = false;
3779
- closeOverlay();
3780
- }, onRunGoal: (run) => {
3781
- const paneTransition = getGoalActivationPaneTransition();
3782
- goalAutoExpandRef.current = paneTransition.goalAutoExpand;
3783
- setGoalAutoExpand(paneTransition.goalAutoExpand);
3784
- setPlanAutoExpand(paneTransition.planAutoExpand);
3785
- if (props.sessionStore) {
3786
- props.sessionStore.overlay = paneTransition.overlay;
3787
- props.sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
3788
- props.sessionStore.planAutoExpand = paneTransition.planAutoExpand;
3789
- }
3790
- if (paneTransition.resetReviewScreen && props.resetUI && props.sessionStore) {
3791
- props.sessionStore.pendingGoalRun = run;
3792
- props.resetUI();
3793
- return;
3794
- }
3795
- setOverlay(paneTransition.overlay);
3796
- startGoalRun(run);
3797
- }, onVerifyGoal: (run) => {
3798
- void verifyGoalRun(run);
3799
- }, onPauseGoal: (run) => {
3800
- pauseGoalRun(run);
3801
- }, onRefineGoal: (run, feedback) => {
3802
- goalAutoExpandRef.current = true;
3803
- setGoalAutoExpand(true);
3804
- void (async () => {
3805
- try {
3806
- await setGoalModeAndPrompt("setup");
3807
- await agentLoop.run(`Refine Goal run ${run.id} (${run.title}) based on this user feedback. Update durable Goal setup only, then stop and reopen the Goal pane for review.\n\nFeedback: ${feedback}`);
3808
- }
3809
- catch (err) {
3810
- log("ERROR", "goal", err instanceof Error ? err.message : String(err));
3811
- setLiveItems((prev) => [...prev, toErrorItem(err, getId(), "Goal")]);
3812
- }
3813
- finally {
3814
- await setGoalModeAndPrompt("off");
3815
- const paneTransition = getGoalSetupFinishedPaneTransition();
3816
- goalAutoExpandRef.current = paneTransition.goalAutoExpand;
3817
- setTimeout(() => {
3818
- const resetUI = props.resetUI;
3819
- const sessionStore = props.sessionStore;
3820
- if (shouldResetUIForGoalSetupPaneTransition({
3821
- hasResetUI: resetUI !== undefined,
3822
- hasSessionStore: sessionStore !== undefined,
3823
- }) &&
3824
- resetUI &&
3825
- sessionStore) {
3826
- sessionStore.overlay = paneTransition.overlay;
3827
- sessionStore.goalAutoExpand = paneTransition.goalAutoExpand;
3828
- sessionStore.planAutoExpand = paneTransition.planAutoExpand;
3829
- resetUI();
3830
- return;
3831
- }
3832
- setGoalAutoExpand(paneTransition.goalAutoExpand);
3833
- setPlanAutoExpand(paneTransition.planAutoExpand);
3834
- setOverlay(paneTransition.overlay);
3835
- }, 300);
3836
- }
3837
- })();
3838
- } })) : isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
3331
+ return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
3839
3332
  if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3840
3333
  props.sessionStore.overlay = null;
3841
3334
  props.resetUI();
@@ -3989,8 +3482,48 @@ export function App(props) {
3989
3482
  log("ERROR", "error", errMsg);
3990
3483
  setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3991
3484
  });
3992
- } })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [liveItems.map((item, index, items) => renderItem(item, index, items)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, continuation: streamedAssistantFlushRef.current.flushedChars > 0 })] }), _jsxs(Box, { ref: mainControlsRef, flexDirection: "column", flexShrink: 0, flexGrow: 0, children: [hiddenQueuedCount > 0 && (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: shouldTopSpaceQueueIndicator ? 2 : 1, flexShrink: 0, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsxs(Text, { color: theme.textDim, children: [hiddenQueuedCount, " message", hiddenQueuedCount > 1 ? "s" : "", " queued"] })] })), _jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.textDim, width: columns, height: 0 }), _jsx(Box, { paddingLeft: 1, paddingRight: 1, width: columns, children: statusSlotVisible ? (activityVisible ? (_jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: !!thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, staticDisplay: true })) : stallStatusVisible ? (_jsx(Text, { color: theme.warning, wrap: "truncate", children: "⚠ API provider stream interrupted — retries exhausted. Your conversation is preserved." })) : doneStatus ? (_jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." }), !renderMarkdown && (_jsx(Text, { color: theme.warning, children: " · raw markdown mode" }))] }))) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." })] })) })] }), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleGoal: () => {
3993
- openOverlay("goal");
3485
+ } })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [uniqueItemsById(liveItems).map((item, index, items) => renderItem(item, index, items)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, continuation: streamedAssistantFlushRef.current.flushedChars > 0 })] }), _jsxs(Box, { ref: mainControlsRef, flexDirection: "column", flexShrink: 0, flexGrow: 0, children: [hiddenQueuedCount > 0 && (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: shouldTopSpaceQueueIndicator ? 2 : 1, flexShrink: 0, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsxs(Text, { color: theme.textDim, children: [hiddenQueuedCount, " message", hiddenQueuedCount > 1 ? "s" : "", " queued"] })] })), _jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.textDim, width: columns, height: 0 }), _jsx(Box, { paddingLeft: 1, paddingRight: 1, width: columns, children: statusSlotVisible ? (activityVisible ? (_jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: !!thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, staticDisplay: true })) : stallStatusVisible ? (_jsx(Text, { color: theme.warning, wrap: "truncate", children: "⚠ API provider stream interrupted — retries exhausted. Your conversation is preserved." })) : doneStatus ? (_jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." }), !renderMarkdown && (_jsx(Text, { color: theme.warning, children: " · raw markdown mode" }))] }))) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." })] })) })] }), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
3486
+ setGoalPickerOpen(false);
3487
+ setTaskPickerTasks(loadTasksSync(displayedCwd));
3488
+ setTaskPickerOpen((open) => !open);
3489
+ }, taskPickerOpen: taskPickerOpen, tasks: taskPickerTasks, onCloseTaskPicker: () => setTaskPickerOpen(false), onStartTask: (task) => {
3490
+ setTaskPickerOpen(false);
3491
+ markTaskInProgress(displayedCwd, task.id);
3492
+ setTaskPickerTasks(loadTasksSync(displayedCwd));
3493
+ startTask(task.title, task.prompt, task.id);
3494
+ }, onRunAllTasks: (task) => {
3495
+ setTaskPickerOpen(false);
3496
+ setRunAllTasks(true);
3497
+ const selected = task
3498
+ ? {
3499
+ id: task.id,
3500
+ title: task.title,
3501
+ prompt: task.prompt || task.text || task.title,
3502
+ }
3503
+ : getNextPendingTask(displayedCwd);
3504
+ if (selected) {
3505
+ markTaskInProgress(displayedCwd, selected.id);
3506
+ setTaskPickerTasks(loadTasksSync(displayedCwd));
3507
+ startTask(selected.title, selected.prompt, selected.id);
3508
+ }
3509
+ }, onDeleteTask: (task) => {
3510
+ const nextTasks = loadTasksSync(displayedCwd).filter((candidate) => candidate.id !== task.id);
3511
+ saveTasksSync(displayedCwd, nextTasks);
3512
+ setTaskPickerTasks(nextTasks);
3513
+ }, goalPickerOpen: goalPickerOpen, goals: goalPickerGoals, onCloseGoalPicker: () => setGoalPickerOpen(false), onRunGoal: (run) => {
3514
+ setGoalPickerOpen(false);
3515
+ startGoalRun(run);
3516
+ }, onDeleteGoal: (run) => {
3517
+ const nextGoals = loadGoalRunsSync(displayedCwd).filter((candidate) => candidate.id !== run.id);
3518
+ saveGoalRunsSync(displayedCwd, nextGoals);
3519
+ setGoalPickerGoals(nextGoals);
3520
+ }, onPauseGoal: (run) => {
3521
+ setGoalPickerOpen(false);
3522
+ pauseGoalRun(run);
3523
+ }, onToggleGoal: () => {
3524
+ setTaskPickerOpen(false);
3525
+ setGoalPickerGoals(loadGoalRunsSync(displayedCwd));
3526
+ setGoalPickerOpen((open) => !open);
3994
3527
  }, onToggleSkills: () => {
3995
3528
  openOverlay("skills");
3996
3529
  }, onTogglePixel: () => {