@kenkaiiii/ggcoder 4.3.216 → 4.3.218

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 (177) 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 +3 -2
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +63 -108
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +2 -1
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +6 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/core/agent-session-compaction.test.d.ts +2 -0
  18. package/dist/core/agent-session-compaction.test.d.ts.map +1 -0
  19. package/dist/core/agent-session-compaction.test.js +121 -0
  20. package/dist/core/agent-session-compaction.test.js.map +1 -0
  21. package/dist/core/agent-session.d.ts.map +1 -1
  22. package/dist/core/agent-session.js +6 -7
  23. package/dist/core/agent-session.js.map +1 -1
  24. package/dist/core/goal-controller.d.ts.map +1 -1
  25. package/dist/core/goal-controller.js +46 -1
  26. package/dist/core/goal-controller.js.map +1 -1
  27. package/dist/core/goal-controller.test.js +39 -0
  28. package/dist/core/goal-controller.test.js.map +1 -1
  29. package/dist/core/goal-store.d.ts +18 -0
  30. package/dist/core/goal-store.d.ts.map +1 -1
  31. package/dist/core/goal-store.js +48 -0
  32. package/dist/core/goal-store.js.map +1 -1
  33. package/dist/core/goal-store.test.js +10 -0
  34. package/dist/core/goal-store.test.js.map +1 -1
  35. package/dist/core/goal-worker.d.ts +9 -1
  36. package/dist/core/goal-worker.d.ts.map +1 -1
  37. package/dist/core/goal-worker.js +66 -19
  38. package/dist/core/goal-worker.js.map +1 -1
  39. package/dist/core/goal-worker.test.js +120 -1
  40. package/dist/core/goal-worker.test.js.map +1 -1
  41. package/dist/core/goal-worktree.d.ts +33 -0
  42. package/dist/core/goal-worktree.d.ts.map +1 -0
  43. package/dist/core/goal-worktree.js +67 -0
  44. package/dist/core/goal-worktree.js.map +1 -0
  45. package/dist/core/goal-worktree.test.d.ts +2 -0
  46. package/dist/core/goal-worktree.test.d.ts.map +1 -0
  47. package/dist/core/goal-worktree.test.js +101 -0
  48. package/dist/core/goal-worktree.test.js.map +1 -0
  49. package/dist/core/repomap-budget.d.ts +7 -0
  50. package/dist/core/repomap-budget.d.ts.map +1 -0
  51. package/dist/core/repomap-budget.js +10 -0
  52. package/dist/core/repomap-budget.js.map +1 -0
  53. package/dist/core/repomap-budget.test.d.ts +2 -0
  54. package/dist/core/repomap-budget.test.d.ts.map +1 -0
  55. package/dist/core/repomap-budget.test.js +26 -0
  56. package/dist/core/repomap-budget.test.js.map +1 -0
  57. package/dist/system-prompt.d.ts.map +1 -1
  58. package/dist/system-prompt.js +4 -0
  59. package/dist/system-prompt.js.map +1 -1
  60. package/dist/system-prompt.test.js +18 -0
  61. package/dist/system-prompt.test.js.map +1 -1
  62. package/dist/tools/goals.d.ts +9 -0
  63. package/dist/tools/goals.d.ts.map +1 -1
  64. package/dist/tools/goals.js +79 -27
  65. package/dist/tools/goals.js.map +1 -1
  66. package/dist/tools/goals.test.js +91 -22
  67. package/dist/tools/goals.test.js.map +1 -1
  68. package/dist/ui/App.d.ts +11 -385
  69. package/dist/ui/App.d.ts.map +1 -1
  70. package/dist/ui/App.js +109 -559
  71. package/dist/ui/App.js.map +1 -1
  72. package/dist/ui/app-items.d.ts +207 -0
  73. package/dist/ui/app-items.d.ts.map +1 -0
  74. package/dist/ui/app-items.js +2 -0
  75. package/dist/ui/app-items.js.map +1 -0
  76. package/dist/ui/app-state-persistence.test.js +4 -1
  77. package/dist/ui/app-state-persistence.test.js.map +1 -1
  78. package/dist/ui/chat-layout-pinning.test.js +11 -1
  79. package/dist/ui/chat-layout-pinning.test.js.map +1 -1
  80. package/dist/ui/components/AnimationContext.d.ts.map +1 -1
  81. package/dist/ui/components/AnimationContext.js +3 -5
  82. package/dist/ui/components/AnimationContext.js.map +1 -1
  83. package/dist/ui/components/AssistantMessage.test.js +12 -12
  84. package/dist/ui/components/AssistantMessage.test.js.map +1 -1
  85. package/dist/ui/components/CompactionNotice.js +2 -2
  86. package/dist/ui/components/CompactionNotice.js.map +1 -1
  87. package/dist/ui/components/Footer.d.ts +1 -0
  88. package/dist/ui/components/Footer.d.ts.map +1 -1
  89. package/dist/ui/components/Footer.js +5 -2
  90. package/dist/ui/components/Footer.js.map +1 -1
  91. package/dist/ui/components/ServerToolExecution.d.ts.map +1 -1
  92. package/dist/ui/components/ServerToolExecution.js +5 -23
  93. package/dist/ui/components/ServerToolExecution.js.map +1 -1
  94. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  95. package/dist/ui/components/SubAgentPanel.js +3 -2
  96. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  97. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  98. package/dist/ui/components/ToolExecution.js +12 -30
  99. package/dist/ui/components/ToolExecution.js.map +1 -1
  100. package/dist/ui/components/ToolGroupExecution.d.ts.map +1 -1
  101. package/dist/ui/components/ToolGroupExecution.js +5 -24
  102. package/dist/ui/components/ToolGroupExecution.js.map +1 -1
  103. package/dist/ui/components/ToolUseLoader.d.ts +3 -1
  104. package/dist/ui/components/ToolUseLoader.d.ts.map +1 -1
  105. package/dist/ui/components/ToolUseLoader.js +2 -2
  106. package/dist/ui/components/ToolUseLoader.js.map +1 -1
  107. package/dist/ui/footer-status-layout.test.js +9 -1
  108. package/dist/ui/footer-status-layout.test.js.map +1 -1
  109. package/dist/ui/goal-events.d.ts +4 -0
  110. package/dist/ui/goal-events.d.ts.map +1 -1
  111. package/dist/ui/goal-events.js +19 -1
  112. package/dist/ui/goal-events.js.map +1 -1
  113. package/dist/ui/goal-events.test.js +15 -1
  114. package/dist/ui/goal-events.test.js.map +1 -1
  115. package/dist/ui/goal-lifecycle-orchestration.test.js +20 -1
  116. package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
  117. package/dist/ui/goal-progress.d.ts +31 -0
  118. package/dist/ui/goal-progress.d.ts.map +1 -0
  119. package/dist/ui/goal-progress.js +152 -0
  120. package/dist/ui/goal-progress.js.map +1 -0
  121. package/dist/ui/item-helpers.d.ts +24 -0
  122. package/dist/ui/item-helpers.d.ts.map +1 -0
  123. package/dist/ui/item-helpers.js +96 -0
  124. package/dist/ui/item-helpers.js.map +1 -0
  125. package/dist/ui/layout-decisions.d.ts +113 -0
  126. package/dist/ui/layout-decisions.d.ts.map +1 -0
  127. package/dist/ui/layout-decisions.js +173 -0
  128. package/dist/ui/layout-decisions.js.map +1 -0
  129. package/dist/ui/prompt-routing.d.ts +29 -0
  130. package/dist/ui/prompt-routing.d.ts.map +1 -0
  131. package/dist/ui/prompt-routing.js +105 -0
  132. package/dist/ui/prompt-routing.js.map +1 -0
  133. package/dist/ui/queued-message.test.js +19 -1
  134. package/dist/ui/queued-message.test.js.map +1 -1
  135. package/dist/ui/render.d.ts +1 -0
  136. package/dist/ui/render.d.ts.map +1 -1
  137. package/dist/ui/render.js +1 -0
  138. package/dist/ui/render.js.map +1 -1
  139. package/dist/ui/scroll-stabilization.test.js +1 -1
  140. package/dist/ui/scroll-stabilization.test.js.map +1 -1
  141. package/dist/ui/slash-command-images.test.js +1 -1
  142. package/dist/ui/slash-command-images.test.js.map +1 -1
  143. package/dist/ui/terminal-history-format.d.ts +41 -0
  144. package/dist/ui/terminal-history-format.d.ts.map +1 -0
  145. package/dist/ui/terminal-history-format.js +131 -0
  146. package/dist/ui/terminal-history-format.js.map +1 -0
  147. package/dist/ui/terminal-history-spacing.d.ts +3 -0
  148. package/dist/ui/terminal-history-spacing.d.ts.map +1 -0
  149. package/dist/ui/terminal-history-spacing.js +28 -0
  150. package/dist/ui/terminal-history-spacing.js.map +1 -0
  151. package/dist/ui/terminal-history-status-renderers.d.ts +16 -0
  152. package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -0
  153. package/dist/ui/terminal-history-status-renderers.js +83 -0
  154. package/dist/ui/terminal-history-status-renderers.js.map +1 -0
  155. package/dist/ui/terminal-history.d.ts.map +1 -1
  156. package/dist/ui/terminal-history.js +30 -246
  157. package/dist/ui/terminal-history.js.map +1 -1
  158. package/dist/ui/terminal-history.test.js +21 -7
  159. package/dist/ui/terminal-history.test.js.map +1 -1
  160. package/dist/ui/thinking-level-cycle.test.d.ts +2 -0
  161. package/dist/ui/thinking-level-cycle.test.d.ts.map +1 -0
  162. package/dist/ui/thinking-level-cycle.test.js +25 -0
  163. package/dist/ui/thinking-level-cycle.test.js.map +1 -0
  164. package/dist/ui/thinking-level.d.ts +5 -0
  165. package/dist/ui/thinking-level.d.ts.map +1 -0
  166. package/dist/ui/thinking-level.js +30 -0
  167. package/dist/ui/thinking-level.js.map +1 -0
  168. package/dist/ui/tool-group-summary.d.ts.map +1 -1
  169. package/dist/ui/tool-group-summary.js +117 -8
  170. package/dist/ui/tool-group-summary.js.map +1 -1
  171. package/dist/ui/tool-group-summary.test.d.ts +2 -0
  172. package/dist/ui/tool-group-summary.test.d.ts.map +1 -0
  173. package/dist/ui/tool-group-summary.test.js +79 -0
  174. package/dist/ui/tool-group-summary.test.js.map +1 -0
  175. package/dist/ui/tui-history-parity.test.js +34 -13
  176. package/dist/ui/tui-history-parity.test.js.map +1 -1
  177. package/package.json +4 -4
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";
@@ -30,14 +29,14 @@ import { PlanOverlay } from "./components/PlanOverlay.js";
30
29
  import { ModelSelector } from "./components/ModelSelector.js";
31
30
  import { GoalOverlay } from "./components/GoalOverlay.js";
32
31
  import { PixelOverlay } from "./components/PixelOverlay.js";
33
- import { buildGoalFinalSummarySections, buildGoalSummaryRows, goalPassedDetail, } from "./goal-summary.js";
34
32
  import { SkillsOverlay } from "./components/SkillsOverlay.js";
35
33
  import { ThemeSelector } from "./components/ThemeSelector.js";
36
34
  import { BackgroundTasksBar, getFooterStatusLayoutDecision, } from "./components/BackgroundTasksBar.js";
37
35
  import { useTheme, useSetTheme } from "./theme/theme.js";
38
36
  import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
39
37
  import { getGitBranch } from "../utils/git.js";
40
- import { getModel, getContextWindow, getMaxThinkingLevel } from "../core/model-registry.js";
38
+ import { getModel, getContextWindow } from "../core/model-registry.js";
39
+ import { BLACK_CIRCLE } from "./constants/figures.js";
41
40
  import { SessionManager } from "../core/session-manager.js";
42
41
  import { appendMessagesToSession as appendSessionMessages, createCompactedSessionCheckpoint, } from "../core/session-compaction.js";
43
42
  import { log } from "../core/logger.js";
@@ -52,18 +51,29 @@ import { loadCustomCommands } from "../core/custom-commands.js";
52
51
  import { buildSystemPrompt } from "../system-prompt.js";
53
52
  import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
54
53
  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";
54
+ import { buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
55
+ import { getRepoMapBudgetForContext } from "../core/repomap-budget.js";
56
56
  import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMessages, } from "../core/repomap-context.js";
57
57
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
58
58
  import { getMCPServers } from "../core/mcp/index.js";
59
59
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
60
60
  import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
61
61
  import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
62
- import { canCompleteGoalRun, decideGoalNextAction, } from "../core/goal-controller.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, getGoalActivationPaneTransition, getGoalSetupFinishedPaneTransition, getGoalSetupPaneTransitionAfterRun, isAgentSpacingItem, MIN_LIVE_AREA_ROWS, nextGoalModeAfterAgentDone, shouldResetUIForGoalSetupPaneTransition, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
71
+ import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, } from "./item-helpers.js";
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,161 +107,27 @@ 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
- }
110
+ /** Tools that get aggregated into a single compact group when possible. */
111
+ const AGGREGATABLE_TOOLS = new Set([
112
+ "read",
113
+ "grep",
114
+ "find",
115
+ "ls",
116
+ "mcp__kencode-search__searchCode",
117
+ "mcp__kencode-search__referenceSources",
118
+ "mcp__kencode-search__discoverRepos",
119
+ ]);
120
+ const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
163
121
  function buildGoalTaskPromptWithReferences(run, taskPrompt) {
164
122
  if (taskPrompt.includes("## Goal References (MANDATORY)"))
165
123
  return taskPrompt;
166
124
  const references = formatGoalReferencesForPrompt(run.references ?? []);
167
125
  return references ? `${references}\n\n${taskPrompt}` : taskPrompt;
168
126
  }
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
- /** Tools that get aggregated into a single compact group when possible. */
209
- const AGGREGATABLE_TOOLS = new Set(["read", "grep", "find", "ls"]);
210
- const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
211
- /**
212
- * Cap memory by replacing old finalized rows with tiny tombstones. The full
213
- * transcript is already printed into terminal scrollback, so the in-memory copy
214
- * only needs enough recent structure to survive remounts and session mirroring.
215
- */
216
- const MAX_LIVE_HISTORY = 200;
217
- function compactHistory(items) {
218
- if (items.length <= MAX_LIVE_HISTORY)
219
- return items;
220
- const cutoff = items.length - MAX_LIVE_HISTORY;
221
- const compacted = new Array(items.length);
222
- for (let i = 0; i < cutoff; i++) {
223
- const it = items[i];
224
- compacted[i] = it.kind === "tombstone" ? it : { kind: "tombstone", id: it.id };
225
- }
226
- for (let i = cutoff; i < items.length; i++) {
227
- compacted[i] = items[i];
228
- }
229
- return compacted;
230
- }
231
- function summarizeGoalCompletion(summary) {
232
- const lines = summary
233
- .split("\n")
234
- .map((line) => line.trim())
235
- .filter((line) => line.length > 0 && line !== "[agent_done]");
236
- const statusLine = lines.find((line) => /^Status:/i.test(line));
237
- const changedLine = lines.find((line) => /^(Changed|Implemented|Fixed|Added|Key findings|Full verifier)/i.test(line));
238
- const verificationLine = lines.find((line) => /^(Verification|Verified|Result):/i.test(line));
239
- return statusLine ?? changedLine ?? verificationLine ?? lines[0];
240
- }
241
- const GOAL_PROGRESS_TEXT_LIMIT = 72;
242
- export function truncateGoalProgressText(text) {
243
- const normalized = text.replace(/\s+/g, " ").trim();
244
- if (normalized.length <= GOAL_PROGRESS_TEXT_LIMIT)
245
- return normalized;
246
- return `${normalized.slice(0, GOAL_PROGRESS_TEXT_LIMIT - 1).trimEnd()}…`;
247
- }
248
- function formatGoalWorkerFinishedTitle(taskTitle, status) {
249
- const prefix = status === "done" ? "Done" : "Failed";
250
- return truncateGoalProgressText(`${prefix}: ${taskTitle}`);
251
- }
252
127
  function goalProgressLoaderStatus(item) {
253
- if (item.status === "failed" || item.status === "fail" || item.status === "blocked")
128
+ if (item.status === "failed" || item.status === "fail" || item.status === "blocked") {
254
129
  return "error";
130
+ }
255
131
  if (item.phase === "worker_finished" ||
256
132
  item.phase === "verifier_finished" ||
257
133
  item.phase === "terminal") {
@@ -274,371 +150,6 @@ function goalProgressColor(item, theme) {
274
150
  return theme.warning;
275
151
  return theme.primary;
276
152
  }
277
- function goalTerminalProgressId(run) {
278
- return `goal-terminal-${run.id}`;
279
- }
280
- function goalTerminalRunIdFromItem(item) {
281
- if (item.kind !== "goal_progress" || item.phase !== "terminal")
282
- return undefined;
283
- if (!item.id.startsWith("goal-terminal-"))
284
- return undefined;
285
- return item.id.slice("goal-terminal-".length);
286
- }
287
- function goalProgressMatchesDraft(item, draft) {
288
- return (item.phase === draft.phase &&
289
- item.title === draft.title &&
290
- item.detail === draft.detail &&
291
- item.workerId === draft.workerId &&
292
- item.status === draft.status &&
293
- JSON.stringify(item.summaryRows ?? []) === JSON.stringify(draft.summaryRows ?? []) &&
294
- JSON.stringify(item.summarySections ?? []) === JSON.stringify(draft.summarySections ?? []));
295
- }
296
- export function appendGoalProgressDraft(items, draft, makeId) {
297
- const previous = items.at(-1);
298
- if (previous?.kind === "goal_progress" && goalProgressMatchesDraft(previous, draft)) {
299
- return items;
300
- }
301
- return [...items, { ...draft, id: makeId() }];
302
- }
303
- /**
304
- * Reconcile terminal Goal cards that are already visible in this UI session.
305
- *
306
- * This intentionally does not synthesize missing cards from durable GoalRun
307
- * state. Goal terminal cards are event notifications: they should appear when
308
- * the terminal event happens in the current UI, not whenever a fresh session
309
- * polls old Goal runs from the Goal pane. Callers that just observed a terminal
310
- * event append that card first, then use this helper to tombstone stale older
311
- * cards for the same run.
312
- */
313
- export function getNextGeneratedItemId(items) {
314
- let max = -1;
315
- for (const item of items) {
316
- const raw = item.id.startsWith("ui-") ? item.id.slice(3) : item.id;
317
- const n = Number(raw);
318
- if (Number.isInteger(n) && n >= 0 && n > max)
319
- max = n;
320
- }
321
- return max + 1;
322
- }
323
- export function completedItemsWithDurableGoalTerminalProgress(items, runs) {
324
- const runIds = new Set(runs.map((run) => run.id));
325
- const terminalByRun = new Map(runs
326
- .map((run) => [run.id, formatGoalTerminalProgress(run)])
327
- .filter((entry) => entry[1] !== null));
328
- if (runIds.size === 0)
329
- return items;
330
- let changed = false;
331
- const reconciled = items.map((item, index) => {
332
- const runId = goalTerminalRunIdFromItem(item);
333
- if (!runId || !runIds.has(runId))
334
- return item;
335
- const draft = terminalByRun.get(runId);
336
- if (draft && goalProgressMatchesDraft(item, draft))
337
- return item;
338
- changed = true;
339
- return { kind: "tombstone", id: `tombstone-${item.id}-${index}` };
340
- });
341
- return changed ? reconciled : items;
342
- }
343
- export function formatGoalTerminalProgress(run) {
344
- switch (run.status) {
345
- case "passed":
346
- return {
347
- kind: "goal_progress",
348
- phase: "terminal",
349
- title: `Goal passed: ${run.title}`,
350
- detail: goalPassedDetail(run),
351
- summaryRows: buildGoalSummaryRows(run),
352
- summarySections: buildGoalFinalSummarySections(run),
353
- status: run.status,
354
- };
355
- case "failed":
356
- return {
357
- kind: "goal_progress",
358
- phase: "terminal",
359
- title: `Goal failed: ${run.title}`,
360
- detail: "Auto-continuation stopped. Check Goal tasks for the failing step.",
361
- summaryRows: buildGoalSummaryRows(run),
362
- status: run.status,
363
- };
364
- case "blocked":
365
- return {
366
- kind: "goal_progress",
367
- phase: "terminal",
368
- title: `Goal blocked: ${run.title}`,
369
- detail: goalHasBlockingPrerequisites(run)
370
- ? formatGoalBlockingPrerequisites(run)
371
- : (run.blockers[0] ?? "A prerequisite or missing verifier blocked progress."),
372
- summaryRows: buildGoalSummaryRows(run),
373
- status: run.status,
374
- };
375
- case "paused":
376
- return {
377
- kind: "goal_progress",
378
- phase: "terminal",
379
- title: `Goal paused: ${run.title}`,
380
- detail: run.blockers[0] ?? "Auto-continuation paused.",
381
- summaryRows: buildGoalSummaryRows(run),
382
- status: run.status,
383
- };
384
- case "draft":
385
- case "ready":
386
- case "running":
387
- case "verifying":
388
- return null;
389
- }
390
- }
391
- export function shouldHideHistoryForOverlayView(isOverlayView, _isAgentRunning) {
392
- // Overlay panes are standalone full-screen states. Finalized chat rows are
393
- // printed outside Ink, so overlays should never replay transcript UI behind them.
394
- return isOverlayView;
395
- }
396
- export function shouldStabilizeOverlayPaneRerender({ overlayPane, isAgentRunning, }) {
397
- return isAgentRunning && overlayPane === "goal";
398
- }
399
- export function shouldHideStaticItemsForOverlayView({ shouldHideHistoryForOverlay, stabilizeOverlayPaneRerender: _stabilizeOverlayPaneRerender, }) {
400
- return shouldHideHistoryForOverlay;
401
- }
402
- export function getDoneFlushDecision({ planOverlayPending, goalMode, goalAutoExpand, }) {
403
- return {
404
- showDoneStatus: !(planOverlayPending ||
405
- goalMode === "planner" ||
406
- goalMode === "setup" ||
407
- goalAutoExpand),
408
- flushLiveItems: true,
409
- };
410
- }
411
- export function getGoalSetupFinishedPaneTransition() {
412
- return {
413
- overlay: "goal",
414
- goalAutoExpand: true,
415
- planAutoExpand: false,
416
- suppressDoneStatus: true,
417
- };
418
- }
419
- export function getGoalSetupPaneTransitionAfterRun({ isGoalSetupCommand, setupPanePending, }) {
420
- return isGoalSetupCommand && setupPanePending ? getGoalSetupFinishedPaneTransition() : null;
421
- }
422
- export function shouldResetUIForSetupPaneTransition({ hasResetUI, hasSessionStore, }) {
423
- // Opening a review pane is a full-screen state transition. A bare React state
424
- // flip hides history in the virtual tree, but it does not reset Ink/log-update's
425
- // already-written terminal frame, so the pane can render below prior chat.
426
- return hasResetUI && hasSessionStore;
427
- }
428
- export const shouldResetUIForGoalSetupPaneTransition = shouldResetUIForSetupPaneTransition;
429
- export function getGoalActivationPaneTransition() {
430
- return { overlay: null, goalAutoExpand: false, planAutoExpand: false, resetReviewScreen: true };
431
- }
432
- export function getGoalContinuationChoiceKey({ runId, decision, }) {
433
- switch (decision.kind) {
434
- case "create_task":
435
- return `${runId}:create_task:${decision.title}:${decision.prompt}`;
436
- case "start_worker":
437
- case "pause":
438
- return `${runId}:${decision.kind}:${decision.task.id}:${decision.attempts}`;
439
- case "run_verifier":
440
- return `${runId}:run_verifier:${decision.command}`;
441
- case "blocked":
442
- case "complete":
443
- case "terminal":
444
- case "wait":
445
- return `${runId}:${decision.kind}:${decision.reason}`;
446
- }
447
- }
448
- export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, hasTallLiveUserMessage = false, hasParagraphBreakLiveUserMessage = false, }) {
449
- const shouldPreserveStatic = isUserScrolled || hasTallLiveUserMessage || hasParagraphBreakLiveUserMessage;
450
- const shouldAutoFollow = !(isUserScrolled || hasTallLiveUserMessage);
451
- return {
452
- preserveStatic: shouldPreserveStatic && hasNewOutput,
453
- autoFollow: shouldAutoFollow,
454
- };
455
- }
456
- export function nextGoalModeAfterAgentDone({ currentMode, runningGoalIds, queuedSyntheticEvents, activeContinuationFlights = 0, wasGoalSetupTurn, }) {
457
- if (wasGoalSetupTurn)
458
- return "off";
459
- if (currentMode === "planner" || currentMode === "setup")
460
- return currentMode;
461
- if (queuedSyntheticEvents > 0)
462
- return "coordinator";
463
- if (activeContinuationFlights > 0)
464
- return "coordinator";
465
- if (currentMode === "coordinator" && runningGoalIds > 0)
466
- return "coordinator";
467
- return "off";
468
- }
469
- export function hasParagraphBreakLiveUserMessage(text) {
470
- return /\n[ \t]*\n/.test(text);
471
- }
472
- export function isTallLiveUserMessage(text, rows) {
473
- return text.split("\n").length > Math.max(8, Math.floor(rows * 0.6));
474
- }
475
- export function getStaticHistoryKey({ resizeKey }) {
476
- return `${resizeKey}`;
477
- }
478
- const MIN_LIVE_AREA_ROWS = 3;
479
- const INPUT_AREA_ROWS = 3;
480
- const STATUS_SLOT_ROWS = 2;
481
- const FOOTER_ONE_LINE_ROWS = 1;
482
- const FOOTER_TWO_LINE_ROWS = 2;
483
- const GOAL_STATUS_ROWS = 1;
484
- const COLLAPSED_FOOTER_STATUS_ROWS = 1;
485
- const MAX_EXPANDED_BACKGROUND_TASK_ROWS = 7;
486
- function isAgentSpacingKind(kind) {
487
- return [
488
- "assistant",
489
- "queued",
490
- "goal_progress",
491
- "tool_start",
492
- "tool_done",
493
- "tool_group",
494
- "server_tool_start",
495
- "server_tool_done",
496
- "subagent_group",
497
- "info",
498
- "error",
499
- "stopped",
500
- "plan_transition",
501
- "goal_agent_transition",
502
- "thinking_transition",
503
- "model_transition",
504
- "theme_transition",
505
- "plan_event",
506
- "update_notice",
507
- "compacting",
508
- "compacted",
509
- "style_pack",
510
- "setup_hint",
511
- ].includes(kind);
512
- }
513
- function isToolBoundaryKind(kind) {
514
- return [
515
- "goal_progress",
516
- "tool_start",
517
- "tool_done",
518
- "tool_group",
519
- "server_tool_start",
520
- "server_tool_done",
521
- "subagent_group",
522
- ].includes(kind);
523
- }
524
- function isAgentSpacingItem(item) {
525
- return isAgentSpacingKind(item.kind);
526
- }
527
- export function shouldTopSpaceAfterPrintedAgentBoundary({ currentKind, previousLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
528
- const needsExternalSpacing = isAgentSpacingKind(currentKind);
529
- if (!needsExternalSpacing)
530
- return false;
531
- if (previousLiveItem !== undefined)
532
- return false;
533
- const previousKind = lastPendingHistoryItem?.kind ?? lastHistoryItem?.kind;
534
- return previousKind !== undefined && isAgentSpacingKind(previousKind);
535
- }
536
- export function shouldTopSpaceAssistantAfterToolBoundary({ text, previousLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
537
- if (text.trim().length === 0)
538
- return false;
539
- if (shouldTopSpaceAfterPrintedAgentBoundary({
540
- currentKind: "assistant",
541
- previousLiveItem,
542
- lastPendingHistoryItem,
543
- lastHistoryItem,
544
- })) {
545
- return true;
546
- }
547
- const previousKind = previousLiveItem?.kind;
548
- return previousKind !== undefined && isToolBoundaryKind(previousKind);
549
- }
550
- export function shouldTopSpaceStreamingAssistant({ visibleStreamingText, lastLiveItem, lastPendingHistoryItem, lastHistoryItem, }) {
551
- return shouldTopSpaceAssistantAfterToolBoundary({
552
- text: visibleStreamingText,
553
- previousLiveItem: lastLiveItem,
554
- lastPendingHistoryItem,
555
- lastHistoryItem,
556
- });
557
- }
558
- export function getChatControlsLayoutDecision({ rows, agentRunning, activityVisible, doneStatusVisible, stallStatusVisible, exitPending, footerStatusLayout, taskBarExpanded, goalStatusEntryCount, footerFitsOnOneLine, }) {
559
- const statusRows = activityVisible || stallStatusVisible || doneStatusVisible || agentRunning
560
- ? STATUS_SLOT_ROWS
561
- : 0;
562
- const footerRows = exitPending || footerFitsOnOneLine ? FOOTER_ONE_LINE_ROWS : FOOTER_TWO_LINE_ROWS;
563
- const goalRows = !exitPending && goalStatusEntryCount > 0 ? GOAL_STATUS_ROWS : 0;
564
- const footerStatusRows = footerStatusLayout.stack
565
- ? Number(footerStatusLayout.hasBackgroundTasks) + Number(footerStatusLayout.hasUpdateNotice)
566
- : footerStatusLayout.hasBackgroundTasks || footerStatusLayout.hasUpdateNotice
567
- ? COLLAPSED_FOOTER_STATUS_ROWS
568
- : 0;
569
- const expandedTaskRows = taskBarExpanded && footerStatusLayout.hasBackgroundTasks
570
- ? MAX_EXPANDED_BACKGROUND_TASK_ROWS - COLLAPSED_FOOTER_STATUS_ROWS
571
- : 0;
572
- const controlsRows = statusRows + INPUT_AREA_ROWS + footerRows + goalRows + footerStatusRows + expandedTaskRows;
573
- const maxControlsRows = Math.max(1, rows - MIN_LIVE_AREA_ROWS);
574
- const boundedControlsRows = Math.min(controlsRows, maxControlsRows);
575
- return {
576
- controlsRows: boundedControlsRows,
577
- liveAreaRows: Math.max(MIN_LIVE_AREA_ROWS, rows - boundedControlsRows),
578
- };
579
- }
580
- // flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
581
- /** Check whether an item is still active (running spinner, pending result). */
582
- export function isActiveItem(item) {
583
- switch (item.kind) {
584
- case "tool_start":
585
- case "server_tool_start":
586
- case "queued":
587
- case "compacting":
588
- return true;
589
- case "tool_group":
590
- return item.tools.some((t) => t.status === "running");
591
- case "subagent_group":
592
- return item.agents.some((a) => a.status === "running");
593
- default:
594
- return false;
595
- }
596
- }
597
- /**
598
- * Partition live items into completed (flushable to finalized history) and still-active.
599
- * Completed items precede active ones — we flush the longest contiguous prefix
600
- * of completed items to keep ordering stable.
601
- */
602
- export function partitionCompleted(items) {
603
- // Find the first active item — everything before it is safe to flush as a
604
- // single chronological prefix. Splitting assistant text out of that prefix
605
- // lets later tool rows print to scrollback above the message that introduced
606
- // them, so keep the prefix intact.
607
- const firstActiveIdx = items.findIndex(isActiveItem);
608
- if (firstActiveIdx === -1) {
609
- return { flushed: items, remaining: [] };
610
- }
611
- if (firstActiveIdx === 0) {
612
- return { flushed: [], remaining: items };
613
- }
614
- return {
615
- flushed: items.slice(0, firstActiveIdx),
616
- remaining: items.slice(firstActiveIdx),
617
- };
618
- }
619
- function normalizeAssistantText(text) {
620
- return stripDoneMarkers(text).trim();
621
- }
622
- function isSameAssistantText(item, text) {
623
- return item.kind === "assistant" && normalizeAssistantText(item.text) === text;
624
- }
625
- export function pinStreamingTextBeforeToolBoundary({ items, visibleStreamingText, thinking, thinkingMs, makeId, }) {
626
- const text = normalizeAssistantText(visibleStreamingText);
627
- if (text.length === 0)
628
- return items;
629
- if (items.some((item) => item.kind === "assistant"))
630
- return items;
631
- return [
632
- ...items,
633
- {
634
- kind: "assistant",
635
- text,
636
- thinking: thinking.length > 0 ? thinking : undefined,
637
- thinkingMs: thinking.length > 0 ? thinkingMs : undefined,
638
- id: makeId(),
639
- },
640
- ];
641
- }
642
153
  // ── Duration summary ─────────────────────────────────────
643
154
  function formatDuration(ms) {
644
155
  const totalSec = Math.round(ms / 1000);
@@ -777,7 +288,7 @@ export function App(props) {
777
288
  const [currentProvider, setCurrentProvider] = useState(props.provider);
778
289
  const [currentTools, setCurrentTools] = useState(props.tools);
779
290
  const currentToolsRef = useRef(props.tools);
780
- const [thinkingEnabled, setThinkingEnabled] = useState(!!props.thinking);
291
+ const [thinkingLevel, setThinkingLevel] = useState(props.thinking);
781
292
  const [renderMarkdown, setRenderMarkdown] = useState(true);
782
293
  const messagesRef = useRef(props.sessionStore?.messages ?? props.messages);
783
294
  const repoMapInjectionEnabledRef = useRef(true);
@@ -923,11 +434,16 @@ export function App(props) {
923
434
  useEffect(() => {
924
435
  onRuntimeStateChange?.({ provider: currentProvider });
925
436
  }, [currentProvider, onRuntimeStateChange]);
437
+ useEffect(() => {
438
+ if (thinkingLevel && !isThinkingLevelSupported(currentProvider, currentModel, thinkingLevel)) {
439
+ setThinkingLevel(getNextThinkingLevel(currentProvider, currentModel, undefined));
440
+ }
441
+ }, [currentProvider, currentModel, thinkingLevel]);
926
442
  useEffect(() => {
927
443
  onRuntimeStateChange?.({
928
- thinking: thinkingEnabled ? getMaxThinkingLevel(currentModel) : undefined,
444
+ thinking: thinkingLevel,
929
445
  });
930
- }, [thinkingEnabled, currentModel, onRuntimeStateChange]);
446
+ }, [thinkingLevel, onRuntimeStateChange]);
931
447
  useEffect(() => {
932
448
  printHistoryItems(history);
933
449
  }, [history, printHistoryItems]);
@@ -1081,6 +597,29 @@ export function App(props) {
1081
597
  }
1082
598
  return newPrompt;
1083
599
  }, [rebuildSystemPrompt]);
600
+ useEffect(() => {
601
+ if (!props.connectInitialMcpTools)
602
+ return;
603
+ let cancelled = false;
604
+ void props
605
+ .connectInitialMcpTools()
606
+ .then((mcpTools) => {
607
+ if (cancelled || mcpTools.length === 0)
608
+ return;
609
+ setCurrentTools((prev) => {
610
+ const next = [...prev.filter((tool) => !tool.name.startsWith("mcp__")), ...mcpTools];
611
+ currentToolsRef.current = next;
612
+ void replaceSystemPrompt({ tools: next });
613
+ return next;
614
+ });
615
+ })
616
+ .catch((err) => {
617
+ log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
618
+ });
619
+ return () => {
620
+ cancelled = true;
621
+ };
622
+ }, [props.connectInitialMcpTools, replaceSystemPrompt]);
1084
623
  const setGoalModeAndPrompt = useCallback(async (nextMode, options) => {
1085
624
  goalModeStateRef.current = nextMode;
1086
625
  if (props.goalModeRef)
@@ -1345,13 +884,10 @@ export function App(props) {
1345
884
  (props.repoMapReadFilesRef?.current.size ?? 0));
1346
885
  }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef]);
1347
886
  const getRepoMapBudget = useCallback(() => {
1348
- const userTurns = messagesRef.current.filter((message) => message.role === "user").length;
1349
- const readCount = props.repoMapReadFilesRef?.current.size ?? 0;
1350
- if (userTurns <= 1 && readCount === 0)
1351
- return FIRST_TURN_REPO_MAP_MAX_CHARS;
1352
- if (readCount > 0)
1353
- return FOCUSED_REPO_MAP_MAX_CHARS;
1354
- return FOCUSED_REPO_MAP_MAX_CHARS + 1000;
887
+ return getRepoMapBudgetForContext({
888
+ messages: messagesRef.current,
889
+ readFileCount: props.repoMapReadFilesRef?.current.size ?? 0,
890
+ });
1355
891
  }, [props.repoMapReadFilesRef]);
1356
892
  const refreshRepoMap = useCallback(async (latestUserPrompt) => {
1357
893
  const rendered = await buildRepoMap({
@@ -1471,7 +1007,7 @@ export function App(props) {
1471
1007
  tools: currentTools,
1472
1008
  webSearch: props.webSearch,
1473
1009
  maxTokens: props.maxTokens,
1474
- thinking: thinkingEnabled ? getMaxThinkingLevel(currentModel) : undefined,
1010
+ thinking: thinkingLevel,
1475
1011
  apiKey: activeApiKey,
1476
1012
  baseUrl: activeBaseUrl,
1477
1013
  accountId: activeAccountId,
@@ -2636,20 +2172,20 @@ export function App(props) {
2636
2172
  }
2637
2173
  }, [agentLoop, handleDoubleExit]);
2638
2174
  const handleToggleThinking = useCallback(() => {
2639
- setThinkingEnabled((prev) => {
2640
- const next = !prev;
2641
- log("INFO", "thinking", `Thinking ${next ? "enabled" : "disabled"}`);
2642
- setLiveItems((items) => [
2643
- ...items,
2644
- { kind: "thinking_transition", active: next, id: getId() },
2645
- ]);
2175
+ setThinkingLevel((prev) => {
2176
+ const next = getNextThinkingLevel(currentProvider, currentModel, prev);
2177
+ log("INFO", "thinking", next ? `Thinking ${next}` : "Thinking disabled");
2646
2178
  if (props.settingsFile) {
2647
2179
  const sm = new SettingsManager(props.settingsFile);
2648
- sm.load().then(() => sm.set("thinkingEnabled", next));
2180
+ void sm.load().then(async () => {
2181
+ await sm.set("thinkingEnabled", !!next);
2182
+ if (next)
2183
+ await sm.set("thinkingLevel", next);
2184
+ });
2649
2185
  }
2650
2186
  return next;
2651
2187
  });
2652
- }, [props.settingsFile]);
2188
+ }, [currentProvider, currentModel, props.settingsFile]);
2653
2189
  const handleModelSelect = useCallback((value) => {
2654
2190
  setOverlay(null);
2655
2191
  const colonIdx = value.indexOf(":");
@@ -2838,7 +2374,7 @@ export function App(props) {
2838
2374
  case "user":
2839
2375
  return (_jsx(UserMessage, { text: item.text, imageCount: item.imageCount, pasteInfo: item.pasteInfo }, item.id));
2840
2376
  case "goal":
2841
- return (_jsx(Box, { 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));
2377
+ return (_jsx(Box, { paddingLeft: 1, marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.success, bold: true, children: "▶ " }), _jsx(Text, { color: theme.textDim, children: "Goal: " }), _jsx(Text, { color: theme.success, children: truncateGoalProgressText(item.title) }), item.workerId ? _jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] }) : null] }) }, item.id));
2842
2378
  case "goal_progress": {
2843
2379
  const color = goalProgressColor(item, theme);
2844
2380
  const loaderStatus = goalProgressLoaderStatus(item);
@@ -2846,15 +2382,15 @@ export function App(props) {
2846
2382
  (item.summaryRows !== undefined && item.summaryRows.length > 0) ||
2847
2383
  (item.summarySections !== undefined && item.summarySections.length > 0);
2848
2384
  const headerContentWidth = Math.max(10, columns - 3);
2849
- 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));
2385
+ return withPrintedBoundarySpacing(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, flexShrink: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { status: loaderStatus, staticDisplay: true, color: color }), _jsx(Box, { flexGrow: 1, width: headerContentWidth, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: color, bold: true, children: truncateGoalProgressText(item.title) }), item.workerId ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] })) : null] }) })] }), hasBody ? (_jsx(MessageResponse, { children: _jsxs(Box, { flexDirection: "column", flexShrink: 1, children: [item.detail ? (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: truncateGoalProgressText(item.detail) })) : null, item.summaryRows?.map((row) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.textDim, children: row.label.padEnd(12) }), _jsx(Text, { color: theme.text, children: truncateGoalProgressText(row.value) }), row.detail ? (_jsxs(Text, { color: theme.textDim, children: [" \u00B7 ", truncateGoalProgressText(row.detail)] })) : null] }, row.label))), item.summarySections?.map((section) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: theme.textDim, bold: true, children: section.title }), section.lines.map((line, sectionLineIndex) => (_jsx(Text, { color: theme.text, wrap: "wrap", children: `• ${truncateGoalProgressText(line)}` }, `${section.title}-${sectionLineIndex}`)))] }, section.title)))] }) })) : null] }, item.id));
2850
2386
  }
2851
2387
  case "style_pack": {
2852
2388
  const names = item.added.map((id) => LANGUAGE_DISPLAY_NAMES[id]);
2853
2389
  const headerLabel = item.added.length > 1 ? "STYLE PACKS ACTIVE" : "STYLE PACK ACTIVE";
2854
- return (_jsxs(Box, { marginTop: 1, 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));
2390
+ return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsxs(Box, { flexShrink: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.language, paddingX: 1, children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.language, bold: true, children: "◆ " }), _jsx(Text, { color: theme.language, bold: true, children: headerLabel })] }), _jsx(Text, { color: theme.text, bold: true, wrap: "wrap", children: names.join(", ") }), item.showSetupHint && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.textMuted, children: "Tip: run " }), _jsx(Text, { color: theme.language, bold: true, children: "/setup" }), _jsx(Text, { color: theme.textMuted, children: " to audit this project against the active pack(s)" })] }) }))] }) }, item.id));
2855
2391
  }
2856
2392
  case "setup_hint":
2857
- return (_jsxs(Box, { marginTop: 1, 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));
2393
+ return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsxs(Box, { flexShrink: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.language, paddingX: 1, children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.language, bold: true, children: "◆ " }), _jsx(Text, { color: theme.language, bold: true, children: "NO STYLE PACKS DETECTED" })] }), _jsx(Text, { color: theme.textMuted, wrap: "wrap", children: "This directory has no recognized language manifest at its root." }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: theme.textMuted, children: "Tip: run " }), _jsx(Text, { color: theme.language, bold: true, children: "/setup" }), _jsx(Text, { color: theme.textMuted, children: " to audit project hygiene or bootstrap a new project from scratch" })] }) })] }) }, item.id));
2858
2394
  case "assistant":
2859
2395
  return (_jsx(AssistantMessage, { text: item.text, thinking: item.thinking, thinkingMs: item.thinkingMs, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, marginTop: assistantMarginTop }, item.id));
2860
2396
  case "tool_start":
@@ -2874,15 +2410,11 @@ export function App(props) {
2874
2410
  case "info":
2875
2411
  return renderStatusMessage(item.id, "○ ", item.text, theme.commandColor, { muted: true });
2876
2412
  case "update_notice":
2877
- return (_jsx(Box, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: theme.commandColor, paddingX: 1, children: _jsxs(Text, { color: theme.commandColor, bold: true, wrap: "wrap", children: ["✨ ", item.text] }) }, item.id));
2413
+ return (_jsx(Box, { paddingLeft: 1, marginTop: 1, flexShrink: 1, children: _jsx(Box, { flexShrink: 1, borderStyle: "round", borderColor: theme.commandColor, paddingX: 1, children: _jsxs(Text, { color: theme.commandColor, bold: true, wrap: "wrap", children: ["✨ ", item.text] }) }) }, item.id));
2878
2414
  case "plan_transition":
2879
- 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 });
2880
2416
  case "goal_agent_transition":
2881
- return renderStatusMessage(item.id, "● ", normalizeStatusText(item.text), theme.commandColor, { bold: true });
2882
- case "thinking_transition": {
2883
- const glyphColor = item.active ? theme.commandColor : theme.textDim;
2884
- return renderStatusMessage(item.id, "✻ ", item.active ? "Thinking ON" : "Thinking OFF", glyphColor, { bold: true, muted: !item.active });
2885
- }
2417
+ return renderStatusMessage(item.id, `${BLACK_CIRCLE} `, normalizeStatusText(item.text), theme.commandColor, { bold: true });
2886
2418
  case "model_transition":
2887
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 });
2888
2420
  case "theme_transition":
@@ -2903,7 +2435,7 @@ export function App(props) {
2903
2435
  // gradient. Glyph `⊘` reads as "stop" without being alarming.
2904
2436
  return renderStatusMessage(item.id, "⊘ ", normalizeStatusText(item.text), theme.commandColor, { bold: true });
2905
2437
  case "step_done":
2906
- return (_jsx(Box, { 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));
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));
2907
2439
  case "queued": {
2908
2440
  const suffix = item.imageCount
2909
2441
  ? ` (+${item.imageCount} image${item.imageCount > 1 ? "s" : ""})`
@@ -2915,7 +2447,7 @@ export function App(props) {
2915
2447
  case "compacted":
2916
2448
  return (_jsx(CompactionDone, { originalCount: item.originalCount, newCount: item.newCount, tokensBefore: item.tokensBefore, tokensAfter: item.tokensAfter }, item.id));
2917
2449
  case "duration":
2918
- return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.textDim, children: ["✻ ", item.verb, " ", formatDuration(item.durationMs)] }) }, item.id));
2450
+ return (_jsx(Box, { paddingLeft: 1, marginTop: 1, children: _jsxs(Text, { color: theme.textDim, children: ["✻ ", item.verb, " ", formatDuration(item.durationMs)] }) }, item.id));
2919
2451
  case "subagent_group":
2920
2452
  return withPrintedBoundarySpacing(_jsx(SubAgentPanel, { agents: item.agents, aborted: item.aborted }, item.id));
2921
2453
  }
@@ -2964,9 +2496,13 @@ export function App(props) {
2964
2496
  const detail = eventInfo?.kind === "worker"
2965
2497
  ? `Inspecting worker result${eventInfo.task ? ` for ${eventInfo.task}` : ""}.`
2966
2498
  : `Inspecting verifier result${eventInfo?.status ? ` (${eventInfo.status})` : ""}.`;
2967
- if (agentRunningRef.current) {
2968
- queuedGoalSyntheticEventsRef.current += 1;
2969
- void setGoalModeAndPrompt("coordinator");
2499
+ const route = routeGoalSyntheticEvent({
2500
+ agentRunning: agentRunningRef.current,
2501
+ queuedSyntheticEvents: queuedGoalSyntheticEventsRef.current,
2502
+ });
2503
+ if (route.action === "queue") {
2504
+ queuedGoalSyntheticEventsRef.current = route.nextQueuedSyntheticEvents;
2505
+ void setGoalModeAndPrompt(route.nextGoalMode);
2970
2506
  appendGoalProgress({
2971
2507
  kind: "goal_progress",
2972
2508
  phase: "orchestrator_reviewing",
@@ -3315,6 +2851,7 @@ export function App(props) {
3315
2851
  cwd: props.cwd,
3316
2852
  provider: currentProvider,
3317
2853
  model: currentModel,
2854
+ thinkingLevel,
3318
2855
  goalRunId: checkedRun.id,
3319
2856
  goalTaskId: decision.task.id,
3320
2857
  taskTitle: decision.task.title,
@@ -3358,6 +2895,7 @@ export function App(props) {
3358
2895
  props.cwd,
3359
2896
  currentProvider,
3360
2897
  currentModel,
2898
+ thinkingLevel,
3361
2899
  appendGoalProgress,
3362
2900
  clearGoalModeIfIdle,
3363
2901
  clearGoalStatusEntry,
@@ -3667,7 +3205,7 @@ export function App(props) {
3667
3205
  contextWindowOptions,
3668
3206
  cwd: displayedCwd,
3669
3207
  gitBranch,
3670
- thinkingLevel: thinkingEnabled ? getMaxThinkingLevel(currentModel) : undefined,
3208
+ thinkingLevel,
3671
3209
  goalMode,
3672
3210
  });
3673
3211
  const chatControlsLayout = getChatControlsLayoutDecision({
@@ -3722,12 +3260,24 @@ export function App(props) {
3722
3260
  const shouldReserveStreamingSpacing = agentLoop.isRunning &&
3723
3261
  !hasLiveAssistantItem &&
3724
3262
  (visibleStreamingText.trim().length > 0 || liveItems.some(isAgentSpacingItem));
3263
+ const lastLiveItem = liveItems.at(-1);
3264
+ const lastPendingHistoryItem = pendingHistoryFlushRef.current.at(-1);
3265
+ const lastHistoryItem = history.at(-1);
3725
3266
  const shouldTopSpaceStreamingText = shouldTopSpaceStreamingAssistant({
3726
3267
  visibleStreamingText,
3727
- lastLiveItem: liveItems.at(-1),
3728
- lastPendingHistoryItem: pendingHistoryFlushRef.current.at(-1),
3729
- lastHistoryItem: history.at(-1),
3268
+ lastLiveItem,
3269
+ lastPendingHistoryItem,
3270
+ lastHistoryItem,
3730
3271
  });
3272
+ const visibleQueuedCount = liveItems.filter((item) => item.kind === "queued").length;
3273
+ const hiddenQueuedCount = Math.max(0, agentLoop.queuedCount - visibleQueuedCount);
3274
+ const shouldTopSpaceQueueIndicator = hiddenQueuedCount > 0 &&
3275
+ shouldTopSpaceAfterPrintedAgentBoundary({
3276
+ currentKind: "queued",
3277
+ previousLiveItem: lastLiveItem,
3278
+ lastPendingHistoryItem,
3279
+ lastHistoryItem,
3280
+ });
3731
3281
  return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: isGoalView ? (_jsx(GoalOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, autoExpandNewest: goalAutoExpand, onClose: () => {
3732
3282
  goalAutoExpandRef.current = false;
3733
3283
  setGoalAutoExpand(false);
@@ -3946,7 +3496,7 @@ export function App(props) {
3946
3496
  log("ERROR", "error", errMsg);
3947
3497
  setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
3948
3498
  });
3949
- } })) : (_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: [agentLoop.queuedCount > 0 && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.warning, bold: true, children: "• " }), _jsxs(Text, { color: theme.textDim, children: [agentLoop.queuedCount, " message", agentLoop.queuedCount > 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: thinkingEnabled, 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: () => {
3499
+ } })) : (_jsxs(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 1, overflowY: "hidden", children: [liveItems.map((item, index, items) => renderItem(item, index, items)), _jsx(StreamingArea, { isRunning: agentLoop.isRunning, streamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, availableTerminalHeight: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText ? 1 : 0, continuation: streamedAssistantFlushRef.current.flushedChars > 0 })] }), _jsxs(Box, { ref: mainControlsRef, flexDirection: "column", flexShrink: 0, flexGrow: 0, children: [hiddenQueuedCount > 0 && (_jsxs(Box, { flexDirection: "row", paddingLeft: 1, marginTop: shouldTopSpaceQueueIndicator ? 2 : 1, flexShrink: 0, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: theme.warning, bold: true, children: "• " }) }), _jsxs(Text, { color: theme.textDim, children: [hiddenQueuedCount, " message", hiddenQueuedCount > 1 ? "s" : "", " queued"] })] })), _jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.textDim, width: columns, height: 0 }), _jsx(Box, { paddingLeft: 1, paddingRight: 1, width: columns, children: statusSlotVisible ? (activityVisible ? (_jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, thinkingEnabled: !!thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, staticDisplay: true })) : stallStatusVisible ? (_jsx(Text, { color: theme.warning, wrap: "truncate", children: "⚠ API provider stream interrupted — retries exhausted. Your conversation is preserved." })) : doneStatus ? (_jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] })) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." }), !renderMarkdown && (_jsx(Text, { color: theme.warning, children: " · raw markdown mode" }))] }))) : (_jsxs(Text, { children: [_jsx(Text, { color: theme.commandColor, children: "⠿ " }), _jsx(Text, { color: theme.textDim, children: "Ready to go.." })] })) })] }), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleGoal: () => {
3950
3500
  openOverlay("goal");
3951
3501
  }, onToggleSkills: () => {
3952
3502
  openOverlay("skills");
@@ -3954,7 +3504,7 @@ export function App(props) {
3954
3504
  openOverlay("pixel");
3955
3505
  }, onToggleMarkdown: () => {
3956
3506
  setRenderMarkdown((prev) => !prev);
3957
- }, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : overlay === "theme" ? (_jsx(ThemeSelector, { onSelect: handleThemeSelect, onCancel: () => setOverlay(null), currentTheme: theme.name })) : (_jsxs(_Fragment, { children: [_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, contextWindowOptions: contextWindowOptions, cwd: displayedCwd, gitBranch: gitBranch, thinkingLevel: thinkingEnabled ? getMaxThinkingLevel(currentModel) : undefined, goalMode: goalMode, exitPending: exitPending, renderMarkdown: renderMarkdown }), !exitPending && _jsx(GoalStatusBar, { entries: goalStatusEntries })] })), (footerStatusLayout.hasBackgroundTasks || footerStatusLayout.hasUpdateNotice) && (_jsxs(Box, { flexDirection: footerStatusLayout.stack ? "column" : "row", width: columns, children: [footerStatusLayout.hasBackgroundTasks && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate, compact: footerStatusLayout.compactBackgroundTasks })), footerStatusLayout.hasUpdateNotice && (_jsx(Box, { paddingLeft: footerStatusLayout.stack || !footerStatusLayout.hasBackgroundTasks ? 1 : 2, paddingRight: 1, children: _jsx(Text, { color: theme.success, bold: true, wrap: "truncate", children: "\u2728 Update ready \u00B7 restart to apply" }) }))] }))] })] })) }));
3507
+ }, cwd: props.cwd, commands: allCommands }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : overlay === "theme" ? (_jsx(ThemeSelector, { onSelect: handleThemeSelect, onCancel: () => setOverlay(null), currentTheme: theme.name })) : (_jsxs(_Fragment, { children: [_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, contextWindowOptions: contextWindowOptions, cwd: displayedCwd, gitBranch: gitBranch, thinkingLevel: thinkingLevel, goalMode: goalMode, exitPending: exitPending, renderMarkdown: renderMarkdown }), !exitPending && _jsx(GoalStatusBar, { entries: goalStatusEntries })] })), (footerStatusLayout.hasBackgroundTasks || footerStatusLayout.hasUpdateNotice) && (_jsxs(Box, { flexDirection: footerStatusLayout.stack ? "column" : "row", width: columns, children: [footerStatusLayout.hasBackgroundTasks && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate, compact: footerStatusLayout.compactBackgroundTasks })), footerStatusLayout.hasUpdateNotice && (_jsx(Box, { paddingLeft: footerStatusLayout.stack || !footerStatusLayout.hasBackgroundTasks ? 1 : 2, paddingRight: 1, children: _jsx(Text, { color: theme.success, bold: true, wrap: "truncate", children: "\u2728 Update ready \u00B7 restart to apply" }) }))] }))] })] })) }));
3958
3508
  }
3959
3509
  function formatRepoMapCommandOutput(enabled, markdown, refreshed) {
3960
3510
  const status = enabled ? "on" : "off";