@kenkaiiii/ggcoder 4.3.220 → 4.3.221

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.d.ts.map +1 -1
  2. package/dist/cli.js +14 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/session-manager.d.ts +1 -0
  5. package/dist/core/session-manager.d.ts.map +1 -1
  6. package/dist/core/session-manager.js +4 -0
  7. package/dist/core/session-manager.js.map +1 -1
  8. package/dist/ui/App.d.ts +4 -0
  9. package/dist/ui/App.d.ts.map +1 -1
  10. package/dist/ui/App.js +46 -2
  11. package/dist/ui/App.js.map +1 -1
  12. package/dist/ui/app-items.d.ts +7 -1
  13. package/dist/ui/app-items.d.ts.map +1 -1
  14. package/dist/ui/app-items.js.map +1 -1
  15. package/dist/ui/components/SessionSummary.d.ts +5 -0
  16. package/dist/ui/components/SessionSummary.d.ts.map +1 -0
  17. package/dist/ui/components/SessionSummary.js +32 -0
  18. package/dist/ui/components/SessionSummary.js.map +1 -0
  19. package/dist/ui/render.d.ts +2 -0
  20. package/dist/ui/render.d.ts.map +1 -1
  21. package/dist/ui/render.js +5 -0
  22. package/dist/ui/render.js.map +1 -1
  23. package/dist/ui/session-summary.d.ts +63 -0
  24. package/dist/ui/session-summary.d.ts.map +1 -0
  25. package/dist/ui/session-summary.js +81 -0
  26. package/dist/ui/session-summary.js.map +1 -0
  27. package/dist/ui/terminal-history.d.ts.map +1 -1
  28. package/dist/ui/terminal-history.js +38 -0
  29. package/dist/ui/terminal-history.js.map +1 -1
  30. package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -1
  31. package/dist/ui/transcript/TranscriptRenderer.js +3 -0
  32. package/dist/ui/transcript/TranscriptRenderer.js.map +1 -1
  33. package/dist/ui/tui-history-parity.test.js +39 -0
  34. package/dist/ui/tui-history-parity.test.js.map +1 -1
  35. package/package.json +4 -4
package/dist/ui/App.js CHANGED
@@ -15,6 +15,7 @@ import { useTranscriptHistory } from "./hooks/useTranscriptHistory.js";
15
15
  import { createWebSearchTool } from "../tools/web-search.js";
16
16
  import { ChatScreen } from "./components/ChatScreen.js";
17
17
  import { FullScreenOverlayRouter } from "./components/FullScreenOverlayRouter.js";
18
+ import { SessionSummaryDisplay } from "./components/SessionSummary.js";
18
19
  import { reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntry, syncGoalStatusEntries, } from "./components/GoalStatusBar.js";
19
20
  import { useTheme, useSetTheme } from "./theme/theme.js";
20
21
  import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
@@ -57,6 +58,7 @@ import { renderTranscriptItem } from "./transcript/TranscriptRenderer.js";
57
58
  import { formatDuration } from "./duration-format.js";
58
59
  import { pickDurationVerb } from "./duration-summary.js";
59
60
  import { toErrorItem } from "./error-item.js";
61
+ import { addLinesChanged, buildSessionSummary, createSessionStats, recordServerToolCall, recordToolEnd, recordTurnEnd, } from "./session-summary.js";
60
62
  import { buildGoalDirtyWorktreePauseRun, buildGoalDirtyWorktreeUserPrompt, buildGoalTaskPromptWithReferences, buildGoalUserPauseRun, goalDirtyWorktreeInfoText, goalRunNeedsExplicitContinuationAfterWorker, goalTaskProgress, shouldKeepGoalRunTrackedAfterDecision, shouldRunGoalTaskInMainCheckout, } from "./goal-run-helpers.js";
61
63
  import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, removeItemsWithIds, uniqueItemsById, } from "./item-helpers.js";
62
64
  export { buildGoalSetupPromptFromPlanner, buildUserContentWithAttachments, collectAssistantTextSince, isGoalPromptCommandName, routePromptCommandInput, runGoalPromptSetupSequence, } from "./prompt-routing.js";
@@ -85,6 +87,7 @@ export function App(props) {
85
87
  // Hoisted before terminal title hook so it can reference them
86
88
  const [lastUserMessage, setLastUserMessage] = useState("");
87
89
  const [exitPending, setExitPending] = useState(false);
90
+ const [quittingSummary, setQuittingSummary] = useState(null);
88
91
  const [goalMode, setGoalMode] = useState(props.sessionStore?.goalMode ?? props.goalModeRef?.current ?? "off");
89
92
  const [planMode, setPlanMode] = useState(props.sessionStore?.planMode ?? props.planModeRef?.current ?? false);
90
93
  // Terminal title — updated later after agentLoop is created
@@ -180,6 +183,8 @@ export function App(props) {
180
183
  const sessionManagerRef = useRef(props.sessionsDir ? new SessionManager(props.sessionsDir) : null);
181
184
  const sessionPathRef = useRef(props.sessionStore?.sessionPath ?? props.sessionPath);
182
185
  const persistedIndexRef = useRef(messagesRef.current.length);
186
+ const sessionStatsRef = useRef(props.sessionStore?.sessionStats ??
187
+ createSessionStats({ sessionId: props.sessionStore?.sessionId ?? props.sessionId }));
183
188
  /** Last actual API-reported input token count (from turn_end). */
184
189
  const lastActualTokensRef = useRef(0);
185
190
  /** Timestamp (ms) when lastActualTokensRef was last updated by turn_end. */
@@ -317,6 +322,10 @@ export function App(props) {
317
322
  if (sessionStore)
318
323
  sessionStore.planMode = planMode;
319
324
  }, [planMode, sessionStore]);
325
+ useEffect(() => {
326
+ if (sessionStore)
327
+ sessionStore.sessionStats = sessionStatsRef.current;
328
+ }, [sessionStore]);
320
329
  // pendingAction is consumed via a useEffect AFTER agentLoop is created
321
330
  // — see below where useAgentLoop is set up.
322
331
  const pendingActionConsumedRef = useRef(false);
@@ -565,9 +574,11 @@ export function App(props) {
565
574
  messages: compactedMessages,
566
575
  });
567
576
  sessionPathRef.current = session.path;
577
+ sessionStatsRef.current.sessionId = session.id;
568
578
  persistedIndexRef.current = compactedMessages.length;
569
579
  if (sessionStore) {
570
580
  sessionStore.sessionPath = session.path;
581
+ sessionStore.sessionId = session.id;
571
582
  sessionStore.messages = [...compactedMessages];
572
583
  }
573
584
  log("INFO", "compaction", "Persisted compacted session checkpoint", { path: session.path });
@@ -582,6 +593,7 @@ export function App(props) {
582
593
  if (sessionStore) {
583
594
  sessionStore.messages = [...allMsgs];
584
595
  sessionStore.sessionPath = sp;
596
+ sessionStore.sessionId = sessionStatsRef.current.sessionId;
585
597
  }
586
598
  }, [appendMessagesToSession, sessionStore]);
587
599
  /**
@@ -1051,6 +1063,14 @@ export function App(props) {
1051
1063
  });
1052
1064
  }, []),
1053
1065
  onToolEnd: useCallback((toolCallId, name, result, isError, durationMs, details) => {
1066
+ recordToolEnd(sessionStatsRef.current, name, isError, durationMs);
1067
+ if (name === "edit" && !isError) {
1068
+ const diff = details?.diff ?? result;
1069
+ addLinesChanged(sessionStatsRef.current, {
1070
+ added: (diff.match(/^\+[^+]/gm) ?? []).length,
1071
+ removed: (diff.match(/^-[^-]/gm) ?? []).length,
1072
+ });
1073
+ }
1054
1074
  // Language-pack detection — gated on `write`/`bash` inside the
1055
1075
  // helper; cheap to call unconditionally. Fire-and-forget; the next
1056
1076
  // LLM turn picks up the swapped system prompt automatically.
@@ -1158,6 +1178,7 @@ export function App(props) {
1158
1178
  }
1159
1179
  }, []),
1160
1180
  onServerToolCall: useCallback((id, name, input, stream) => {
1181
+ recordServerToolCall(sessionStatsRef.current);
1161
1182
  log("INFO", "server_tool", `Server tool call: ${name}`, { id });
1162
1183
  const startedAt = Date.now();
1163
1184
  const animateUntil = startedAt + RUNNING_INDICATOR_ANIMATION_MS;
@@ -1231,6 +1252,7 @@ export function App(props) {
1231
1252
  });
1232
1253
  }, [queueFlush]),
1233
1254
  onTurnEnd: useCallback((turn, stopReason, usage) => {
1255
+ recordTurnEnd(sessionStatsRef.current, usage);
1234
1256
  log("INFO", "turn", `Turn ${turn} ended`, {
1235
1257
  stopReason,
1236
1258
  inputTokens: String(usage.inputTokens),
@@ -1540,6 +1562,24 @@ export function App(props) {
1540
1562
  return () => clearTimeout(timer);
1541
1563
  }
1542
1564
  }, [agentLoop.isRunning, sessionStore, props.resetUI]);
1565
+ const showSessionSummaryAndExit = useCallback(() => {
1566
+ const summary = buildSessionSummary({
1567
+ stats: sessionStatsRef.current,
1568
+ provider: currentProvider,
1569
+ model: currentModel,
1570
+ cwd: displayedCwd,
1571
+ footer: sessionStatsRef.current.sessionId
1572
+ ? `To resume this session: ggcoder --resume ${sessionStatsRef.current.sessionId}`
1573
+ : undefined,
1574
+ });
1575
+ setDoneStatus(null);
1576
+ setExitPending(false);
1577
+ setOverlay(null);
1578
+ setLiveItems([]);
1579
+ setQuittingSummary(summary);
1580
+ writeStdout("\x1b[2J\x1b[3J\x1b[H");
1581
+ setTimeout(() => process.exit(0), 150);
1582
+ }, [currentModel, currentProvider, displayedCwd, writeStdout]);
1543
1583
  // Consume pending post-remount work once on mount. Set by resetUI options
1544
1584
  // for paths that remount AND immediately drive work (plan accept/reject,
1545
1585
  // pixel fix, Goal approval). The work survives the unmount because
@@ -1610,7 +1650,7 @@ export function App(props) {
1610
1650
  if (compactionAbortRef.current === ac)
1611
1651
  compactionAbortRef.current = null;
1612
1652
  },
1613
- quit: () => process.exit(0),
1653
+ quit: showSessionSummaryAndExit,
1614
1654
  clearSession: () => {
1615
1655
  if (props.resetUI) {
1616
1656
  void (async () => {
@@ -1773,12 +1813,13 @@ export function App(props) {
1773
1813
  props.resetUI,
1774
1814
  props.sessionStore,
1775
1815
  rebuildSystemPrompt,
1816
+ showSessionSummaryAndExit,
1776
1817
  reloadCustomCommands,
1777
1818
  replaceSystemPrompt,
1778
1819
  setActiveGoalReferences,
1779
1820
  setGoalModeAndPrompt,
1780
1821
  ]);
1781
- const handleDoubleExit = useDoublePress(setExitPending, () => process.exit(0));
1822
+ const handleDoubleExit = useDoublePress(setExitPending, showSessionSummaryAndExit);
1782
1823
  const handleAbort = useCallback(() => {
1783
1824
  if (agentLoop.isRunning) {
1784
1825
  agentLoop.clearQueue();
@@ -3157,6 +3198,9 @@ export function App(props) {
3157
3198
  : isPlanView
3158
3199
  ? "plan"
3159
3200
  : null;
3201
+ if (quittingSummary) {
3202
+ return (_jsx(Box, { flexDirection: "column", width: columns, flexShrink: 0, flexGrow: 0, children: _jsx(SessionSummaryDisplay, { summary: quittingSummary }) }));
3203
+ }
3160
3204
  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 ? 1 : 0, streamingContinuation: streamedAssistantFlushRef.current.flushedChars > 0, 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: {
3161
3205
  onSubmit: handleSubmit,
3162
3206
  onAbort: handleAbort,