@kenkaiiii/ggcoder 4.3.206 → 4.3.207

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 (74) hide show
  1. package/dist/core/goal-controller.js +13 -13
  2. package/dist/core/goal-controller.js.map +1 -1
  3. package/dist/core/goal-controller.test.js +54 -12
  4. package/dist/core/goal-controller.test.js.map +1 -1
  5. package/dist/core/goal-worker-dev-server-lifecycle.test.d.ts +2 -0
  6. package/dist/core/goal-worker-dev-server-lifecycle.test.d.ts.map +1 -0
  7. package/dist/core/goal-worker-dev-server-lifecycle.test.js +68 -0
  8. package/dist/core/goal-worker-dev-server-lifecycle.test.js.map +1 -0
  9. package/dist/core/goal-worker.d.ts +7 -3
  10. package/dist/core/goal-worker.d.ts.map +1 -1
  11. package/dist/core/goal-worker.js +15 -5
  12. package/dist/core/goal-worker.js.map +1 -1
  13. package/dist/core/goal-worker.test.js +19 -1
  14. package/dist/core/goal-worker.test.js.map +1 -1
  15. package/dist/core/model-registry.test.js +51 -1
  16. package/dist/core/model-registry.test.js.map +1 -1
  17. package/dist/core/process-manager-dev-server-repro.test.d.ts +2 -0
  18. package/dist/core/process-manager-dev-server-repro.test.d.ts.map +1 -0
  19. package/dist/core/process-manager-dev-server-repro.test.js +100 -0
  20. package/dist/core/process-manager-dev-server-repro.test.js.map +1 -0
  21. package/dist/core/process-manager.js +2 -2
  22. package/dist/core/process-manager.js.map +1 -1
  23. package/dist/core/prompt-commands.d.ts.map +1 -1
  24. package/dist/core/prompt-commands.js +2 -1
  25. package/dist/core/prompt-commands.js.map +1 -1
  26. package/dist/core/prompt-commands.test.js +2 -0
  27. package/dist/core/prompt-commands.test.js.map +1 -1
  28. package/dist/system-prompt.d.ts.map +1 -1
  29. package/dist/system-prompt.js +1 -0
  30. package/dist/system-prompt.js.map +1 -1
  31. package/dist/tools/edit.d.ts.map +1 -1
  32. package/dist/tools/edit.js +22 -12
  33. package/dist/tools/edit.js.map +1 -1
  34. package/dist/tools/edit.test.js +29 -6
  35. package/dist/tools/edit.test.js.map +1 -1
  36. package/dist/ui/App.d.ts +39 -3
  37. package/dist/ui/App.d.ts.map +1 -1
  38. package/dist/ui/App.js +197 -91
  39. package/dist/ui/App.js.map +1 -1
  40. package/dist/ui/app-state-persistence.test.js +76 -2
  41. package/dist/ui/app-state-persistence.test.js.map +1 -1
  42. package/dist/ui/components/GoalOverlay.d.ts +54 -1
  43. package/dist/ui/components/GoalOverlay.d.ts.map +1 -1
  44. package/dist/ui/components/GoalOverlay.js +392 -53
  45. package/dist/ui/components/GoalOverlay.js.map +1 -1
  46. package/dist/ui/components/InputArea.d.ts.map +1 -1
  47. package/dist/ui/components/InputArea.js +38 -1
  48. package/dist/ui/components/InputArea.js.map +1 -1
  49. package/dist/ui/components/InputArea.test.d.ts +2 -0
  50. package/dist/ui/components/InputArea.test.d.ts.map +1 -0
  51. package/dist/ui/components/InputArea.test.js +79 -0
  52. package/dist/ui/components/InputArea.test.js.map +1 -0
  53. package/dist/ui/components/ToolExecution.d.ts.map +1 -1
  54. package/dist/ui/components/ToolExecution.js +2 -2
  55. package/dist/ui/components/ToolExecution.js.map +1 -1
  56. package/dist/ui/goal-events.d.ts +88 -1
  57. package/dist/ui/goal-events.d.ts.map +1 -1
  58. package/dist/ui/goal-events.js +249 -28
  59. package/dist/ui/goal-events.js.map +1 -1
  60. package/dist/ui/goal-events.test.js +89 -4
  61. package/dist/ui/goal-events.test.js.map +1 -1
  62. package/dist/ui/goal-overlay.test.js +155 -1
  63. package/dist/ui/goal-overlay.test.js.map +1 -1
  64. package/dist/ui/render.d.ts +3 -1
  65. package/dist/ui/render.d.ts.map +1 -1
  66. package/dist/ui/render.js +2 -0
  67. package/dist/ui/render.js.map +1 -1
  68. package/dist/ui/scroll-stabilization.test.js +49 -2
  69. package/dist/ui/scroll-stabilization.test.js.map +1 -1
  70. package/dist/ui/slash-command-images.test.d.ts +2 -0
  71. package/dist/ui/slash-command-images.test.d.ts.map +1 -0
  72. package/dist/ui/slash-command-images.test.js +47 -0
  73. package/dist/ui/slash-command-images.test.js.map +1 -0
  74. package/package.json +5 -5
package/dist/ui/App.js CHANGED
@@ -96,6 +96,64 @@ function toErrorItem(err, id, contextPrefix) {
96
96
  id,
97
97
  };
98
98
  }
99
+ export function routePromptCommandInput(input, promptCommands = PROMPT_COMMANDS, customCommands = []) {
100
+ const trimmed = input.trim();
101
+ if (!trimmed.startsWith("/"))
102
+ return null;
103
+ const parts = trimmed.slice(1).split(" ");
104
+ const cmdName = parts[0];
105
+ const cmdArgs = parts.slice(1).join(" ").trim();
106
+ const builtinCmd = promptCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
107
+ const customCmd = !builtinCmd ? customCommands.find((c) => c.name === cmdName) : undefined;
108
+ const promptText = builtinCmd?.prompt ?? customCmd?.prompt;
109
+ if (!promptText)
110
+ return null;
111
+ return {
112
+ cmdName,
113
+ cmdArgs,
114
+ promptText,
115
+ fullPrompt: cmdArgs ? `${promptText}\n\n## User Instructions\n\n${cmdArgs}` : promptText,
116
+ };
117
+ }
118
+ export function buildUserContentWithAttachments(text, inputImages, modelSupportsImages) {
119
+ if (inputImages.length === 0)
120
+ return text;
121
+ const parts = [];
122
+ if (text) {
123
+ parts.push({ type: "text", text });
124
+ }
125
+ for (const img of inputImages) {
126
+ if (img.kind === "text") {
127
+ parts.push({
128
+ type: "text",
129
+ text: `<file name="${img.fileName}">\n${img.data}\n</file>`,
130
+ });
131
+ }
132
+ else if (modelSupportsImages) {
133
+ parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
134
+ }
135
+ else {
136
+ // GLM models: save image to temp file and instruct model to use vision MCP tool
137
+ const ext = img.mediaType.split("/")[1] ?? "png";
138
+ const tmpPath = `/tmp/ggcoder-img-${Date.now()}.${ext}`;
139
+ try {
140
+ writeFileSync(tmpPath, Buffer.from(img.data, "base64"));
141
+ parts.push({
142
+ type: "text",
143
+ text: `[User attached an image saved at: ${tmpPath} — use the image_analysis tool to view and analyze it]`,
144
+ });
145
+ }
146
+ catch {
147
+ parts.push({
148
+ type: "text",
149
+ text: `[User attached an image but it could not be saved for analysis]`,
150
+ });
151
+ }
152
+ }
153
+ }
154
+ // If only text parts remain after stripping images, simplify to plain string
155
+ return parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts;
156
+ }
99
157
  /** Tools that get aggregated into a single compact group when concurrent. */
100
158
  const AGGREGATABLE_TOOLS = new Set(["read", "grep", "find", "ls"]);
101
159
  const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
@@ -135,6 +193,64 @@ function formatGoalWorkerFinishedTitle(taskTitle, status) {
135
193
  ? `Worker finished: ${taskTitle}. Reporting back.`
136
194
  : `Worker failed: ${taskTitle}. Reporting back.`;
137
195
  }
196
+ function countGoalTasksByStatus(tasks, status) {
197
+ return tasks.filter((task) => task.status === status).length;
198
+ }
199
+ function firstText(values) {
200
+ return values.find((value) => value !== undefined && value.trim().length > 0)?.trim();
201
+ }
202
+ function truncateGoalSummary(value, maxLength = 90) {
203
+ const normalized = value.replace(/\s+/g, " ").trim();
204
+ if (normalized.length <= maxLength)
205
+ return normalized;
206
+ return `${normalized.slice(0, maxLength - 1)}…`;
207
+ }
208
+ export function buildGoalSummaryRows(run) {
209
+ const rows = [];
210
+ const doneTasks = countGoalTasksByStatus(run.tasks, "done");
211
+ const failedTasks = countGoalTasksByStatus(run.tasks, "failed");
212
+ const blockedTasks = countGoalTasksByStatus(run.tasks, "blocked");
213
+ const taskSuffix = [
214
+ failedTasks > 0 ? `${failedTasks} failed` : undefined,
215
+ blockedTasks > 0 ? `${blockedTasks} blocked` : undefined,
216
+ ].filter((item) => item !== undefined);
217
+ rows.push({
218
+ label: "Tasks",
219
+ value: run.tasks.length > 0 ? `${doneTasks}/${run.tasks.length} done` : "none",
220
+ ...(taskSuffix.length > 0 ? { detail: taskSuffix.join(", ") } : {}),
221
+ });
222
+ const verifierResult = run.verifier?.lastResult;
223
+ const verifierDetail = firstText([verifierResult?.outputPath, run.verifier?.command]);
224
+ rows.push({
225
+ label: "Verifier",
226
+ value: verifierResult?.status ?? (run.verifier?.command ? "ready" : "missing"),
227
+ ...(verifierDetail ? { detail: truncateGoalSummary(verifierDetail) } : {}),
228
+ });
229
+ const latestEvidence = run.evidence.at(-1);
230
+ rows.push({
231
+ label: "Evidence",
232
+ value: `${run.evidence.length} recorded`,
233
+ ...(latestEvidence
234
+ ? { detail: truncateGoalSummary(latestEvidence.path ?? latestEvidence.label) }
235
+ : {}),
236
+ });
237
+ if (run.status === "blocked" || run.status === "paused" || run.blockers.length > 0) {
238
+ rows.push({
239
+ label: run.status === "paused" ? "Paused on" : "Blocked on",
240
+ value: truncateGoalSummary(goalHasBlockingPrerequisites(run)
241
+ ? formatGoalBlockingPrerequisites(run)
242
+ : (run.blockers[0] ?? "manual review"), 110),
243
+ });
244
+ }
245
+ else if (run.successCriteria.length > 0) {
246
+ rows.push({
247
+ label: "Criteria",
248
+ value: `${run.successCriteria.length} checked`,
249
+ detail: truncateGoalSummary(run.successCriteria[0] ?? "", 80),
250
+ });
251
+ }
252
+ return rows.slice(0, 4);
253
+ }
138
254
  export function formatGoalTerminalProgress(run) {
139
255
  switch (run.status) {
140
256
  case "passed":
@@ -143,6 +259,7 @@ export function formatGoalTerminalProgress(run) {
143
259
  phase: "terminal",
144
260
  title: `Goal passed: ${run.title}`,
145
261
  detail: "Verifier evidence is recorded; auto-continuation stopped.",
262
+ summaryRows: buildGoalSummaryRows(run),
146
263
  status: run.status,
147
264
  };
148
265
  case "failed":
@@ -151,6 +268,7 @@ export function formatGoalTerminalProgress(run) {
151
268
  phase: "terminal",
152
269
  title: `Goal failed: ${run.title}`,
153
270
  detail: "Auto-continuation stopped. Check Goal tasks for the failing step.",
271
+ summaryRows: buildGoalSummaryRows(run),
154
272
  status: run.status,
155
273
  };
156
274
  case "blocked":
@@ -161,6 +279,7 @@ export function formatGoalTerminalProgress(run) {
161
279
  detail: goalHasBlockingPrerequisites(run)
162
280
  ? formatGoalBlockingPrerequisites(run)
163
281
  : (run.blockers[0] ?? "A prerequisite or missing verifier blocked progress."),
282
+ summaryRows: buildGoalSummaryRows(run),
164
283
  status: run.status,
165
284
  };
166
285
  case "paused":
@@ -169,6 +288,7 @@ export function formatGoalTerminalProgress(run) {
169
288
  phase: "terminal",
170
289
  title: `Goal paused: ${run.title}`,
171
290
  detail: run.blockers[0] ?? "Auto-continuation paused.",
291
+ summaryRows: buildGoalSummaryRows(run),
172
292
  status: run.status,
173
293
  };
174
294
  case "draft":
@@ -185,12 +305,25 @@ export function shouldHideHistoryForOverlayView(isOverlayView, isAgentRunning) {
185
305
  // agent is running we keep Static mounted because resetUI would abort the run.
186
306
  return isOverlayView && !isAgentRunning;
187
307
  }
188
- export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, }) {
308
+ export function shouldStabilizeOverlayPaneRerender({ overlayPane, isAgentRunning, }) {
309
+ return isAgentRunning && (overlayPane === "goal" || overlayPane === "plan");
310
+ }
311
+ export function shouldHideStaticItemsForOverlayView({ shouldHideHistoryForOverlay, stabilizeOverlayPaneRerender, }) {
312
+ return shouldHideHistoryForOverlay && !stabilizeOverlayPaneRerender;
313
+ }
314
+ export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, hasTallLiveUserMessage = false, }) {
315
+ const shouldStabilize = isUserScrolled || hasTallLiveUserMessage;
189
316
  return {
190
- preserveStatic: isUserScrolled && hasNewOutput,
191
- autoFollow: !isUserScrolled,
317
+ preserveStatic: shouldStabilize && hasNewOutput,
318
+ autoFollow: !shouldStabilize,
192
319
  };
193
320
  }
321
+ export function isTallLiveUserMessage(text, rows) {
322
+ return text.split("\n").length > Math.max(8, Math.floor(rows * 0.6));
323
+ }
324
+ export function getStaticHistoryKey({ resizeKey, staticKey, }) {
325
+ return `${resizeKey}-${staticKey}`;
326
+ }
194
327
  // flushOnTurnText, flushOnTurnEnd are imported from ./live-item-flush.ts
195
328
  /** Check whether an item is still active (running spinner, pending result). */
196
329
  function isActiveItem(item) {
@@ -415,7 +548,7 @@ export function App(props) {
415
548
  const cwdRef = useRef(props.cwd);
416
549
  const [displayedCwd, setDisplayedCwd] = useState(props.cwd);
417
550
  const [staticKey, setStaticKey] = useState(0);
418
- const [doneStatus, setDoneStatus] = useState(null);
551
+ const [doneStatus, setDoneStatus] = useState(props.sessionStore?.doneStatus ?? null);
419
552
  // Suppress "done" status when a plan overlay is about to open
420
553
  const planOverlayPendingRef = useRef(false);
421
554
  const [gitBranch, setGitBranch] = useState(null);
@@ -552,6 +685,10 @@ export function App(props) {
552
685
  if (sessionStore)
553
686
  sessionStore.liveItems = liveItems;
554
687
  }, [liveItems, sessionStore]);
688
+ useEffect(() => {
689
+ if (sessionStore)
690
+ sessionStore.doneStatus = doneStatus;
691
+ }, [doneStatus, sessionStore]);
555
692
  useEffect(() => {
556
693
  if (sessionStore)
557
694
  sessionStore.planSteps = planSteps;
@@ -2086,49 +2223,49 @@ export function App(props) {
2086
2223
  return;
2087
2224
  }
2088
2225
  // Handle prompt-template commands (built-in + custom from .gg/commands/)
2089
- if (trimmed.startsWith("/")) {
2090
- const parts = trimmed.slice(1).split(" ");
2091
- const cmdName = parts[0];
2092
- const cmdArgs = parts.slice(1).join(" ").trim();
2093
- const builtinCmd = getPromptCommand(cmdName);
2094
- const customCmd = !builtinCmd ? customCommands.find((c) => c.name === cmdName) : undefined;
2095
- const promptText = builtinCmd?.prompt ?? customCmd?.prompt;
2096
- if (promptText) {
2097
- log("INFO", "command", `Prompt command: /${cmdName}${cmdArgs ? ` (args: ${cmdArgs})` : ""}`);
2098
- // Move live items into history before starting
2099
- setLiveItems((prev) => {
2100
- if (prev.length > 0) {
2101
- pendingFlushRef.current = [...pendingFlushRef.current, ...prev];
2102
- }
2103
- return [];
2104
- });
2105
- // Show the command name as the user message
2106
- const userItem = { kind: "user", text: trimmed, id: getId() };
2107
- setLastUserMessage(trimmed);
2108
- setDoneStatus(null);
2109
- setLiveItems([userItem]);
2110
- // Send the full prompt to the agent, with user args appended if provided
2111
- const fullPrompt = cmdArgs
2112
- ? `${promptText}\n\n## User Instructions\n\n${cmdArgs}`
2113
- : promptText;
2114
- try {
2115
- await agentLoop.run(fullPrompt);
2116
- }
2117
- catch (err) {
2118
- const msg = err instanceof Error ? err.message : String(err);
2119
- log("ERROR", "error", msg);
2120
- const isAbort = msg.includes("aborted") || msg.includes("abort");
2121
- setLiveItems((prev) => [
2122
- ...prev,
2123
- isAbort
2124
- ? { kind: "stopped", text: "Request was stopped.", id: getId() }
2125
- : toErrorItem(err, getId()),
2126
- ]);
2226
+ const promptCommandRoute = routePromptCommandInput(trimmed, PROMPT_COMMANDS, customCommands);
2227
+ if (promptCommandRoute) {
2228
+ const { cmdName, cmdArgs, fullPrompt } = promptCommandRoute;
2229
+ log("INFO", "command", `Prompt command: /${cmdName}${cmdArgs ? ` (args: ${cmdArgs})` : ""}`);
2230
+ // Move live items into history before starting
2231
+ setLiveItems((prev) => {
2232
+ if (prev.length > 0) {
2233
+ pendingFlushRef.current = [...pendingFlushRef.current, ...prev];
2127
2234
  }
2128
- // Reload custom commands in case a setup command created new ones
2129
- reloadCustomCommands();
2130
- return;
2235
+ return [];
2236
+ });
2237
+ const hasImages = inputImages.length > 0;
2238
+ const modelInfo = getModel(currentModel);
2239
+ const modelSupportsImages = modelInfo?.supportsImages ?? true;
2240
+ const userContent = buildUserContentWithAttachments(fullPrompt, inputImages, modelSupportsImages);
2241
+ // Show the typed command as the user message
2242
+ const userItem = {
2243
+ kind: "user",
2244
+ text: trimmed,
2245
+ imageCount: hasImages ? inputImages.length : undefined,
2246
+ id: getId(),
2247
+ };
2248
+ setLastUserMessage(trimmed);
2249
+ setDoneStatus(null);
2250
+ setLiveItems([userItem]);
2251
+ // Send the full prompt to the agent, with user args appended if provided
2252
+ try {
2253
+ await agentLoop.run(userContent);
2131
2254
  }
2255
+ catch (err) {
2256
+ const msg = err instanceof Error ? err.message : String(err);
2257
+ log("ERROR", "error", msg);
2258
+ const isAbort = msg.includes("aborted") || msg.includes("abort");
2259
+ setLiveItems((prev) => [
2260
+ ...prev,
2261
+ isAbort
2262
+ ? { kind: "stopped", text: "Request was stopped.", id: getId() }
2263
+ : toErrorItem(err, getId()),
2264
+ ]);
2265
+ }
2266
+ // Reload custom commands in case a setup command created new ones
2267
+ reloadCustomCommands();
2268
+ return;
2132
2269
  }
2133
2270
  // Check slash commands
2134
2271
  if (props.onSlashCommand && input.startsWith("/")) {
@@ -2142,47 +2279,7 @@ export function App(props) {
2142
2279
  const hasImages = inputImages.length > 0;
2143
2280
  const modelInfo = getModel(currentModel);
2144
2281
  const modelSupportsImages = modelInfo?.supportsImages ?? true;
2145
- let userContent;
2146
- if (hasImages) {
2147
- const parts = [];
2148
- if (trimmed) {
2149
- parts.push({ type: "text", text: trimmed });
2150
- }
2151
- for (const img of inputImages) {
2152
- if (img.kind === "text") {
2153
- parts.push({
2154
- type: "text",
2155
- text: `<file name="${img.fileName}">\n${img.data}\n</file>`,
2156
- });
2157
- }
2158
- else if (modelSupportsImages) {
2159
- parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
2160
- }
2161
- else {
2162
- // GLM models: save image to temp file and instruct model to use vision MCP tool
2163
- const ext = img.mediaType.split("/")[1] ?? "png";
2164
- const tmpPath = `/tmp/ggcoder-img-${Date.now()}.${ext}`;
2165
- try {
2166
- writeFileSync(tmpPath, Buffer.from(img.data, "base64"));
2167
- parts.push({
2168
- type: "text",
2169
- text: `[User attached an image saved at: ${tmpPath} — use the image_analysis tool to view and analyze it]`,
2170
- });
2171
- }
2172
- catch {
2173
- parts.push({
2174
- type: "text",
2175
- text: `[User attached an image but it could not be saved for analysis]`,
2176
- });
2177
- }
2178
- }
2179
- }
2180
- // If only text parts remain after stripping images, simplify to plain string
2181
- userContent = parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts;
2182
- }
2183
- else {
2184
- userContent = input;
2185
- }
2282
+ const userContent = buildUserContentWithAttachments(input, inputImages, modelSupportsImages);
2186
2283
  // ── Queue message if agent is already running ──
2187
2284
  if (agentLoop.isRunning) {
2188
2285
  log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
@@ -2464,7 +2561,7 @@ export function App(props) {
2464
2561
  ? "◆ "
2465
2562
  : "! "
2466
2563
  : "↻ ";
2467
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", flexShrink: 1, children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: color, bold: true, children: glyph }), _jsx(Text, { color: color, bold: true, children: item.title }), item.workerId ? _jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] }) : null] }), item.detail ? (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: ` ${item.detail}` })) : null] }, item.id));
2564
+ return (_jsxs(Box, { marginTop: 1, flexDirection: "column", flexShrink: 1, children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { color: color, bold: true, children: glyph }), _jsx(Text, { color: color, bold: true, children: item.title }), item.workerId ? _jsxs(Text, { color: theme.textDim, children: [" \u00B7 worker ", item.workerId] }) : null] }), item.detail ? (_jsx(Text, { color: theme.textDim, wrap: "wrap", children: ` ${item.detail}` })) : null, item.summaryRows && item.summaryRows.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, flexShrink: 1, children: item.summaryRows.map((row) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: theme.textDim, children: row.label.padEnd(10) }), _jsx(Text, { color: theme.text, children: row.value }), row.detail ? _jsxs(Text, { color: theme.textDim, children: [" \u00B7 ", row.detail] }) : null] }, row.label))) })) : null] }, item.id));
2468
2565
  }
2469
2566
  case "style_pack": {
2470
2567
  const names = item.added.map((id) => LANGUAGE_DISPLAY_NAMES[id]);
@@ -2633,8 +2730,9 @@ export function App(props) {
2633
2730
  props.sessionStore.overlay = kind;
2634
2731
  if (kind !== "plan")
2635
2732
  props.sessionStore.planAutoExpand = false;
2636
- if (agentLoop.isRunning)
2733
+ if (agentLoop.isRunning && kind !== "goal" && kind !== "plan") {
2637
2734
  props.sessionStore.pendingResetUI = true;
2735
+ }
2638
2736
  }
2639
2737
  if (kind !== "plan")
2640
2738
  setPlanAutoExpand(false);
@@ -2649,8 +2747,6 @@ export function App(props) {
2649
2747
  else {
2650
2748
  if (props.sessionStore) {
2651
2749
  props.sessionStore.overlay = null;
2652
- if (agentLoop.isRunning)
2653
- props.sessionStore.pendingResetUI = true;
2654
2750
  }
2655
2751
  setOverlay(null);
2656
2752
  }
@@ -2954,6 +3050,7 @@ export function App(props) {
2954
3050
  model: currentModel,
2955
3051
  goalRunId: run.id,
2956
3052
  goalTaskId: decision.task.id,
3053
+ taskTitle: decision.task.title,
2957
3054
  prompt: decision.task.prompt,
2958
3055
  });
2959
3056
  await upsertGoalRun(props.cwd, {
@@ -3330,7 +3427,16 @@ export function App(props) {
3330
3427
  const isPixelView = overlay === "pixel";
3331
3428
  const isOverlayView = isTaskView || isGoalView || isSkillsView || isPlanView || isEyesView || isPixelView;
3332
3429
  const shouldHideHistoryForOverlay = shouldHideHistoryForOverlayView(isOverlayView, agentLoop.isRunning);
3333
- return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: shouldHideHistoryForOverlay ? [] : history, style: { width: "100%" }, children: (item) => (_jsx(Box, { flexDirection: "column", paddingRight: 1, children: renderItem(item) }, item.id)) }, `${resizeKey}-${staticKey}`), isTaskView ? (_jsx(TaskOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, onClose: () => {
3430
+ const stabilizeOverlayPaneRerender = shouldStabilizeOverlayPaneRerender({
3431
+ overlayPane: overlay,
3432
+ isAgentRunning: agentLoop.isRunning,
3433
+ });
3434
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: shouldHideStaticItemsForOverlayView({
3435
+ shouldHideHistoryForOverlay,
3436
+ stabilizeOverlayPaneRerender,
3437
+ })
3438
+ ? []
3439
+ : history, style: { width: "100%" }, children: (item) => (_jsx(Box, { flexDirection: "column", paddingRight: 1, children: renderItem(item) }, item.id)) }, getStaticHistoryKey({ resizeKey, staticKey })), isTaskView ? (_jsx(TaskOverlay, { cwd: props.cwd, agentRunning: agentLoop.isRunning, onClose: () => {
3334
3440
  if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
3335
3441
  props.sessionStore.overlay = null;
3336
3442
  props.resetUI();