@prestyj/cli 4.3.239 → 4.4.0
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 +4 -2
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +17 -2
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/shared.d.ts.map +1 -1
- package/dist/cli/shared.js +27 -5
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -4
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +26 -14
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +18 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +62 -8
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.js +2 -2
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +4 -0
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/model-registry.d.ts +21 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +47 -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 +14 -5
- package/dist/core/oauth/gemini.js.map +1 -1
- package/dist/core/oauth/kimi.d.ts +46 -0
- package/dist/core/oauth/kimi.d.ts.map +1 -0
- package/dist/core/oauth/kimi.js +278 -0
- package/dist/core/oauth/kimi.js.map +1 -0
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +22 -2
- package/dist/interactive.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +35 -16
- 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/goals.d.ts +1 -1
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +9 -2
- package/dist/tools/index.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/tools/read.d.ts +6 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +70 -4
- package/dist/tools/read.js.map +1 -1
- package/dist/ui/App.d.ts +3 -0
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +164 -24
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +3 -1
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +13 -4
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/chat-layout-pinning.test.js +1 -1
- package/dist/ui/chat-layout-pinning.test.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +7 -0
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +38 -12
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/AssistantMessage.test.js +1 -1
- package/dist/ui/components/AssistantMessage.test.js.map +1 -1
- package/dist/ui/components/ChatInputStack.d.ts +2 -0
- package/dist/ui/components/ChatInputStack.d.ts.map +1 -1
- package/dist/ui/components/ChatInputStack.js.map +1 -1
- package/dist/ui/components/ChatStatusRow.d.ts +2 -0
- package/dist/ui/components/ChatStatusRow.d.ts.map +1 -1
- package/dist/ui/components/ChatStatusRow.js +40 -1
- package/dist/ui/components/ChatStatusRow.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/MessageResponse.d.ts +7 -1
- package/dist/ui/components/MessageResponse.d.ts.map +1 -1
- package/dist/ui/components/MessageResponse.js +2 -2
- package/dist/ui/components/MessageResponse.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts.map +1 -1
- package/dist/ui/components/ToolExecution.js +72 -15
- package/dist/ui/components/ToolExecution.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 +16 -4
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +46 -2
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/layout-decisions.d.ts +4 -0
- package/dist/ui/layout-decisions.d.ts.map +1 -1
- package/dist/ui/layout-decisions.js.map +1 -1
- package/dist/ui/login.js +2 -2
- 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 +50 -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 +1 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +33 -0
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/slash-command-images.test.js +55 -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 +36 -10
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/terminal-history.test.js +3 -3
- package/dist/ui/terminal-history.test.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 +16 -2
- package/dist/ui/transcript/presentation.js.map +1 -1
- package/dist/ui/tui-history-parity.test.js +7 -3
- 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 +37 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +156 -1
- package/dist/utils/image.js.map +1 -1
- package/dist/utils/plan-steps.d.ts +29 -2
- package/dist/utils/plan-steps.d.ts.map +1 -1
- package/dist/utils/plan-steps.js +88 -16
- package/dist/utils/plan-steps.js.map +1 -1
- package/dist/utils/plan-steps.test.js +144 -1
- package/dist/utils/plan-steps.test.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 "@prestyj/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";
|
|
@@ -23,7 +23,7 @@ import { SessionSummaryDisplay } from "./components/SessionSummary.js";
|
|
|
23
23
|
import { useTheme, useSetTheme } from "./theme/theme.js";
|
|
24
24
|
import { useTerminalTitle } from "./hooks/useTerminalTitle.js";
|
|
25
25
|
import { getGitBranch } from "../utils/git.js";
|
|
26
|
-
import { getModel } from "../core/model-registry.js";
|
|
26
|
+
import { getModel, getVideoByteLimit } from "../core/model-registry.js";
|
|
27
27
|
import { SessionManager } from "../core/session-manager.js";
|
|
28
28
|
import { log } from "../core/logger.js";
|
|
29
29
|
import { getPendingUpdate, startPeriodicUpdateCheck, stopPeriodicUpdateCheck, } from "../core/auto-update.js";
|
|
@@ -36,10 +36,10 @@ 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 { reconcileGoalStatusEntriesWithRuns, removeGoalStatusEntry, syncGoalStatusEntries, } from "./components/GoalStatusBar.js";
|
|
39
|
-
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
40
|
-
import {
|
|
39
|
+
import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, rebasePlanSteps, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
|
|
40
|
+
import { getAllMcpServers } from "../core/mcp/index.js";
|
|
41
41
|
import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
|
|
42
|
-
import { splitAssistantStreamingText } from "./utils/assistant-stream-split.js";
|
|
42
|
+
import { splitAssistantStreamingText, estimateRenderedRows, } from "./utils/assistant-stream-split.js";
|
|
43
43
|
import { getNextPendingTask, markTaskInProgress } from "../core/task-store.js";
|
|
44
44
|
import { buildUserContentWithAttachments } from "./prompt-routing.js";
|
|
45
45
|
import { submitPromptCommand } from "./submit-prompt-command.js";
|
|
@@ -772,6 +772,8 @@ export function App(props) {
|
|
|
772
772
|
tools: currentTools,
|
|
773
773
|
webSearch: props.webSearch,
|
|
774
774
|
maxTokens: props.maxTokens,
|
|
775
|
+
supportsImages: getModel(currentModel)?.supportsImages ?? true,
|
|
776
|
+
supportsVideo: getModel(currentModel)?.supportsVideo ?? false,
|
|
775
777
|
thinking: thinkingLevel,
|
|
776
778
|
apiKey: activeApiKey,
|
|
777
779
|
baseUrl: activeBaseUrl,
|
|
@@ -889,10 +891,32 @@ export function App(props) {
|
|
|
889
891
|
if (planStepsRef.current.length > 0) {
|
|
890
892
|
const completed = findCompletedMarkers(text);
|
|
891
893
|
if (completed.size > 0) {
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
894
|
+
const planPath = approvedPlanPathRef.current;
|
|
895
|
+
// The agent can rewrite/expand the approved plan mid-run, so the
|
|
896
|
+
// snapshot captured at approval goes stale (wrong total, and new
|
|
897
|
+
// [DONE:n] markers match nothing). Re-extract from the live plan
|
|
898
|
+
// file and re-base onto it before applying markers. The file read
|
|
899
|
+
// is async; apply markers inside the same async step to avoid a
|
|
900
|
+
// race with the snapshot. Fall back to the in-memory snapshot if
|
|
901
|
+
// there is no plan path or the read fails.
|
|
902
|
+
const applyMarkers = (base) => {
|
|
903
|
+
const updated = markStepsCompleted(base, completed);
|
|
904
|
+
if (updated !== planStepsRef.current) {
|
|
905
|
+
planStepsRef.current = updated;
|
|
906
|
+
setPlanSteps(updated);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
if (planPath) {
|
|
910
|
+
void import("node:fs/promises")
|
|
911
|
+
.then(({ readFile }) => readFile(planPath, "utf-8"))
|
|
912
|
+
.then((planContent) => {
|
|
913
|
+
const fresh = extractPlanSteps(planContent);
|
|
914
|
+
applyMarkers(rebasePlanSteps(planStepsRef.current, fresh));
|
|
915
|
+
})
|
|
916
|
+
.catch(() => applyMarkers(planStepsRef.current));
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
applyMarkers(planStepsRef.current);
|
|
896
920
|
}
|
|
897
921
|
// Real progress happened — reset the stuck-guard so the next
|
|
898
922
|
// step gets its own fresh nudge budget.
|
|
@@ -1229,6 +1253,18 @@ export function App(props) {
|
|
|
1229
1253
|
log("INFO", "server_tool", `Server tool call: ${name}`, { id });
|
|
1230
1254
|
const startedAt = Date.now();
|
|
1231
1255
|
const animateUntil = startedAt + RUNNING_INDICATOR_ANIMATION_MS;
|
|
1256
|
+
// Feed the pinned LiveToolPanel so provider-side tools (Anthropic's
|
|
1257
|
+
// native web_search) appear in the same rolling window as client
|
|
1258
|
+
// tools. `input` carries the tool args (e.g. { query }) the row reads.
|
|
1259
|
+
setLiveToolFeed((prev) => [
|
|
1260
|
+
...prev,
|
|
1261
|
+
{
|
|
1262
|
+
id,
|
|
1263
|
+
name,
|
|
1264
|
+
args: (input ?? {}),
|
|
1265
|
+
status: "running",
|
|
1266
|
+
},
|
|
1267
|
+
].slice(-(LIVE_TOOL_PANEL_ROWS * 2)));
|
|
1232
1268
|
// Flush completed items (including assistant text) before adding server
|
|
1233
1269
|
// tool UI — same rationale as onToolStart.
|
|
1234
1270
|
setLiveItems((prev) => {
|
|
@@ -1243,6 +1279,11 @@ export function App(props) {
|
|
|
1243
1279
|
if (flushed.length > 0) {
|
|
1244
1280
|
queueFlush(flushed);
|
|
1245
1281
|
}
|
|
1282
|
+
// The pre-tool text was just pinned; the hook resets its streaming
|
|
1283
|
+
// buffer at this same boundary. Reset the progressive-flush offset
|
|
1284
|
+
// so post-tool text is measured from zero (stale flushedChars would
|
|
1285
|
+
// otherwise slice into the fresh, shorter buffer).
|
|
1286
|
+
streamedAssistantFlushRef.current = { flushedChars: 0, text: "" };
|
|
1246
1287
|
return [
|
|
1247
1288
|
...remaining,
|
|
1248
1289
|
{
|
|
@@ -1259,6 +1300,9 @@ export function App(props) {
|
|
|
1259
1300
|
}, [queueFlush]),
|
|
1260
1301
|
onServerToolResult: useCallback((toolUseId, resultType, data) => {
|
|
1261
1302
|
log("INFO", "server_tool", `Server tool result`, { toolUseId, resultType });
|
|
1303
|
+
// Mark the panel entry done. Aborts never reach here (handled in
|
|
1304
|
+
// onAborted), so a result that arrives is always a normal completion.
|
|
1305
|
+
setLiveToolFeed((prev) => prev.map((entry) => entry.id === toolUseId ? { ...entry, status: "done" } : entry));
|
|
1262
1306
|
setLiveItems((prev) => {
|
|
1263
1307
|
let updated;
|
|
1264
1308
|
const startIdx = prev.findIndex((item) => item.kind === "server_tool_start" && item.serverToolCallId === toolUseId);
|
|
@@ -1324,7 +1368,7 @@ export function App(props) {
|
|
|
1324
1368
|
return remaining;
|
|
1325
1369
|
});
|
|
1326
1370
|
}, [queueFlush]),
|
|
1327
|
-
onDone: useCallback((durationMs, toolsUsed) => {
|
|
1371
|
+
onDone: useCallback((durationMs, toolsUsed, runStats) => {
|
|
1328
1372
|
log("INFO", "agent", `Agent done`, {
|
|
1329
1373
|
duration: `${durationMs}ms`,
|
|
1330
1374
|
toolsUsed: toolsUsed.join(",") || "none",
|
|
@@ -1337,7 +1381,13 @@ export function App(props) {
|
|
|
1337
1381
|
// Still flush live transcript rows before the pane remounts; otherwise
|
|
1338
1382
|
// setup output remains in ephemeral liveItems and appears to vanish.
|
|
1339
1383
|
if (doneDecision.showDoneStatus) {
|
|
1340
|
-
setDoneStatus({
|
|
1384
|
+
setDoneStatus({
|
|
1385
|
+
durationMs,
|
|
1386
|
+
toolsUsed,
|
|
1387
|
+
verb: pickDurationVerb(toolsUsed),
|
|
1388
|
+
counts: runStats?.counts,
|
|
1389
|
+
tokens: runStats?.tokens,
|
|
1390
|
+
});
|
|
1341
1391
|
playNotificationSound();
|
|
1342
1392
|
}
|
|
1343
1393
|
// Keep the final assistant response mounted in the live frame after a
|
|
@@ -1465,10 +1515,14 @@ export function App(props) {
|
|
|
1465
1515
|
const imageCount = typeof content === "string"
|
|
1466
1516
|
? undefined
|
|
1467
1517
|
: content.filter((c) => c.type === "image").length || undefined;
|
|
1518
|
+
const videoCount = typeof content === "string"
|
|
1519
|
+
? undefined
|
|
1520
|
+
: content.filter((c) => c.type === "video").length || undefined;
|
|
1468
1521
|
const userItem = {
|
|
1469
1522
|
kind: "user",
|
|
1470
1523
|
text: displayText,
|
|
1471
1524
|
imageCount,
|
|
1525
|
+
videoCount,
|
|
1472
1526
|
id: getId(),
|
|
1473
1527
|
};
|
|
1474
1528
|
setLastUserMessage(displayText);
|
|
@@ -1505,6 +1559,13 @@ export function App(props) {
|
|
|
1505
1559
|
},
|
|
1506
1560
|
];
|
|
1507
1561
|
}, []),
|
|
1562
|
+
onRetry: useCallback(() => {
|
|
1563
|
+
// Roll back any pending progressive flushes from the aborted attempt.
|
|
1564
|
+
// Without this, a stall retry regenerates the preamble and the old
|
|
1565
|
+
// flushed paragraph + the new one both end up in terminal history.
|
|
1566
|
+
pendingHistoryFlushRef.current = pendingHistoryFlushRef.current.filter((item) => item.kind !== "assistant");
|
|
1567
|
+
streamedAssistantFlushRef.current = { flushedChars: 0, text: "" };
|
|
1568
|
+
}, []),
|
|
1508
1569
|
});
|
|
1509
1570
|
const continueGoalRun = useCallback((run) => {
|
|
1510
1571
|
const cwd = cwdRef.current;
|
|
@@ -1891,6 +1952,9 @@ export function App(props) {
|
|
|
1891
1952
|
void agentLoop.run(action.prompt).catch((err) => {
|
|
1892
1953
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1893
1954
|
log("ERROR", "error", errMsg);
|
|
1955
|
+
if (agentLoop.isRunning) {
|
|
1956
|
+
agentLoop.reset();
|
|
1957
|
+
}
|
|
1894
1958
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
1895
1959
|
});
|
|
1896
1960
|
// Intentional one-shot: run once on mount, never re-fire on re-render.
|
|
@@ -2056,31 +2120,35 @@ export function App(props) {
|
|
|
2056
2120
|
}
|
|
2057
2121
|
// ── Build user content (shared by normal + queued paths) ──
|
|
2058
2122
|
const hasImages = inputImages.length > 0;
|
|
2123
|
+
const imageCount = inputImages.filter((img) => img.kind === "image").length;
|
|
2124
|
+
const videoCount = inputImages.filter((img) => img.kind === "video").length;
|
|
2059
2125
|
const modelInfo = getModel(currentModel);
|
|
2060
2126
|
const modelSupportsImages = modelInfo?.supportsImages ?? true;
|
|
2061
|
-
const
|
|
2127
|
+
const modelSupportsVideo = modelInfo?.supportsVideo ?? false;
|
|
2128
|
+
const userContent = buildUserContentWithAttachments(input, inputImages, modelSupportsImages, modelSupportsVideo);
|
|
2062
2129
|
// ── Queue message if agent is already running ──
|
|
2063
2130
|
if (agentLoop.isRunning) {
|
|
2064
2131
|
log("INFO", "queue", `Queued message: ${trimmed.length > 80 ? trimmed.slice(0, 80) + "..." : trimmed}`);
|
|
2065
2132
|
agentLoop.queueMessage(userContent, input);
|
|
2066
2133
|
let displayText = input;
|
|
2067
2134
|
if (hasImages) {
|
|
2068
|
-
const { cleanText } = await
|
|
2135
|
+
const { cleanText } = await extractMediaPaths(input, props.cwd);
|
|
2069
2136
|
displayText = cleanText;
|
|
2070
2137
|
}
|
|
2071
2138
|
const queuedItem = {
|
|
2072
2139
|
kind: "queued",
|
|
2073
2140
|
text: displayText,
|
|
2074
|
-
imageCount:
|
|
2141
|
+
imageCount: imageCount > 0 ? imageCount : undefined,
|
|
2142
|
+
videoCount: videoCount > 0 ? videoCount : undefined,
|
|
2075
2143
|
id: getId(),
|
|
2076
2144
|
};
|
|
2077
2145
|
setLiveItems((prev) => [...prev, queuedItem]);
|
|
2078
2146
|
return;
|
|
2079
2147
|
}
|
|
2080
|
-
// Build display text — strip image paths, show badges instead
|
|
2148
|
+
// Build display text — strip image/video paths, show badges instead
|
|
2081
2149
|
let displayText = input;
|
|
2082
2150
|
if (hasImages) {
|
|
2083
|
-
const { cleanText } = await
|
|
2151
|
+
const { cleanText } = await extractMediaPaths(input, props.cwd);
|
|
2084
2152
|
displayText = cleanText;
|
|
2085
2153
|
}
|
|
2086
2154
|
let imagePreviews;
|
|
@@ -2096,7 +2164,8 @@ export function App(props) {
|
|
|
2096
2164
|
const userItem = {
|
|
2097
2165
|
kind: "user",
|
|
2098
2166
|
text: displayText,
|
|
2099
|
-
imageCount:
|
|
2167
|
+
imageCount: imageCount > 0 ? imageCount : undefined,
|
|
2168
|
+
videoCount: videoCount > 0 ? videoCount : undefined,
|
|
2100
2169
|
imagePreviews,
|
|
2101
2170
|
pasteInfo,
|
|
2102
2171
|
id: getId(),
|
|
@@ -2130,6 +2199,12 @@ export function App(props) {
|
|
|
2130
2199
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2131
2200
|
log("ERROR", "error", msg);
|
|
2132
2201
|
const isAbort = msg.includes("aborted") || msg.includes("abort");
|
|
2202
|
+
// If the agent loop threw but left isRunning in a stale true state
|
|
2203
|
+
// (can happen when the finally block hasn't been processed by React
|
|
2204
|
+
// yet), reset it so the user isn't deadlocked with a non-working UI.
|
|
2205
|
+
if (agentLoop.isRunning) {
|
|
2206
|
+
agentLoop.reset();
|
|
2207
|
+
}
|
|
2133
2208
|
setLiveItems((prev) => [
|
|
2134
2209
|
...prev,
|
|
2135
2210
|
isAbort
|
|
@@ -2220,8 +2295,14 @@ export function App(props) {
|
|
|
2220
2295
|
rebuildPromptWithTools(next);
|
|
2221
2296
|
return next;
|
|
2222
2297
|
});
|
|
2223
|
-
// Reconnect MCP servers
|
|
2224
|
-
|
|
2298
|
+
// Reconnect MCP servers ONLY when the resolved server set actually
|
|
2299
|
+
// changes. GLM is the only provider with a different set (Z.AI
|
|
2300
|
+
// servers), so a switch that doesn't involve GLM on either side
|
|
2301
|
+
// keeps the identical set — tearing down a live stdio child (e.g.
|
|
2302
|
+
// kencode-search) and re-spawning `npx` there only risks a failed
|
|
2303
|
+
// re-spawn that would silently drop the tools.
|
|
2304
|
+
const glmInvolved = newProvider === "glm" || prevProvider === "glm";
|
|
2305
|
+
if (props.mcpManager && glmInvolved) {
|
|
2225
2306
|
void (async () => {
|
|
2226
2307
|
// Disconnect old MCP servers
|
|
2227
2308
|
await props.mcpManager.dispose();
|
|
@@ -2240,7 +2321,11 @@ export function App(props) {
|
|
|
2240
2321
|
apiKey = props.credentialsByProvider?.["glm"]?.accessToken;
|
|
2241
2322
|
}
|
|
2242
2323
|
try {
|
|
2243
|
-
|
|
2324
|
+
// Use getAllMcpServers so user-configured servers (from
|
|
2325
|
+
// ~/.ezcoder/mcp.json and ./.gg/mcp.json) survive the reconnect —
|
|
2326
|
+
// getMCPServers returns provider defaults only.
|
|
2327
|
+
const servers = await getAllMcpServers(newProvider, apiKey, props.cwd);
|
|
2328
|
+
const mcpTools = await props.mcpManager.connectAll(servers);
|
|
2244
2329
|
setCurrentTools((prev) => {
|
|
2245
2330
|
const next = [...prev.filter((t) => !t.name.startsWith("mcp__")), ...mcpTools];
|
|
2246
2331
|
rebuildPromptWithTools(next);
|
|
@@ -2262,7 +2347,26 @@ export function App(props) {
|
|
|
2262
2347
|
}
|
|
2263
2348
|
return newProvider;
|
|
2264
2349
|
});
|
|
2265
|
-
|
|
2350
|
+
// The `read` tool's video capability (its description + native-video
|
|
2351
|
+
// execute path) is baked in at creation from the model's `maxVideoBytes`.
|
|
2352
|
+
// Switching to/from a video-capable model (e.g. text-only MiMo-V2.5-Pro →
|
|
2353
|
+
// omnimodal MiMo-V2.5) must rebuild it, or the tool keeps telling the model
|
|
2354
|
+
// it can't watch video. Rebuild reuses the read tracker, so read-before-edit
|
|
2355
|
+
// history survives. Provider-change rebuilds the prompt above; this covers
|
|
2356
|
+
// same-provider model switches too.
|
|
2357
|
+
setCurrentModel((prevModel) => {
|
|
2358
|
+
if (props.rebuildReadTool &&
|
|
2359
|
+
getVideoByteLimit(prevModel) !== getVideoByteLimit(newModelId)) {
|
|
2360
|
+
const newReadTool = props.rebuildReadTool(newModelId);
|
|
2361
|
+
setCurrentTools((prev) => {
|
|
2362
|
+
const next = prev.map((tool) => (tool.name === "read" ? newReadTool : tool));
|
|
2363
|
+
currentToolsRef.current = next;
|
|
2364
|
+
void replaceSystemPrompt({ tools: next });
|
|
2365
|
+
return next;
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
return newModelId;
|
|
2369
|
+
});
|
|
2266
2370
|
const modelInfo = getModel(newModelId);
|
|
2267
2371
|
const displayName = modelInfo?.name ?? newModelId;
|
|
2268
2372
|
setLiveItems((prev) => [
|
|
@@ -2277,7 +2381,14 @@ export function App(props) {
|
|
|
2277
2381
|
await sm.set("defaultModel", newModelId);
|
|
2278
2382
|
});
|
|
2279
2383
|
}
|
|
2280
|
-
}, [
|
|
2384
|
+
}, [
|
|
2385
|
+
props.settingsFile,
|
|
2386
|
+
props.mcpManager,
|
|
2387
|
+
props.credentialsByProvider,
|
|
2388
|
+
props.authStorage,
|
|
2389
|
+
props.rebuildReadTool,
|
|
2390
|
+
replaceSystemPrompt,
|
|
2391
|
+
]);
|
|
2281
2392
|
const handleThemeSelect = useCallback((name) => {
|
|
2282
2393
|
setOverlay(null);
|
|
2283
2394
|
if (switchTheme) {
|
|
@@ -2497,6 +2608,9 @@ export function App(props) {
|
|
|
2497
2608
|
setDoneStatus(null);
|
|
2498
2609
|
setLiveItems([taskItem]);
|
|
2499
2610
|
void agentLoop.run(fullPrompt).catch((err) => {
|
|
2611
|
+
if (agentLoop.isRunning) {
|
|
2612
|
+
agentLoop.reset();
|
|
2613
|
+
}
|
|
2500
2614
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
2501
2615
|
});
|
|
2502
2616
|
}, [agentLoop, currentModel, currentProvider, props]);
|
|
@@ -2545,7 +2659,23 @@ export function App(props) {
|
|
|
2545
2659
|
// every chunk boundary. Slicing by the prospective flush here keeps the live
|
|
2546
2660
|
// frame height monotonic, so the footer never bounces.
|
|
2547
2661
|
const alreadyFlushedChars = streamedAssistantFlushRef.current.flushedChars;
|
|
2548
|
-
|
|
2662
|
+
// Retry-safety gate: don't commit streamed paragraphs to permanent scrollback
|
|
2663
|
+
// while the text is still small enough to live entirely in the live region.
|
|
2664
|
+
// A silent stall-retry (agent-loop.ts) restarts the LLM call from scratch and
|
|
2665
|
+
// regenerates the opening text — reworded, so the byte-identical dedup can't
|
|
2666
|
+
// catch it. Anything already printed to scrollback can't be un-written, so the
|
|
2667
|
+
// regen appends as a second ⏺ bullet that paraphrases the first. Keeping short
|
|
2668
|
+
// streamed text live (it clears via setStreamingText("") on retry) closes that
|
|
2669
|
+
// hole. We only start flushing once the unflushed text would overflow the live
|
|
2670
|
+
// area — the original anti-jump purpose, which only matters for long responses
|
|
2671
|
+
// that are far less likely to be a stalled preamble. Once flushing has begun
|
|
2672
|
+
// for this turn (alreadyFlushedChars > 0) we keep flushing every boundary so
|
|
2673
|
+
// committed continuation paragraphs stay consistent with the live tail.
|
|
2674
|
+
const unflushedStreamingRows = rawVisibleStreamingText
|
|
2675
|
+
? estimateRenderedRows(rawVisibleStreamingText.slice(alreadyFlushedChars), columns)
|
|
2676
|
+
: 0;
|
|
2677
|
+
const shouldFlushStreamedText = alreadyFlushedChars > 0 || unflushedStreamingRows > measuredLiveAreaRows;
|
|
2678
|
+
const pendingFlushChars = rawVisibleStreamingText && shouldFlushStreamedText
|
|
2549
2679
|
? splitAssistantStreamingText(rawVisibleStreamingText.slice(alreadyFlushedChars)).flushedText
|
|
2550
2680
|
.length
|
|
2551
2681
|
: 0;
|
|
@@ -2556,6 +2686,13 @@ export function App(props) {
|
|
|
2556
2686
|
}
|
|
2557
2687
|
if (rawVisibleStreamingText === streamedAssistantFlushRef.current.text)
|
|
2558
2688
|
return;
|
|
2689
|
+
if (!shouldFlushStreamedText) {
|
|
2690
|
+
streamedAssistantFlushRef.current = {
|
|
2691
|
+
...streamedAssistantFlushRef.current,
|
|
2692
|
+
text: rawVisibleStreamingText,
|
|
2693
|
+
};
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2559
2696
|
const alreadyFlushed = streamedAssistantFlushRef.current.flushedChars;
|
|
2560
2697
|
const unflushedText = rawVisibleStreamingText.slice(alreadyFlushed);
|
|
2561
2698
|
const split = splitAssistantStreamingText(unflushedText);
|
|
@@ -2578,7 +2715,7 @@ export function App(props) {
|
|
|
2578
2715
|
...streamedAssistantFlushRef.current,
|
|
2579
2716
|
text: rawVisibleStreamingText,
|
|
2580
2717
|
};
|
|
2581
|
-
}, [rawVisibleStreamingText, queueFlush]);
|
|
2718
|
+
}, [rawVisibleStreamingText, shouldFlushStreamedText, queueFlush]);
|
|
2582
2719
|
const visibleStreamingText = stripDoneMarkers(rawVisibleStreamingText.slice(alreadyFlushedChars + pendingFlushChars));
|
|
2583
2720
|
const lastLiveItem = liveItems.at(-1);
|
|
2584
2721
|
// For spacing decisions, the previous row is the last item that actually
|
|
@@ -3018,6 +3155,9 @@ export function App(props) {
|
|
|
3018
3155
|
void agentLoop.run(rejectionMsg).catch((err) => {
|
|
3019
3156
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3020
3157
|
log("ERROR", "error", errMsg);
|
|
3158
|
+
if (agentLoop.isRunning) {
|
|
3159
|
+
agentLoop.reset();
|
|
3160
|
+
}
|
|
3021
3161
|
setLiveItems((prev) => [...prev, toErrorItem(err, getId())]);
|
|
3022
3162
|
});
|
|
3023
3163
|
};
|