@serjm/deepseek-code 0.3.1

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.
Files changed (216) hide show
  1. package/CONTRIBUTING.md +73 -0
  2. package/README.md +194 -0
  3. package/README.ru.md +194 -0
  4. package/dist/api/index.d.ts +77 -0
  5. package/dist/api/index.d.ts.map +1 -0
  6. package/dist/api/index.js +263 -0
  7. package/dist/api/index.js.map +1 -0
  8. package/dist/cli/headless.d.ts +22 -0
  9. package/dist/cli/headless.d.ts.map +1 -0
  10. package/dist/cli/headless.js +122 -0
  11. package/dist/cli/headless.js.map +1 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +90 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/cli/interactive.d.ts +18 -0
  17. package/dist/cli/interactive.d.ts.map +1 -0
  18. package/dist/cli/interactive.js +75 -0
  19. package/dist/cli/interactive.js.map +1 -0
  20. package/dist/commands/index.d.ts +30 -0
  21. package/dist/commands/index.d.ts.map +1 -0
  22. package/dist/commands/index.js +964 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/config/defaults.d.ts +37 -0
  25. package/dist/config/defaults.d.ts.map +1 -0
  26. package/dist/config/defaults.js +39 -0
  27. package/dist/config/defaults.js.map +1 -0
  28. package/dist/config/loader.d.ts +4 -0
  29. package/dist/config/loader.d.ts.map +1 -0
  30. package/dist/config/loader.js +76 -0
  31. package/dist/config/loader.js.map +1 -0
  32. package/dist/core/agent-loop.d.ts +111 -0
  33. package/dist/core/agent-loop.d.ts.map +1 -0
  34. package/dist/core/agent-loop.js +485 -0
  35. package/dist/core/agent-loop.js.map +1 -0
  36. package/dist/core/checkpoint.d.ts +10 -0
  37. package/dist/core/checkpoint.d.ts.map +1 -0
  38. package/dist/core/checkpoint.js +83 -0
  39. package/dist/core/checkpoint.js.map +1 -0
  40. package/dist/core/extensions.d.ts +55 -0
  41. package/dist/core/extensions.d.ts.map +1 -0
  42. package/dist/core/extensions.js +113 -0
  43. package/dist/core/extensions.js.map +1 -0
  44. package/dist/core/git.d.ts +68 -0
  45. package/dist/core/git.d.ts.map +1 -0
  46. package/dist/core/git.js +148 -0
  47. package/dist/core/git.js.map +1 -0
  48. package/dist/core/hooks.d.ts +37 -0
  49. package/dist/core/hooks.d.ts.map +1 -0
  50. package/dist/core/hooks.js +77 -0
  51. package/dist/core/hooks.js.map +1 -0
  52. package/dist/core/i18n.d.ts +90 -0
  53. package/dist/core/i18n.d.ts.map +1 -0
  54. package/dist/core/i18n.js +253 -0
  55. package/dist/core/i18n.js.map +1 -0
  56. package/dist/core/lsp.d.ts +74 -0
  57. package/dist/core/lsp.d.ts.map +1 -0
  58. package/dist/core/lsp.js +239 -0
  59. package/dist/core/lsp.js.map +1 -0
  60. package/dist/core/mcp.d.ts +49 -0
  61. package/dist/core/mcp.d.ts.map +1 -0
  62. package/dist/core/mcp.js +195 -0
  63. package/dist/core/mcp.js.map +1 -0
  64. package/dist/core/memory.d.ts +38 -0
  65. package/dist/core/memory.d.ts.map +1 -0
  66. package/dist/core/memory.js +231 -0
  67. package/dist/core/memory.js.map +1 -0
  68. package/dist/core/metrics.d.ts +36 -0
  69. package/dist/core/metrics.d.ts.map +1 -0
  70. package/dist/core/metrics.js +111 -0
  71. package/dist/core/metrics.js.map +1 -0
  72. package/dist/core/review.d.ts +27 -0
  73. package/dist/core/review.d.ts.map +1 -0
  74. package/dist/core/review.js +201 -0
  75. package/dist/core/review.js.map +1 -0
  76. package/dist/core/sandbox.d.ts +52 -0
  77. package/dist/core/sandbox.d.ts.map +1 -0
  78. package/dist/core/sandbox.js +140 -0
  79. package/dist/core/sandbox.js.map +1 -0
  80. package/dist/core/scheduler.d.ts +56 -0
  81. package/dist/core/scheduler.d.ts.map +1 -0
  82. package/dist/core/scheduler.js +167 -0
  83. package/dist/core/scheduler.js.map +1 -0
  84. package/dist/core/session.d.ts +49 -0
  85. package/dist/core/session.d.ts.map +1 -0
  86. package/dist/core/session.js +127 -0
  87. package/dist/core/session.js.map +1 -0
  88. package/dist/core/skills.d.ts +36 -0
  89. package/dist/core/skills.d.ts.map +1 -0
  90. package/dist/core/skills.js +90 -0
  91. package/dist/core/skills.js.map +1 -0
  92. package/dist/core/subagent.d.ts +45 -0
  93. package/dist/core/subagent.d.ts.map +1 -0
  94. package/dist/core/subagent.js +130 -0
  95. package/dist/core/subagent.js.map +1 -0
  96. package/dist/core/themes.d.ts +35 -0
  97. package/dist/core/themes.d.ts.map +1 -0
  98. package/dist/core/themes.js +188 -0
  99. package/dist/core/themes.js.map +1 -0
  100. package/dist/tools/bash.d.ts +3 -0
  101. package/dist/tools/bash.d.ts.map +1 -0
  102. package/dist/tools/bash.js +92 -0
  103. package/dist/tools/bash.js.map +1 -0
  104. package/dist/tools/chrome-manager.d.ts +35 -0
  105. package/dist/tools/chrome-manager.d.ts.map +1 -0
  106. package/dist/tools/chrome-manager.js +163 -0
  107. package/dist/tools/chrome-manager.js.map +1 -0
  108. package/dist/tools/chrome.d.ts +78 -0
  109. package/dist/tools/chrome.d.ts.map +1 -0
  110. package/dist/tools/chrome.js +1058 -0
  111. package/dist/tools/chrome.js.map +1 -0
  112. package/dist/tools/edit.d.ts +3 -0
  113. package/dist/tools/edit.d.ts.map +1 -0
  114. package/dist/tools/edit.js +81 -0
  115. package/dist/tools/edit.js.map +1 -0
  116. package/dist/tools/glob.d.ts +3 -0
  117. package/dist/tools/glob.d.ts.map +1 -0
  118. package/dist/tools/glob.js +41 -0
  119. package/dist/tools/glob.js.map +1 -0
  120. package/dist/tools/grep.d.ts +3 -0
  121. package/dist/tools/grep.d.ts.map +1 -0
  122. package/dist/tools/grep.js +74 -0
  123. package/dist/tools/grep.js.map +1 -0
  124. package/dist/tools/path-safety.d.ts +3 -0
  125. package/dist/tools/path-safety.d.ts.map +1 -0
  126. package/dist/tools/path-safety.js +19 -0
  127. package/dist/tools/path-safety.js.map +1 -0
  128. package/dist/tools/read.d.ts +3 -0
  129. package/dist/tools/read.d.ts.map +1 -0
  130. package/dist/tools/read.js +58 -0
  131. package/dist/tools/read.js.map +1 -0
  132. package/dist/tools/registry.d.ts +4 -0
  133. package/dist/tools/registry.d.ts.map +1 -0
  134. package/dist/tools/registry.js +43 -0
  135. package/dist/tools/registry.js.map +1 -0
  136. package/dist/tools/types.d.ts +47 -0
  137. package/dist/tools/types.d.ts.map +1 -0
  138. package/dist/tools/types.js +90 -0
  139. package/dist/tools/types.js.map +1 -0
  140. package/dist/tools/write.d.ts +3 -0
  141. package/dist/tools/write.d.ts.map +1 -0
  142. package/dist/tools/write.js +51 -0
  143. package/dist/tools/write.js.map +1 -0
  144. package/dist/ui/activity-cards.d.ts +50 -0
  145. package/dist/ui/activity-cards.d.ts.map +1 -0
  146. package/dist/ui/activity-cards.js +185 -0
  147. package/dist/ui/activity-cards.js.map +1 -0
  148. package/dist/ui/app.d.ts +9 -0
  149. package/dist/ui/app.d.ts.map +1 -0
  150. package/dist/ui/app.js +852 -0
  151. package/dist/ui/app.js.map +1 -0
  152. package/dist/ui/chat-view.d.ts +10 -0
  153. package/dist/ui/chat-view.d.ts.map +1 -0
  154. package/dist/ui/chat-view.js +94 -0
  155. package/dist/ui/chat-view.js.map +1 -0
  156. package/dist/ui/error-boundary.d.ts +13 -0
  157. package/dist/ui/error-boundary.d.ts.map +1 -0
  158. package/dist/ui/error-boundary.js +16 -0
  159. package/dist/ui/error-boundary.js.map +1 -0
  160. package/dist/ui/fade-in.d.ts +8 -0
  161. package/dist/ui/fade-in.d.ts.map +1 -0
  162. package/dist/ui/fade-in.js +14 -0
  163. package/dist/ui/fade-in.js.map +1 -0
  164. package/dist/ui/input-bar.d.ts +16 -0
  165. package/dist/ui/input-bar.d.ts.map +1 -0
  166. package/dist/ui/input-bar.js +269 -0
  167. package/dist/ui/input-bar.js.map +1 -0
  168. package/dist/ui/markdown-view.d.ts +9 -0
  169. package/dist/ui/markdown-view.d.ts.map +1 -0
  170. package/dist/ui/markdown-view.js +240 -0
  171. package/dist/ui/markdown-view.js.map +1 -0
  172. package/dist/ui/matrix-rain.d.ts +2 -0
  173. package/dist/ui/matrix-rain.d.ts.map +1 -0
  174. package/dist/ui/matrix-rain.js +134 -0
  175. package/dist/ui/matrix-rain.js.map +1 -0
  176. package/dist/ui/reasoning-view.d.ts +12 -0
  177. package/dist/ui/reasoning-view.d.ts.map +1 -0
  178. package/dist/ui/reasoning-view.js +34 -0
  179. package/dist/ui/reasoning-view.js.map +1 -0
  180. package/dist/ui/results-panel.d.ts +11 -0
  181. package/dist/ui/results-panel.d.ts.map +1 -0
  182. package/dist/ui/results-panel.js +17 -0
  183. package/dist/ui/results-panel.js.map +1 -0
  184. package/dist/ui/setup-wizard.d.ts +30 -0
  185. package/dist/ui/setup-wizard.d.ts.map +1 -0
  186. package/dist/ui/setup-wizard.js +166 -0
  187. package/dist/ui/setup-wizard.js.map +1 -0
  188. package/dist/ui/status-bar.d.ts +14 -0
  189. package/dist/ui/status-bar.d.ts.map +1 -0
  190. package/dist/ui/status-bar.js +63 -0
  191. package/dist/ui/status-bar.js.map +1 -0
  192. package/dist/ui/tool-activity-card.d.ts +9 -0
  193. package/dist/ui/tool-activity-card.d.ts.map +1 -0
  194. package/dist/ui/tool-activity-card.js +172 -0
  195. package/dist/ui/tool-activity-card.js.map +1 -0
  196. package/dist/ui/tool-call-view.d.ts +9 -0
  197. package/dist/ui/tool-call-view.d.ts.map +1 -0
  198. package/dist/ui/tool-call-view.js +149 -0
  199. package/dist/ui/tool-call-view.js.map +1 -0
  200. package/dist/utils/clipboard.d.ts +6 -0
  201. package/dist/utils/clipboard.d.ts.map +1 -0
  202. package/dist/utils/clipboard.js +56 -0
  203. package/dist/utils/clipboard.js.map +1 -0
  204. package/dist/utils/ignore.d.ts +6 -0
  205. package/dist/utils/ignore.d.ts.map +1 -0
  206. package/dist/utils/ignore.js +40 -0
  207. package/dist/utils/ignore.js.map +1 -0
  208. package/dist/utils/logger.d.ts +4 -0
  209. package/dist/utils/logger.d.ts.map +1 -0
  210. package/dist/utils/logger.js +13 -0
  211. package/dist/utils/logger.js.map +1 -0
  212. package/dist/utils/string-width.d.ts +6 -0
  213. package/dist/utils/string-width.d.ts.map +1 -0
  214. package/dist/utils/string-width.js +37 -0
  215. package/dist/utils/string-width.js.map +1 -0
  216. package/package.json +68 -0
package/dist/ui/app.js ADDED
@@ -0,0 +1,852 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback, useRef, useEffect } from 'react';
3
+ import { Box, Text, useInput, useApp, useStdin } from 'ink';
4
+ import { ChatView } from './chat-view.js';
5
+ import { InputBar } from './input-bar.js';
6
+ import { StatusBar } from './status-bar.js';
7
+ import { DEEPSEEK_MODELS } from '../config/defaults.js';
8
+ import { saveConfig } from '../config/loader.js';
9
+ import { AgentLoop } from '../core/agent-loop.js';
10
+ import { saveSession, getLastSessionId, writeExecutionBundle, writeSessionHandoff } from '../core/session.js';
11
+ import { hooksManager } from '../core/hooks.js';
12
+ import { mcpManager } from '../core/mcp.js';
13
+ import { subAgentManager } from '../core/subagent.js';
14
+ import { skillsManager } from '../core/skills.js';
15
+ import { lspManager } from '../core/lsp.js';
16
+ import { scheduler } from '../core/scheduler.js';
17
+ import { themeManager } from '../core/themes.js';
18
+ import { i18n } from '../core/i18n.js';
19
+ import { Logo, SetupWizard, useSetupWizard } from './setup-wizard.js';
20
+ import { executeSlashCommand } from '../commands/index.js';
21
+ /** Empty input hint timeout in ms before showing the guide text */
22
+ const EMPTY_INPUT_HINT_DELAY = 2000;
23
+ function stripExecutionSummary(content) {
24
+ return content.replace(/\n\n━━━ Execution Summary ━━━[\s\S]*$/u, '').trimEnd();
25
+ }
26
+ function historyForModel(messages) {
27
+ return messages.flatMap(message => {
28
+ if (message.role === 'tool') {
29
+ try {
30
+ const parsed = typeof message.content === 'string' ? JSON.parse(message.content) : message.content;
31
+ if (parsed?.type === 'tool_activity_card')
32
+ return [];
33
+ }
34
+ catch {
35
+ // Keep non-UI tool messages.
36
+ }
37
+ }
38
+ if (message.role === 'assistant' && typeof message.content === 'string') {
39
+ return [{ ...message, content: stripExecutionSummary(message.content) }];
40
+ }
41
+ return [message];
42
+ });
43
+ }
44
+ // Setup wizard step components are now in ./setup-wizard.tsx
45
+ // Logo, SetupWizard, useSetupWizard imported from there
46
+ export function App({ config, options }) {
47
+ const { exit } = useApp();
48
+ const [approvalMode, setApprovalMode] = useState(options.approvalMode ?? (options.turbo ? 'turbo' : config.approvalMode));
49
+ const [messages, setMessages] = useState([]);
50
+ const [isProcessing, setIsProcessing] = useState(false);
51
+ const [statusText, setStatusText] = useState(i18n.t('ready'));
52
+ const [localApiKey, setLocalApiKey] = useState(config.apiKey || '');
53
+ const agentLoopRef = useRef(null);
54
+ const abortControllerRef = useRef(null);
55
+ const pendingApprovalResolveRef = useRef(null);
56
+ const liveToolMessageIndexRef = useRef(-1);
57
+ const prevToolCallsRef = useRef([]);
58
+ const [toolCalls, setToolCalls] = useState([]);
59
+ const [pendingApproval, setPendingApproval] = useState(null);
60
+ const [approvalCursor, setApprovalCursor] = useState(0);
61
+ const [pendingClear, setPendingClear] = useState(false);
62
+ const [clearCursor, setClearCursor] = useState(0);
63
+ const exemptedToolsRef = useRef(new Set());
64
+ const approvalModeRef = useRef(approvalMode);
65
+ const sessionIdRef = useRef('');
66
+ const initializedRef = useRef(false);
67
+ const [emptyInputHint, setEmptyInputHint] = useState(false);
68
+ const emptyInputTimerRef = useRef(null);
69
+ const [chatScrollOffset, setChatScrollOffset] = useState(0);
70
+ const [scrollMode, setScrollMode] = useState('follow');
71
+ const [newMessagesWhilePaused, setNewMessagesWhilePaused] = useState(false);
72
+ const visibleMessageCountRef = useRef(0);
73
+ const [contextPercent, setContextPercent] = useState(0);
74
+ const [totalTokens, setTotalTokens] = useState(0);
75
+ const [estimatedCost, setEstimatedCost] = useState(0);
76
+ const [pendingImage, setPendingImage] = useState(null);
77
+ const [themePicker, setThemePicker] = useState(null);
78
+ const [modelPicker, setModelPicker] = useState(null);
79
+ const [serviceNotice, setServiceNotice] = useState(null);
80
+ const serviceNoticeTimerRef = useRef(null);
81
+ const addServiceNotice = useCallback((text) => {
82
+ setServiceNotice(text);
83
+ if (serviceNoticeTimerRef.current)
84
+ clearTimeout(serviceNoticeTimerRef.current);
85
+ serviceNoticeTimerRef.current = setTimeout(() => setServiceNotice(null), 3000);
86
+ }, []);
87
+ // Keep approvalModeRef in sync so onApprovalRequest always uses the current mode
88
+ // even when changed via Tab while the agent is running
89
+ useEffect(() => { approvalModeRef.current = approvalMode; }, [approvalMode]);
90
+ // Check if API key is configured (non-empty) — used early for locale detection
91
+ const hasApiKey = !!(config.apiKey || process.env.DEEPSEEK_API_KEY) &&
92
+ (config.apiKey || process.env.DEEPSEEK_API_KEY || '').trim().length > 0;
93
+ // Set locale from config on mount (before first render)
94
+ // Auto-detect system locale on first launch (when no API key is configured)
95
+ const localeRef = useRef(false);
96
+ if (!localeRef.current) {
97
+ localeRef.current = true;
98
+ const lang = (config.language || (hasApiKey ? 'en' : i18n.detectLocale()));
99
+ i18n.setLocale(lang);
100
+ }
101
+ // Setup wizard — use the hook from setup-wizard.tsx
102
+ const initialStep = hasApiKey ? 'done' : 'lang';
103
+ const [setupWizardState, setupWizardActions] = useSetupWizard(config, initialStep, (updatedConfig) => {
104
+ setLocalApiKey(updatedConfig.apiKey || '');
105
+ setApprovalMode(updatedConfig.approvalMode || 'default');
106
+ }, (msg) => {
107
+ setMessages(prev => [...prev, msg]);
108
+ });
109
+ const { step: setupStep, apiKeyError, langCursor, themeCursor, modelCursor, modeCursor, langOptions, modeOptions } = setupWizardState;
110
+ const { handleApiKeySubmit, finishSetup, setLangCursor, setThemeCursor, setModelCursor, setModeCursor, setStep: setSetupStep, setApiKeyError } = setupWizardActions;
111
+ const setupStepRef = useRef(setupStep);
112
+ setupStepRef.current = setupStep;
113
+ // Initialize session and services on mount
114
+ useEffect(() => {
115
+ if (initializedRef.current)
116
+ return;
117
+ initializedRef.current = true;
118
+ // Set locale from config on startup
119
+ // (already set above, but keep for safety)
120
+ (async () => {
121
+ if (options.continue_) {
122
+ const lastId = await getLastSessionId();
123
+ if (lastId)
124
+ sessionIdRef.current = lastId;
125
+ }
126
+ if (!sessionIdRef.current) {
127
+ sessionIdRef.current = await saveSession({});
128
+ }
129
+ subAgentManager.setApiConfig({ ...config, apiKey: localApiKey || config.apiKey });
130
+ // Initialize services in background
131
+ await Promise.allSettled([
132
+ mcpManager.loadConfig().then(() => mcpManager.connectAll()),
133
+ skillsManager.loadAll(),
134
+ hooksManager.load(),
135
+ lspManager.load().then(() => lspManager.initializeAll()),
136
+ subAgentManager.loadFromDir(),
137
+ scheduler.load(),
138
+ ]);
139
+ // Chrome не инициализируется при старте.
140
+ // Он запускается только когда:
141
+ // - агент вызывает chrome tool
142
+ // - пользователь запускает /browser-test
143
+ // - пользователь запускает /chrome
144
+ setStatusText(i18n.t('ready'));
145
+ })();
146
+ }, []);
147
+ // Register soft-cancel hook for the SIGINT handler in interactive.ts.
148
+ // While isProcessing, interactive.ts's onSIGINT calls this instead of process.exit().
149
+ useEffect(() => {
150
+ const proc = process;
151
+ if (isProcessing) {
152
+ proc.__agentSoftCancel = () => {
153
+ abortControllerRef.current?.abort();
154
+ if (pendingApprovalResolveRef.current) {
155
+ pendingApprovalResolveRef.current(false);
156
+ pendingApprovalResolveRef.current = null;
157
+ }
158
+ setPendingApproval(null);
159
+ setStatusText(i18n.t('cancelled'));
160
+ };
161
+ }
162
+ else {
163
+ proc.__agentSoftCancel = undefined;
164
+ }
165
+ return () => { proc.__agentSoftCancel = undefined; };
166
+ }, [isProcessing]);
167
+ const { stdin } = useStdin();
168
+ // Compensate scroll offset when new visible messages arrive while paused
169
+ useEffect(() => {
170
+ if (setupStepRef.current !== 'done')
171
+ return;
172
+ const visible = messages.filter(m => m.role !== 'tool').length;
173
+ if (scrollMode === 'paused' && visible > visibleMessageCountRef.current) {
174
+ const diff = visible - visibleMessageCountRef.current;
175
+ setChatScrollOffset(prev => prev + diff);
176
+ setNewMessagesWhilePaused(true);
177
+ }
178
+ visibleMessageCountRef.current = visible;
179
+ }, [messages.length, scrollMode]);
180
+ // Sync prevToolCallsRef with toolCalls state
181
+ useEffect(() => {
182
+ prevToolCallsRef.current = toolCalls;
183
+ }, [toolCalls]);
184
+ // Detect End key from raw stdin (Ink does not expose it in useInput)
185
+ useEffect(() => {
186
+ if (!stdin)
187
+ return;
188
+ const handler = (data) => {
189
+ if (setupStepRef.current !== 'done')
190
+ return;
191
+ const seq = data.toString();
192
+ if (seq === '\x1b[F' || seq === '\x1b[4~' || seq === '\x1b[8~' || seq === '\x1bOF') {
193
+ setChatScrollOffset(0);
194
+ setScrollMode('follow');
195
+ setNewMessagesWhilePaused(false);
196
+ }
197
+ };
198
+ stdin.on('data', handler);
199
+ return () => { stdin.off('data', handler); };
200
+ }, [stdin]);
201
+ // Slash commands — delegated to commands/index.ts
202
+ const handleSlashCommand = useCallback(async (input) => {
203
+ const ctx = {
204
+ config,
205
+ approvalMode,
206
+ messages,
207
+ setMessages,
208
+ setStatusText,
209
+ setSetupStep,
210
+ addServiceNotice,
211
+ getMetrics: () => agentLoopRef.current?.getMetrics(),
212
+ onThemePicker: () => {
213
+ const themes = themeManager.listThemes();
214
+ const currentName = themeManager.theme.name;
215
+ const idx = themes.findIndex(t => t.name === currentName);
216
+ setThemePicker({ themes, selectedIndex: Math.max(0, idx) });
217
+ },
218
+ onModelPicker: () => {
219
+ const idx = DEEPSEEK_MODELS.findIndex(m => m.id === config.model);
220
+ setModelPicker({ selectedIndex: Math.max(0, idx) });
221
+ },
222
+ };
223
+ return executeSlashCommand(input, ctx);
224
+ }, [config, approvalMode, messages, addServiceNotice]);
225
+ const handleSubmit = useCallback(async (input) => {
226
+ // Show hint on empty input
227
+ if (!input.trim()) {
228
+ setEmptyInputHint(true);
229
+ if (emptyInputTimerRef.current)
230
+ clearTimeout(emptyInputTimerRef.current);
231
+ emptyInputTimerRef.current = setTimeout(() => setEmptyInputHint(false), EMPTY_INPUT_HINT_DELAY);
232
+ return;
233
+ }
234
+ if (isProcessing)
235
+ return;
236
+ // Handle setup wizard steps
237
+ if (setupStep === 'apikey') {
238
+ await handleApiKeySubmit(input);
239
+ return;
240
+ }
241
+ if (setupStep === 'theme' || setupStep === 'mode') {
242
+ return;
243
+ }
244
+ if (setupStep !== 'done') {
245
+ setMessages(prev => [...prev, {
246
+ role: 'assistant',
247
+ content: 'Сначала завершите настройку. Используйте /setup для повторного запуска.',
248
+ }]);
249
+ return;
250
+ }
251
+ if (input.startsWith('/')) {
252
+ try {
253
+ const handled = await handleSlashCommand(input);
254
+ if (handled)
255
+ return;
256
+ }
257
+ catch (err) {
258
+ setMessages(prev => [...prev, {
259
+ role: 'assistant',
260
+ content: `${i18n.t('error')}: ${err.message}`,
261
+ }]);
262
+ return;
263
+ }
264
+ // Unknown slash command — show local error, do NOT send to model
265
+ setMessages(prev => [...prev, {
266
+ role: 'assistant',
267
+ content: `Неизвестная команда: \`${input.trim().split(/\s+/)[0]}\`. Введите \`/help\` для списка команд.`,
268
+ }]);
269
+ return;
270
+ }
271
+ // Abort previous request if still running
272
+ if (abortControllerRef.current) {
273
+ abortControllerRef.current.abort();
274
+ }
275
+ const abortController = new AbortController();
276
+ abortControllerRef.current = abortController;
277
+ let userContent = input;
278
+ if (pendingImage) {
279
+ userContent = [
280
+ { type: 'text', text: input },
281
+ { type: 'image_url', image_url: { url: `data:${pendingImage.mimeType};base64,${pendingImage.base64}` } },
282
+ ];
283
+ setPendingImage(null);
284
+ }
285
+ const userMessage = { role: 'user', content: userContent };
286
+ setMessages(prev => [...prev, userMessage]);
287
+ setIsProcessing(true);
288
+ setStatusText(i18n.t('working'));
289
+ setToolCalls([]);
290
+ liveToolMessageIndexRef.current = -1;
291
+ setChatScrollOffset(0);
292
+ setScrollMode('follow');
293
+ setNewMessagesWhilePaused(false);
294
+ try {
295
+ await hooksManager.execute('UserPromptSubmit', {
296
+ event: 'UserPromptSubmit',
297
+ projectDir: process.cwd(),
298
+ });
299
+ // Create fresh AgentLoop every time (avoids stale closures)
300
+ agentLoopRef.current = new AgentLoop({ ...config, apiKey: localApiKey || config.apiKey }, {
301
+ approvalMode,
302
+ cwd: process.cwd(),
303
+ signal: abortController.signal,
304
+ onToolCall: (tc) => {
305
+ const updatedCalls = [...prevToolCallsRef.current, tc];
306
+ prevToolCallsRef.current = updatedCalls;
307
+ setToolCalls(updatedCalls);
308
+ setStatusText(`🔧 ${tc.name}...`);
309
+ // Add/update live tool activity card in chat messages
310
+ setMessages(prev => {
311
+ const idx = liveToolMessageIndexRef.current;
312
+ const card = { type: 'tool_activity_card', toolCalls: updatedCalls, status: 'live' };
313
+ if (idx >= 0 && idx < prev.length && prev[idx]?.role === 'tool') {
314
+ // Update existing card
315
+ const updated = [...prev];
316
+ updated[idx] = { role: 'tool', content: JSON.stringify(card) };
317
+ return updated;
318
+ }
319
+ // Add new card
320
+ liveToolMessageIndexRef.current = prev.length;
321
+ return [...prev, { role: 'tool', content: JSON.stringify(card) }];
322
+ });
323
+ },
324
+ onToolResult: (result) => {
325
+ setStatusText(result.success ? `✅ ${result.toolName} ${i18n.t('toolDone')}` : `❌ ${result.toolName} ${i18n.t('toolError')}`);
326
+ },
327
+ onReasoningChunk: () => { },
328
+ onStreamChunk: (chunk) => {
329
+ setMessages(prev => {
330
+ const last = prev[prev.length - 1];
331
+ if (last?.role === 'assistant') {
332
+ // If last content is empty, replace with chunk; otherwise append
333
+ const updated = [...prev];
334
+ updated[updated.length - 1] = { ...last, content: last.content + chunk };
335
+ return updated;
336
+ }
337
+ return [...prev, { role: 'assistant', content: chunk }];
338
+ });
339
+ },
340
+ // onResponse intentionally removed — onStreamChunk handles all text
341
+ // Avoids duplicate "assistant" message push that caused text doubling
342
+ onResponse: () => { },
343
+ onError: () => {
344
+ // Handled by handleSubmit catch block — adding here would create duplicate
345
+ // assistant messages, breaking the conversation structure for the next request
346
+ },
347
+ onApprovalRequest: async (toolName, args) => {
348
+ if (approvalModeRef.current === 'turbo')
349
+ return true;
350
+ if (approvalModeRef.current === 'auto-edit' && (toolName === 'write_file' || toolName === 'edit'))
351
+ return true;
352
+ if (approvalModeRef.current === 'plan')
353
+ return false;
354
+ if (exemptedToolsRef.current.has(toolName))
355
+ return true;
356
+ // Default mode — ask user for confirmation
357
+ return new Promise((resolve) => {
358
+ pendingApprovalResolveRef.current = resolve;
359
+ setPendingApproval({ toolName, args, resolve });
360
+ });
361
+ },
362
+ });
363
+ const finalResponse = await agentLoopRef.current.run(input, historyForModel(messages));
364
+ const toolHistory = agentLoopRef.current.getToolCallHistory();
365
+ const bundleFile = await writeExecutionBundle({
366
+ sessionId: sessionIdRef.current,
367
+ prompt: input,
368
+ response: finalResponse,
369
+ approvalMode,
370
+ toolCalls: toolHistory.map(toolCall => ({
371
+ id: toolCall.id,
372
+ name: toolCall.name,
373
+ status: toolCall.status,
374
+ durationMs: toolCall.durationMs,
375
+ error: toolCall.error,
376
+ result: toolCall.result,
377
+ })),
378
+ });
379
+ const handoffFile = await writeSessionHandoff({
380
+ sessionId: sessionIdRef.current,
381
+ prompt: input,
382
+ response: finalResponse,
383
+ approvalMode,
384
+ toolCalls: toolHistory.map(toolCall => ({
385
+ name: toolCall.name,
386
+ status: toolCall.status,
387
+ durationMs: toolCall.durationMs,
388
+ error: toolCall.error,
389
+ })),
390
+ });
391
+ await saveSession({
392
+ id: sessionIdRef.current,
393
+ messageCount: messages.length + 2,
394
+ toolCallCount: toolHistory.length,
395
+ approvalMode,
396
+ lastPrompt: input,
397
+ lastResponse: finalResponse,
398
+ summary: finalResponse,
399
+ handoffFile,
400
+ bundleFile,
401
+ });
402
+ // Single batch: all final UI updates at once (no await between setState calls)
403
+ setIsProcessing(false);
404
+ setStatusText(i18n.t('ready'));
405
+ // Convert live tool activity card to compact summary
406
+ if (liveToolMessageIndexRef.current >= 0 && toolHistory.length > 0) {
407
+ setMessages(prev => {
408
+ const idx = liveToolMessageIndexRef.current;
409
+ if (idx >= 0 && idx < prev.length && prev[idx]?.role === 'tool') {
410
+ const updated = [...prev];
411
+ updated[idx] = {
412
+ role: 'tool',
413
+ content: JSON.stringify({ type: 'tool_activity_card', toolCalls: toolHistory, status: 'compact' }),
414
+ };
415
+ return updated;
416
+ }
417
+ return prev;
418
+ });
419
+ }
420
+ liveToolMessageIndexRef.current = -1;
421
+ }
422
+ catch (err) {
423
+ const error = err;
424
+ // Don't show error on cancellation
425
+ if (error.name === 'AbortError' || error.message.includes('abort') || error.message.includes('cancel')) {
426
+ return;
427
+ }
428
+ const msg = error.message || '';
429
+ let friendlyMsg;
430
+ if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
431
+ friendlyMsg = i18n.t('apiErrorAuth');
432
+ }
433
+ else if (msg.includes('429') || msg.includes('rate limit')) {
434
+ friendlyMsg = i18n.t('apiErrorRateLimit');
435
+ }
436
+ else if (/5\d{2}|server error|Service Unavailable/i.test(msg)) {
437
+ friendlyMsg = i18n.t('apiErrorServer');
438
+ }
439
+ else if (/ECONNRESET|ECONNREFUSED|ENOTFOUND/i.test(msg)) {
440
+ friendlyMsg = i18n.t('apiErrorNetwork');
441
+ }
442
+ else if (/ETIMEDOUT|timed out/i.test(msg)) {
443
+ friendlyMsg = i18n.t('apiErrorTimeout');
444
+ }
445
+ else {
446
+ friendlyMsg = `${i18n.t('error')}: ${msg}`;
447
+ }
448
+ setMessages(prev => [...prev, {
449
+ role: 'assistant',
450
+ content: friendlyMsg,
451
+ }]);
452
+ const handoffFile = await writeSessionHandoff({
453
+ sessionId: sessionIdRef.current,
454
+ prompt: input,
455
+ error: friendlyMsg,
456
+ approvalMode,
457
+ });
458
+ const bundleFile = await writeExecutionBundle({
459
+ sessionId: sessionIdRef.current,
460
+ prompt: input,
461
+ error: friendlyMsg,
462
+ approvalMode,
463
+ });
464
+ await saveSession({
465
+ id: sessionIdRef.current,
466
+ messageCount: messages.length + 2,
467
+ approvalMode,
468
+ lastPrompt: input,
469
+ lastError: friendlyMsg,
470
+ summary: friendlyMsg,
471
+ handoffFile,
472
+ bundleFile,
473
+ });
474
+ }
475
+ finally {
476
+ // Safety net: ensure UI is always reset regardless of exit path
477
+ setIsProcessing(false);
478
+ setStatusText(i18n.t('ready'));
479
+ // Clear any pending approval (covers error/abort exit paths)
480
+ if (pendingApprovalResolveRef.current) {
481
+ pendingApprovalResolveRef.current(false);
482
+ pendingApprovalResolveRef.current = null;
483
+ }
484
+ setPendingApproval(null);
485
+ if (abortControllerRef.current === abortController) {
486
+ abortControllerRef.current = null;
487
+ }
488
+ }
489
+ }, [messages, isProcessing, setupStep, handleApiKeySubmit, handleSlashCommand, approvalMode, config, localApiKey]);
490
+ useInput((_input, key) => {
491
+ const step = setupStepRef.current;
492
+ // Ctrl+C: delegate to interactive.ts SIGINT handler.
493
+ // - During processing: soft cancel (via __agentSoftCancel)
494
+ // - During Ready: double Ctrl+C guard (first shows hint, second exits)
495
+ // - Never call exit() here — that would bypass the double-Ctrl+C guard.
496
+ if (key.ctrl && _input === 'c') {
497
+ if (isProcessing && abortControllerRef.current) {
498
+ abortControllerRef.current.abort();
499
+ setStatusText(i18n.t('cancelled'));
500
+ if (pendingApprovalResolveRef.current) {
501
+ pendingApprovalResolveRef.current(false);
502
+ pendingApprovalResolveRef.current = null;
503
+ }
504
+ setPendingApproval(null);
505
+ return;
506
+ }
507
+ // When not processing: set flag so SIGINT handler can exit immediately
508
+ // (interactive.ts checks __pendingExit for immediate exit after agent finishes)
509
+ const proc = process;
510
+ proc.__pendingExit = true;
511
+ return;
512
+ }
513
+ // When not in setup mode, let InputBar handle all keyboard input
514
+ if (step === 'done') {
515
+ // Handle clear confirmation dialog
516
+ if (pendingClear) {
517
+ if (key.upArrow) {
518
+ setClearCursor(prev => Math.max(0, prev - 1));
519
+ }
520
+ else if (key.downArrow) {
521
+ setClearCursor(prev => Math.min(1, prev + 1));
522
+ }
523
+ else if (key.return) {
524
+ if (clearCursor === 0)
525
+ executeClear();
526
+ else
527
+ setPendingClear(false);
528
+ }
529
+ else if (key.escape) {
530
+ setPendingClear(false);
531
+ }
532
+ return;
533
+ }
534
+ // Handle approval dialog
535
+ if (pendingApproval) {
536
+ if (key.upArrow) {
537
+ setApprovalCursor(prev => Math.max(0, prev - 1));
538
+ }
539
+ else if (key.downArrow) {
540
+ setApprovalCursor(prev => Math.min(3, prev + 1));
541
+ }
542
+ else if (key.return) {
543
+ const cursor = approvalCursor;
544
+ const resolve = pendingApproval.resolve;
545
+ const toolName = pendingApproval.toolName;
546
+ setApprovalCursor(0);
547
+ setPendingApproval(null);
548
+ pendingApprovalResolveRef.current = null;
549
+ if (cursor === 0) {
550
+ resolve(true);
551
+ }
552
+ else if (cursor === 1) {
553
+ resolve(false);
554
+ }
555
+ else if (cursor === 2) {
556
+ exemptedToolsRef.current.add(toolName);
557
+ addServiceNotice(`🔇 ${toolName}: больше не спрашивать в этой сессии`);
558
+ resolve(true);
559
+ }
560
+ else {
561
+ setApprovalMode('turbo');
562
+ saveConfig({ ...config, approvalMode: 'turbo' }).catch(() => { });
563
+ addServiceNotice('⚡ Turbo режим включён: инструменты выполняются без подтверждения.');
564
+ resolve(true);
565
+ }
566
+ }
567
+ else if (key.escape) {
568
+ setApprovalCursor(0);
569
+ const resolve = pendingApproval.resolve;
570
+ setPendingApproval(null);
571
+ pendingApprovalResolveRef.current = null;
572
+ resolve(false);
573
+ }
574
+ return;
575
+ }
576
+ // Tab for approval mode cycling — instant switch, no y/n confirmation
577
+ if (key.tab) {
578
+ setApprovalMode(prev => {
579
+ const modes = ['plan', 'default', 'auto-edit', 'turbo'];
580
+ const nextIdx = (modes.indexOf(prev) + 1) % modes.length;
581
+ const newMode = modes[nextIdx];
582
+ saveConfig({ ...config, approvalMode: newMode }).catch(() => { });
583
+ // Local warning — NOT an agent message, NOT sent to model
584
+ if (newMode === 'turbo') {
585
+ addServiceNotice('⚡ Включён режим Turbo: инструменты будут выполняться без подтверждения.');
586
+ }
587
+ else if (prev === 'turbo') {
588
+ addServiceNotice('Режим Turbo выключен.');
589
+ }
590
+ else {
591
+ addServiceNotice(`Режим: ${newMode}`);
592
+ }
593
+ return newMode;
594
+ });
595
+ return;
596
+ }
597
+ // Scroll chat history — always works regardless of processing state.
598
+ // PageUp: scroll up by ~half a screen
599
+ if (key.pageUp) {
600
+ const visibleCount = messages.filter(m => m.role !== 'tool').length;
601
+ const next = Math.min(chatScrollOffset + 10, Math.max(0, visibleCount - 1));
602
+ if (next > 0)
603
+ setScrollMode('paused');
604
+ setChatScrollOffset(next);
605
+ return;
606
+ }
607
+ // PageDown: scroll down by ~half a screen
608
+ if (key.pageDown) {
609
+ const next = Math.max(0, chatScrollOffset - 10);
610
+ setChatScrollOffset(next);
611
+ if (next === 0) {
612
+ setScrollMode('follow');
613
+ setNewMessagesWhilePaused(false);
614
+ }
615
+ return;
616
+ }
617
+ // Theme picker: interactive selection
618
+ if (themePicker) {
619
+ if (key.escape) {
620
+ setThemePicker(null);
621
+ return;
622
+ }
623
+ if (key.upArrow) {
624
+ setThemePicker(prev => {
625
+ if (!prev)
626
+ return null;
627
+ return { ...prev, selectedIndex: Math.max(0, prev.selectedIndex - 1) };
628
+ });
629
+ return;
630
+ }
631
+ if (key.downArrow) {
632
+ setThemePicker(prev => {
633
+ if (!prev)
634
+ return null;
635
+ return { ...prev, selectedIndex: Math.min(prev.themes.length - 1, prev.selectedIndex + 1) };
636
+ });
637
+ return;
638
+ }
639
+ if (key.return) {
640
+ const picker = themePicker;
641
+ const chosen = picker.themes[picker.selectedIndex];
642
+ themeManager.setTheme(chosen.name);
643
+ setThemePicker(null);
644
+ addServiceNotice(`🎨 Тема изменена: ${chosen.name}`);
645
+ return;
646
+ }
647
+ return;
648
+ }
649
+ // Model picker: interactive selection
650
+ if (modelPicker) {
651
+ if (key.escape) {
652
+ setModelPicker(null);
653
+ return;
654
+ }
655
+ if (key.upArrow) {
656
+ setModelPicker(prev => prev ? { selectedIndex: Math.max(0, prev.selectedIndex - 1) } : null);
657
+ return;
658
+ }
659
+ if (key.downArrow) {
660
+ setModelPicker(prev => prev ? { selectedIndex: Math.min(DEEPSEEK_MODELS.length - 1, prev.selectedIndex + 1) } : null);
661
+ return;
662
+ }
663
+ if (key.return) {
664
+ const chosen = DEEPSEEK_MODELS[modelPicker.selectedIndex];
665
+ if (chosen) {
666
+ saveConfig({ ...config, model: chosen.id }).catch(() => { });
667
+ config.model = chosen.id;
668
+ setModelPicker(null);
669
+ addServiceNotice(`🤖 Модель: ${chosen.label} (${chosen.id})`);
670
+ }
671
+ return;
672
+ }
673
+ return;
674
+ }
675
+ // ArrowUp/ArrowDown: scroll by 1 line, but only when InputBar is disabled (processing)
676
+ // When InputBar is active, arrows belong to input history/suggestions.
677
+ if (key.upArrow && isProcessing) {
678
+ const visibleCount = messages.filter(m => m.role !== 'tool').length;
679
+ const next = Math.min(chatScrollOffset + 1, Math.max(0, visibleCount - 1));
680
+ if (next > 0)
681
+ setScrollMode('paused');
682
+ setChatScrollOffset(next);
683
+ return;
684
+ }
685
+ if (key.downArrow && isProcessing) {
686
+ const next = Math.max(0, chatScrollOffset - 1);
687
+ setChatScrollOffset(next);
688
+ if (next === 0) {
689
+ setScrollMode('follow');
690
+ setNewMessagesWhilePaused(false);
691
+ }
692
+ return;
693
+ }
694
+ return;
695
+ }
696
+ // Step 1: Language selection — arrows + Enter + Escape
697
+ if (step === 'lang') {
698
+ if (key.escape) {
699
+ exit();
700
+ return;
701
+ }
702
+ if (key.downArrow) {
703
+ setLangCursor(Math.min(langCursor + 1, langOptions.length - 1));
704
+ }
705
+ else if (key.upArrow) {
706
+ setLangCursor(Math.max(langCursor - 1, 0));
707
+ }
708
+ else if (key.return) {
709
+ const lang = langOptions[langCursor];
710
+ i18n.setLocale(lang);
711
+ setSetupStep('apikey');
712
+ setLangCursor(0);
713
+ }
714
+ return;
715
+ }
716
+ // Step 2: API key — handled via InputBar, Escape to go back
717
+ if (step === 'apikey') {
718
+ if (key.escape) {
719
+ setSetupStep('lang');
720
+ setLangCursor(0);
721
+ }
722
+ return;
723
+ }
724
+ // Step 3: Theme selection — arrows + Enter + Escape
725
+ if (step === 'theme') {
726
+ if (key.escape) {
727
+ setSetupStep('apikey');
728
+ setApiKeyError('');
729
+ return;
730
+ }
731
+ const themes = themeManager.listThemes();
732
+ if (key.downArrow) {
733
+ const next = Math.min(themeCursor + 1, themes.length - 1);
734
+ themeManager.setTheme(themes[next].name);
735
+ setThemeCursor(next);
736
+ }
737
+ else if (key.upArrow) {
738
+ const prev = Math.max(themeCursor - 1, 0);
739
+ themeManager.setTheme(themes[prev].name);
740
+ setThemeCursor(prev);
741
+ }
742
+ else if (key.return) {
743
+ setSetupStep('model');
744
+ setModelCursor(0);
745
+ }
746
+ return;
747
+ }
748
+ // Step 4: Model selection — arrows + Enter + Escape
749
+ if (step === 'model') {
750
+ if (key.escape) {
751
+ setSetupStep('theme');
752
+ return;
753
+ }
754
+ if (key.downArrow) {
755
+ setModelCursor(Math.min(modelCursor + 1, DEEPSEEK_MODELS.length - 1));
756
+ }
757
+ else if (key.upArrow) {
758
+ setModelCursor(Math.max(modelCursor - 1, 0));
759
+ }
760
+ else if (key.return) {
761
+ setSetupStep('mode');
762
+ setModeCursor(0);
763
+ }
764
+ return;
765
+ }
766
+ // Step 5: Mode selection — arrows + Enter + Escape
767
+ if (step === 'mode') {
768
+ if (key.escape) {
769
+ setSetupStep('model');
770
+ return;
771
+ }
772
+ if (key.downArrow) {
773
+ setModeCursor(Math.min(modeCursor + 1, modeOptions.length - 1));
774
+ }
775
+ else if (key.upArrow) {
776
+ setModeCursor(Math.max(modeCursor - 1, 0));
777
+ }
778
+ else if (key.return) {
779
+ finishSetup();
780
+ }
781
+ }
782
+ });
783
+ // Get context usage percent from AgentLoop metrics
784
+ useEffect(() => {
785
+ if (!isProcessing || !agentLoopRef.current) {
786
+ return;
787
+ }
788
+ const interval = setInterval(() => {
789
+ if (agentLoopRef.current) {
790
+ const metrics = agentLoopRef.current.getMetrics();
791
+ setContextPercent(metrics.getCurrentWindowPercent());
792
+ setTotalTokens(metrics.totalTokens);
793
+ setEstimatedCost(metrics.estimatedCostUSD(config.model));
794
+ }
795
+ }, 2000);
796
+ return () => clearInterval(interval);
797
+ }, [isProcessing]);
798
+ const executeClear = useCallback(() => {
799
+ setMessages([]);
800
+ setToolCalls([]);
801
+ setPendingApproval(null);
802
+ setPendingImage(null);
803
+ setScrollMode('follow');
804
+ setNewMessagesWhilePaused(false);
805
+ setChatScrollOffset(0);
806
+ liveToolMessageIndexRef.current = -1;
807
+ setServiceNotice(null);
808
+ if (serviceNoticeTimerRef.current) {
809
+ clearTimeout(serviceNoticeTimerRef.current);
810
+ serviceNoticeTimerRef.current = null;
811
+ }
812
+ setPendingClear(false);
813
+ }, []);
814
+ const handleClear = useCallback(() => {
815
+ if (messages.length === 0 && toolCalls.length === 0) {
816
+ executeClear();
817
+ return;
818
+ }
819
+ setPendingClear(true);
820
+ setClearCursor(0);
821
+ }, [messages.length, toolCalls.length, executeClear]);
822
+ const handleExit = useCallback(() => { exit(); }, [exit]);
823
+ const colors = themeManager.getColors();
824
+ return (_jsxs(Box, { flexDirection: 'column', height: '100%', children: [setupStep !== 'done'
825
+ ? _jsx(SetupWizard, { state: {
826
+ step: setupStep,
827
+ apiKeyError,
828
+ langCursor,
829
+ themeCursor,
830
+ modelCursor,
831
+ modeCursor,
832
+ langOptions,
833
+ modeOptions,
834
+ } })
835
+ : (_jsxs(Box, { flexDirection: 'column', flexGrow: 1, children: [_jsx(Logo, {}), _jsx(ChatView, { messages: messages, scrollOffset: chatScrollOffset, hasNewMessages: newMessagesWhilePaused }), 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: "\uD83C\uDFA8 \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: "\uD83E\uDD16 \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" }) })] })), 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: "\uD83D\uDD14 \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: [[
836
+ '✅ Подтвердить',
837
+ '❌ Отклонить',
838
+ `🔇 Не спрашивать для "${pendingApproval.toolName}"`,
839
+ '⚡ Turbo — выполнять всё без вопросов',
840
+ ].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: "\u26A0\uFE0F \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: [['✅ Да, очистить', '❌ Отмена'].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: isProcessing, onClear: handleClear, onExit: handleExit, isMasked: setupStep === 'apikey', isSetupMode: setupStep !== 'done', blockInput: setupStep === 'done' && (pendingApproval !== null || pendingClear), emptyHint: emptyInputHint, onImagePaste: (base64, mimeType) => {
841
+ const model = config.model ?? '';
842
+ if (!model.includes('vl') && !model.includes('vision')) {
843
+ setMessages(prev => [...prev, {
844
+ role: 'assistant',
845
+ content: `⚠️ Вставка изображения требует модель с поддержкой vision.\nТекущая модель: ${model || 'неизвестно'}\nИспользуйте модель с "vl" или "vision" в названии.`,
846
+ }]);
847
+ return;
848
+ }
849
+ setPendingImage({ base64, mimeType });
850
+ } }), _jsx(StatusBar, { mode: approvalMode, status: statusText, messageCount: messages.length, isProcessing: isProcessing, contextPercent: contextPercent, totalTokens: totalTokens, estimatedCost: estimatedCost, model: config.model })] }));
851
+ }
852
+ //# sourceMappingURL=app.js.map