@serjm/deepseek-code 0.4.3 → 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 +61 -0
- package/README.md +72 -109
- package/README.ru.md +73 -109
- 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 +26 -21
- package/dist/commands/index.js.map +1 -1
- package/dist/config/defaults.js +7 -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 +317 -102
- 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/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 +229 -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';
|
|
@@ -20,6 +20,7 @@ import { i18n } from '../core/i18n.js';
|
|
|
20
20
|
import { Logo, SetupWizard, useSetupWizard } from './setup-wizard.js';
|
|
21
21
|
import { executeSlashCommand } from '../commands/index.js';
|
|
22
22
|
import { checkLatestVersion } from '../commands/update-checker.js';
|
|
23
|
+
import { logEvent } from '../utils/logger.js';
|
|
23
24
|
/** Empty input hint timeout in ms before showing the guide text */
|
|
24
25
|
const EMPTY_INPUT_HINT_DELAY = 2000;
|
|
25
26
|
function stripExecutionSummary(content) {
|
|
@@ -43,6 +44,33 @@ function historyForModel(messages) {
|
|
|
43
44
|
return [message];
|
|
44
45
|
});
|
|
45
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
|
+
}
|
|
46
74
|
// Setup wizard step components are now in ./setup-wizard.tsx
|
|
47
75
|
// Logo, SetupWizard, useSetupWizard imported from there
|
|
48
76
|
export function App({ config, options }) {
|
|
@@ -50,12 +78,20 @@ export function App({ config, options }) {
|
|
|
50
78
|
const [approvalMode, setApprovalMode] = useState(options.approvalMode ?? (options.turbo ? 'turbo' : config.approvalMode));
|
|
51
79
|
const [messages, setMessages] = useState([]);
|
|
52
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]);
|
|
53
85
|
const [statusText, setStatusText] = useState(i18n.t('ready'));
|
|
54
86
|
const [localApiKey, setLocalApiKey] = useState(config.apiKey || '');
|
|
55
87
|
const agentLoopRef = useRef(null);
|
|
56
88
|
const abortControllerRef = useRef(null);
|
|
57
89
|
const pendingApprovalResolveRef = useRef(null);
|
|
58
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([]);
|
|
59
95
|
const prevToolCallsRef = useRef([]);
|
|
60
96
|
const [toolCalls, setToolCalls] = useState([]);
|
|
61
97
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
@@ -68,20 +104,69 @@ export function App({ config, options }) {
|
|
|
68
104
|
const initializedRef = useRef(false);
|
|
69
105
|
const [emptyInputHint, setEmptyInputHint] = useState(false);
|
|
70
106
|
const emptyInputTimerRef = useRef(null);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
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);
|
|
75
111
|
const [contextPercent, setContextPercent] = useState(0);
|
|
112
|
+
const [compactProgress, setCompactProgress] = useState(0);
|
|
76
113
|
const [totalTokens, setTotalTokens] = useState(0);
|
|
77
114
|
const [estimatedCost, setEstimatedCost] = useState(0);
|
|
78
115
|
const [pendingImage, setPendingImage] = useState(null);
|
|
116
|
+
const [followUpCount, setFollowUpCount] = useState(0);
|
|
79
117
|
const [themePicker, setThemePicker] = useState(null);
|
|
80
118
|
const [modelPicker, setModelPicker] = useState(null);
|
|
81
119
|
const [langPicker, setLangPicker] = useState(null);
|
|
82
120
|
const [serviceNotice, setServiceNotice] = useState(null);
|
|
83
121
|
const serviceNoticeTimerRef = useRef(null);
|
|
84
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]);
|
|
85
170
|
const addServiceNotice = useCallback((text) => {
|
|
86
171
|
setServiceNotice(text);
|
|
87
172
|
if (serviceNoticeTimerRef.current)
|
|
@@ -167,60 +252,13 @@ export function App({ config, options }) {
|
|
|
167
252
|
setStatusText(i18n.t('ready'));
|
|
168
253
|
})();
|
|
169
254
|
}, []);
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
const proc = process;
|
|
174
|
-
if (isProcessing) {
|
|
175
|
-
proc.__agentSoftCancel = () => {
|
|
176
|
-
abortControllerRef.current?.abort();
|
|
177
|
-
if (pendingApprovalResolveRef.current) {
|
|
178
|
-
pendingApprovalResolveRef.current(false);
|
|
179
|
-
pendingApprovalResolveRef.current = null;
|
|
180
|
-
}
|
|
181
|
-
setPendingApproval(null);
|
|
182
|
-
setStatusText(i18n.t('cancelled'));
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
proc.__agentSoftCancel = undefined;
|
|
187
|
-
}
|
|
188
|
-
return () => { proc.__agentSoftCancel = undefined; };
|
|
189
|
-
}, [isProcessing]);
|
|
190
|
-
const { stdin } = useStdin();
|
|
191
|
-
// Compensate scroll offset when new visible messages arrive while paused
|
|
192
|
-
useEffect(() => {
|
|
193
|
-
if (setupStepRef.current !== 'done')
|
|
194
|
-
return;
|
|
195
|
-
const visible = messages.filter(m => m.role !== 'tool').length;
|
|
196
|
-
if (scrollMode === 'paused' && visible > visibleMessageCountRef.current) {
|
|
197
|
-
const diff = visible - visibleMessageCountRef.current;
|
|
198
|
-
setChatScrollOffset(prev => prev + diff);
|
|
199
|
-
setNewMessagesWhilePaused(true);
|
|
200
|
-
}
|
|
201
|
-
visibleMessageCountRef.current = visible;
|
|
202
|
-
}, [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.
|
|
203
258
|
// Sync prevToolCallsRef with toolCalls state
|
|
204
259
|
useEffect(() => {
|
|
205
260
|
prevToolCallsRef.current = toolCalls;
|
|
206
261
|
}, [toolCalls]);
|
|
207
|
-
// Detect End key from raw stdin (Ink does not expose it in useInput)
|
|
208
|
-
useEffect(() => {
|
|
209
|
-
if (!stdin)
|
|
210
|
-
return;
|
|
211
|
-
const handler = (data) => {
|
|
212
|
-
if (setupStepRef.current !== 'done')
|
|
213
|
-
return;
|
|
214
|
-
const seq = data.toString();
|
|
215
|
-
if (seq === '\x1b[F' || seq === '\x1b[4~' || seq === '\x1b[8~' || seq === '\x1bOF') {
|
|
216
|
-
setChatScrollOffset(0);
|
|
217
|
-
setScrollMode('follow');
|
|
218
|
-
setNewMessagesWhilePaused(false);
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
stdin.on('data', handler);
|
|
222
|
-
return () => { stdin.off('data', handler); };
|
|
223
|
-
}, [stdin]);
|
|
224
262
|
// Slash commands — delegated to commands/index.ts
|
|
225
263
|
const handleSlashCommand = useCallback(async (input) => {
|
|
226
264
|
const ctx = {
|
|
@@ -261,8 +299,18 @@ export function App({ config, options }) {
|
|
|
261
299
|
emptyInputTimerRef.current = setTimeout(() => setEmptyInputHint(false), EMPTY_INPUT_HINT_DELAY);
|
|
262
300
|
return;
|
|
263
301
|
}
|
|
264
|
-
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
|
+
}
|
|
265
312
|
return;
|
|
313
|
+
}
|
|
266
314
|
// Handle setup wizard steps
|
|
267
315
|
if (setupStep === 'apikey') {
|
|
268
316
|
await handleApiKeySubmit(input);
|
|
@@ -278,6 +326,7 @@ export function App({ config, options }) {
|
|
|
278
326
|
}]);
|
|
279
327
|
return;
|
|
280
328
|
}
|
|
329
|
+
setIsPaused(false);
|
|
281
330
|
if (input.startsWith('/')) {
|
|
282
331
|
try {
|
|
283
332
|
const handled = await handleSlashCommand(input);
|
|
@@ -304,6 +353,23 @@ export function App({ config, options }) {
|
|
|
304
353
|
}
|
|
305
354
|
const abortController = new AbortController();
|
|
306
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');
|
|
307
373
|
let userContent = input;
|
|
308
374
|
if (pendingImage) {
|
|
309
375
|
userContent = [
|
|
@@ -325,9 +391,7 @@ export function App({ config, options }) {
|
|
|
325
391
|
setStatusText(i18n.t('working'));
|
|
326
392
|
setToolCalls([]);
|
|
327
393
|
liveToolMessageIndexRef.current = -1;
|
|
328
|
-
|
|
329
|
-
setScrollMode('follow');
|
|
330
|
-
setNewMessagesWhilePaused(false);
|
|
394
|
+
currentCardToolsRef.current = [];
|
|
331
395
|
try {
|
|
332
396
|
await hooksManager.execute('UserPromptSubmit', {
|
|
333
397
|
event: 'UserPromptSubmit',
|
|
@@ -340,21 +404,22 @@ export function App({ config, options }) {
|
|
|
340
404
|
signal: abortController.signal,
|
|
341
405
|
budget: budgetRef.current,
|
|
342
406
|
onToolCall: (tc) => {
|
|
407
|
+
// Commit any buffered assistant text first so it lands above the card.
|
|
408
|
+
flushPending();
|
|
343
409
|
const updatedCalls = [...prevToolCallsRef.current, tc];
|
|
344
410
|
prevToolCallsRef.current = updatedCalls;
|
|
345
411
|
setToolCalls(updatedCalls);
|
|
412
|
+
currentCardToolsRef.current = [...currentCardToolsRef.current, tc];
|
|
346
413
|
setStatusText(`[tool] ${tc.name}...`);
|
|
347
|
-
// Add/update live tool activity card
|
|
414
|
+
// Add/update the live tool activity card (one card per tool batch).
|
|
348
415
|
setMessages(prev => {
|
|
349
416
|
const idx = liveToolMessageIndexRef.current;
|
|
350
|
-
const card = { type: 'tool_activity_card', toolCalls:
|
|
417
|
+
const card = { type: 'tool_activity_card', toolCalls: currentCardToolsRef.current, status: 'live' };
|
|
351
418
|
if (idx >= 0 && idx < prev.length && prev[idx]?.role === 'tool') {
|
|
352
|
-
// Update existing card
|
|
353
419
|
const updated = [...prev];
|
|
354
420
|
updated[idx] = { role: 'tool', content: JSON.stringify(card) };
|
|
355
421
|
return updated;
|
|
356
422
|
}
|
|
357
|
-
// Add new card
|
|
358
423
|
liveToolMessageIndexRef.current = prev.length;
|
|
359
424
|
return [...prev, { role: 'tool', content: JSON.stringify(card) }];
|
|
360
425
|
});
|
|
@@ -364,16 +429,8 @@ export function App({ config, options }) {
|
|
|
364
429
|
},
|
|
365
430
|
onReasoningChunk: () => { },
|
|
366
431
|
onStreamChunk: (chunk) => {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (last?.role === 'assistant') {
|
|
370
|
-
// If last content is empty, replace with chunk; otherwise append
|
|
371
|
-
const updated = [...prev];
|
|
372
|
-
updated[updated.length - 1] = { ...last, content: last.content + chunk };
|
|
373
|
-
return updated;
|
|
374
|
-
}
|
|
375
|
-
return [...prev, { role: 'assistant', content: chunk }];
|
|
376
|
-
});
|
|
432
|
+
pendingChunksRef.current += chunk;
|
|
433
|
+
scheduleFlush();
|
|
377
434
|
},
|
|
378
435
|
// onResponse intentionally removed — onStreamChunk handles all text
|
|
379
436
|
// Avoids duplicate "assistant" message push that caused text doubling
|
|
@@ -382,6 +439,18 @@ export function App({ config, options }) {
|
|
|
382
439
|
// Handled by handleSubmit catch block — adding here would create duplicate
|
|
383
440
|
// assistant messages, breaking the conversation structure for the next request
|
|
384
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
|
+
},
|
|
385
454
|
onApprovalRequest: async (toolName, args) => {
|
|
386
455
|
if (approvalModeRef.current === 'turbo')
|
|
387
456
|
return true;
|
|
@@ -399,6 +468,10 @@ export function App({ config, options }) {
|
|
|
399
468
|
},
|
|
400
469
|
});
|
|
401
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
|
+
}
|
|
402
475
|
const toolHistory = agentLoopRef.current.getToolCallHistory();
|
|
403
476
|
const bundleFile = await writeExecutionBundle({
|
|
404
477
|
sessionId: sessionIdRef.current,
|
|
@@ -440,15 +513,16 @@ export function App({ config, options }) {
|
|
|
440
513
|
// Single batch: all final UI updates at once (no await between setState calls)
|
|
441
514
|
setIsProcessing(false);
|
|
442
515
|
setStatusText(i18n.t('ready'));
|
|
443
|
-
//
|
|
444
|
-
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;
|
|
445
519
|
setMessages(prev => {
|
|
446
520
|
const idx = liveToolMessageIndexRef.current;
|
|
447
521
|
if (idx >= 0 && idx < prev.length && prev[idx]?.role === 'tool') {
|
|
448
522
|
const updated = [...prev];
|
|
449
523
|
updated[idx] = {
|
|
450
524
|
role: 'tool',
|
|
451
|
-
content: JSON.stringify({ type: 'tool_activity_card', toolCalls:
|
|
525
|
+
content: JSON.stringify({ type: 'tool_activity_card', toolCalls: cardTools, status: 'compact' }),
|
|
452
526
|
};
|
|
453
527
|
return updated;
|
|
454
528
|
}
|
|
@@ -456,33 +530,16 @@ export function App({ config, options }) {
|
|
|
456
530
|
});
|
|
457
531
|
}
|
|
458
532
|
liveToolMessageIndexRef.current = -1;
|
|
533
|
+
currentCardToolsRef.current = [];
|
|
459
534
|
}
|
|
460
535
|
catch (err) {
|
|
461
536
|
const error = err;
|
|
462
|
-
//
|
|
463
|
-
if (error
|
|
537
|
+
// User-triggered cancellation is expected and should not become an error message.
|
|
538
|
+
if (isUserCancellationError(error, abortController.signal)) {
|
|
464
539
|
return;
|
|
465
540
|
}
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
469
|
-
friendlyMsg = i18n.t('apiErrorAuth');
|
|
470
|
-
}
|
|
471
|
-
else if (msg.includes('429') || msg.includes('rate limit')) {
|
|
472
|
-
friendlyMsg = i18n.t('apiErrorRateLimit');
|
|
473
|
-
}
|
|
474
|
-
else if (/5\d{2}|server error|Service Unavailable/i.test(msg)) {
|
|
475
|
-
friendlyMsg = i18n.t('apiErrorServer');
|
|
476
|
-
}
|
|
477
|
-
else if (/ECONNRESET|ECONNREFUSED|ENOTFOUND/i.test(msg)) {
|
|
478
|
-
friendlyMsg = i18n.t('apiErrorNetwork');
|
|
479
|
-
}
|
|
480
|
-
else if (/ETIMEDOUT|timed out/i.test(msg)) {
|
|
481
|
-
friendlyMsg = i18n.t('apiErrorTimeout');
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
friendlyMsg = `${i18n.t('error')}: ${msg}`;
|
|
485
|
-
}
|
|
541
|
+
const friendlyMsg = formatAgentError(error);
|
|
542
|
+
const toolHistory = agentLoopRef.current?.getToolCallHistory() ?? [];
|
|
486
543
|
setMessages(prev => [...prev, {
|
|
487
544
|
role: 'assistant',
|
|
488
545
|
content: friendlyMsg,
|
|
@@ -492,16 +549,31 @@ export function App({ config, options }) {
|
|
|
492
549
|
prompt: input,
|
|
493
550
|
error: friendlyMsg,
|
|
494
551
|
approvalMode,
|
|
552
|
+
toolCalls: toolHistory.map(toolCall => ({
|
|
553
|
+
name: toolCall.name,
|
|
554
|
+
status: toolCall.status,
|
|
555
|
+
durationMs: toolCall.durationMs,
|
|
556
|
+
error: toolCall.error,
|
|
557
|
+
})),
|
|
495
558
|
});
|
|
496
559
|
const bundleFile = await writeExecutionBundle({
|
|
497
560
|
sessionId: sessionIdRef.current,
|
|
498
561
|
prompt: input,
|
|
499
562
|
error: friendlyMsg,
|
|
500
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
|
+
})),
|
|
501
572
|
});
|
|
502
573
|
await saveSession({
|
|
503
574
|
id: sessionIdRef.current,
|
|
504
575
|
messageCount: messages.length + 2,
|
|
576
|
+
toolCallCount: toolHistory.length,
|
|
505
577
|
approvalMode,
|
|
506
578
|
lastPrompt: input,
|
|
507
579
|
lastError: friendlyMsg,
|
|
@@ -511,9 +583,14 @@ export function App({ config, options }) {
|
|
|
511
583
|
});
|
|
512
584
|
}
|
|
513
585
|
finally {
|
|
586
|
+
logEvent('agent run: finally (clearing softCancel)');
|
|
514
587
|
// Safety net: ensure UI is always reset regardless of exit path
|
|
588
|
+
flushPending();
|
|
515
589
|
setIsProcessing(false);
|
|
516
|
-
|
|
590
|
+
// Don't overwrite status if user paused the agent
|
|
591
|
+
if (!isPausedRef.current) {
|
|
592
|
+
setStatusText(i18n.t('ready'));
|
|
593
|
+
}
|
|
517
594
|
// Clear any pending approval (covers error/abort exit paths)
|
|
518
595
|
if (pendingApprovalResolveRef.current) {
|
|
519
596
|
pendingApprovalResolveRef.current(false);
|
|
@@ -523,29 +600,58 @@ export function App({ config, options }) {
|
|
|
523
600
|
if (abortControllerRef.current === abortController) {
|
|
524
601
|
abortControllerRef.current = null;
|
|
525
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;
|
|
526
609
|
}
|
|
527
610
|
}, [messages, isProcessing, setupStep, handleApiKeySubmit, handleSlashCommand, approvalMode, config, localApiKey]);
|
|
528
611
|
useInput((_input, key) => {
|
|
529
612
|
const step = setupStepRef.current;
|
|
530
|
-
// Ctrl+C
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
// -
|
|
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.
|
|
534
618
|
if (key.ctrl && _input === 'c') {
|
|
535
|
-
|
|
619
|
+
logEvent('useInput Ctrl+C', { isProcessing, isPaused, hasAbort: !!abortControllerRef.current, step });
|
|
620
|
+
if (isProcessing && abortControllerRef.current && !isPaused) {
|
|
621
|
+
logEvent('useInput Ctrl+C -> abort+pause');
|
|
536
622
|
abortControllerRef.current.abort();
|
|
537
|
-
|
|
623
|
+
setIsPaused(true);
|
|
624
|
+
setStatusText(i18n.t('paused'));
|
|
538
625
|
if (pendingApprovalResolveRef.current) {
|
|
539
626
|
pendingApprovalResolveRef.current(false);
|
|
540
627
|
pendingApprovalResolveRef.current = null;
|
|
541
628
|
}
|
|
542
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
|
+
}
|
|
543
636
|
return;
|
|
544
637
|
}
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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);
|
|
549
655
|
return;
|
|
550
656
|
}
|
|
551
657
|
// When not in setup mode, let InputBar handle all keyboard input
|
|
@@ -632,26 +738,9 @@ export function App({ config, options }) {
|
|
|
632
738
|
});
|
|
633
739
|
return;
|
|
634
740
|
}
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
const visibleCount = messages.filter(m => m.role !== 'tool').length;
|
|
639
|
-
const next = Math.min(chatScrollOffset + 10, Math.max(0, visibleCount - 1));
|
|
640
|
-
if (next > 0)
|
|
641
|
-
setScrollMode('paused');
|
|
642
|
-
setChatScrollOffset(next);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
// PageDown: scroll down by ~half a screen
|
|
646
|
-
if (key.pageDown) {
|
|
647
|
-
const next = Math.max(0, chatScrollOffset - 10);
|
|
648
|
-
setChatScrollOffset(next);
|
|
649
|
-
if (next === 0) {
|
|
650
|
-
setScrollMode('follow');
|
|
651
|
-
setNewMessagesWhilePaused(false);
|
|
652
|
-
}
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
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.
|
|
655
744
|
// Theme picker: interactive selection
|
|
656
745
|
if (themePicker) {
|
|
657
746
|
if (key.escape) {
|
|
@@ -738,23 +827,10 @@ export function App({ config, options }) {
|
|
|
738
827
|
}
|
|
739
828
|
return;
|
|
740
829
|
}
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const next = Math.min(chatScrollOffset + 1, Math.max(0, visibleCount - 1));
|
|
746
|
-
if (next > 0)
|
|
747
|
-
setScrollMode('paused');
|
|
748
|
-
setChatScrollOffset(next);
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
if (key.downArrow && isProcessing) {
|
|
752
|
-
const next = Math.max(0, chatScrollOffset - 1);
|
|
753
|
-
setChatScrollOffset(next);
|
|
754
|
-
if (next === 0) {
|
|
755
|
-
setScrollMode('follow');
|
|
756
|
-
setNewMessagesWhilePaused(false);
|
|
757
|
-
}
|
|
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'));
|
|
758
834
|
return;
|
|
759
835
|
}
|
|
760
836
|
return;
|
|
@@ -866,9 +942,7 @@ export function App({ config, options }) {
|
|
|
866
942
|
setToolCalls([]);
|
|
867
943
|
setPendingApproval(null);
|
|
868
944
|
setPendingImage(null);
|
|
869
|
-
|
|
870
|
-
setNewMessagesWhilePaused(false);
|
|
871
|
-
setChatScrollOffset(0);
|
|
945
|
+
setChatEpoch(e => e + 1); // remount Static so the scrollback resets
|
|
872
946
|
liveToolMessageIndexRef.current = -1;
|
|
873
947
|
setServiceNotice(null);
|
|
874
948
|
if (serviceNoticeTimerRef.current) {
|
|
@@ -887,7 +961,7 @@ export function App({ config, options }) {
|
|
|
887
961
|
}, [messages.length, toolCalls.length, executeClear]);
|
|
888
962
|
const handleExit = useCallback(() => { exit(); }, [exit]);
|
|
889
963
|
const colors = themeManager.getColors();
|
|
890
|
-
return (_jsxs(Box, { flexDirection: 'column',
|
|
964
|
+
return (_jsxs(Box, { flexDirection: 'column', children: [setupStep !== 'done'
|
|
891
965
|
? _jsx(SetupWizard, { state: {
|
|
892
966
|
step: setupStep,
|
|
893
967
|
apiKeyError,
|
|
@@ -898,21 +972,14 @@ export function App({ config, options }) {
|
|
|
898
972
|
langOptions,
|
|
899
973
|
modeOptions,
|
|
900
974
|
} })
|
|
901
|
-
: (_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: [[
|
|
902
976
|
'[ok] Подтвердить',
|
|
903
977
|
'[no] Отклонить',
|
|
904
978
|
`[mute] Не спрашивать для "${pendingApproval.toolName}"`,
|
|
905
979
|
'[turbo] Выполнять всё без вопросов',
|
|
906
|
-
].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:
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
role: 'assistant',
|
|
911
|
-
content: `[warn] Вставка изображения требует модель с поддержкой vision.\nТекущая модель: ${model || 'неизвестно'}\nИспользуйте модель с "vl" или "vision" в названии.`,
|
|
912
|
-
}]);
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
setPendingImage({ base64, mimeType });
|
|
916
|
-
} }), _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 })] }));
|
|
917
984
|
}
|
|
918
985
|
//# sourceMappingURL=app.js.map
|