@kenkaiiii/ggcoder 4.3.237 → 4.3.238

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 (82) hide show
  1. package/README.md +3 -1
  2. package/dist/cli.js +2 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +12 -4
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/model-registry.d.ts +1 -0
  8. package/dist/core/model-registry.d.ts.map +1 -1
  9. package/dist/core/model-registry.js +23 -16
  10. package/dist/core/model-registry.js.map +1 -1
  11. package/dist/core/model-registry.test.js +13 -0
  12. package/dist/core/model-registry.test.js.map +1 -1
  13. package/dist/core/oauth/gemini.d.ts.map +1 -1
  14. package/dist/core/oauth/gemini.js +10 -5
  15. package/dist/core/oauth/gemini.js.map +1 -1
  16. package/dist/interactive.d.ts.map +1 -1
  17. package/dist/interactive.js +17 -1
  18. package/dist/interactive.js.map +1 -1
  19. package/dist/system-prompt.d.ts.map +1 -1
  20. package/dist/system-prompt.js +22 -14
  21. package/dist/system-prompt.js.map +1 -1
  22. package/dist/system-prompt.test.js +8 -1
  23. package/dist/system-prompt.test.js.map +1 -1
  24. package/dist/tools/prompt-hints.d.ts +23 -4
  25. package/dist/tools/prompt-hints.d.ts.map +1 -1
  26. package/dist/tools/prompt-hints.js +35 -10
  27. package/dist/tools/prompt-hints.js.map +1 -1
  28. package/dist/ui/App.d.ts.map +1 -1
  29. package/dist/ui/App.js +95 -14
  30. package/dist/ui/App.js.map +1 -1
  31. package/dist/ui/app-items.d.ts +2 -0
  32. package/dist/ui/app-items.d.ts.map +1 -1
  33. package/dist/ui/app-items.js +12 -3
  34. package/dist/ui/app-items.js.map +1 -1
  35. package/dist/ui/components/InputArea.d.ts.map +1 -1
  36. package/dist/ui/components/InputArea.js +17 -7
  37. package/dist/ui/components/InputArea.js.map +1 -1
  38. package/dist/ui/components/UserMessage.d.ts +2 -1
  39. package/dist/ui/components/UserMessage.d.ts.map +1 -1
  40. package/dist/ui/components/UserMessage.js +6 -2
  41. package/dist/ui/components/UserMessage.js.map +1 -1
  42. package/dist/ui/hooks/useAgentLoop.d.ts +10 -2
  43. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  44. package/dist/ui/hooks/useAgentLoop.js +9 -0
  45. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  46. package/dist/ui/login.js +1 -1
  47. package/dist/ui/login.js.map +1 -1
  48. package/dist/ui/prompt-routing.d.ts +2 -2
  49. package/dist/ui/prompt-routing.d.ts.map +1 -1
  50. package/dist/ui/prompt-routing.js +26 -1
  51. package/dist/ui/prompt-routing.js.map +1 -1
  52. package/dist/ui/queued-message.test.js +5 -1
  53. package/dist/ui/queued-message.test.js.map +1 -1
  54. package/dist/ui/render.d.ts.map +1 -1
  55. package/dist/ui/render.js +32 -0
  56. package/dist/ui/render.js.map +1 -1
  57. package/dist/ui/slash-command-images.test.js +33 -3
  58. package/dist/ui/slash-command-images.test.js.map +1 -1
  59. package/dist/ui/submit-prompt-command.d.ts.map +1 -1
  60. package/dist/ui/submit-prompt-command.js +6 -3
  61. package/dist/ui/submit-prompt-command.js.map +1 -1
  62. package/dist/ui/terminal-history.js +4 -2
  63. package/dist/ui/terminal-history.js.map +1 -1
  64. package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -1
  65. package/dist/ui/transcript/TranscriptRenderer.js +12 -10
  66. package/dist/ui/transcript/TranscriptRenderer.js.map +1 -1
  67. package/dist/ui/transcript/presentation.d.ts.map +1 -1
  68. package/dist/ui/transcript/presentation.js +7 -1
  69. package/dist/ui/transcript/presentation.js.map +1 -1
  70. package/dist/ui/tui-history-parity.test.js +6 -2
  71. package/dist/ui/tui-history-parity.test.js.map +1 -1
  72. package/dist/ui/utils/assistant-stream-split.d.ts +10 -0
  73. package/dist/ui/utils/assistant-stream-split.d.ts.map +1 -1
  74. package/dist/ui/utils/assistant-stream-split.js +19 -0
  75. package/dist/ui/utils/assistant-stream-split.js.map +1 -1
  76. package/dist/ui/utils/assistant-stream-split.test.js +21 -1
  77. package/dist/ui/utils/assistant-stream-split.test.js.map +1 -1
  78. package/dist/utils/image.d.ts +14 -1
  79. package/dist/utils/image.d.ts.map +1 -1
  80. package/dist/utils/image.js +63 -1
  81. package/dist/utils/image.js.map +1 -1
  82. package/package.json +3 -3
package/dist/ui/App.js CHANGED
@@ -12,7 +12,7 @@ import { useDoublePress } from "./hooks/useDoublePress.js";
12
12
  import { useTaskBarStore, useTaskBarPolling, focusTaskBar, exitTaskBar, expandTaskBar, collapseTaskBar, navigateTaskBar, killTask, } from "./stores/taskbar-store.js";
13
13
  import { playNotificationSound } from "../utils/sound.js";
14
14
  import {} from "@kenkaiiii/gg-ai";
15
- import { downscaleForPreview, extractImagePaths } from "../utils/image.js";
15
+ import { downscaleForPreview, extractMediaPaths } from "../utils/image.js";
16
16
  import { useAgentLoop } from "./hooks/useAgentLoop.js";
17
17
  import { useTranscriptHistory } from "./hooks/useTranscriptHistory.js";
18
18
  import { createWebSearchTool } from "../tools/web-search.js";
@@ -36,9 +36,9 @@ import { detectLanguages } from "../core/language-detector.js";
36
36
  import { detectVerifyCommands } from "../core/verify-commands.js";
37
37
  import { RewindOverlay } from "./components/RewindOverlay.js";
38
38
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
39
- import { getMCPServers } from "../core/mcp/index.js";
39
+ import { getAllMcpServers } from "../core/mcp/index.js";
40
40
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
41
- import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
41
+ import { splitAssistantStreamingText, estimateRenderedRows, } from "./utils/assistant-stream-split.js";
42
42
  import { getNextPendingTask, markTaskInProgress } from "../core/tasks-store.js";
43
43
  import { buildUserContentWithAttachments } from "./prompt-routing.js";
44
44
  import { submitPromptCommand } from "./submit-prompt-command.js";
@@ -563,6 +563,8 @@ export function App(props) {
563
563
  tools: currentTools,
564
564
  webSearch: props.webSearch,
565
565
  maxTokens: props.maxTokens,
566
+ supportsImages: getModel(currentModel)?.supportsImages ?? true,
567
+ supportsVideo: getModel(currentModel)?.supportsVideo ?? false,
566
568
  thinking: thinkingLevel,
567
569
  apiKey: activeApiKey,
568
570
  baseUrl: activeBaseUrl,
@@ -1020,6 +1022,18 @@ export function App(props) {
1020
1022
  log("INFO", "server_tool", `Server tool call: ${name}`, { id });
1021
1023
  const startedAt = Date.now();
1022
1024
  const animateUntil = startedAt + RUNNING_INDICATOR_ANIMATION_MS;
1025
+ // Feed the pinned LiveToolPanel so provider-side tools (Anthropic's
1026
+ // native web_search) appear in the same rolling window as client
1027
+ // tools. `input` carries the tool args (e.g. { query }) the row reads.
1028
+ setLiveToolFeed((prev) => [
1029
+ ...prev,
1030
+ {
1031
+ id,
1032
+ name,
1033
+ args: (input ?? {}),
1034
+ status: "running",
1035
+ },
1036
+ ].slice(-(LIVE_TOOL_PANEL_ROWS * 2)));
1023
1037
  // Flush completed items (including assistant text) before adding server
1024
1038
  // tool UI — same rationale as onToolStart.
1025
1039
  setLiveItems((prev) => {
@@ -1050,6 +1064,9 @@ export function App(props) {
1050
1064
  }, [queueFlush]),
1051
1065
  onServerToolResult: useCallback((toolUseId, resultType, data) => {
1052
1066
  log("INFO", "server_tool", `Server tool result`, { toolUseId, resultType });
1067
+ // Mark the panel entry done. Aborts never reach here (handled in
1068
+ // onAborted), so a result that arrives is always a normal completion.
1069
+ setLiveToolFeed((prev) => prev.map((entry) => entry.id === toolUseId ? { ...entry, status: "done" } : entry));
1053
1070
  setLiveItems((prev) => {
1054
1071
  let updated;
1055
1072
  const startIdx = prev.findIndex((item) => item.kind === "server_tool_start" && item.serverToolCallId === toolUseId);
@@ -1255,10 +1272,14 @@ export function App(props) {
1255
1272
  const imageCount = typeof content === "string"
1256
1273
  ? undefined
1257
1274
  : content.filter((c) => c.type === "image").length || undefined;
1275
+ const videoCount = typeof content === "string"
1276
+ ? undefined
1277
+ : content.filter((c) => c.type === "video").length || undefined;
1258
1278
  const userItem = {
1259
1279
  kind: "user",
1260
1280
  text: displayText,
1261
1281
  imageCount,
1282
+ videoCount,
1262
1283
  id: getId(),
1263
1284
  };
1264
1285
  setLastUserMessage(displayText);
@@ -1295,6 +1316,13 @@ export function App(props) {
1295
1316
  },
1296
1317
  ];
1297
1318
  }, []),
1319
+ onRetry: useCallback(() => {
1320
+ // Roll back any pending progressive flushes from the aborted attempt.
1321
+ // Without this, a stall retry regenerates the preamble and the old
1322
+ // flushed paragraph + the new one both end up in terminal history.
1323
+ pendingHistoryFlushRef.current = pendingHistoryFlushRef.current.filter((item) => item.kind !== "assistant");
1324
+ streamedAssistantFlushRef.current = { flushedChars: 0, text: "" };
1325
+ }, []),
1298
1326
  });
1299
1327
  // First-time-per-project auto-run of /setup. Bound after `agentLoop` is in
1300
1328
  // scope so the ref closure can dispatch to it. Called from the initial
@@ -1410,6 +1438,9 @@ export function App(props) {
1410
1438
  void agentLoop.run(action.prompt).catch((err) => {
1411
1439
  const errMsg = err instanceof Error ? err.message : String(err);
1412
1440
  log("ERROR", "error", errMsg);
1441
+ if (agentLoop.isRunning) {
1442
+ agentLoop.reset();
1443
+ }
1413
1444
  setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
1414
1445
  });
1415
1446
  // Intentional one-shot: run once on mount, never re-fire on re-render.
@@ -1564,31 +1595,35 @@ export function App(props) {
1564
1595
  }
1565
1596
  // ── Build user content (shared by normal + queued paths) ──
1566
1597
  const hasImages = inputImages.length > 0;
1598
+ const imageCount = inputImages.filter((img) => img.kind === "image").length;
1599
+ const videoCount = inputImages.filter((img) => img.kind === "video").length;
1567
1600
  const modelInfo = getModel(currentModel);
1568
1601
  const modelSupportsImages = modelInfo?.supportsImages ?? true;
1569
- const userContent = buildUserContentWithAttachments(input, inputImages, modelSupportsImages);
1602
+ const modelSupportsVideo = modelInfo?.supportsVideo ?? false;
1603
+ const userContent = buildUserContentWithAttachments(input, inputImages, modelSupportsImages, modelSupportsVideo);
1570
1604
  // ── Queue message if agent is already running ──
1571
1605
  if (agentLoop.isRunning) {
1572
1606
  log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
1573
1607
  agentLoop.queueMessage(userContent, input);
1574
1608
  let displayText = input;
1575
1609
  if (hasImages) {
1576
- const { cleanText } = await extractImagePaths(input, props.cwd);
1610
+ const { cleanText } = await extractMediaPaths(input, props.cwd);
1577
1611
  displayText = cleanText;
1578
1612
  }
1579
1613
  const queuedItem = {
1580
1614
  kind: "queued",
1581
1615
  text: displayText,
1582
- imageCount: hasImages ? inputImages.length : undefined,
1616
+ imageCount: imageCount > 0 ? imageCount : undefined,
1617
+ videoCount: videoCount > 0 ? videoCount : undefined,
1583
1618
  id: getId(),
1584
1619
  };
1585
1620
  setLiveItems((prev) => [...prev, queuedItem]);
1586
1621
  return;
1587
1622
  }
1588
- // Build display text — strip image paths, show badges instead
1623
+ // Build display text — strip image/video paths, show badges instead
1589
1624
  let displayText = input;
1590
1625
  if (hasImages) {
1591
- const { cleanText } = await extractImagePaths(input, props.cwd);
1626
+ const { cleanText } = await extractMediaPaths(input, props.cwd);
1592
1627
  displayText = cleanText;
1593
1628
  }
1594
1629
  let imagePreviews;
@@ -1604,7 +1639,8 @@ export function App(props) {
1604
1639
  const userItem = {
1605
1640
  kind: "user",
1606
1641
  text: displayText,
1607
- imageCount: hasImages ? inputImages.length : undefined,
1642
+ imageCount: imageCount > 0 ? imageCount : undefined,
1643
+ videoCount: videoCount > 0 ? videoCount : undefined,
1608
1644
  imagePreviews,
1609
1645
  pasteInfo,
1610
1646
  id: getId(),
@@ -1638,6 +1674,12 @@ export function App(props) {
1638
1674
  const msg = err instanceof Error ? err.message : String(err);
1639
1675
  log("ERROR", "error", msg);
1640
1676
  const isAbort = msg.includes("aborted") || msg.includes("abort");
1677
+ // If the agent loop threw but left isRunning in a stale true state
1678
+ // (can happen when the finally block hasn't been processed by React
1679
+ // yet), reset it so the user isn't deadlocked with a non-working UI.
1680
+ if (agentLoop.isRunning) {
1681
+ agentLoop.reset();
1682
+ }
1641
1683
  setLiveItems((prev) => [
1642
1684
  ...prev,
1643
1685
  isAbort
@@ -1728,8 +1770,14 @@ export function App(props) {
1728
1770
  rebuildPromptWithTools(next);
1729
1771
  return next;
1730
1772
  });
1731
- // Reconnect MCP servers
1732
- if (props.mcpManager) {
1773
+ // Reconnect MCP servers ONLY when the resolved server set actually
1774
+ // changes. GLM is the only provider with a different set (Z.AI
1775
+ // servers), so a switch that doesn't involve GLM on either side
1776
+ // keeps the identical set — tearing down a live stdio child (e.g.
1777
+ // kencode-search) and re-spawning `npx` there only risks a failed
1778
+ // re-spawn that would silently drop the tools.
1779
+ const glmInvolved = newProvider === "glm" || prevProvider === "glm";
1780
+ if (props.mcpManager && glmInvolved) {
1733
1781
  void (async () => {
1734
1782
  // Disconnect old MCP servers
1735
1783
  await props.mcpManager.dispose();
@@ -1748,7 +1796,11 @@ export function App(props) {
1748
1796
  apiKey = props.credentialsByProvider?.["glm"]?.accessToken;
1749
1797
  }
1750
1798
  try {
1751
- const mcpTools = await props.mcpManager.connectAll(getMCPServers(newProvider, apiKey));
1799
+ // Use getAllMcpServers so user-configured servers (from
1800
+ // ~/.gg/mcp.json and ./.gg/mcp.json) survive the reconnect —
1801
+ // getMCPServers returns provider defaults only.
1802
+ const servers = await getAllMcpServers(newProvider, apiKey, props.cwd);
1803
+ const mcpTools = await props.mcpManager.connectAll(servers);
1752
1804
  setCurrentTools((prev) => {
1753
1805
  const next = [...prev.filter((t) => !t.name.startsWith("mcp__")), ...mcpTools];
1754
1806
  rebuildPromptWithTools(next);
@@ -1999,6 +2051,9 @@ export function App(props) {
1999
2051
  setDoneStatus(null);
2000
2052
  setLiveItems([taskItem]);
2001
2053
  void agentLoop.run(fullPrompt).catch((err) => {
2054
+ if (agentLoop.isRunning) {
2055
+ agentLoop.reset();
2056
+ }
2002
2057
  setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
2003
2058
  });
2004
2059
  }, [agentLoop, currentModel, currentProvider, props]);
@@ -2047,7 +2102,23 @@ export function App(props) {
2047
2102
  // every chunk boundary. Slicing by the prospective flush here keeps the live
2048
2103
  // frame height monotonic, so the footer never bounces.
2049
2104
  const alreadyFlushedChars = streamedAssistantFlushRef.current.flushedChars;
2050
- const pendingFlushChars = rawVisibleStreamingText
2105
+ // Retry-safety gate: don't commit streamed paragraphs to permanent scrollback
2106
+ // while the text is still small enough to live entirely in the live region.
2107
+ // A silent stall-retry (agent-loop.ts) restarts the LLM call from scratch and
2108
+ // regenerates the opening text — reworded, so the byte-identical dedup can't
2109
+ // catch it. Anything already printed to scrollback can't be un-written, so the
2110
+ // regen appends as a second ⏺ bullet that paraphrases the first. Keeping short
2111
+ // streamed text live (it clears via setStreamingText("") on retry) closes that
2112
+ // hole. We only start flushing once the unflushed text would overflow the live
2113
+ // area — the original anti-jump purpose, which only matters for long responses
2114
+ // that are far less likely to be a stalled preamble. Once flushing has begun
2115
+ // for this turn (alreadyFlushedChars > 0) we keep flushing every boundary so
2116
+ // committed continuation paragraphs stay consistent with the live tail.
2117
+ const unflushedStreamingRows = rawVisibleStreamingText
2118
+ ? estimateRenderedRows(rawVisibleStreamingText.slice(alreadyFlushedChars), columns)
2119
+ : 0;
2120
+ const shouldFlushStreamedText = alreadyFlushedChars > 0 || unflushedStreamingRows > measuredLiveAreaRows;
2121
+ const pendingFlushChars = rawVisibleStreamingText && shouldFlushStreamedText
2051
2122
  ? splitAssistantStreamingText(rawVisibleStreamingText.slice(alreadyFlushedChars)).flushedText
2052
2123
  .length
2053
2124
  : 0;
@@ -2058,6 +2129,13 @@ export function App(props) {
2058
2129
  }
2059
2130
  if (rawVisibleStreamingText === streamedAssistantFlushRef.current.text)
2060
2131
  return;
2132
+ if (!shouldFlushStreamedText) {
2133
+ streamedAssistantFlushRef.current = {
2134
+ ...streamedAssistantFlushRef.current,
2135
+ text: rawVisibleStreamingText,
2136
+ };
2137
+ return;
2138
+ }
2061
2139
  const alreadyFlushed = streamedAssistantFlushRef.current.flushedChars;
2062
2140
  const unflushedText = rawVisibleStreamingText.slice(alreadyFlushed);
2063
2141
  const split = splitAssistantStreamingText(unflushedText);
@@ -2080,7 +2158,7 @@ export function App(props) {
2080
2158
  ...streamedAssistantFlushRef.current,
2081
2159
  text: rawVisibleStreamingText,
2082
2160
  };
2083
- }, [rawVisibleStreamingText, queueFlush]);
2161
+ }, [rawVisibleStreamingText, shouldFlushStreamedText, queueFlush]);
2084
2162
  const visibleStreamingText = stripDoneMarkers(rawVisibleStreamingText.slice(alreadyFlushedChars + pendingFlushChars));
2085
2163
  const lastLiveItem = liveItems.at(-1);
2086
2164
  // For spacing decisions, the previous row is the last item that actually
@@ -2384,6 +2462,9 @@ export function App(props) {
2384
2462
  void agentLoop.run(rejectionMsg).catch((err) => {
2385
2463
  const errMsg = err instanceof Error ? err.message : String(err);
2386
2464
  log("ERROR", "error", errMsg);
2465
+ if (agentLoop.isRunning) {
2466
+ agentLoop.reset();
2467
+ }
2387
2468
  setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
2388
2469
  });
2389
2470
  };