@kenkaiiii/ggcoder 4.3.236 → 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/loop-breaker.d.ts +46 -0
- package/dist/core/loop-breaker.d.ts.map +1 -0
- package/dist/core/loop-breaker.js +79 -0
- package/dist/core/loop-breaker.js.map +1 -0
- package/dist/core/loop-breaker.test.d.ts +2 -0
- package/dist/core/loop-breaker.test.d.ts.map +1 -0
- package/dist/core/loop-breaker.test.js +110 -0
- package/dist/core/loop-breaker.test.js.map +1 -0
- 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/core/regrounding.d.ts +23 -0
- package/dist/core/regrounding.d.ts.map +1 -0
- package/dist/core/regrounding.js +21 -0
- package/dist/core/regrounding.js.map +1 -0
- package/dist/core/regrounding.test.d.ts +2 -0
- package/dist/core/regrounding.test.d.ts.map +1 -0
- package/dist/core/regrounding.test.js +38 -0
- package/dist/core/regrounding.test.js.map +1 -0
- 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 +124 -16
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +22 -3
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +23 -3
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/components/ChatInputStack.js +1 -1
- package/dist/ui/components/ChatInputStack.js.map +1 -1
- package/dist/ui/components/IdealHookMessage.d.ts +3 -1
- package/dist/ui/components/IdealHookMessage.d.ts.map +1 -1
- package/dist/ui/components/IdealHookMessage.js +4 -2
- package/dist/ui/components/IdealHookMessage.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 +17 -2
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +117 -9
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useChatLayoutMeasurements.d.ts.map +1 -1
- package/dist/ui/hooks/useChatLayoutMeasurements.js +6 -7
- package/dist/ui/hooks/useChatLayoutMeasurements.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 +38 -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 +13 -9
- 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 +13 -11
- 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,14 +36,16 @@ 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";
|
|
45
45
|
import { handleUiSlashCommand } from "./submit-slash-commands.js";
|
|
46
46
|
import { buildIdealReviewMessage, evaluateIdealReview } from "../core/ideal-review.js";
|
|
47
|
+
import { buildLoopBreakMessage, evaluateLoopBreak } from "../core/loop-breaker.js";
|
|
48
|
+
import { buildRegroundingMessage } from "../core/regrounding.js";
|
|
47
49
|
import { getNextThinkingLevel, isThinkingLevelSupported } from "./thinking-level.js";
|
|
48
50
|
import { getDoneFlushDecision, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
|
|
49
51
|
import { isTranscriptSpacingItem } from "./transcript/spacing.js";
|
|
@@ -56,7 +58,7 @@ import { pickDurationVerb } from "./duration-summary.js";
|
|
|
56
58
|
import { toErrorItem } from "./error-item.js";
|
|
57
59
|
import { addLinesChanged, buildSessionSummary, createSessionStats, recordServerToolCall, recordToolEnd, recordTurnEnd, } from "./session-summary.js";
|
|
58
60
|
import { compactHistory, getNextGeneratedItemId, isActiveItem, isSameAssistantText, normalizeAssistantText, partitionCompleted, pinStreamingTextBeforeToolBoundary, removeItemsWithIds, uniqueItemsById, } from "./item-helpers.js";
|
|
59
|
-
import { IDEAL_HOOK_NOTICE_TEXT, lastVisibleTranscriptItem } from "./app-items.js";
|
|
61
|
+
import { IDEAL_HOOK_NOTICE_TEXT, LOOP_BREAK_NOTICE_TEXT, REGROUNDING_NOTICE_TEXT, lastVisibleTranscriptItem, } from "./app-items.js";
|
|
60
62
|
export { buildUserContentWithAttachments, routePromptCommandInput } from "./prompt-routing.js";
|
|
61
63
|
export { getNextThinkingLevel } from "./thinking-level.js";
|
|
62
64
|
export { getChatControlsLayoutDecision, getDoneFlushDecision, getScrollStabilizationDecision, getStaticHistoryKey, hasParagraphBreakLiveUserMessage, isTallLiveUserMessage, shouldHideHistoryForOverlayView, shouldHideStaticItemsForOverlayView, shouldStabilizeOverlayPaneRerender, shouldTopSpaceAfterPrintedAgentBoundary, shouldTopSpaceAssistantAfterToolBoundary, shouldTopSpaceStreamingAssistant, } from "./layout-decisions.js";
|
|
@@ -561,6 +563,8 @@ export function App(props) {
|
|
|
561
563
|
tools: currentTools,
|
|
562
564
|
webSearch: props.webSearch,
|
|
563
565
|
maxTokens: props.maxTokens,
|
|
566
|
+
supportsImages: getModel(currentModel)?.supportsImages ?? true,
|
|
567
|
+
supportsVideo: getModel(currentModel)?.supportsVideo ?? false,
|
|
564
568
|
thinking: thinkingLevel,
|
|
565
569
|
apiKey: activeApiKey,
|
|
566
570
|
baseUrl: activeBaseUrl,
|
|
@@ -580,10 +584,35 @@ export function App(props) {
|
|
|
580
584
|
});
|
|
581
585
|
setLiveItems((prev) => [
|
|
582
586
|
...prev,
|
|
583
|
-
{ kind: "ideal_hook", text: IDEAL_HOOK_NOTICE_TEXT, id: getId() },
|
|
587
|
+
{ kind: "ideal_hook", text: IDEAL_HOOK_NOTICE_TEXT, tone: "review", id: getId() },
|
|
584
588
|
]);
|
|
585
589
|
return buildIdealReviewMessage(decision.reasons);
|
|
586
590
|
},
|
|
591
|
+
getLoopBreakMessage: (stats) => {
|
|
592
|
+
if (!idealReviewEnabledRef.current)
|
|
593
|
+
return null;
|
|
594
|
+
const decision = evaluateLoopBreak(stats);
|
|
595
|
+
if (!decision.shouldBreak)
|
|
596
|
+
return null;
|
|
597
|
+
log("INFO", "loop-break", "Injecting loop-break nudge", {
|
|
598
|
+
reasons: decision.reasons.join(", "),
|
|
599
|
+
});
|
|
600
|
+
setLiveItems((prev) => [
|
|
601
|
+
...prev,
|
|
602
|
+
{ kind: "ideal_hook", text: LOOP_BREAK_NOTICE_TEXT, tone: "warning", id: getId() },
|
|
603
|
+
]);
|
|
604
|
+
return buildLoopBreakMessage(decision.reasons);
|
|
605
|
+
},
|
|
606
|
+
getRegroundingMessage: (originalRequest) => {
|
|
607
|
+
if (!idealReviewEnabledRef.current)
|
|
608
|
+
return null;
|
|
609
|
+
log("INFO", "reground", "Injecting re-grounding after compaction", {});
|
|
610
|
+
setLiveItems((prev) => [
|
|
611
|
+
...prev,
|
|
612
|
+
{ kind: "ideal_hook", text: REGROUNDING_NOTICE_TEXT, tone: "info", id: getId() },
|
|
613
|
+
]);
|
|
614
|
+
return buildRegroundingMessage(originalRequest);
|
|
615
|
+
},
|
|
587
616
|
}, {
|
|
588
617
|
onComplete: useCallback(() => {
|
|
589
618
|
persistNewMessages();
|
|
@@ -993,6 +1022,18 @@ export function App(props) {
|
|
|
993
1022
|
log("INFO", "server_tool", `Server tool call: ${name}`, { id });
|
|
994
1023
|
const startedAt = Date.now();
|
|
995
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)));
|
|
996
1037
|
// Flush completed items (including assistant text) before adding server
|
|
997
1038
|
// tool UI — same rationale as onToolStart.
|
|
998
1039
|
setLiveItems((prev) => {
|
|
@@ -1023,6 +1064,9 @@ export function App(props) {
|
|
|
1023
1064
|
}, [queueFlush]),
|
|
1024
1065
|
onServerToolResult: useCallback((toolUseId, resultType, data) => {
|
|
1025
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));
|
|
1026
1070
|
setLiveItems((prev) => {
|
|
1027
1071
|
let updated;
|
|
1028
1072
|
const startIdx = prev.findIndex((item) => item.kind === "server_tool_start" && item.serverToolCallId === toolUseId);
|
|
@@ -1228,10 +1272,14 @@ export function App(props) {
|
|
|
1228
1272
|
const imageCount = typeof content === "string"
|
|
1229
1273
|
? undefined
|
|
1230
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;
|
|
1231
1278
|
const userItem = {
|
|
1232
1279
|
kind: "user",
|
|
1233
1280
|
text: displayText,
|
|
1234
1281
|
imageCount,
|
|
1282
|
+
videoCount,
|
|
1235
1283
|
id: getId(),
|
|
1236
1284
|
};
|
|
1237
1285
|
setLastUserMessage(displayText);
|
|
@@ -1268,6 +1316,13 @@ export function App(props) {
|
|
|
1268
1316
|
},
|
|
1269
1317
|
];
|
|
1270
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
|
+
}, []),
|
|
1271
1326
|
});
|
|
1272
1327
|
// First-time-per-project auto-run of /setup. Bound after `agentLoop` is in
|
|
1273
1328
|
// scope so the ref closure can dispatch to it. Called from the initial
|
|
@@ -1383,6 +1438,9 @@ export function App(props) {
|
|
|
1383
1438
|
void agentLoop.run(action.prompt).catch((err) => {
|
|
1384
1439
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1385
1440
|
log("ERROR", "error", errMsg);
|
|
1441
|
+
if (agentLoop.isRunning) {
|
|
1442
|
+
agentLoop.reset();
|
|
1443
|
+
}
|
|
1386
1444
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
1387
1445
|
});
|
|
1388
1446
|
// Intentional one-shot: run once on mount, never re-fire on re-render.
|
|
@@ -1537,31 +1595,35 @@ export function App(props) {
|
|
|
1537
1595
|
}
|
|
1538
1596
|
// ── Build user content (shared by normal + queued paths) ──
|
|
1539
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;
|
|
1540
1600
|
const modelInfo = getModel(currentModel);
|
|
1541
1601
|
const modelSupportsImages = modelInfo?.supportsImages ?? true;
|
|
1542
|
-
const
|
|
1602
|
+
const modelSupportsVideo = modelInfo?.supportsVideo ?? false;
|
|
1603
|
+
const userContent = buildUserContentWithAttachments(input, inputImages, modelSupportsImages, modelSupportsVideo);
|
|
1543
1604
|
// ── Queue message if agent is already running ──
|
|
1544
1605
|
if (agentLoop.isRunning) {
|
|
1545
1606
|
log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
|
|
1546
1607
|
agentLoop.queueMessage(userContent, input);
|
|
1547
1608
|
let displayText = input;
|
|
1548
1609
|
if (hasImages) {
|
|
1549
|
-
const { cleanText } = await
|
|
1610
|
+
const { cleanText } = await extractMediaPaths(input, props.cwd);
|
|
1550
1611
|
displayText = cleanText;
|
|
1551
1612
|
}
|
|
1552
1613
|
const queuedItem = {
|
|
1553
1614
|
kind: "queued",
|
|
1554
1615
|
text: displayText,
|
|
1555
|
-
imageCount:
|
|
1616
|
+
imageCount: imageCount > 0 ? imageCount : undefined,
|
|
1617
|
+
videoCount: videoCount > 0 ? videoCount : undefined,
|
|
1556
1618
|
id: getId(),
|
|
1557
1619
|
};
|
|
1558
1620
|
setLiveItems((prev) => [...prev, queuedItem]);
|
|
1559
1621
|
return;
|
|
1560
1622
|
}
|
|
1561
|
-
// Build display text — strip image paths, show badges instead
|
|
1623
|
+
// Build display text — strip image/video paths, show badges instead
|
|
1562
1624
|
let displayText = input;
|
|
1563
1625
|
if (hasImages) {
|
|
1564
|
-
const { cleanText } = await
|
|
1626
|
+
const { cleanText } = await extractMediaPaths(input, props.cwd);
|
|
1565
1627
|
displayText = cleanText;
|
|
1566
1628
|
}
|
|
1567
1629
|
let imagePreviews;
|
|
@@ -1577,7 +1639,8 @@ export function App(props) {
|
|
|
1577
1639
|
const userItem = {
|
|
1578
1640
|
kind: "user",
|
|
1579
1641
|
text: displayText,
|
|
1580
|
-
imageCount:
|
|
1642
|
+
imageCount: imageCount > 0 ? imageCount : undefined,
|
|
1643
|
+
videoCount: videoCount > 0 ? videoCount : undefined,
|
|
1581
1644
|
imagePreviews,
|
|
1582
1645
|
pasteInfo,
|
|
1583
1646
|
id: getId(),
|
|
@@ -1611,6 +1674,12 @@ export function App(props) {
|
|
|
1611
1674
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1612
1675
|
log("ERROR", "error", msg);
|
|
1613
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
|
+
}
|
|
1614
1683
|
setLiveItems((prev) => [
|
|
1615
1684
|
...prev,
|
|
1616
1685
|
isAbort
|
|
@@ -1701,8 +1770,14 @@ export function App(props) {
|
|
|
1701
1770
|
rebuildPromptWithTools(next);
|
|
1702
1771
|
return next;
|
|
1703
1772
|
});
|
|
1704
|
-
// Reconnect MCP servers
|
|
1705
|
-
|
|
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) {
|
|
1706
1781
|
void (async () => {
|
|
1707
1782
|
// Disconnect old MCP servers
|
|
1708
1783
|
await props.mcpManager.dispose();
|
|
@@ -1721,7 +1796,11 @@ export function App(props) {
|
|
|
1721
1796
|
apiKey = props.credentialsByProvider?.["glm"]?.accessToken;
|
|
1722
1797
|
}
|
|
1723
1798
|
try {
|
|
1724
|
-
|
|
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);
|
|
1725
1804
|
setCurrentTools((prev) => {
|
|
1726
1805
|
const next = [...prev.filter((t) => !t.name.startsWith("mcp__")), ...mcpTools];
|
|
1727
1806
|
rebuildPromptWithTools(next);
|
|
@@ -1972,6 +2051,9 @@ export function App(props) {
|
|
|
1972
2051
|
setDoneStatus(null);
|
|
1973
2052
|
setLiveItems([taskItem]);
|
|
1974
2053
|
void agentLoop.run(fullPrompt).catch((err) => {
|
|
2054
|
+
if (agentLoop.isRunning) {
|
|
2055
|
+
agentLoop.reset();
|
|
2056
|
+
}
|
|
1975
2057
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
1976
2058
|
});
|
|
1977
2059
|
}, [agentLoop, currentModel, currentProvider, props]);
|
|
@@ -2020,7 +2102,23 @@ export function App(props) {
|
|
|
2020
2102
|
// every chunk boundary. Slicing by the prospective flush here keeps the live
|
|
2021
2103
|
// frame height monotonic, so the footer never bounces.
|
|
2022
2104
|
const alreadyFlushedChars = streamedAssistantFlushRef.current.flushedChars;
|
|
2023
|
-
|
|
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
|
|
2024
2122
|
? splitAssistantStreamingText(rawVisibleStreamingText.slice(alreadyFlushedChars)).flushedText
|
|
2025
2123
|
.length
|
|
2026
2124
|
: 0;
|
|
@@ -2031,6 +2129,13 @@ export function App(props) {
|
|
|
2031
2129
|
}
|
|
2032
2130
|
if (rawVisibleStreamingText === streamedAssistantFlushRef.current.text)
|
|
2033
2131
|
return;
|
|
2132
|
+
if (!shouldFlushStreamedText) {
|
|
2133
|
+
streamedAssistantFlushRef.current = {
|
|
2134
|
+
...streamedAssistantFlushRef.current,
|
|
2135
|
+
text: rawVisibleStreamingText,
|
|
2136
|
+
};
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2034
2139
|
const alreadyFlushed = streamedAssistantFlushRef.current.flushedChars;
|
|
2035
2140
|
const unflushedText = rawVisibleStreamingText.slice(alreadyFlushed);
|
|
2036
2141
|
const split = splitAssistantStreamingText(unflushedText);
|
|
@@ -2053,7 +2158,7 @@ export function App(props) {
|
|
|
2053
2158
|
...streamedAssistantFlushRef.current,
|
|
2054
2159
|
text: rawVisibleStreamingText,
|
|
2055
2160
|
};
|
|
2056
|
-
}, [rawVisibleStreamingText, queueFlush]);
|
|
2161
|
+
}, [rawVisibleStreamingText, shouldFlushStreamedText, queueFlush]);
|
|
2057
2162
|
const visibleStreamingText = stripDoneMarkers(rawVisibleStreamingText.slice(alreadyFlushedChars + pendingFlushChars));
|
|
2058
2163
|
const lastLiveItem = liveItems.at(-1);
|
|
2059
2164
|
// For spacing decisions, the previous row is the last item that actually
|
|
@@ -2357,6 +2462,9 @@ export function App(props) {
|
|
|
2357
2462
|
void agentLoop.run(rejectionMsg).catch((err) => {
|
|
2358
2463
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2359
2464
|
log("ERROR", "error", errMsg);
|
|
2465
|
+
if (agentLoop.isRunning) {
|
|
2466
|
+
agentLoop.reset();
|
|
2467
|
+
}
|
|
2360
2468
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
2361
2469
|
});
|
|
2362
2470
|
};
|