@kenkaiiii/ggcoder 4.3.162 → 4.3.164

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 (35) hide show
  1. package/dist/cli.js +37 -3
  2. package/dist/cli.js.map +1 -1
  3. package/dist/core/auth-storage.d.ts.map +1 -1
  4. package/dist/core/auth-storage.js +20 -1
  5. package/dist/core/auth-storage.js.map +1 -1
  6. package/dist/core/prompt-commands.js +4 -4
  7. package/dist/core/settings-manager.d.ts +0 -1
  8. package/dist/core/settings-manager.d.ts.map +1 -1
  9. package/dist/core/settings-manager.js +0 -2
  10. package/dist/core/settings-manager.js.map +1 -1
  11. package/dist/core/slash-commands.d.ts.map +1 -1
  12. package/dist/core/slash-commands.js +0 -14
  13. package/dist/core/slash-commands.js.map +1 -1
  14. package/dist/core/style-packs/index.d.ts.map +1 -1
  15. package/dist/core/style-packs/index.js +20 -0
  16. package/dist/core/style-packs/index.js.map +1 -1
  17. package/dist/core/style-packs/packs.d.ts.map +1 -1
  18. package/dist/core/style-packs/packs.js +6 -5
  19. package/dist/core/style-packs/packs.js.map +1 -1
  20. package/dist/ui/App.d.ts +40 -1
  21. package/dist/ui/App.d.ts.map +1 -1
  22. package/dist/ui/App.js +180 -70
  23. package/dist/ui/App.js.map +1 -1
  24. package/dist/ui/hooks/useTerminalSize.d.ts +12 -3
  25. package/dist/ui/hooks/useTerminalSize.d.ts.map +1 -1
  26. package/dist/ui/hooks/useTerminalSize.js +16 -1
  27. package/dist/ui/hooks/useTerminalSize.js.map +1 -1
  28. package/dist/ui/render.d.ts +42 -0
  29. package/dist/ui/render.d.ts.map +1 -1
  30. package/dist/ui/render.js +12 -1
  31. package/dist/ui/render.js.map +1 -1
  32. package/dist/utils/error-handler.d.ts.map +1 -1
  33. package/dist/utils/error-handler.js +2 -0
  34. package/dist/utils/error-handler.js.map +1 -1
  35. package/package.json +4 -4
package/dist/ui/App.js CHANGED
@@ -54,7 +54,6 @@ import { detectVerifyCommands } from "../core/verify-commands.js";
54
54
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
55
55
  import { getMCPServers } from "../core/mcp/index.js";
56
56
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
57
- import { Buddy } from "./buddy/Buddy.js";
58
57
  // ── Provider Error Hints ──────────────────────────────────
59
58
  /** Detect provider-side errors and return a user-facing hint. */
60
59
  function getProviderErrorHint(message) {
@@ -320,10 +319,12 @@ export function App(props) {
320
319
  const [taskCount, setTaskCount] = useState(() => getTaskCount(props.cwd));
321
320
  const [eyesCount, setEyesCount] = useState(() => isEyesActive(props.cwd) ? journalCount({ status: "open" }, props.cwd) : undefined);
322
321
  const [updatePending, setUpdatePending] = useState(() => getPendingUpdate(props.version) !== null);
323
- const [runAllTasks, setRunAllTasks] = useState(false);
324
- const runAllTasksRef = useRef(false);
322
+ // Seed from sessionStore so "Run All" chaining survives the resetUI()
323
+ // remount that startTask() triggers between tasks.
324
+ const [runAllTasks, setRunAllTasks] = useState(props.sessionStore?.runAllTasks ?? false);
325
+ const runAllTasksRef = useRef(props.sessionStore?.runAllTasks ?? false);
325
326
  const startTaskRef = useRef(() => { });
326
- const runAllPixelRef = useRef(false);
327
+ const runAllPixelRef = useRef(props.sessionStore?.runAllPixel ?? false);
327
328
  const currentPixelFixRef = useRef(null);
328
329
  const startPixelFixRef = useRef(() => { });
329
330
  const cwdRef = useRef(props.cwd);
@@ -342,7 +343,22 @@ export function App(props) {
342
343
  const approvedPlanPathRef = useRef(props.sessionStore?.approvedPlanPath);
343
344
  const planStepsRef = useRef(props.sessionStore?.planSteps ?? []);
344
345
  const [planSteps, setPlanSteps] = useState(props.sessionStore?.planSteps ?? []);
345
- const nextIdRef = useRef(0);
346
+ // Seed the per-item ID counter so it doesn't collide with IDs already in
347
+ // sessionStore.history (which survives remount). Without this, a remount
348
+ // (resize, overlay toggle, etc.) starts the counter at 0 and new items
349
+ // generate ids "0", "1", "2"… that collide with the same ids from the
350
+ // previous mount, triggering React's duplicate-key warning and causing
351
+ // duplicate/omitted renders.
352
+ const nextIdRef = useRef((() => {
353
+ const hist = props.sessionStore?.history ?? props.initialHistory ?? [];
354
+ let max = -1;
355
+ for (const item of hist) {
356
+ const n = Number(item.id);
357
+ if (Number.isFinite(n) && n > max)
358
+ max = n;
359
+ }
360
+ return max + 1;
361
+ })());
346
362
  const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
347
363
  const sessionPathRef = useRef(props.sessionStore?.sessionPath ?? props.sessionPath);
348
364
  const persistedIndexRef = useRef(messagesRef.current.length);
@@ -648,15 +664,13 @@ export function App(props) {
648
664
  await applyLanguageDetectionRef.current("tool");
649
665
  };
650
666
  // ── Compaction ─────────────────────────────────────────
651
- // Load settings for auto-compaction + buddy
667
+ // Load settings for auto-compaction
652
668
  const settingsRef = useRef(null);
653
- const [buddyEnabled, setBuddyEnabled] = useState(false);
654
669
  useEffect(() => {
655
670
  if (props.settingsFile) {
656
671
  const sm = new SettingsManager(props.settingsFile);
657
672
  sm.load().then(() => {
658
673
  settingsRef.current = sm;
659
- setBuddyEnabled(sm.get("buddyEnabled") ?? false);
660
674
  });
661
675
  }
662
676
  }, [props.settingsFile]);
@@ -1367,7 +1381,7 @@ export function App(props) {
1367
1381
  }
1368
1382
  return item;
1369
1383
  });
1370
- return [...next, { kind: "info", text: "Request was stopped.", id: getId() }];
1384
+ return [...next, { kind: "stopped", text: "Request was stopped.", id: getId() }];
1371
1385
  });
1372
1386
  }, []),
1373
1387
  onQueuedStart: useCallback((content) => {
@@ -1431,7 +1445,7 @@ export function App(props) {
1431
1445
  setLiveItems((prev) => [
1432
1446
  ...prev,
1433
1447
  isAbort
1434
- ? { kind: "info", text: "Auto-setup cancelled.", id: getId() }
1448
+ ? { kind: "stopped", text: "Auto-setup cancelled.", id: getId() }
1435
1449
  : { kind: "error", message: msg, id: getId() },
1436
1450
  ]);
1437
1451
  }
@@ -1454,6 +1468,29 @@ export function App(props) {
1454
1468
  useEffect(() => {
1455
1469
  setTitleRunning(agentLoop.isRunning);
1456
1470
  }, [agentLoop.isRunning]);
1471
+ // Mirror agent running state into sessionStore so renderApp's resize
1472
+ // handler and overlay toggles can skip their unmount/remount while the
1473
+ // agent is in flight (unmounting fires useAgentLoop's cleanup which
1474
+ // aborts the in-flight request). On the running→idle transition,
1475
+ // consume any pendingResetUI flag set during the run by scheduling a
1476
+ // deferred resetUI to clean up accumulated log-update drift. The 100ms
1477
+ // setTimeout lets onDone's two-phase flush commit to sessionStore.history
1478
+ // first, so the chat isn't lost. The cleanup also bails if the user
1479
+ // started a new run before the timer fires, to avoid aborting it.
1480
+ useEffect(() => {
1481
+ if (!sessionStore)
1482
+ return;
1483
+ sessionStore.isAgentRunning = agentLoop.isRunning;
1484
+ if (!agentLoop.isRunning && sessionStore.pendingResetUI) {
1485
+ sessionStore.pendingResetUI = false;
1486
+ const timer = setTimeout(() => {
1487
+ if (sessionStore.isAgentRunning)
1488
+ return;
1489
+ props.resetUI?.();
1490
+ }, 100);
1491
+ return () => clearTimeout(timer);
1492
+ }
1493
+ }, [agentLoop.isRunning, sessionStore, props.resetUI]);
1457
1494
  // Consume sessionStore.pendingAction once on mount. Set by resetUI options
1458
1495
  // for paths that remount AND immediately drive the agent (plan accept,
1459
1496
  // plan reject, startTask, pixel fix). The action survives the unmount
@@ -1467,7 +1504,14 @@ export function App(props) {
1467
1504
  pendingActionConsumedRef.current = true;
1468
1505
  if (sessionStore)
1469
1506
  sessionStore.pendingAction = undefined;
1470
- if (action.infoText) {
1507
+ if (action.planEvent) {
1508
+ const ev = action.planEvent;
1509
+ setLiveItems((prev) => [
1510
+ ...prev,
1511
+ { kind: "plan_event", event: ev.event, detail: ev.detail, id: getId() },
1512
+ ]);
1513
+ }
1514
+ else if (action.infoText) {
1471
1515
  setLiveItems((prev) => [
1472
1516
  ...prev,
1473
1517
  { kind: "info", text: action.infoText, id: getId() },
@@ -1626,37 +1670,23 @@ export function App(props) {
1626
1670
  messagesRef.current[0] = { role: "system", content: newPrompt };
1627
1671
  }
1628
1672
  })();
1629
- setLiveItems([{ kind: "info", text: "Approved plan dismissed.", id: getId() }]);
1630
- return;
1631
- }
1632
- // Handle /buddy — toggle companion
1633
- if (trimmed === "/buddy") {
1634
- const next = !buddyEnabled;
1635
- setBuddyEnabled(next);
1636
- if (settingsRef.current) {
1637
- settingsRef.current.set("buddyEnabled", next);
1638
- }
1639
- setLiveItems((items) => [
1640
- ...items,
1641
- {
1642
- kind: "info",
1643
- text: next
1644
- ? "Buddy enabled! Your companion will appear near the prompt."
1645
- : "Buddy disabled.",
1646
- id: getId(),
1647
- },
1648
- ]);
1673
+ setLiveItems([{ kind: "plan_event", event: "dismissed", id: getId() }]);
1649
1674
  return;
1650
1675
  }
1651
1676
  // Handle /plans — open plan pane
1652
1677
  if (trimmed === "/plans") {
1653
- if (props.resetUI && props.sessionStore) {
1678
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
1654
1679
  props.sessionStore.overlay = "plan";
1655
1680
  props.sessionStore.planAutoExpand = false;
1656
1681
  props.resetUI();
1657
1682
  }
1658
1683
  else {
1659
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
1684
+ if (props.sessionStore) {
1685
+ props.sessionStore.overlay = "plan";
1686
+ props.sessionStore.planAutoExpand = false;
1687
+ if (agentLoop.isRunning)
1688
+ props.sessionStore.pendingResetUI = true;
1689
+ }
1660
1690
  setPlanAutoExpand(false);
1661
1691
  setOverlay("plan");
1662
1692
  }
@@ -1698,7 +1728,7 @@ export function App(props) {
1698
1728
  setLiveItems((prev) => [
1699
1729
  ...prev,
1700
1730
  isAbort
1701
- ? { kind: "info", text: "Request was stopped.", id: getId() }
1731
+ ? { kind: "stopped", text: "Request was stopped.", id: getId() }
1702
1732
  : { kind: "error", message: msg, id: getId() },
1703
1733
  ]);
1704
1734
  }
@@ -1822,7 +1852,7 @@ export function App(props) {
1822
1852
  setLiveItems((prev) => [
1823
1853
  ...prev,
1824
1854
  isAbort
1825
- ? { kind: "info", text: "Request was stopped.", id: getId() }
1855
+ ? { kind: "stopped", text: "Request was stopped.", id: getId() }
1826
1856
  : { kind: "error", message: msg, id: getId() },
1827
1857
  ]);
1828
1858
  }
@@ -1843,7 +1873,7 @@ export function App(props) {
1843
1873
  log("INFO", "thinking", `Thinking ${next ? "enabled" : "disabled"}`);
1844
1874
  setLiveItems((items) => [
1845
1875
  ...items,
1846
- { kind: "info", text: `Thinking ${next ? "on" : "off"}`, id: getId() },
1876
+ { kind: "thinking_transition", active: next, id: getId() },
1847
1877
  ]);
1848
1878
  if (props.settingsFile) {
1849
1879
  const sm = new SettingsManager(props.settingsFile);
@@ -1919,7 +1949,7 @@ export function App(props) {
1919
1949
  const displayName = modelInfo?.name ?? newModelId;
1920
1950
  setLiveItems((prev) => [
1921
1951
  ...prev,
1922
- { kind: "info", text: `Switched to ${displayName}`, id: getId() },
1952
+ { kind: "model_transition", modelName: displayName, id: getId() },
1923
1953
  ]);
1924
1954
  // Persist model selection for next CLI launch
1925
1955
  if (props.settingsFile) {
@@ -1940,10 +1970,7 @@ export function App(props) {
1940
1970
  const sm = new SettingsManager(props.settingsFile);
1941
1971
  sm.load().then(() => sm.set("theme", name));
1942
1972
  }
1943
- setLiveItems((prev) => [
1944
- ...prev,
1945
- { kind: "info", text: `Theme switched to: ${name}`, id: getId() },
1946
- ]);
1973
+ setLiveItems((prev) => [...prev, { kind: "theme_transition", themeName: name, id: getId() }]);
1947
1974
  }, [switchTheme, props.settingsFile]);
1948
1975
  // All available slash commands for the command palette — ordered by how
1949
1976
  // commonly they're used and grouped by purpose; /quit stays dead last.
@@ -2032,6 +2059,49 @@ export function App(props) {
2032
2059
  return (_jsx(Box, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: theme.success, paddingX: 1, children: _jsxs(Text, { color: theme.success, bold: true, wrap: "wrap", children: ["✨ ", item.text] }) }, item.id));
2033
2060
  case "plan_transition":
2034
2061
  return (_jsx(Box, { marginTop: 1, flexShrink: 1, children: _jsxs(Text, { color: theme.planPrimary, bold: true, wrap: "wrap", children: [item.active ? "● " : "● ", item.text] }) }, item.id));
2062
+ case "thinking_transition": {
2063
+ // Borderless. While in liveItems the glyph color cycles through the
2064
+ // shared TRANSITION_COLORS gradient (~500ms per color). Once the item
2065
+ // flushes to <Static>, its last-rendered color sticks — re-renders
2066
+ // don't propagate into scrollback. Cheap: the animation timer is
2067
+ // already running for the activity indicator.
2068
+ const glyphFrame = item.active
2069
+ ? deriveFrame(animTick, 500, THINKING_BORDER_COLORS.length)
2070
+ : 0;
2071
+ const glyphColor = item.active ? THINKING_BORDER_COLORS[glyphFrame] : theme.textDim;
2072
+ return (_jsxs(Box, { marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: glyphColor, bold: true, children: "✻ " }), _jsx(Text, { color: item.active ? theme.accent : theme.textDim, bold: true, children: item.active ? "Thinking ON" : "Thinking OFF" })] }, item.id));
2073
+ }
2074
+ case "model_transition": {
2075
+ // Same animated-gradient pattern as thinking_transition, distinct
2076
+ // glyph (▸) and primary-blue model name so the two transitions read
2077
+ // as related but different.
2078
+ const glyphFrame = deriveFrame(animTick, 500, THINKING_BORDER_COLORS.length);
2079
+ const glyphColor = THINKING_BORDER_COLORS[glyphFrame];
2080
+ return (_jsxs(Box, { marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: glyphColor, bold: true, children: "▸ " }), _jsx(Text, { color: theme.textDim, children: "Switched to " }), _jsx(Text, { color: theme.primary, bold: true, children: item.modelName })] }, item.id));
2081
+ }
2082
+ case "theme_transition": {
2083
+ // Same family as model/thinking transitions. The ◐ glyph (half-filled
2084
+ // circle) reads as the light/dark dichotomy.
2085
+ const glyphFrame = deriveFrame(animTick, 500, THINKING_BORDER_COLORS.length);
2086
+ const glyphColor = THINKING_BORDER_COLORS[glyphFrame];
2087
+ return (_jsxs(Box, { marginTop: 1, flexShrink: 1, children: [_jsx(Text, { color: glyphColor, bold: true, children: "◐ " }), _jsx(Text, { color: theme.textDim, children: "Theme switched to " }), _jsx(Text, { color: theme.primary, bold: true, children: item.themeName })] }, item.id));
2088
+ }
2089
+ case "plan_event": {
2090
+ // Plan-domain status changes (approve / reject / dismiss). Uses
2091
+ // theme.planPrimary to match the existing plan_transition family,
2092
+ // distinct from the model/thinking gradient.
2093
+ const label = item.event === "approved"
2094
+ ? "Plan approved"
2095
+ : item.event === "rejected"
2096
+ ? "Plan rejected"
2097
+ : "Plan dismissed";
2098
+ return (_jsxs(Box, { marginTop: 1, flexShrink: 1, children: [_jsxs(Text, { color: theme.planPrimary, bold: true, children: ["○ ", label] }), item.detail ? _jsx(Text, { color: theme.textDim, children: ` — "${item.detail}"` }) : null] }, item.id));
2099
+ }
2100
+ case "stopped":
2101
+ // Cancellation / abort acknowledgement (ESC, auto-setup cancel, etc.).
2102
+ // Muted dim treatment — this is an ack, not a state change worth a
2103
+ // gradient. Glyph `⊘` reads as "stop" without being alarming.
2104
+ return (_jsx(Box, { marginTop: 1, flexShrink: 1, children: _jsxs(Text, { color: theme.textDim, bold: true, children: ["⊘ ", item.text] }) }, item.id));
2035
2105
  case "step_done":
2036
2106
  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));
2037
2107
  case "queued":
@@ -2115,7 +2185,7 @@ export function App(props) {
2115
2185
  setLiveItems((prev) => [
2116
2186
  ...prev,
2117
2187
  isAbort
2118
- ? { kind: "info", text: "Request was stopped.", id: getId() }
2188
+ ? { kind: "stopped", text: "Request was stopped.", id: getId() }
2119
2189
  : { kind: "error", message: msg, id: getId() },
2120
2190
  ]);
2121
2191
  setRunAllTasks(false);
@@ -2134,7 +2204,9 @@ export function App(props) {
2134
2204
  startTaskRef.current = startTask;
2135
2205
  useEffect(() => {
2136
2206
  runAllTasksRef.current = runAllTasks;
2137
- }, [runAllTasks]);
2207
+ if (props.sessionStore)
2208
+ props.sessionStore.runAllTasks = runAllTasks;
2209
+ }, [runAllTasks, props.sessionStore]);
2138
2210
  const startPixelFix = useCallback((errorId) => {
2139
2211
  void (async () => {
2140
2212
  try {
@@ -2210,25 +2282,32 @@ export function App(props) {
2210
2282
  })();
2211
2283
  }, [props.cwd, stdout, agentLoop, currentProvider, currentModel]);
2212
2284
  startPixelFixRef.current = startPixelFix;
2213
- const [runAllPixel, setRunAllPixel] = useState(false);
2285
+ // Seed from sessionStore so "Fix All" chaining survives a deferred
2286
+ // resetUI() if it fires between pixel fixes (e.g. user toggled a pane).
2287
+ const [runAllPixel, setRunAllPixel] = useState(props.sessionStore?.runAllPixel ?? false);
2214
2288
  useEffect(() => {
2215
2289
  runAllPixelRef.current = runAllPixel;
2216
- }, [runAllPixel]);
2290
+ if (props.sessionStore)
2291
+ props.sessionStore.runAllPixel = runAllPixel;
2292
+ }, [runAllPixel, props.sessionStore]);
2217
2293
  const isTaskView = overlay === "tasks";
2218
2294
  const isSkillsView = overlay === "skills";
2219
2295
  const isPlanView = overlay === "plan";
2220
2296
  const isEyesView = overlay === "eyes";
2221
2297
  const isPixelView = overlay === "pixel";
2222
2298
  const isOverlayView = isTaskView || isSkillsView || isPlanView || isEyesView || isPixelView;
2223
- return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: isOverlayView ? [] : 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: () => {
2224
- if (props.resetUI && props.sessionStore) {
2299
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: isOverlayView && !agentLoop.isRunning ? [] : 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: () => {
2300
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2225
2301
  props.sessionStore.overlay = null;
2226
2302
  props.resetUI();
2227
2303
  }
2228
2304
  else {
2229
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2305
+ if (props.sessionStore) {
2306
+ props.sessionStore.overlay = null;
2307
+ if (agentLoop.isRunning)
2308
+ props.sessionStore.pendingResetUI = true;
2309
+ }
2230
2310
  setTaskCount(getTaskCount(props.cwd));
2231
- setStaticKey((k) => k + 1);
2232
2311
  setOverlay(null);
2233
2312
  }
2234
2313
  }, onWorkOnTask: (title, prompt, taskId) => {
@@ -2243,13 +2322,16 @@ export function App(props) {
2243
2322
  startTask(next.title, next.prompt, next.id);
2244
2323
  }
2245
2324
  } })) : isPixelView ? (_jsx(PixelOverlay, { version: props.version, agentRunning: agentLoop.isRunning, onClose: () => {
2246
- if (props.resetUI && props.sessionStore) {
2325
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2247
2326
  props.sessionStore.overlay = null;
2248
2327
  props.resetUI();
2249
2328
  }
2250
2329
  else {
2251
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2252
- setStaticKey((k) => k + 1);
2330
+ if (props.sessionStore) {
2331
+ props.sessionStore.overlay = null;
2332
+ if (agentLoop.isRunning)
2333
+ props.sessionStore.pendingResetUI = true;
2334
+ }
2253
2335
  setOverlay(null);
2254
2336
  }
2255
2337
  }, onFixOne: (entry) => {
@@ -2263,38 +2345,48 @@ export function App(props) {
2263
2345
  setRunAllPixel(true);
2264
2346
  startPixelFix(first.errorId);
2265
2347
  } })) : isSkillsView ? (_jsx(SkillsOverlay, { cwd: props.cwd, onClose: () => {
2266
- if (props.resetUI && props.sessionStore) {
2348
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2267
2349
  props.sessionStore.overlay = null;
2268
2350
  props.resetUI();
2269
2351
  }
2270
2352
  else {
2271
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2272
- setStaticKey((k) => k + 1);
2353
+ if (props.sessionStore) {
2354
+ props.sessionStore.overlay = null;
2355
+ if (agentLoop.isRunning)
2356
+ props.sessionStore.pendingResetUI = true;
2357
+ }
2273
2358
  setOverlay(null);
2274
2359
  }
2275
2360
  } })) : isEyesView ? (_jsx(EyesOverlay, { cwd: props.cwd, onClose: () => {
2276
- if (props.resetUI && props.sessionStore) {
2361
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2277
2362
  props.sessionStore.overlay = null;
2278
2363
  props.resetUI();
2279
2364
  }
2280
2365
  else {
2281
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2366
+ if (props.sessionStore) {
2367
+ props.sessionStore.overlay = null;
2368
+ if (agentLoop.isRunning)
2369
+ props.sessionStore.pendingResetUI = true;
2370
+ }
2282
2371
  setEyesCount(isEyesActive(props.cwd) ? journalCount({ status: "open" }, props.cwd) : undefined);
2283
- setStaticKey((k) => k + 1);
2284
2372
  setOverlay(null);
2285
2373
  }
2286
2374
  }, onQueueMessage: (msg) => {
2287
2375
  agentLoop.queueMessage(msg);
2288
2376
  } })) : isPlanView ? (_jsx(PlanOverlay, { cwd: props.cwd, autoExpandNewest: planAutoExpand, onClose: () => {
2289
2377
  planOverlayPendingRef.current = false;
2290
- if (props.resetUI && props.sessionStore) {
2378
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2291
2379
  props.sessionStore.overlay = null;
2292
2380
  props.sessionStore.planAutoExpand = false;
2293
2381
  props.resetUI();
2294
2382
  }
2295
2383
  else {
2296
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2297
- setStaticKey((k) => k + 1);
2384
+ if (props.sessionStore) {
2385
+ props.sessionStore.overlay = null;
2386
+ props.sessionStore.planAutoExpand = false;
2387
+ if (agentLoop.isRunning)
2388
+ props.sessionStore.pendingResetUI = true;
2389
+ }
2298
2390
  setPlanAutoExpand(false);
2299
2391
  setOverlay(null);
2300
2392
  }
@@ -2332,7 +2424,7 @@ export function App(props) {
2332
2424
  sessionPath: newSessionPath,
2333
2425
  pendingAction: {
2334
2426
  prompt: "The plan has been approved. Implement it now, following each step in order.",
2335
- infoText: "Plan approved — starting fresh session for implementation",
2427
+ planEvent: { event: "approved" },
2336
2428
  },
2337
2429
  });
2338
2430
  return;
@@ -2380,7 +2472,7 @@ export function App(props) {
2380
2472
  props.resetUI({
2381
2473
  pendingAction: {
2382
2474
  prompt: rejectionMsg,
2383
- infoText: `Plan rejected "${feedback}"`,
2475
+ planEvent: { event: "rejected", detail: feedback },
2384
2476
  },
2385
2477
  });
2386
2478
  return;
@@ -2403,30 +2495,48 @@ export function App(props) {
2403
2495
  ? THINKING_BORDER_COLORS[thinkingBorderFrame]
2404
2496
  : "transparent", paddingLeft: 1, paddingRight: 1, width: columns, children: _jsx(ActivityIndicator, { phase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, thinkingMs: agentLoop.thinkingMs, isThinking: agentLoop.isThinking, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, userMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), planMode: planMode, retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length }) })) : agentLoop.stallError ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.warning, children: "⚠ API provider stream interrupted — retries exhausted." }), _jsx(Text, { color: theme.textDim, children: " Your conversation is preserved. Send a message to continue." })] })) : (doneStatus &&
2405
2497
  !agentLoop.isRunning && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.success, children: ["✻ ", doneStatus.verb, " ", formatDuration(doneStatus.durationMs)] }) }))), agentLoop.queuedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.accent, children: ["⏳ ", agentLoop.queuedCount, " message", agentLoop.queuedCount > 1 ? "s" : "", " queued"] }) })), _jsx(InputArea, { onSubmit: handleSubmit, onAbort: handleAbort, disabled: agentLoop.isRunning, isActive: !taskBarFocused && !overlay, onDownAtEnd: handleFocusTaskBar, onShiftTab: handleToggleThinking, onToggleTasks: () => {
2406
- if (props.resetUI && props.sessionStore) {
2498
+ // While the agent is running, skip the screen-clear + staticKey
2499
+ // bump that would otherwise wipe the chat history from scrollback.
2500
+ // Just flip the overlay state — Ink's log-update handles the
2501
+ // live-area transition (chat input → TaskOverlay) natively, and
2502
+ // the chat history above stays in scrollback. When the overlay
2503
+ // closes, the history is still there (banner included).
2504
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2407
2505
  props.sessionStore.overlay = "tasks";
2408
2506
  props.resetUI();
2409
2507
  }
2410
2508
  else {
2411
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2509
+ if (props.sessionStore) {
2510
+ props.sessionStore.overlay = "tasks";
2511
+ if (agentLoop.isRunning)
2512
+ props.sessionStore.pendingResetUI = true;
2513
+ }
2412
2514
  setOverlay("tasks");
2413
2515
  }
2414
2516
  }, onToggleSkills: () => {
2415
- if (props.resetUI && props.sessionStore) {
2517
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2416
2518
  props.sessionStore.overlay = "skills";
2417
2519
  props.resetUI();
2418
2520
  }
2419
2521
  else {
2420
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2522
+ if (props.sessionStore) {
2523
+ props.sessionStore.overlay = "skills";
2524
+ if (agentLoop.isRunning)
2525
+ props.sessionStore.pendingResetUI = true;
2526
+ }
2421
2527
  setOverlay("skills");
2422
2528
  }
2423
2529
  }, onTogglePixel: () => {
2424
- if (props.resetUI && props.sessionStore) {
2530
+ if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2425
2531
  props.sessionStore.overlay = "pixel";
2426
2532
  props.resetUI();
2427
2533
  }
2428
2534
  else {
2429
- stdout?.write("\x1b[2J\x1b[3J\x1b[H");
2535
+ if (props.sessionStore) {
2536
+ props.sessionStore.overlay = "pixel";
2537
+ if (agentLoop.isRunning)
2538
+ props.sessionStore.pendingResetUI = true;
2539
+ }
2430
2540
  setOverlay("pixel");
2431
2541
  }
2432
2542
  }, onTogglePlanMode: () => {
@@ -2442,6 +2552,6 @@ export function App(props) {
2442
2552
  id: getId(),
2443
2553
  },
2444
2554
  ]);
2445
- }, cwd: props.cwd, commands: allCommands, eyesCount: eyesCount }), 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 })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, cwd: displayedCwd, gitBranch: gitBranch, thinkingEnabled: thinkingEnabled, planMode: planMode, exitPending: exitPending })), buddyEnabled && _jsx(Buddy, { phase: agentLoop.activityPhase }), (bgTasks.length > 0 || (eyesCount !== undefined && eyesCount > 0) || updatePending) && (_jsxs(Box, { children: [bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate })), eyesCount !== undefined && eyesCount > 0 && (_jsx(Box, { paddingLeft: bgTasks.length > 0 ? 2 : 1, paddingRight: 1, children: _jsx(Text, { color: theme.accent, bold: true, children: `${eyesCount} eyes signal${eyesCount === 1 ? "" : "s"} · Run /eyes-improve to enhance GG Coder` }) })), updatePending && (_jsx(Box, { paddingLeft: bgTasks.length > 0 || (eyesCount !== undefined && eyesCount > 0) ? 2 : 1, paddingRight: 1, children: _jsx(Text, { color: theme.success, bold: true, children: "\u2728 Update ready \u00B7 restart to apply" }) }))] }))] }))] }));
2555
+ }, cwd: props.cwd, commands: allCommands, eyesCount: eyesCount }), 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 })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, cwd: displayedCwd, gitBranch: gitBranch, thinkingEnabled: thinkingEnabled, planMode: planMode, exitPending: exitPending })), (bgTasks.length > 0 || (eyesCount !== undefined && eyesCount > 0) || updatePending) && (_jsxs(Box, { children: [bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate })), eyesCount !== undefined && eyesCount > 0 && (_jsx(Box, { paddingLeft: bgTasks.length > 0 ? 2 : 1, paddingRight: 1, children: _jsx(Text, { color: theme.accent, bold: true, children: `${eyesCount} eyes signal${eyesCount === 1 ? "" : "s"} · Run /eyes-improve to enhance GG Coder` }) })), updatePending && (_jsx(Box, { paddingLeft: bgTasks.length > 0 || (eyesCount !== undefined && eyesCount > 0) ? 2 : 1, paddingRight: 1, children: _jsx(Text, { color: theme.success, bold: true, children: "\u2728 Update ready \u00B7 restart to apply" }) }))] }))] }))] }));
2446
2556
  }
2447
2557
  //# sourceMappingURL=App.js.map