@serjm/deepseek-code 0.4.2 → 0.4.5
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/CHANGELOG.md +75 -0
- package/README.md +72 -108
- package/README.ru.md +73 -108
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +65 -2
- package/dist/api/index.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +15 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +65 -3
- package/dist/cli/interactive.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +63 -41
- package/dist/commands/index.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +8 -7
- package/dist/config/defaults.js.map +1 -1
- package/dist/core/agent-loop.d.ts +44 -2
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-loop.js +318 -59
- package/dist/core/agent-loop.js.map +1 -1
- package/dist/core/i18n.d.ts +3 -0
- package/dist/core/i18n.d.ts.map +1 -1
- package/dist/core/i18n.js +9 -0
- package/dist/core/i18n.js.map +1 -1
- package/dist/core/metrics.d.ts +3 -1
- package/dist/core/metrics.d.ts.map +1 -1
- package/dist/core/metrics.js +34 -5
- package/dist/core/metrics.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +299 -20
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/chrome.d.ts.map +1 -1
- package/dist/tools/chrome.js +1 -0
- package/dist/tools/chrome.js.map +1 -1
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +40 -3
- package/dist/tools/glob.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +69 -13
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +91 -0
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/types.d.ts +21 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +34 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/ui/app.d.ts.map +1 -1
- package/dist/ui/app.js +234 -162
- package/dist/ui/app.js.map +1 -1
- package/dist/ui/chat-view.d.ts +24 -3
- package/dist/ui/chat-view.d.ts.map +1 -1
- package/dist/ui/chat-view.js +116 -58
- package/dist/ui/chat-view.js.map +1 -1
- package/dist/ui/input-bar.d.ts.map +1 -1
- package/dist/ui/input-bar.js +38 -4
- package/dist/ui/input-bar.js.map +1 -1
- package/dist/ui/setup-wizard.js +1 -1
- package/dist/ui/setup-wizard.js.map +1 -1
- package/dist/ui/status-bar.d.ts +5 -1
- package/dist/ui/status-bar.d.ts.map +1 -1
- package/dist/ui/status-bar.js +10 -4
- package/dist/ui/status-bar.js.map +1 -1
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +47 -0
- package/dist/utils/logger.js.map +1 -1
- package/package.json +3 -2
package/dist/ui/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
3
|
-
import { Box, Text, useInput, useApp
|
|
3
|
+
import { Box, Text, useInput, useApp } from 'ink';
|
|
4
4
|
import { ChatView } from './chat-view.js';
|
|
5
5
|
import { InputBar } from './input-bar.js';
|
|
6
6
|
import { StatusBar } from './status-bar.js';
|
|
@@ -14,11 +14,13 @@ import { subAgentManager } from '../core/subagent.js';
|
|
|
14
14
|
import { skillsManager } from '../core/skills.js';
|
|
15
15
|
import { lspManager } from '../core/lsp.js';
|
|
16
16
|
import { scheduler } from '../core/scheduler.js';
|
|
17
|
+
import { chromeManager } from '../tools/chrome-manager.js';
|
|
17
18
|
import { themeManager } from '../core/themes.js';
|
|
18
19
|
import { i18n } from '../core/i18n.js';
|
|
19
20
|
import { Logo, SetupWizard, useSetupWizard } from './setup-wizard.js';
|
|
20
21
|
import { executeSlashCommand } from '../commands/index.js';
|
|
21
22
|
import { checkLatestVersion } from '../commands/update-checker.js';
|
|
23
|
+
import { logEvent } from '../utils/logger.js';
|
|
22
24
|
/** Empty input hint timeout in ms before showing the guide text */
|
|
23
25
|
const EMPTY_INPUT_HINT_DELAY = 2000;
|
|
24
26
|
function stripExecutionSummary(content) {
|
|
@@ -42,6 +44,33 @@ function historyForModel(messages) {
|
|
|
42
44
|
return [message];
|
|
43
45
|
});
|
|
44
46
|
}
|
|
47
|
+
function isUserCancellationError(error, signal) {
|
|
48
|
+
if (!signal.aborted)
|
|
49
|
+
return false;
|
|
50
|
+
if (error.name === 'StreamTimeoutError')
|
|
51
|
+
return false;
|
|
52
|
+
const msg = error.message || '';
|
|
53
|
+
return error.name === 'AbortError' || /abort|cancel/i.test(msg);
|
|
54
|
+
}
|
|
55
|
+
function formatAgentError(error) {
|
|
56
|
+
const msg = error.message || '';
|
|
57
|
+
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
58
|
+
return i18n.t('apiErrorAuth');
|
|
59
|
+
}
|
|
60
|
+
if (msg.includes('429') || msg.includes('rate limit')) {
|
|
61
|
+
return i18n.t('apiErrorRateLimit');
|
|
62
|
+
}
|
|
63
|
+
if (/5\d{2}|server error|Service Unavailable/i.test(msg)) {
|
|
64
|
+
return i18n.t('apiErrorServer');
|
|
65
|
+
}
|
|
66
|
+
if (/ECONNRESET|ECONNREFUSED|ENOTFOUND/i.test(msg)) {
|
|
67
|
+
return i18n.t('apiErrorNetwork');
|
|
68
|
+
}
|
|
69
|
+
if (error.name === 'StreamTimeoutError' || /ETIMEDOUT|timed out|Stream timeout/i.test(msg)) {
|
|
70
|
+
return i18n.t('apiErrorTimeout');
|
|
71
|
+
}
|
|
72
|
+
return `${i18n.t('error')}: ${msg}`;
|
|
73
|
+
}
|
|
45
74
|
// Setup wizard step components are now in ./setup-wizard.tsx
|
|
46
75
|
// Logo, SetupWizard, useSetupWizard imported from there
|
|
47
76
|
export function App({ config, options }) {
|
|
@@ -49,12 +78,20 @@ export function App({ config, options }) {
|
|
|
49
78
|
const [approvalMode, setApprovalMode] = useState(options.approvalMode ?? (options.turbo ? 'turbo' : config.approvalMode));
|
|
50
79
|
const [messages, setMessages] = useState([]);
|
|
51
80
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
81
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
82
|
+
const isPausedRef = useRef(false);
|
|
83
|
+
// Keep isPausedRef in sync so finally block can read it without stale closure
|
|
84
|
+
useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]);
|
|
52
85
|
const [statusText, setStatusText] = useState(i18n.t('ready'));
|
|
53
86
|
const [localApiKey, setLocalApiKey] = useState(config.apiKey || '');
|
|
54
87
|
const agentLoopRef = useRef(null);
|
|
55
88
|
const abortControllerRef = useRef(null);
|
|
56
89
|
const pendingApprovalResolveRef = useRef(null);
|
|
57
90
|
const liveToolMessageIndexRef = useRef(-1);
|
|
91
|
+
// Tools belonging to the CURRENT (still-live) tool card. Each tool batch gets
|
|
92
|
+
// its own card so completed batches can be committed to the Static scrollback
|
|
93
|
+
// while only the active batch stays in the live region.
|
|
94
|
+
const currentCardToolsRef = useRef([]);
|
|
58
95
|
const prevToolCallsRef = useRef([]);
|
|
59
96
|
const [toolCalls, setToolCalls] = useState([]);
|
|
60
97
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
@@ -67,20 +104,69 @@ export function App({ config, options }) {
|
|
|
67
104
|
const initializedRef = useRef(false);
|
|
68
105
|
const [emptyInputHint, setEmptyInputHint] = useState(false);
|
|
69
106
|
const emptyInputTimerRef = useRef(null);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
107
|
+
// Chat history is rendered via Ink <Static> (see ChatView): finalized messages
|
|
108
|
+
// are printed once to the terminal scrollback (no flicker, native mouse scroll).
|
|
109
|
+
// `chatEpoch` remounts Static to reset the scrollback on /clear.
|
|
110
|
+
const [chatEpoch, setChatEpoch] = useState(0);
|
|
74
111
|
const [contextPercent, setContextPercent] = useState(0);
|
|
112
|
+
const [compactProgress, setCompactProgress] = useState(0);
|
|
75
113
|
const [totalTokens, setTotalTokens] = useState(0);
|
|
76
114
|
const [estimatedCost, setEstimatedCost] = useState(0);
|
|
77
115
|
const [pendingImage, setPendingImage] = useState(null);
|
|
116
|
+
const [followUpCount, setFollowUpCount] = useState(0);
|
|
78
117
|
const [themePicker, setThemePicker] = useState(null);
|
|
79
118
|
const [modelPicker, setModelPicker] = useState(null);
|
|
80
119
|
const [langPicker, setLangPicker] = useState(null);
|
|
81
120
|
const [serviceNotice, setServiceNotice] = useState(null);
|
|
82
121
|
const serviceNoticeTimerRef = useRef(null);
|
|
83
122
|
const budgetRef = useRef(undefined);
|
|
123
|
+
// Double Ctrl+C to exit when idle/paused (raw mode owns Ctrl+C on all platforms).
|
|
124
|
+
const ctrlCExitArmedRef = useRef(false);
|
|
125
|
+
const ctrlCExitTimerRef = useRef(null);
|
|
126
|
+
// ── Stream chunk batching ──────────────────────────────────────────────
|
|
127
|
+
const pendingChunksRef = useRef('');
|
|
128
|
+
const flushTimerRef = useRef(null);
|
|
129
|
+
// Flush buffered assistant text into the message list. Starting a new text
|
|
130
|
+
// block also finalizes the previous live tool card (→ compact) so it commits
|
|
131
|
+
// to the Static scrollback as a completed item.
|
|
132
|
+
const flushPending = useCallback(() => {
|
|
133
|
+
if (flushTimerRef.current) {
|
|
134
|
+
clearTimeout(flushTimerRef.current);
|
|
135
|
+
flushTimerRef.current = null;
|
|
136
|
+
}
|
|
137
|
+
const buffered = pendingChunksRef.current;
|
|
138
|
+
if (!buffered)
|
|
139
|
+
return;
|
|
140
|
+
pendingChunksRef.current = '';
|
|
141
|
+
setMessages(prev => {
|
|
142
|
+
const last = prev[prev.length - 1];
|
|
143
|
+
if (last?.role === 'assistant') {
|
|
144
|
+
const updated = [...prev];
|
|
145
|
+
updated[updated.length - 1] = { ...last, content: last.content + buffered };
|
|
146
|
+
return updated;
|
|
147
|
+
}
|
|
148
|
+
const updated = [...prev];
|
|
149
|
+
const cardIdx = liveToolMessageIndexRef.current;
|
|
150
|
+
if (cardIdx >= 0 && cardIdx < updated.length && updated[cardIdx]?.role === 'tool' && currentCardToolsRef.current.length > 0) {
|
|
151
|
+
updated[cardIdx] = {
|
|
152
|
+
role: 'tool',
|
|
153
|
+
content: JSON.stringify({ type: 'tool_activity_card', toolCalls: currentCardToolsRef.current, status: 'compact' }),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
liveToolMessageIndexRef.current = -1;
|
|
157
|
+
currentCardToolsRef.current = [];
|
|
158
|
+
updated.push({ role: 'assistant', content: buffered });
|
|
159
|
+
return updated;
|
|
160
|
+
});
|
|
161
|
+
}, []);
|
|
162
|
+
const scheduleFlush = useCallback(() => {
|
|
163
|
+
if (flushTimerRef.current)
|
|
164
|
+
return;
|
|
165
|
+
flushTimerRef.current = setTimeout(() => {
|
|
166
|
+
flushTimerRef.current = null;
|
|
167
|
+
flushPending();
|
|
168
|
+
}, 75);
|
|
169
|
+
}, [flushPending]);
|
|
84
170
|
const addServiceNotice = useCallback((text) => {
|
|
85
171
|
setServiceNotice(text);
|
|
86
172
|
if (serviceNoticeTimerRef.current)
|
|
@@ -139,6 +225,10 @@ export function App({ config, options }) {
|
|
|
139
225
|
subAgentManager.loadFromDir(),
|
|
140
226
|
scheduler.load(),
|
|
141
227
|
]);
|
|
228
|
+
// Применяем сохранённый режим Chrome (headed/headless) из конфига.
|
|
229
|
+
// Только обновляет headlessMode в chrome-manager, НЕ запускает браузер.
|
|
230
|
+
// Браузер стартует только при реальном вызове chrome tool, browser-test или /chrome.
|
|
231
|
+
chromeManager.setHeadlessMode(config.chromeHeadless ?? false);
|
|
142
232
|
// Chrome не инициализируется при старте.
|
|
143
233
|
// Он запускается только когда:
|
|
144
234
|
// - агент вызывает chrome tool
|
|
@@ -162,60 +252,13 @@ export function App({ config, options }) {
|
|
|
162
252
|
setStatusText(i18n.t('ready'));
|
|
163
253
|
})();
|
|
164
254
|
}, []);
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
const proc = process;
|
|
169
|
-
if (isProcessing) {
|
|
170
|
-
proc.__agentSoftCancel = () => {
|
|
171
|
-
abortControllerRef.current?.abort();
|
|
172
|
-
if (pendingApprovalResolveRef.current) {
|
|
173
|
-
pendingApprovalResolveRef.current(false);
|
|
174
|
-
pendingApprovalResolveRef.current = null;
|
|
175
|
-
}
|
|
176
|
-
setPendingApproval(null);
|
|
177
|
-
setStatusText(i18n.t('cancelled'));
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
proc.__agentSoftCancel = undefined;
|
|
182
|
-
}
|
|
183
|
-
return () => { proc.__agentSoftCancel = undefined; };
|
|
184
|
-
}, [isProcessing]);
|
|
185
|
-
const { stdin } = useStdin();
|
|
186
|
-
// Compensate scroll offset when new visible messages arrive while paused
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
if (setupStepRef.current !== 'done')
|
|
189
|
-
return;
|
|
190
|
-
const visible = messages.filter(m => m.role !== 'tool').length;
|
|
191
|
-
if (scrollMode === 'paused' && visible > visibleMessageCountRef.current) {
|
|
192
|
-
const diff = visible - visibleMessageCountRef.current;
|
|
193
|
-
setChatScrollOffset(prev => prev + diff);
|
|
194
|
-
setNewMessagesWhilePaused(true);
|
|
195
|
-
}
|
|
196
|
-
visibleMessageCountRef.current = visible;
|
|
197
|
-
}, [messages.length, scrollMode]);
|
|
255
|
+
// __agentSoftCancel is now set synchronously in handleSubmit (before agentLoop.run())
|
|
256
|
+
// to eliminate the async useEffect race window on Windows where SIGINT fires
|
|
257
|
+
// before the effect commits. Cleanup is handled in finally block.
|
|
198
258
|
// Sync prevToolCallsRef with toolCalls state
|
|
199
259
|
useEffect(() => {
|
|
200
260
|
prevToolCallsRef.current = toolCalls;
|
|
201
261
|
}, [toolCalls]);
|
|
202
|
-
// Detect End key from raw stdin (Ink does not expose it in useInput)
|
|
203
|
-
useEffect(() => {
|
|
204
|
-
if (!stdin)
|
|
205
|
-
return;
|
|
206
|
-
const handler = (data) => {
|
|
207
|
-
if (setupStepRef.current !== 'done')
|
|
208
|
-
return;
|
|
209
|
-
const seq = data.toString();
|
|
210
|
-
if (seq === '\x1b[F' || seq === '\x1b[4~' || seq === '\x1b[8~' || seq === '\x1bOF') {
|
|
211
|
-
setChatScrollOffset(0);
|
|
212
|
-
setScrollMode('follow');
|
|
213
|
-
setNewMessagesWhilePaused(false);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
stdin.on('data', handler);
|
|
217
|
-
return () => { stdin.off('data', handler); };
|
|
218
|
-
}, [stdin]);
|
|
219
262
|
// Slash commands — delegated to commands/index.ts
|
|
220
263
|
const handleSlashCommand = useCallback(async (input) => {
|
|
221
264
|
const ctx = {
|
|
@@ -256,8 +299,18 @@ export function App({ config, options }) {
|
|
|
256
299
|
emptyInputTimerRef.current = setTimeout(() => setEmptyInputHint(false), EMPTY_INPUT_HINT_DELAY);
|
|
257
300
|
return;
|
|
258
301
|
}
|
|
259
|
-
if (isProcessing)
|
|
302
|
+
if (isProcessing) {
|
|
303
|
+
// Live follow-up: inject into current active AgentLoop
|
|
304
|
+
if (agentLoopRef.current) {
|
|
305
|
+
agentLoopRef.current.addUserFollowUp(input.trim());
|
|
306
|
+
setFollowUpCount(c => c + 1);
|
|
307
|
+
addServiceNotice('[queue] Follow-up #' + (followUpCount + 1) + ' added. Agent will continue after current step.');
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
addServiceNotice('Agent is not active. Send it as a new message.');
|
|
311
|
+
}
|
|
260
312
|
return;
|
|
313
|
+
}
|
|
261
314
|
// Handle setup wizard steps
|
|
262
315
|
if (setupStep === 'apikey') {
|
|
263
316
|
await handleApiKeySubmit(input);
|
|
@@ -273,6 +326,7 @@ export function App({ config, options }) {
|
|
|
273
326
|
}]);
|
|
274
327
|
return;
|
|
275
328
|
}
|
|
329
|
+
setIsPaused(false);
|
|
276
330
|
if (input.startsWith('/')) {
|
|
277
331
|
try {
|
|
278
332
|
const handled = await handleSlashCommand(input);
|
|
@@ -299,6 +353,23 @@ export function App({ config, options }) {
|
|
|
299
353
|
}
|
|
300
354
|
const abortController = new AbortController();
|
|
301
355
|
abortControllerRef.current = abortController;
|
|
356
|
+
// SYNCHRONOUS: register soft-cancel hook before agentLoop.run().
|
|
357
|
+
// The old useEffect-based registration had a race window on Windows where
|
|
358
|
+
// SIGINT could fire between isProcessing=true and the effect commit.
|
|
359
|
+
const proc = process;
|
|
360
|
+
proc.__agentSoftCancel = () => {
|
|
361
|
+
logEvent('__agentSoftCancel invoked');
|
|
362
|
+
abortController.abort();
|
|
363
|
+
setIsPaused(true);
|
|
364
|
+
if (pendingApprovalResolveRef.current) {
|
|
365
|
+
pendingApprovalResolveRef.current(false);
|
|
366
|
+
pendingApprovalResolveRef.current = null;
|
|
367
|
+
}
|
|
368
|
+
setPendingApproval(null);
|
|
369
|
+
setStatusText(i18n.t('paused'));
|
|
370
|
+
};
|
|
371
|
+
proc.__agentAbortController = abortController;
|
|
372
|
+
logEvent('agent run: softCancel registered');
|
|
302
373
|
let userContent = input;
|
|
303
374
|
if (pendingImage) {
|
|
304
375
|
userContent = [
|
|
@@ -320,9 +391,7 @@ export function App({ config, options }) {
|
|
|
320
391
|
setStatusText(i18n.t('working'));
|
|
321
392
|
setToolCalls([]);
|
|
322
393
|
liveToolMessageIndexRef.current = -1;
|
|
323
|
-
|
|
324
|
-
setScrollMode('follow');
|
|
325
|
-
setNewMessagesWhilePaused(false);
|
|
394
|
+
currentCardToolsRef.current = [];
|
|
326
395
|
try {
|
|
327
396
|
await hooksManager.execute('UserPromptSubmit', {
|
|
328
397
|
event: 'UserPromptSubmit',
|
|
@@ -335,21 +404,22 @@ export function App({ config, options }) {
|
|
|
335
404
|
signal: abortController.signal,
|
|
336
405
|
budget: budgetRef.current,
|
|
337
406
|
onToolCall: (tc) => {
|
|
407
|
+
// Commit any buffered assistant text first so it lands above the card.
|
|
408
|
+
flushPending();
|
|
338
409
|
const updatedCalls = [...prevToolCallsRef.current, tc];
|
|
339
410
|
prevToolCallsRef.current = updatedCalls;
|
|
340
411
|
setToolCalls(updatedCalls);
|
|
412
|
+
currentCardToolsRef.current = [...currentCardToolsRef.current, tc];
|
|
341
413
|
setStatusText(`[tool] ${tc.name}...`);
|
|
342
|
-
// Add/update live tool activity card
|
|
414
|
+
// Add/update the live tool activity card (one card per tool batch).
|
|
343
415
|
setMessages(prev => {
|
|
344
416
|
const idx = liveToolMessageIndexRef.current;
|
|
345
|
-
const card = { type: 'tool_activity_card', toolCalls:
|
|
417
|
+
const card = { type: 'tool_activity_card', toolCalls: currentCardToolsRef.current, status: 'live' };
|
|
346
418
|
if (idx >= 0 && idx < prev.length && prev[idx]?.role === 'tool') {
|
|
347
|
-
// Update existing card
|
|
348
419
|
const updated = [...prev];
|
|
349
420
|
updated[idx] = { role: 'tool', content: JSON.stringify(card) };
|
|
350
421
|
return updated;
|
|
351
422
|
}
|
|
352
|
-
// Add new card
|
|
353
423
|
liveToolMessageIndexRef.current = prev.length;
|
|
354
424
|
return [...prev, { role: 'tool', content: JSON.stringify(card) }];
|
|
355
425
|
});
|
|
@@ -359,16 +429,8 @@ export function App({ config, options }) {
|
|
|
359
429
|
},
|
|
360
430
|
onReasoningChunk: () => { },
|
|
361
431
|
onStreamChunk: (chunk) => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (last?.role === 'assistant') {
|
|
365
|
-
// If last content is empty, replace with chunk; otherwise append
|
|
366
|
-
const updated = [...prev];
|
|
367
|
-
updated[updated.length - 1] = { ...last, content: last.content + chunk };
|
|
368
|
-
return updated;
|
|
369
|
-
}
|
|
370
|
-
return [...prev, { role: 'assistant', content: chunk }];
|
|
371
|
-
});
|
|
432
|
+
pendingChunksRef.current += chunk;
|
|
433
|
+
scheduleFlush();
|
|
372
434
|
},
|
|
373
435
|
// onResponse intentionally removed — onStreamChunk handles all text
|
|
374
436
|
// Avoids duplicate "assistant" message push that caused text doubling
|
|
@@ -377,6 +439,18 @@ export function App({ config, options }) {
|
|
|
377
439
|
// Handled by handleSubmit catch block — adding here would create duplicate
|
|
378
440
|
// assistant messages, breaking the conversation structure for the next request
|
|
379
441
|
},
|
|
442
|
+
onCompactStart: () => {
|
|
443
|
+
setCompactProgress(5);
|
|
444
|
+
setStatusText('Compacting context...');
|
|
445
|
+
},
|
|
446
|
+
onCompactProgress: (event) => {
|
|
447
|
+
setCompactProgress(event.progress);
|
|
448
|
+
setStatusText(`Compacting context ${event.progress}%...`);
|
|
449
|
+
},
|
|
450
|
+
onCompactEnd: (event) => {
|
|
451
|
+
setCompactProgress(0);
|
|
452
|
+
setStatusText(event.phase === 'done' ? 'Context compacted' : 'Context compact failed');
|
|
453
|
+
},
|
|
380
454
|
onApprovalRequest: async (toolName, args) => {
|
|
381
455
|
if (approvalModeRef.current === 'turbo')
|
|
382
456
|
return true;
|
|
@@ -394,6 +468,10 @@ export function App({ config, options }) {
|
|
|
394
468
|
},
|
|
395
469
|
});
|
|
396
470
|
const finalResponse = await agentLoopRef.current.run(input, historyForModel(messages));
|
|
471
|
+
// Abort during run: skip session save, let finally handle UI reset
|
|
472
|
+
if (abortController.signal.aborted) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
397
475
|
const toolHistory = agentLoopRef.current.getToolCallHistory();
|
|
398
476
|
const bundleFile = await writeExecutionBundle({
|
|
399
477
|
sessionId: sessionIdRef.current,
|
|
@@ -435,15 +513,16 @@ export function App({ config, options }) {
|
|
|
435
513
|
// Single batch: all final UI updates at once (no await between setState calls)
|
|
436
514
|
setIsProcessing(false);
|
|
437
515
|
setStatusText(i18n.t('ready'));
|
|
438
|
-
//
|
|
439
|
-
if (liveToolMessageIndexRef.current >= 0 &&
|
|
516
|
+
// Finalize the last live tool batch (if the turn ended on tools) to compact.
|
|
517
|
+
if (liveToolMessageIndexRef.current >= 0 && currentCardToolsRef.current.length > 0) {
|
|
518
|
+
const cardTools = currentCardToolsRef.current;
|
|
440
519
|
setMessages(prev => {
|
|
441
520
|
const idx = liveToolMessageIndexRef.current;
|
|
442
521
|
if (idx >= 0 && idx < prev.length && prev[idx]?.role === 'tool') {
|
|
443
522
|
const updated = [...prev];
|
|
444
523
|
updated[idx] = {
|
|
445
524
|
role: 'tool',
|
|
446
|
-
content: JSON.stringify({ type: 'tool_activity_card', toolCalls:
|
|
525
|
+
content: JSON.stringify({ type: 'tool_activity_card', toolCalls: cardTools, status: 'compact' }),
|
|
447
526
|
};
|
|
448
527
|
return updated;
|
|
449
528
|
}
|
|
@@ -451,33 +530,16 @@ export function App({ config, options }) {
|
|
|
451
530
|
});
|
|
452
531
|
}
|
|
453
532
|
liveToolMessageIndexRef.current = -1;
|
|
533
|
+
currentCardToolsRef.current = [];
|
|
454
534
|
}
|
|
455
535
|
catch (err) {
|
|
456
536
|
const error = err;
|
|
457
|
-
//
|
|
458
|
-
if (error
|
|
537
|
+
// User-triggered cancellation is expected and should not become an error message.
|
|
538
|
+
if (isUserCancellationError(error, abortController.signal)) {
|
|
459
539
|
return;
|
|
460
540
|
}
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
464
|
-
friendlyMsg = i18n.t('apiErrorAuth');
|
|
465
|
-
}
|
|
466
|
-
else if (msg.includes('429') || msg.includes('rate limit')) {
|
|
467
|
-
friendlyMsg = i18n.t('apiErrorRateLimit');
|
|
468
|
-
}
|
|
469
|
-
else if (/5\d{2}|server error|Service Unavailable/i.test(msg)) {
|
|
470
|
-
friendlyMsg = i18n.t('apiErrorServer');
|
|
471
|
-
}
|
|
472
|
-
else if (/ECONNRESET|ECONNREFUSED|ENOTFOUND/i.test(msg)) {
|
|
473
|
-
friendlyMsg = i18n.t('apiErrorNetwork');
|
|
474
|
-
}
|
|
475
|
-
else if (/ETIMEDOUT|timed out/i.test(msg)) {
|
|
476
|
-
friendlyMsg = i18n.t('apiErrorTimeout');
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
friendlyMsg = `${i18n.t('error')}: ${msg}`;
|
|
480
|
-
}
|
|
541
|
+
const friendlyMsg = formatAgentError(error);
|
|
542
|
+
const toolHistory = agentLoopRef.current?.getToolCallHistory() ?? [];
|
|
481
543
|
setMessages(prev => [...prev, {
|
|
482
544
|
role: 'assistant',
|
|
483
545
|
content: friendlyMsg,
|
|
@@ -487,16 +549,31 @@ export function App({ config, options }) {
|
|
|
487
549
|
prompt: input,
|
|
488
550
|
error: friendlyMsg,
|
|
489
551
|
approvalMode,
|
|
552
|
+
toolCalls: toolHistory.map(toolCall => ({
|
|
553
|
+
name: toolCall.name,
|
|
554
|
+
status: toolCall.status,
|
|
555
|
+
durationMs: toolCall.durationMs,
|
|
556
|
+
error: toolCall.error,
|
|
557
|
+
})),
|
|
490
558
|
});
|
|
491
559
|
const bundleFile = await writeExecutionBundle({
|
|
492
560
|
sessionId: sessionIdRef.current,
|
|
493
561
|
prompt: input,
|
|
494
562
|
error: friendlyMsg,
|
|
495
563
|
approvalMode,
|
|
564
|
+
toolCalls: toolHistory.map(toolCall => ({
|
|
565
|
+
id: toolCall.id,
|
|
566
|
+
name: toolCall.name,
|
|
567
|
+
status: toolCall.status,
|
|
568
|
+
durationMs: toolCall.durationMs,
|
|
569
|
+
error: toolCall.error,
|
|
570
|
+
result: toolCall.result,
|
|
571
|
+
})),
|
|
496
572
|
});
|
|
497
573
|
await saveSession({
|
|
498
574
|
id: sessionIdRef.current,
|
|
499
575
|
messageCount: messages.length + 2,
|
|
576
|
+
toolCallCount: toolHistory.length,
|
|
500
577
|
approvalMode,
|
|
501
578
|
lastPrompt: input,
|
|
502
579
|
lastError: friendlyMsg,
|
|
@@ -506,9 +583,14 @@ export function App({ config, options }) {
|
|
|
506
583
|
});
|
|
507
584
|
}
|
|
508
585
|
finally {
|
|
586
|
+
logEvent('agent run: finally (clearing softCancel)');
|
|
509
587
|
// Safety net: ensure UI is always reset regardless of exit path
|
|
588
|
+
flushPending();
|
|
510
589
|
setIsProcessing(false);
|
|
511
|
-
|
|
590
|
+
// Don't overwrite status if user paused the agent
|
|
591
|
+
if (!isPausedRef.current) {
|
|
592
|
+
setStatusText(i18n.t('ready'));
|
|
593
|
+
}
|
|
512
594
|
// Clear any pending approval (covers error/abort exit paths)
|
|
513
595
|
if (pendingApprovalResolveRef.current) {
|
|
514
596
|
pendingApprovalResolveRef.current(false);
|
|
@@ -518,29 +600,58 @@ export function App({ config, options }) {
|
|
|
518
600
|
if (abortControllerRef.current === abortController) {
|
|
519
601
|
abortControllerRef.current = null;
|
|
520
602
|
}
|
|
603
|
+
// Cleanup synchronous SIGINT hooks
|
|
604
|
+
const proc2 = process;
|
|
605
|
+
if (proc2.__agentSoftCancel)
|
|
606
|
+
proc2.__agentSoftCancel = undefined;
|
|
607
|
+
if (proc2.__agentAbortController === abortController)
|
|
608
|
+
proc2.__agentAbortController = undefined;
|
|
521
609
|
}
|
|
522
610
|
}, [messages, isProcessing, setupStep, handleApiKeySubmit, handleSlashCommand, approvalMode, config, localApiKey]);
|
|
523
611
|
useInput((_input, key) => {
|
|
524
612
|
const step = setupStepRef.current;
|
|
525
|
-
// Ctrl+C
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
// -
|
|
613
|
+
// Ctrl+C is handled here because Ink raw mode delivers it as input (not a
|
|
614
|
+
// SIGINT signal) on all platforms when exitOnCtrlC is false. The SIGINT
|
|
615
|
+
// handler in interactive.ts is only a fallback for non-TTY runs.
|
|
616
|
+
// - While the agent runs: first Ctrl+C aborts/pauses, never exits.
|
|
617
|
+
// - When idle or already paused: double Ctrl+C within 3s exits.
|
|
529
618
|
if (key.ctrl && _input === 'c') {
|
|
530
|
-
|
|
619
|
+
logEvent('useInput Ctrl+C', { isProcessing, isPaused, hasAbort: !!abortControllerRef.current, step });
|
|
620
|
+
if (isProcessing && abortControllerRef.current && !isPaused) {
|
|
621
|
+
logEvent('useInput Ctrl+C -> abort+pause');
|
|
531
622
|
abortControllerRef.current.abort();
|
|
532
|
-
|
|
623
|
+
setIsPaused(true);
|
|
624
|
+
setStatusText(i18n.t('paused'));
|
|
533
625
|
if (pendingApprovalResolveRef.current) {
|
|
534
626
|
pendingApprovalResolveRef.current(false);
|
|
535
627
|
pendingApprovalResolveRef.current = null;
|
|
536
628
|
}
|
|
537
629
|
setPendingApproval(null);
|
|
630
|
+
// Reset exit-arming: the interrupt is the user's action this press.
|
|
631
|
+
ctrlCExitArmedRef.current = false;
|
|
632
|
+
if (ctrlCExitTimerRef.current) {
|
|
633
|
+
clearTimeout(ctrlCExitTimerRef.current);
|
|
634
|
+
ctrlCExitTimerRef.current = null;
|
|
635
|
+
}
|
|
538
636
|
return;
|
|
539
637
|
}
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
638
|
+
// Idle or paused — second press within the window exits.
|
|
639
|
+
if (ctrlCExitArmedRef.current) {
|
|
640
|
+
logEvent('useInput Ctrl+C -> doublePress -> exit()');
|
|
641
|
+
if (ctrlCExitTimerRef.current)
|
|
642
|
+
clearTimeout(ctrlCExitTimerRef.current);
|
|
643
|
+
exit();
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
logEvent('useInput Ctrl+C -> firstPress armed');
|
|
647
|
+
ctrlCExitArmedRef.current = true;
|
|
648
|
+
addServiceNotice(i18n.t('ctrlCHint'));
|
|
649
|
+
if (ctrlCExitTimerRef.current)
|
|
650
|
+
clearTimeout(ctrlCExitTimerRef.current);
|
|
651
|
+
ctrlCExitTimerRef.current = setTimeout(() => {
|
|
652
|
+
ctrlCExitArmedRef.current = false;
|
|
653
|
+
ctrlCExitTimerRef.current = null;
|
|
654
|
+
}, 3000);
|
|
544
655
|
return;
|
|
545
656
|
}
|
|
546
657
|
// When not in setup mode, let InputBar handle all keyboard input
|
|
@@ -627,26 +738,9 @@ export function App({ config, options }) {
|
|
|
627
738
|
});
|
|
628
739
|
return;
|
|
629
740
|
}
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
|
|
633
|
-
const visibleCount = messages.filter(m => m.role !== 'tool').length;
|
|
634
|
-
const next = Math.min(chatScrollOffset + 10, Math.max(0, visibleCount - 1));
|
|
635
|
-
if (next > 0)
|
|
636
|
-
setScrollMode('paused');
|
|
637
|
-
setChatScrollOffset(next);
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
// PageDown: scroll down by ~half a screen
|
|
641
|
-
if (key.pageDown) {
|
|
642
|
-
const next = Math.max(0, chatScrollOffset - 10);
|
|
643
|
-
setChatScrollOffset(next);
|
|
644
|
-
if (next === 0) {
|
|
645
|
-
setScrollMode('follow');
|
|
646
|
-
setNewMessagesWhilePaused(false);
|
|
647
|
-
}
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
741
|
+
// Chat history scrolling is handled natively by the terminal (messages are
|
|
742
|
+
// committed to the scrollback via Ink <Static>), so PageUp/PageDown are no
|
|
743
|
+
// longer intercepted here.
|
|
650
744
|
// Theme picker: interactive selection
|
|
651
745
|
if (themePicker) {
|
|
652
746
|
if (key.escape) {
|
|
@@ -733,23 +827,10 @@ export function App({ config, options }) {
|
|
|
733
827
|
}
|
|
734
828
|
return;
|
|
735
829
|
}
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const next = Math.min(chatScrollOffset + 1, Math.max(0, visibleCount - 1));
|
|
741
|
-
if (next > 0)
|
|
742
|
-
setScrollMode('paused');
|
|
743
|
-
setChatScrollOffset(next);
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
if (key.downArrow && isProcessing) {
|
|
747
|
-
const next = Math.max(0, chatScrollOffset - 1);
|
|
748
|
-
setChatScrollOffset(next);
|
|
749
|
-
if (next === 0) {
|
|
750
|
-
setScrollMode('follow');
|
|
751
|
-
setNewMessagesWhilePaused(false);
|
|
752
|
-
}
|
|
830
|
+
// Esc when paused: dismiss pause, return to ready
|
|
831
|
+
if (key.escape && isPaused && !pendingApproval && !pendingClear && !themePicker && !modelPicker && !langPicker) {
|
|
832
|
+
setIsPaused(false);
|
|
833
|
+
setStatusText(i18n.t('ready'));
|
|
753
834
|
return;
|
|
754
835
|
}
|
|
755
836
|
return;
|
|
@@ -861,9 +942,7 @@ export function App({ config, options }) {
|
|
|
861
942
|
setToolCalls([]);
|
|
862
943
|
setPendingApproval(null);
|
|
863
944
|
setPendingImage(null);
|
|
864
|
-
|
|
865
|
-
setNewMessagesWhilePaused(false);
|
|
866
|
-
setChatScrollOffset(0);
|
|
945
|
+
setChatEpoch(e => e + 1); // remount Static so the scrollback resets
|
|
867
946
|
liveToolMessageIndexRef.current = -1;
|
|
868
947
|
setServiceNotice(null);
|
|
869
948
|
if (serviceNoticeTimerRef.current) {
|
|
@@ -882,7 +961,7 @@ export function App({ config, options }) {
|
|
|
882
961
|
}, [messages.length, toolCalls.length, executeClear]);
|
|
883
962
|
const handleExit = useCallback(() => { exit(); }, [exit]);
|
|
884
963
|
const colors = themeManager.getColors();
|
|
885
|
-
return (_jsxs(Box, { flexDirection: 'column',
|
|
964
|
+
return (_jsxs(Box, { flexDirection: 'column', children: [setupStep !== 'done'
|
|
886
965
|
? _jsx(SetupWizard, { state: {
|
|
887
966
|
step: setupStep,
|
|
888
967
|
apiKeyError,
|
|
@@ -893,21 +972,14 @@ export function App({ config, options }) {
|
|
|
893
972
|
langOptions,
|
|
894
973
|
modeOptions,
|
|
895
974
|
} })
|
|
896
|
-
: (_jsxs(Box, { flexDirection: 'column',
|
|
975
|
+
: (_jsxs(Box, { flexDirection: 'column', children: [_jsx(ChatView, { messages: messages, isProcessing: isProcessing, epoch: chatEpoch, header: _jsx(Logo, {}) }), serviceNotice && (_jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsx(Text, { color: colors.primary, children: serviceNotice }) })), themePicker && (_jsxs(Box, { flexDirection: 'column', marginLeft: 2, marginBottom: 1, borderStyle: 'round', borderColor: colors.primary, children: [_jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { bold: true, color: colors.primary, children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0435\u043C\u0443" }) }), _jsx(Box, { marginLeft: 1, marginTop: 1, flexDirection: 'column', children: themePicker.themes.map((t, i) => (_jsx(Box, { children: _jsxs(Text, { color: i === themePicker.selectedIndex ? colors.primary : colors.textMuted, children: [i === themePicker.selectedIndex ? '▸ ' : ' ', t.name, t.name === themeManager.theme.name ? ' (текущая)' : ''] }) }, t.name))) }), _jsx(Box, { marginLeft: 1, marginBottom: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C Esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0430" }) })] })), modelPicker && (_jsxs(Box, { flexDirection: 'column', marginLeft: 2, marginBottom: 1, borderStyle: 'round', borderColor: colors.primary, children: [_jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { bold: true, color: colors.primary, children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043C\u043E\u0434\u0435\u043B\u044C" }) }), _jsx(Box, { marginLeft: 1, marginTop: 1, flexDirection: 'column', children: DEEPSEEK_MODELS.map((m, i) => (_jsxs(Box, { flexDirection: 'column', children: [_jsxs(Text, { color: i === modelPicker.selectedIndex ? colors.primary : colors.textMuted, children: [i === modelPicker.selectedIndex ? '▸ ' : ' ', _jsx(Text, { bold: i === modelPicker.selectedIndex, children: m.label }), m.id === config.model ? _jsx(Text, { dimColor: true, children: " (\u0442\u0435\u043A\u0443\u0449\u0430\u044F)" }) : null] }), _jsxs(Text, { dimColor: true, children: [' ', m.description] })] }, m.id))) }), _jsx(Box, { marginLeft: 1, marginBottom: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C Esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0430" }) })] })), langPicker && (_jsxs(Box, { flexDirection: 'column', marginLeft: 2, marginBottom: 1, borderStyle: 'round', borderColor: colors.primary, children: [_jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { bold: true, color: colors.primary, children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u044F\u0437\u044B\u043A / Select language / \u9009\u62E9\u8BED\u8A00" }) }), _jsx(Box, { marginLeft: 1, marginTop: 1, flexDirection: 'column', children: i18n.listLocales().map((loc, i) => (_jsx(Box, { children: _jsxs(Text, { color: i === langPicker.selectedIndex ? colors.primary : colors.textMuted, children: [i === langPicker.selectedIndex ? '▸ ' : ' ', loc.name, loc.code === i18n.getLocale() ? _jsx(Text, { dimColor: true, children: " (\u0442\u0435\u043A\u0443\u0449\u0438\u0439)" }) : null] }) }, loc.code))) }), _jsx(Box, { marginLeft: 1, marginBottom: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C Esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0430" }) })] })), pendingApproval && (_jsxs(Box, { flexDirection: 'column', marginLeft: 2, marginBottom: 1, borderStyle: 'round', borderColor: colors.warning, children: [_jsx(Box, { children: _jsx(Text, { bold: true, color: colors.warning, children: "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0432\u044B\u0437\u043E\u0432 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430?" }) }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { bold: true, color: colors.text, children: pendingApproval.toolName }) }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: colors.textMuted, children: JSON.stringify(pendingApproval.args, null, 2).slice(0, 200) }) }), _jsxs(Box, { flexDirection: 'column', marginTop: 1, children: [[
|
|
897
976
|
'[ok] Подтвердить',
|
|
898
977
|
'[no] Отклонить',
|
|
899
978
|
`[mute] Не спрашивать для "${pendingApproval.toolName}"`,
|
|
900
979
|
'[turbo] Выполнять всё без вопросов',
|
|
901
|
-
].map((label, i) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: approvalCursor === i ? colors.primary : colors.text, children: [approvalCursor === i ? '> ' : ' ', label] }) }, i))), _jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u0432\u044B\u0431\u0440\u0430\u0442\u044C Esc \u2014 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C" }) })] })] })), pendingClear && (_jsxs(Box, { flexDirection: 'column', marginLeft: 2, marginBottom: 1, borderStyle: 'round', borderColor: colors.warning, children: [_jsx(Box, { children: _jsx(Text, { bold: true, color: colors.warning, children: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0447\u0430\u0442\u0430?" }) }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: colors.textMuted, children: [messages.length, " \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u043E. \u041E\u0442\u043C\u0435\u043D\u0443 \u043D\u0435\u043B\u044C\u0437\u044F."] }) }), _jsxs(Box, { flexDirection: 'column', marginTop: 1, children: [['[ok] Да, очистить', '[no] Отмена'].map((label, i) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: clearCursor === i ? colors.primary : colors.text, children: [clearCursor === i ? '> ' : ' ', label] }) }, i))), _jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u0432\u044B\u0431\u0440\u0430\u0442\u044C Esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0430" }) })] })] }))] })), _jsx(InputBar, { onSubmit: handleSubmit, disabled:
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
role: 'assistant',
|
|
906
|
-
content: `[warn] Вставка изображения требует модель с поддержкой vision.\nТекущая модель: ${model || 'неизвестно'}\nИспользуйте модель с "vl" или "vision" в названии.`,
|
|
907
|
-
}]);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
setPendingImage({ base64, mimeType });
|
|
911
|
-
} }), _jsx(StatusBar, { mode: approvalMode, status: statusText, messageCount: messages.length, isProcessing: isProcessing, contextPercent: contextPercent, totalTokens: totalTokens, estimatedCost: estimatedCost, model: config.model })] }));
|
|
980
|
+
].map((label, i) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: approvalCursor === i ? colors.primary : colors.text, children: [approvalCursor === i ? '> ' : ' ', label] }) }, i))), _jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u0432\u044B\u0431\u0440\u0430\u0442\u044C Esc \u2014 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C" }) })] })] })), pendingClear && (_jsxs(Box, { flexDirection: 'column', marginLeft: 2, marginBottom: 1, borderStyle: 'round', borderColor: colors.warning, children: [_jsx(Box, { children: _jsx(Text, { bold: true, color: colors.warning, children: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0447\u0430\u0442\u0430?" }) }), _jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: colors.textMuted, children: [messages.length, " \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u043E. \u041E\u0442\u043C\u0435\u043D\u0443 \u043D\u0435\u043B\u044C\u0437\u044F."] }) }), _jsxs(Box, { flexDirection: 'column', marginTop: 1, children: [['[ok] Да, очистить', '[no] Отмена'].map((label, i) => (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: clearCursor === i ? colors.primary : colors.text, children: [clearCursor === i ? '> ' : ' ', label] }) }, i))), _jsx(Box, { marginLeft: 1, marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: "\u2191\u2193 \u2014 \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F Enter \u2014 \u0432\u044B\u0431\u0440\u0430\u0442\u044C Esc \u2014 \u043E\u0442\u043C\u0435\u043D\u0430" }) })] })] }))] })), _jsx(InputBar, { onSubmit: handleSubmit, disabled: false,
|
|
981
|
+
// disabled is no longer tied to isProcessing — input is allowed during processing.
|
|
982
|
+
// blockInput still handles approval/clear dialogs.
|
|
983
|
+
onClear: handleClear, onExit: handleExit, isMasked: setupStep === 'apikey', isSetupMode: setupStep !== 'done', blockInput: setupStep === 'done' && (pendingApproval !== null || pendingClear), emptyHint: emptyInputHint, onImagePaste: (base64, mimeType) => setPendingImage({ base64, mimeType }) }), _jsx(StatusBar, { mode: approvalMode, status: statusText, messageCount: messages.length, isProcessing: isProcessing, isPaused: isPaused, contextPercent: contextPercent, compactProgress: compactProgress, totalTokens: totalTokens, estimatedCost: estimatedCost, model: config.model, followUpCount: followUpCount })] }));
|
|
912
984
|
}
|
|
913
985
|
//# sourceMappingURL=app.js.map
|