@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.
- package/README.md +3 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +12 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/model-registry.d.ts +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +23 -16
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-registry.test.js +13 -0
- package/dist/core/model-registry.test.js.map +1 -1
- package/dist/core/oauth/gemini.d.ts.map +1 -1
- package/dist/core/oauth/gemini.js +10 -5
- package/dist/core/oauth/gemini.js.map +1 -1
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +17 -1
- package/dist/interactive.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +22 -14
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +8 -1
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts +23 -4
- package/dist/tools/prompt-hints.d.ts.map +1 -1
- package/dist/tools/prompt-hints.js +35 -10
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +95 -14
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +2 -0
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +12 -3
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +17 -7
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/UserMessage.d.ts +2 -1
- package/dist/ui/components/UserMessage.d.ts.map +1 -1
- package/dist/ui/components/UserMessage.js +6 -2
- package/dist/ui/components/UserMessage.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +10 -2
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +9 -0
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/login.js +1 -1
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/prompt-routing.d.ts +2 -2
- package/dist/ui/prompt-routing.d.ts.map +1 -1
- package/dist/ui/prompt-routing.js +26 -1
- package/dist/ui/prompt-routing.js.map +1 -1
- package/dist/ui/queued-message.test.js +5 -1
- package/dist/ui/queued-message.test.js.map +1 -1
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +32 -0
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/slash-command-images.test.js +33 -3
- package/dist/ui/slash-command-images.test.js.map +1 -1
- package/dist/ui/submit-prompt-command.d.ts.map +1 -1
- package/dist/ui/submit-prompt-command.js +6 -3
- package/dist/ui/submit-prompt-command.js.map +1 -1
- package/dist/ui/terminal-history.js +4 -2
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -1
- package/dist/ui/transcript/TranscriptRenderer.js +12 -10
- package/dist/ui/transcript/TranscriptRenderer.js.map +1 -1
- package/dist/ui/transcript/presentation.d.ts.map +1 -1
- package/dist/ui/transcript/presentation.js +7 -1
- package/dist/ui/transcript/presentation.js.map +1 -1
- package/dist/ui/tui-history-parity.test.js +6 -2
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/dist/ui/utils/assistant-stream-split.d.ts +10 -0
- package/dist/ui/utils/assistant-stream-split.d.ts.map +1 -1
- package/dist/ui/utils/assistant-stream-split.js +19 -0
- package/dist/ui/utils/assistant-stream-split.js.map +1 -1
- package/dist/ui/utils/assistant-stream-split.test.js +21 -1
- package/dist/ui/utils/assistant-stream-split.test.js.map +1 -1
- package/dist/utils/image.d.ts +14 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +63 -1
- package/dist/utils/image.js.map +1 -1
- 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,
|
|
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 {
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|