@kenkaiiii/ggcoder 4.3.227 → 4.3.229

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 (103) hide show
  1. package/README.md +12 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/checkpoint-store.d.ts +93 -0
  6. package/dist/core/checkpoint-store.d.ts.map +1 -0
  7. package/dist/core/checkpoint-store.js +188 -0
  8. package/dist/core/checkpoint-store.js.map +1 -0
  9. package/dist/core/checkpoint-store.test.d.ts +2 -0
  10. package/dist/core/checkpoint-store.test.d.ts.map +1 -0
  11. package/dist/core/checkpoint-store.test.js +111 -0
  12. package/dist/core/checkpoint-store.test.js.map +1 -0
  13. package/dist/core/compaction/compactor.d.ts.map +1 -1
  14. package/dist/core/compaction/compactor.js +47 -25
  15. package/dist/core/compaction/compactor.js.map +1 -1
  16. package/dist/core/compaction/compactor.test.js +11 -6
  17. package/dist/core/compaction/compactor.test.js.map +1 -1
  18. package/dist/core/prompt-commands.d.ts.map +1 -1
  19. package/dist/core/prompt-commands.js +39 -74
  20. package/dist/core/prompt-commands.js.map +1 -1
  21. package/dist/core/prompt-commands.test.js +8 -5
  22. package/dist/core/prompt-commands.test.js.map +1 -1
  23. package/dist/core/slash-commands.d.ts.map +1 -1
  24. package/dist/core/slash-commands.js +10 -0
  25. package/dist/core/slash-commands.js.map +1 -1
  26. package/dist/tools/checkpoint-hook.test.d.ts +2 -0
  27. package/dist/tools/checkpoint-hook.test.d.ts.map +1 -0
  28. package/dist/tools/checkpoint-hook.test.js +65 -0
  29. package/dist/tools/checkpoint-hook.test.js.map +1 -0
  30. package/dist/tools/edit.d.ts +1 -1
  31. package/dist/tools/edit.d.ts.map +1 -1
  32. package/dist/tools/edit.js +3 -1
  33. package/dist/tools/edit.js.map +1 -1
  34. package/dist/tools/index.d.ts +7 -0
  35. package/dist/tools/index.d.ts.map +1 -1
  36. package/dist/tools/index.js +5 -2
  37. package/dist/tools/index.js.map +1 -1
  38. package/dist/tools/read.d.ts.map +1 -1
  39. package/dist/tools/read.js +14 -1
  40. package/dist/tools/read.js.map +1 -1
  41. package/dist/tools/read.test.js +4 -0
  42. package/dist/tools/read.test.js.map +1 -1
  43. package/dist/tools/screenshot.d.ts +25 -0
  44. package/dist/tools/screenshot.d.ts.map +1 -0
  45. package/dist/tools/screenshot.js +165 -0
  46. package/dist/tools/screenshot.js.map +1 -0
  47. package/dist/tools/screenshot.test.d.ts +2 -0
  48. package/dist/tools/screenshot.test.d.ts.map +1 -0
  49. package/dist/tools/screenshot.test.js +145 -0
  50. package/dist/tools/screenshot.test.js.map +1 -0
  51. package/dist/tools/write.d.ts +1 -1
  52. package/dist/tools/write.d.ts.map +1 -1
  53. package/dist/tools/write.js +3 -1
  54. package/dist/tools/write.js.map +1 -1
  55. package/dist/ui/App.d.ts +3 -0
  56. package/dist/ui/App.d.ts.map +1 -1
  57. package/dist/ui/App.js +140 -5
  58. package/dist/ui/App.js.map +1 -1
  59. package/dist/ui/app-items.d.ts +11 -0
  60. package/dist/ui/app-items.d.ts.map +1 -1
  61. package/dist/ui/app-items.js.map +1 -1
  62. package/dist/ui/components/ChatScreen.d.ts +4 -0
  63. package/dist/ui/components/ChatScreen.d.ts.map +1 -1
  64. package/dist/ui/components/ChatScreen.js +1 -1
  65. package/dist/ui/components/ChatScreen.js.map +1 -1
  66. package/dist/ui/components/InputArea.d.ts +10 -1
  67. package/dist/ui/components/InputArea.d.ts.map +1 -1
  68. package/dist/ui/components/InputArea.js +17 -1
  69. package/dist/ui/components/InputArea.js.map +1 -1
  70. package/dist/ui/components/RewindOverlay.d.ts +18 -0
  71. package/dist/ui/components/RewindOverlay.d.ts.map +1 -0
  72. package/dist/ui/components/RewindOverlay.js +52 -0
  73. package/dist/ui/components/RewindOverlay.js.map +1 -0
  74. package/dist/ui/components/SlashStyledSelectList.d.ts +7 -1
  75. package/dist/ui/components/SlashStyledSelectList.d.ts.map +1 -1
  76. package/dist/ui/components/SlashStyledSelectList.js +2 -2
  77. package/dist/ui/components/SlashStyledSelectList.js.map +1 -1
  78. package/dist/ui/hooks/useAgentLoop.d.ts +8 -2
  79. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  80. package/dist/ui/hooks/useAgentLoop.js +25 -4
  81. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  82. package/dist/ui/render.d.ts +2 -0
  83. package/dist/ui/render.d.ts.map +1 -1
  84. package/dist/ui/render.js +1 -0
  85. package/dist/ui/render.js.map +1 -1
  86. package/dist/ui/terminal-history.d.ts.map +1 -1
  87. package/dist/ui/terminal-history.js +40 -0
  88. package/dist/ui/terminal-history.js.map +1 -1
  89. package/dist/ui/terminal-history.test.js +105 -0
  90. package/dist/ui/terminal-history.test.js.map +1 -1
  91. package/dist/ui/utils/terminal-graphics.d.ts +16 -0
  92. package/dist/ui/utils/terminal-graphics.d.ts.map +1 -0
  93. package/dist/ui/utils/terminal-graphics.js +68 -0
  94. package/dist/ui/utils/terminal-graphics.js.map +1 -0
  95. package/dist/ui/utils/terminal-graphics.test.d.ts +2 -0
  96. package/dist/ui/utils/terminal-graphics.test.d.ts.map +1 -0
  97. package/dist/ui/utils/terminal-graphics.test.js +61 -0
  98. package/dist/ui/utils/terminal-graphics.test.js.map +1 -0
  99. package/dist/utils/image.d.ts +9 -0
  100. package/dist/utils/image.d.ts.map +1 -1
  101. package/dist/utils/image.js +25 -0
  102. package/dist/utils/image.js.map +1 -1
  103. package/package.json +6 -5
package/dist/ui/App.js CHANGED
@@ -14,7 +14,7 @@ import { useDoublePress } from "./hooks/useDoublePress.js";
14
14
  import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTaskBar, collapseTaskBar, navigateTaskBar, killTask, } from "./stores/taskbar-store.js";
15
15
  import { playNotificationSound } from "../utils/sound.js";
16
16
  import {} from "@kenkaiiii/gg-ai";
17
- import { extractImagePaths } from "../utils/image.js";
17
+ import { downscaleForPreview, extractImagePaths } from "../utils/image.js";
18
18
  import { useAgentLoop } from "./hooks/useAgentLoop.js";
19
19
  import { useTranscriptHistory } from "./hooks/useTranscriptHistory.js";
20
20
  import { createWebSearchTool } from "../tools/web-search.js";
@@ -36,6 +36,7 @@ import { isFirstTimeSetup, markSetupAudited, getAnnouncedLanguages, markLanguage
36
36
  import { loadCustomCommands } from "../core/custom-commands.js";
37
37
  import { detectLanguages } from "../core/language-detector.js";
38
38
  import { detectVerifyCommands } from "../core/verify-commands.js";
39
+ import { RewindOverlay } from "./components/RewindOverlay.js";
39
40
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
40
41
  import { getMCPServers } from "../core/mcp/index.js";
41
42
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
@@ -75,6 +76,23 @@ const AGGREGATABLE_TOOLS = new Set([
75
76
  ]);
76
77
  const RUNNING_INDICATOR_ANIMATION_MS = 1_200;
77
78
  // ── App Component ──────────────────────────────────────────
79
+ /**
80
+ * Extract inline image previews carried on a tool result's `details` payload.
81
+ * Image-producing tools (e.g. screenshot) attach `{ imagePreviews: [...] }` so
82
+ * the terminal-history printer can render the captured pixels inline.
83
+ */
84
+ function extractToolImagePreviews(details) {
85
+ if (!details || typeof details !== "object")
86
+ return undefined;
87
+ const previews = details.imagePreviews;
88
+ if (!Array.isArray(previews))
89
+ return undefined;
90
+ const valid = previews.filter((p) => typeof p === "object" &&
91
+ p !== null &&
92
+ typeof p.base64 === "string" &&
93
+ typeof p.mediaType === "string");
94
+ return valid.length > 0 ? valid : undefined;
95
+ }
78
96
  export function App(props) {
79
97
  const theme = useTheme();
80
98
  const switchTheme = useSetTheme();
@@ -119,6 +137,10 @@ export function App(props) {
119
137
  const [overlay, setOverlay] = useState(props.sessionStore?.overlay ?? props.initialOverlay ?? null);
120
138
  const [goalStatusEntries, setGoalStatusEntries] = useState(props.sessionStore?.goalStatusEntries ?? []);
121
139
  const [updatePending, setUpdatePending] = useState(() => getPendingUpdate(props.version) !== null);
140
+ // Signal that pushes text into the InputArea composer (e.g. restoring queued
141
+ // messages after an interrupt). Bumping `nonce` triggers the injection even
142
+ // when the text is identical to a prior restore.
143
+ const [composerInject, setComposerInject] = useState(null);
122
144
  const agentRunningRef = useRef(false);
123
145
  const runningGoalIdsRef = useRef(new Set());
124
146
  const activeVerifierRunIdsRef = useRef(new Set());
@@ -134,6 +156,10 @@ export function App(props) {
134
156
  const startPixelFixRef = useRef(() => { });
135
157
  const cwdRef = useRef(props.cwd);
136
158
  const [displayedCwd, setDisplayedCwd] = useState(props.cwd);
159
+ // /rewind overlay: holds the checkpoint list while the picker is open.
160
+ const [rewindCheckpoints, setRewindCheckpoints] = useState(null);
161
+ // Monotonic user-turn counter keying per-turn checkpoints.
162
+ const rewindTurnRef = useRef(0);
137
163
  const taskPicker = useTaskPickerController({
138
164
  displayedCwd,
139
165
  onStartTask: (title, prompt, taskId) => startTaskRef.current(title, prompt, taskId),
@@ -971,6 +997,7 @@ export function App(props) {
971
997
  isError,
972
998
  durationMs,
973
999
  details,
1000
+ imagePreviews: extractToolImagePreviews(details),
974
1001
  id: startItem.id,
975
1002
  };
976
1003
  updated = [...prev];
@@ -988,6 +1015,7 @@ export function App(props) {
988
1015
  isError,
989
1016
  durationMs,
990
1017
  details,
1018
+ imagePreviews: extractToolImagePreviews(details),
991
1019
  id: getId(),
992
1020
  },
993
1021
  ];
@@ -1478,6 +1506,31 @@ export function App(props) {
1478
1506
  // has not grown.
1479
1507
  await applyLanguageDetectionRef.current("input");
1480
1508
  }
1509
+ // /rewind — open the checkpoint picker (needs React state + the store).
1510
+ if (trimmed === "/rewind") {
1511
+ const store = props.checkpointStore;
1512
+ if (!store) {
1513
+ setLiveItems((prev) => [
1514
+ ...prev,
1515
+ { kind: "info", text: "Checkpoints are not available in this session.", id: getId() },
1516
+ ]);
1517
+ return;
1518
+ }
1519
+ const cps = await store.listCheckpoints();
1520
+ if (cps.length === 0) {
1521
+ setLiveItems((prev) => [
1522
+ ...prev,
1523
+ {
1524
+ kind: "info",
1525
+ text: "No checkpoints yet \u2014 edit a file through ggcoder first.",
1526
+ id: getId(),
1527
+ },
1528
+ ]);
1529
+ return;
1530
+ }
1531
+ setRewindCheckpoints(cps);
1532
+ return;
1533
+ }
1481
1534
  if (await handleUiSlashCommand(trimmed, {
1482
1535
  openModelSelector: () => setOverlay("model"),
1483
1536
  compactConversation: async () => {
@@ -1591,7 +1644,7 @@ export function App(props) {
1591
1644
  // ── Queue message if agent is already running ──
1592
1645
  if (agentLoop.isRunning) {
1593
1646
  log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
1594
- agentLoop.queueMessage(userContent);
1647
+ agentLoop.queueMessage(userContent, input);
1595
1648
  let displayText = input;
1596
1649
  if (hasImages) {
1597
1650
  const { cleanText } = await extractImagePaths(input, props.cwd);
@@ -1612,10 +1665,21 @@ export function App(props) {
1612
1665
  const { cleanText } = await extractImagePaths(input, props.cwd);
1613
1666
  displayText = cleanText;
1614
1667
  }
1668
+ let imagePreviews;
1669
+ if (hasImages) {
1670
+ const built = await Promise.all(inputImages
1671
+ .filter((img) => img.kind === "image")
1672
+ .map(async (img) => {
1673
+ const downscaled = await downscaleForPreview(Buffer.from(img.data, "base64"));
1674
+ return { base64: downscaled.toString("base64"), mediaType: img.mediaType };
1675
+ }));
1676
+ imagePreviews = built.length > 0 ? built : undefined;
1677
+ }
1615
1678
  const userItem = {
1616
1679
  kind: "user",
1617
1680
  text: displayText,
1618
1681
  imageCount: hasImages ? inputImages.length : undefined,
1682
+ imagePreviews,
1619
1683
  pasteInfo,
1620
1684
  id: getId(),
1621
1685
  };
@@ -1628,6 +1692,17 @@ export function App(props) {
1628
1692
  setPlanSteps([]);
1629
1693
  }
1630
1694
  finalizeSubmittedUserItem(userItem);
1695
+ // Open a per-turn checkpoint capturing the conversation position right
1696
+ // before this exchange, so /rewind can restore pre-turn code/chat state.
1697
+ if (props.checkpointStore) {
1698
+ rewindTurnRef.current += 1;
1699
+ await props.checkpointStore
1700
+ .openCheckpoint({
1701
+ turnIndex: rewindTurnRef.current,
1702
+ messageIndex: messagesRef.current.length,
1703
+ })
1704
+ .catch(() => { });
1705
+ }
1631
1706
  // Run agent
1632
1707
  try {
1633
1708
  await agentLoop.run(userContent);
@@ -1663,7 +1738,13 @@ export function App(props) {
1663
1738
  const handleDoubleExit = useDoublePress(setExitPending, showSessionSummaryAndExit);
1664
1739
  const handleAbort = useCallback(() => {
1665
1740
  if (agentLoop.isRunning) {
1666
- agentLoop.clearQueue();
1741
+ // Restore any unsent queued messages to the composer instead of dropping
1742
+ // them, so an interrupt never silently discards what the user typed.
1743
+ const queuedText = agentLoop.drainQueuedText();
1744
+ if (queuedText) {
1745
+ setLiveItems((prev) => prev.filter((item) => item.kind !== "queued"));
1746
+ setComposerInject({ text: queuedText, nonce: nextIdRef.current++ });
1747
+ }
1667
1748
  agentLoop.abort();
1668
1749
  }
1669
1750
  else if (compactionAbortRef.current) {
@@ -1672,7 +1753,7 @@ export function App(props) {
1672
1753
  else {
1673
1754
  handleDoubleExit();
1674
1755
  }
1675
- }, [agentLoop, handleDoubleExit]);
1756
+ }, [agentLoop, handleDoubleExit, setLiveItems]);
1676
1757
  const handleToggleThinking = useCallback(() => {
1677
1758
  setThinkingLevel((prev) => {
1678
1759
  const next = getNextThinkingLevel(currentProvider, currentModel, prev);
@@ -1834,6 +1915,12 @@ export function App(props) {
1834
1915
  { name: "compact", aliases: ["c"], description: "Compact context", sectionTitle: "built-in" },
1835
1916
  { name: "clear", aliases: [], description: "Clear session", sectionTitle: "built-in" },
1836
1917
  { name: "theme", aliases: ["t"], description: "Switch theme", sectionTitle: "built-in" },
1918
+ {
1919
+ name: "rewind",
1920
+ aliases: [],
1921
+ description: "Restore files/conversation to a checkpoint",
1922
+ sectionTitle: "built-in",
1923
+ },
1837
1924
  ...orderedPromptCommands,
1838
1925
  ...remainingPromptCommands,
1839
1926
  ...customCommands.map((cmd) => ({
@@ -2051,6 +2138,53 @@ export function App(props) {
2051
2138
  lastPendingHistoryItem,
2052
2139
  lastHistoryItem,
2053
2140
  });
2141
+ const handleRewindCancel = useCallback(() => setRewindCheckpoints(null), []);
2142
+ const handleRewindRestore = useCallback((id, mode) => {
2143
+ const store = props.checkpointStore;
2144
+ setRewindCheckpoints(null);
2145
+ if (!store)
2146
+ return;
2147
+ void (async () => {
2148
+ try {
2149
+ const result = await store.restore(id, mode);
2150
+ const turnNum = id.replace(/^cp-0*/, "") || id;
2151
+ const detailParts = [];
2152
+ if (mode === "code" || mode === "both") {
2153
+ detailParts.push(`${result.filesRestored} file${result.filesRestored === 1 ? "" : "s"} restored`);
2154
+ }
2155
+ if (mode === "conversation" || mode === "both")
2156
+ detailParts.push("conversation rewound");
2157
+ const infoText = `Rewound to checkpoint #${turnNum} (${detailParts.join(", ")}).`;
2158
+ if (mode === "conversation" || mode === "both") {
2159
+ const truncated = messagesRef.current.slice(0, Math.max(1, result.messageIndex));
2160
+ messagesRef.current = truncated;
2161
+ persistedIndexRef.current = truncated.length;
2162
+ agentLoop.reset();
2163
+ // Remount in lockstep so the banner + confirmation re-render cleanly
2164
+ // after the conversation context is truncated (CLAUDE.md pattern).
2165
+ if (props.resetUI) {
2166
+ props.resetUI({
2167
+ wipeSession: true,
2168
+ messages: truncated,
2169
+ history: [
2170
+ { kind: "banner", id: "banner" },
2171
+ { kind: "info", text: infoText, id: getId() },
2172
+ ],
2173
+ });
2174
+ return;
2175
+ }
2176
+ }
2177
+ setLiveItems((prev) => [...prev, { kind: "info", text: infoText, id: getId() }]);
2178
+ }
2179
+ catch (err) {
2180
+ const msg = err instanceof Error ? err.message : String(err);
2181
+ setLiveItems((prev) => [
2182
+ ...prev,
2183
+ { kind: "info", text: `Rewind failed: ${msg}`, id: getId() },
2184
+ ]);
2185
+ }
2186
+ })();
2187
+ }, [props.checkpointStore, props.resetUI, agentLoop, messagesRef, persistedIndexRef]);
2054
2188
  const handleCloseRemountableOverlay = () => {
2055
2189
  if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
2056
2190
  props.sessionStore.overlay = null;
@@ -2293,9 +2427,10 @@ export function App(props) {
2293
2427
  if (quittingSummary) {
2294
2428
  return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: _jsx(SessionSummaryDisplay, { summary: quittingSummary }) }));
2295
2429
  }
2296
- return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: fullScreenOverlay ? (_jsx(FullScreenOverlayRouter, { overlay: fullScreenOverlay, version: props.version, cwd: props.cwd, agentRunning: agentLoop.isRunning, planAutoExpand: planAutoExpand, onClosePixel: handleCloseRemountableOverlay, onPixelFixOne: handlePixelFixOne, onPixelFixAll: handlePixelFixAll, onCloseSkills: handleCloseRemountableOverlay, onClosePlan: handleClosePlanOverlay, onApprovePlan: handleApprovePlan, onRejectPlan: handleRejectPlan })) : (_jsx(ChatScreen, { columns: columns, liveItems: uniqueItemsById(liveItems), renderItem: renderItem, isRunning: agentLoop.isRunning, visibleStreamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveStreamingSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, measuredLiveAreaRows: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText || streamingContinuesFlushed ? 1 : 0, streamingContinuation: streamingContinuesFlushed, controlsRef: mainControlsRef, hiddenQueuedCount: hiddenQueuedCount, queueIndicatorMarginTop: shouldTopSpaceQueueIndicator ? 2 : 1, theme: theme, statusSlotVisible: statusSlotVisible, activityVisible: activityVisible, stallStatusVisible: stallStatusVisible, doneStatus: doneStatus, activityPhase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, isThinking: agentLoop.isThinking, thinkingLevel: thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, lastUserMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, formatDuration: formatDuration, inputControls: {
2430
+ return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: rewindCheckpoints ? (_jsx(RewindOverlay, { checkpoints: rewindCheckpoints, onRestore: handleRewindRestore, onCancel: handleRewindCancel })) : fullScreenOverlay ? (_jsx(FullScreenOverlayRouter, { overlay: fullScreenOverlay, version: props.version, cwd: props.cwd, agentRunning: agentLoop.isRunning, planAutoExpand: planAutoExpand, onClosePixel: handleCloseRemountableOverlay, onPixelFixOne: handlePixelFixOne, onPixelFixAll: handlePixelFixAll, onCloseSkills: handleCloseRemountableOverlay, onClosePlan: handleClosePlanOverlay, onApprovePlan: handleApprovePlan, onRejectPlan: handleRejectPlan })) : (_jsx(ChatScreen, { columns: columns, liveItems: uniqueItemsById(liveItems), renderItem: renderItem, isRunning: agentLoop.isRunning, visibleStreamingText: visibleStreamingText, streamingThinking: agentLoop.streamingThinking, thinkingMs: agentLoop.thinkingMs, reserveStreamingSpacing: shouldReserveStreamingSpacing, renderMarkdown: renderMarkdown, measuredLiveAreaRows: measuredLiveAreaRows, assistantMarginTop: shouldTopSpaceStreamingText || streamingContinuesFlushed ? 1 : 0, streamingContinuation: streamingContinuesFlushed, controlsRef: mainControlsRef, hiddenQueuedCount: hiddenQueuedCount, queueIndicatorMarginTop: shouldTopSpaceQueueIndicator ? 2 : 1, theme: theme, statusSlotVisible: statusSlotVisible, activityVisible: activityVisible, stallStatusVisible: stallStatusVisible, doneStatus: doneStatus, activityPhase: agentLoop.activityPhase, elapsedMs: agentLoop.elapsedMs, runStartRef: agentLoop.runStartRef, isThinking: agentLoop.isThinking, thinkingLevel: thinkingLevel, tokenEstimate: agentLoop.streamedTokenEstimate, charCountRef: agentLoop.charCountRef, realTokensAccumRef: agentLoop.realTokensAccumRef, lastUserMessage: lastUserMessage, activeToolNames: agentLoop.activeToolCalls.map((tc) => tc.name), retryInfo: agentLoop.retryInfo, planDone: planSteps.filter((s) => s.completed).length, planTotal: planSteps.length, formatDuration: formatDuration, inputControls: {
2297
2431
  onSubmit: handleSubmit,
2298
2432
  onAbort: handleAbort,
2433
+ injectText: composerInject,
2299
2434
  inputActive: !taskBarFocused && !overlay,
2300
2435
  onDownAtEnd: handleFocusTaskBar,
2301
2436
  onShiftTab: handleToggleThinking,