@serjm/deepseek-code 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +12 -0
- package/README.ru.md +12 -0
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +17 -5
- package/dist/api/index.js.map +1 -1
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +2 -0
- package/dist/cli/headless.js.map +1 -1
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +188 -79
- package/dist/commands/index.js.map +1 -1
- package/dist/config/defaults.d.ts +2 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +4 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/core/agent-loop.d.ts +27 -1
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-loop.js +165 -10
- package/dist/core/agent-loop.js.map +1 -1
- package/dist/core/i18n.d.ts +2 -0
- package/dist/core/i18n.d.ts.map +1 -1
- package/dist/core/i18n.js +15 -9
- package/dist/core/i18n.js.map +1 -1
- package/dist/core/metrics.d.ts +53 -0
- package/dist/core/metrics.d.ts.map +1 -1
- package/dist/core/metrics.js +205 -6
- package/dist/core/metrics.js.map +1 -1
- package/dist/tools/chrome.js +53 -53
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +30 -8
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/types.d.ts +28 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +10 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +13 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/activity-cards.js +9 -9
- package/dist/ui/activity-cards.js.map +1 -1
- package/dist/ui/app.d.ts.map +1 -1
- package/dist/ui/app.js +59 -14
- package/dist/ui/app.js.map +1 -1
- package/dist/ui/chat-view.js +2 -2
- 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 +268 -70
- package/dist/ui/input-bar.js.map +1 -1
- package/dist/ui/markdown-view.d.ts +9 -0
- package/dist/ui/markdown-view.d.ts.map +1 -1
- package/dist/ui/markdown-view.js +70 -34
- package/dist/ui/markdown-view.js.map +1 -1
- package/dist/ui/reasoning-view.js +1 -1
- package/dist/ui/reasoning-view.js.map +1 -1
- package/dist/ui/results-panel.js +1 -1
- package/dist/ui/results-panel.js.map +1 -1
- package/dist/ui/setup-wizard.js +5 -5
- package/dist/ui/setup-wizard.js.map +1 -1
- package/dist/ui/status-bar.js +1 -1
- package/dist/ui/status-bar.js.map +1 -1
- package/dist/ui/tool-activity-card.js +9 -9
- package/dist/ui/tool-activity-card.js.map +1 -1
- package/dist/ui/tool-call-view.js +7 -7
- package/dist/ui/tool-call-view.js.map +1 -1
- package/package.json +2 -1
package/dist/commands/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { saveConfig } from '../config/loader.js';
|
|
|
18
18
|
import { getDefaultTools, getToolsForMode } from '../tools/registry.js';
|
|
19
19
|
import { browserTest, getLastBrowserTestResult, browserRealTest } from '../tools/chrome.js';
|
|
20
20
|
import { chromeManager } from '../tools/chrome-manager.js';
|
|
21
|
+
import { AUDIT_BUDGET_PRESET } from '../tools/types.js';
|
|
21
22
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
22
23
|
function generateFollowups(lastContent) {
|
|
23
24
|
const suggestions = [];
|
|
@@ -43,28 +44,85 @@ function generateFollowups(lastContent) {
|
|
|
43
44
|
}
|
|
44
45
|
return suggestions;
|
|
45
46
|
}
|
|
47
|
+
// ─── Russian descriptions for commands ──────────────────────────────────────
|
|
48
|
+
const RU_DESCRIPTIONS = {
|
|
49
|
+
'/help': 'Показать эту справку',
|
|
50
|
+
'/setup': 'Настройки: язык, API-ключ, тема, режим',
|
|
51
|
+
'/remember': 'Сохранить в память: /remember <текст>',
|
|
52
|
+
'/forget': 'Удалить из памяти по поиску',
|
|
53
|
+
'/memory': 'Показать все сохранённые записи',
|
|
54
|
+
'/compress': 'Сжать историю чата',
|
|
55
|
+
'/checkpoint': 'Создать git-чекпоинт',
|
|
56
|
+
'/restore': 'Список или восстановление чекпоинта: /restore [id]',
|
|
57
|
+
'/mcp': 'MCP-серверы: /mcp list | connect',
|
|
58
|
+
'/skills': 'Список или описание навыка',
|
|
59
|
+
'/agents': 'Список активных под-агентов',
|
|
60
|
+
'/review': 'Ревью кода: /review all|diff|auto',
|
|
61
|
+
'/sandbox': 'Запустить команду в sandbox',
|
|
62
|
+
'/git': 'Git: /git commit|branch|diff|status',
|
|
63
|
+
'/loop': 'Планировщик: /loop <интервал> <задача>',
|
|
64
|
+
'/stats': 'Статистика сессии с токенами',
|
|
65
|
+
'/theme': 'Сменить тему или открыть выбор',
|
|
66
|
+
'/model': 'Сменить модель или открыть выбор: /model [id]',
|
|
67
|
+
'/lang': 'Сменить язык: /lang en|ru|zh',
|
|
68
|
+
'/extensions': 'Список установленных расширений',
|
|
69
|
+
'/followup': 'Сгенерировать предложения продолжения',
|
|
70
|
+
'/logs': 'Показать последние логи',
|
|
71
|
+
'/plan': 'Обзор возможностей',
|
|
72
|
+
'/tools': 'Показать доступные инструменты и статус',
|
|
73
|
+
'/capabilities': 'Полная матрица возможностей',
|
|
74
|
+
'/browser-test': 'Запустить тест Chrome',
|
|
75
|
+
'/browser-real-test': 'Smoke-тест реальных сайтов',
|
|
76
|
+
'/last-browser-test': 'Показать последний отчёт browser-test',
|
|
77
|
+
'/chrome': 'Режим Chrome: --headed|--headless|-s',
|
|
78
|
+
'/budget': 'Бюджет: /budget status|off|audit|small',
|
|
79
|
+
};
|
|
80
|
+
function getDescription(name) {
|
|
81
|
+
if (i18n.getLocale() === 'ru') {
|
|
82
|
+
return RU_DESCRIPTIONS[name] ?? COMMANDS.find(c => c.name === name)?.description ?? '';
|
|
83
|
+
}
|
|
84
|
+
return COMMANDS.find(c => c.name === name)?.description ?? '';
|
|
85
|
+
}
|
|
46
86
|
// ─── Command handlers ────────────────────────────────────────────────────────
|
|
47
87
|
async function cmdHelp(ctx) {
|
|
48
|
-
const
|
|
88
|
+
const locale = i18n.getLocale();
|
|
89
|
+
const helpLine = (left, right) => `${left}\n ${right}`;
|
|
90
|
+
const lines = [`${i18n.t('helpCommands')}:`];
|
|
49
91
|
for (const cmd of COMMANDS) {
|
|
50
92
|
if (cmd.name === '/language')
|
|
51
93
|
continue;
|
|
52
|
-
lines.push(
|
|
53
|
-
}
|
|
54
|
-
lines.push('', '
|
|
55
|
-
lines.push('', '
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
94
|
+
lines.push('', helpLine(cmd.name, getDescription(cmd.name)));
|
|
95
|
+
}
|
|
96
|
+
lines.push('', helpLine('/clear', locale === 'ru' ? 'Очистить историю чата (с подтверждением)' : 'Clear chat history (with confirmation)'));
|
|
97
|
+
lines.push('', `${i18n.t('helpKeyboard')}:`);
|
|
98
|
+
if (locale === 'ru') {
|
|
99
|
+
lines.push('', helpLine('Ctrl+L', 'Очистить чат (открывает диалог подтверждения)'));
|
|
100
|
+
lines.push('', helpLine('Ctrl+C', 'Отменить выполнение / двойное нажатие для выхода'));
|
|
101
|
+
lines.push('', helpLine('Alt+V', 'Вставить изображение из буфера (только vision модели)'));
|
|
102
|
+
if (platform() === 'win32') {
|
|
103
|
+
lines.push('', ' Для Windows: Если Alt+V не работает, убедитесь, что терминал передаёт');
|
|
104
|
+
lines.push(' правильные Alt/Meta-последовательности. Windows Terminal ≥ 1.14 работает корректно.');
|
|
105
|
+
lines.push(' В старых терминалах попробуйте: Настройки → Совместимость → "Использовать Alt как Meta".');
|
|
106
|
+
}
|
|
107
|
+
lines.push('', helpLine('Tab', 'Цикл режимов: plan → default → auto-edit → turbo'));
|
|
108
|
+
lines.push('', helpLine('PageUp / PageDown', 'Прокрутка истории чата'));
|
|
109
|
+
lines.push('', helpLine('End', 'Перейти к последнему сообщению'));
|
|
110
|
+
lines.push('', helpLine('Shift+Enter / Alt+Enter', 'Новая строка в поле ввода'));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
lines.push('', helpLine('Ctrl+L', 'Clear chat (opens confirmation dialog)'));
|
|
114
|
+
lines.push('', helpLine('Ctrl+C', 'Cancel running agent / double-tap to exit'));
|
|
115
|
+
lines.push('', helpLine('Alt+V', 'Paste image from clipboard (vision models only)'));
|
|
116
|
+
if (platform() === 'win32') {
|
|
117
|
+
lines.push('', ' Windows note: If Alt+V does not work, ensure your terminal sends');
|
|
118
|
+
lines.push(' proper Alt/Meta sequences. Windows Terminal ≥ 1.14 works correctly.');
|
|
119
|
+
lines.push(' In older terminals try: Settings → Compatibility → "Use Alt as Meta key".');
|
|
120
|
+
}
|
|
121
|
+
lines.push('', helpLine('Tab', 'Cycle approval mode: plan → default → auto-edit → turbo'));
|
|
122
|
+
lines.push('', helpLine('PageUp / PageDown', 'Scroll chat history'));
|
|
123
|
+
lines.push('', helpLine('End', 'Jump to latest message'));
|
|
124
|
+
lines.push('', helpLine('Shift+Enter / Alt+Enter', 'Insert newline in input'));
|
|
125
|
+
}
|
|
68
126
|
ctx.setMessages(prev => [...prev, { role: 'assistant', content: lines.join('\n') }]);
|
|
69
127
|
return true;
|
|
70
128
|
}
|
|
@@ -90,7 +148,7 @@ async function cmdRemember(ctx, input) {
|
|
|
90
148
|
});
|
|
91
149
|
ctx.setMessages(prev => [...prev, {
|
|
92
150
|
role: 'assistant',
|
|
93
|
-
content:
|
|
151
|
+
content: `[ok] Saved to memory: "${text.slice(0, 100)}${text.length > 100 ? '...' : ''}"`,
|
|
94
152
|
}]);
|
|
95
153
|
return true;
|
|
96
154
|
}
|
|
@@ -116,7 +174,7 @@ async function cmdForget(ctx, input) {
|
|
|
116
174
|
}
|
|
117
175
|
ctx.setMessages(prev => [...prev, {
|
|
118
176
|
role: 'assistant',
|
|
119
|
-
content:
|
|
177
|
+
content: `[ok] Deleted ${results.length} memory/memories matching: "${query}"`,
|
|
120
178
|
}]);
|
|
121
179
|
return true;
|
|
122
180
|
}
|
|
@@ -132,7 +190,7 @@ async function cmdMemory(ctx) {
|
|
|
132
190
|
const memoryList = memories.map((m, i) => `${i + 1}. **${m.name}** — ${m.description}`).join('\n');
|
|
133
191
|
ctx.setMessages(prev => [...prev, {
|
|
134
192
|
role: 'assistant',
|
|
135
|
-
content:
|
|
193
|
+
content: `**Memories** (${memories.length}):\n\n${memoryList}`,
|
|
136
194
|
}]);
|
|
137
195
|
return true;
|
|
138
196
|
}
|
|
@@ -157,7 +215,7 @@ async function cmdCompress(ctx) {
|
|
|
157
215
|
const systemMsg = ctx.messages.find(m => m.role === 'system');
|
|
158
216
|
ctx.setMessages([
|
|
159
217
|
...(systemMsg ? [systemMsg] : []),
|
|
160
|
-
{ role: 'assistant', content:
|
|
218
|
+
{ role: 'assistant', content: `**Context Compressed**\n\nOriginal: ${msgCount} messages (~${(totalLen / 1024).toFixed(1)}KB)\n\n**Summary:**\n${summary}` },
|
|
161
219
|
]);
|
|
162
220
|
}
|
|
163
221
|
catch {
|
|
@@ -188,7 +246,7 @@ async function cmdCheckpoint(ctx, input) {
|
|
|
188
246
|
}
|
|
189
247
|
ctx.setMessages(prev => [...prev, {
|
|
190
248
|
role: 'assistant',
|
|
191
|
-
content:
|
|
249
|
+
content: `[ok] Checkpoint created: **${cp.id}**\nFiles: ${cp.files.length > 0 ? cp.files.join(', ') : '(no changes)'}`,
|
|
192
250
|
}]);
|
|
193
251
|
return true;
|
|
194
252
|
}
|
|
@@ -206,7 +264,7 @@ async function cmdRestore(ctx, input) {
|
|
|
206
264
|
const list = checkpoints.slice(0, 10).map((cp, i) => `${i + 1}. **${cp.id}** — ${cp.message} (${new Date(cp.timestamp).toLocaleString()})`).join('\n');
|
|
207
265
|
ctx.setMessages(prev => [...prev, {
|
|
208
266
|
role: 'assistant',
|
|
209
|
-
content:
|
|
267
|
+
content: `**Checkpoints:**\n${list}\n\nUse \`/restore <id>\` to restore.`,
|
|
210
268
|
}]);
|
|
211
269
|
return true;
|
|
212
270
|
}
|
|
@@ -216,14 +274,14 @@ async function cmdRestore(ctx, input) {
|
|
|
216
274
|
ctx.setMessages(prev => [...prev, {
|
|
217
275
|
role: 'assistant',
|
|
218
276
|
content: restoredMessages
|
|
219
|
-
?
|
|
220
|
-
:
|
|
277
|
+
? `[ok] Restored checkpoint: ${arg}`
|
|
278
|
+
: `[err] Could not restore checkpoint: ${arg}`,
|
|
221
279
|
}]);
|
|
222
280
|
}
|
|
223
281
|
catch (err) {
|
|
224
282
|
ctx.setMessages(prev => [...prev, {
|
|
225
283
|
role: 'assistant',
|
|
226
|
-
content:
|
|
284
|
+
content: `[err] Restore failed: ${err.message}`,
|
|
227
285
|
}]);
|
|
228
286
|
}
|
|
229
287
|
ctx.setStatusText('Ready');
|
|
@@ -261,13 +319,13 @@ async function cmdMcp(ctx, input) {
|
|
|
261
319
|
await server.connect();
|
|
262
320
|
ctx.setMessages(prev => [...prev, {
|
|
263
321
|
role: 'assistant',
|
|
264
|
-
content:
|
|
322
|
+
content: `[ok] Connected to MCP server: ${name} (${server.tools.length} tools)`,
|
|
265
323
|
}]);
|
|
266
324
|
}
|
|
267
325
|
catch (err) {
|
|
268
326
|
ctx.setMessages(prev => [...prev, {
|
|
269
327
|
role: 'assistant',
|
|
270
|
-
content:
|
|
328
|
+
content: `[err] MCP connection failed: ${err.message}`,
|
|
271
329
|
}]);
|
|
272
330
|
}
|
|
273
331
|
}
|
|
@@ -292,7 +350,7 @@ async function cmdSkills(ctx, input) {
|
|
|
292
350
|
}
|
|
293
351
|
ctx.setMessages(prev => [...prev, {
|
|
294
352
|
role: 'assistant',
|
|
295
|
-
content:
|
|
353
|
+
content: `**Skill: ${skill.name}**\n\n${skill.description || ''}`,
|
|
296
354
|
}]);
|
|
297
355
|
}
|
|
298
356
|
else {
|
|
@@ -302,7 +360,7 @@ async function cmdSkills(ctx, input) {
|
|
|
302
360
|
: skills.map(s => `- **${s.name}**: ${s.description}`).join('\n');
|
|
303
361
|
ctx.setMessages(prev => [...prev, {
|
|
304
362
|
role: 'assistant',
|
|
305
|
-
content:
|
|
363
|
+
content: `**Available Skills**\n\n${list}`,
|
|
306
364
|
}]);
|
|
307
365
|
}
|
|
308
366
|
return true;
|
|
@@ -337,13 +395,13 @@ async function cmdReview(ctx, input) {
|
|
|
337
395
|
options.autoFix = true;
|
|
338
396
|
}
|
|
339
397
|
ctx.setStatusText('Reviewing code...');
|
|
340
|
-
ctx.setMessages(prev => [...prev, { role: 'assistant', content: '
|
|
398
|
+
ctx.setMessages(prev => [...prev, { role: 'assistant', content: 'Running code review...' }]);
|
|
341
399
|
try {
|
|
342
400
|
const result = await reviewCode(ctx.config, options);
|
|
343
401
|
const report = formatReviewReport(result);
|
|
344
402
|
ctx.setMessages(prev => [...prev, {
|
|
345
403
|
role: 'assistant',
|
|
346
|
-
content: `**Code Review Results**\n\nScore: **${result.score}/100**\nIssues: ${result.issues.length}\nDuration: ${(result.durationMs / 1000).toFixed(1)}s\n\n${report || '
|
|
404
|
+
content: `**Code Review Results**\n\nScore: **${result.score}/100**\nIssues: ${result.issues.length}\nDuration: ${(result.durationMs / 1000).toFixed(1)}s\n\n${report || '[ok] No issues found.'}\n\n${result.summary}`,
|
|
347
405
|
}]);
|
|
348
406
|
}
|
|
349
407
|
catch (err) {
|
|
@@ -390,8 +448,8 @@ async function cmdSandbox(ctx, input) {
|
|
|
390
448
|
ctx.setMessages(prev => [...prev, {
|
|
391
449
|
role: 'assistant',
|
|
392
450
|
content: result.exitCode === 0
|
|
393
|
-
?
|
|
394
|
-
:
|
|
451
|
+
? `[ok] **Sandbox Result** (${(result.durationMs / 1000).toFixed(1)}s, exit: ${result.exitCode})\n\n${result.stdout.slice(0, 2000)}${result.stderr ? `\n\n**Stderr:**\n${result.stderr.slice(0, 1000)}` : ''}`
|
|
452
|
+
: `[err] **Sandbox Error** (exit: ${result.exitCode})\n\n${result.stderr.slice(0, 2000)}`,
|
|
395
453
|
}]);
|
|
396
454
|
}
|
|
397
455
|
catch (err) {
|
|
@@ -417,14 +475,14 @@ async function cmdGit(ctx, input) {
|
|
|
417
475
|
ctx.setMessages(prev => [...prev, {
|
|
418
476
|
role: 'assistant',
|
|
419
477
|
content: result.success
|
|
420
|
-
?
|
|
421
|
-
:
|
|
478
|
+
? `[ok] Committed: \`${msg}\` (${result.hash?.slice(0, 7)})`
|
|
479
|
+
: `[err] Commit failed: ${result.error}`,
|
|
422
480
|
}]);
|
|
423
481
|
}
|
|
424
482
|
catch (err) {
|
|
425
483
|
ctx.setMessages(prev => [...prev, {
|
|
426
484
|
role: 'assistant',
|
|
427
|
-
content:
|
|
485
|
+
content: `[err] Commit failed: ${err.message}`,
|
|
428
486
|
}]);
|
|
429
487
|
}
|
|
430
488
|
ctx.setStatusText('Ready');
|
|
@@ -444,14 +502,14 @@ async function cmdGit(ctx, input) {
|
|
|
444
502
|
ctx.setMessages(prev => [...prev, {
|
|
445
503
|
role: 'assistant',
|
|
446
504
|
content: result.success
|
|
447
|
-
?
|
|
448
|
-
:
|
|
505
|
+
? `Switched to branch: ${name}`
|
|
506
|
+
: `[err] ${result.error}`,
|
|
449
507
|
}]);
|
|
450
508
|
}
|
|
451
509
|
catch (err) {
|
|
452
510
|
ctx.setMessages(prev => [...prev, {
|
|
453
511
|
role: 'assistant',
|
|
454
|
-
content:
|
|
512
|
+
content: `[err] Branch failed: ${err.message}`,
|
|
455
513
|
}]);
|
|
456
514
|
}
|
|
457
515
|
return true;
|
|
@@ -467,7 +525,7 @@ async function cmdGit(ctx, input) {
|
|
|
467
525
|
catch (err) {
|
|
468
526
|
ctx.setMessages(prev => [...prev, {
|
|
469
527
|
role: 'assistant',
|
|
470
|
-
content:
|
|
528
|
+
content: `[err] Diff failed: ${err.message}`,
|
|
471
529
|
}]);
|
|
472
530
|
}
|
|
473
531
|
return true;
|
|
@@ -484,7 +542,7 @@ async function cmdGit(ctx, input) {
|
|
|
484
542
|
catch (err) {
|
|
485
543
|
ctx.setMessages(prev => [...prev, {
|
|
486
544
|
role: 'assistant',
|
|
487
|
-
content:
|
|
545
|
+
content: `[err] Status failed: ${err.message}`,
|
|
488
546
|
}]);
|
|
489
547
|
}
|
|
490
548
|
return true;
|
|
@@ -519,7 +577,7 @@ async function cmdLoop(ctx, input) {
|
|
|
519
577
|
scheduler.clearAll();
|
|
520
578
|
ctx.setMessages(prev => [...prev, {
|
|
521
579
|
role: 'assistant',
|
|
522
|
-
content: '
|
|
580
|
+
content: '[ok] All tasks cleared.',
|
|
523
581
|
}]);
|
|
524
582
|
}
|
|
525
583
|
else if (sub) {
|
|
@@ -537,7 +595,7 @@ async function cmdLoop(ctx, input) {
|
|
|
537
595
|
const task = scheduler.addTask(prompt, intervalMs);
|
|
538
596
|
ctx.setMessages(prev => [...prev, {
|
|
539
597
|
role: 'assistant',
|
|
540
|
-
content:
|
|
598
|
+
content: `[ok] Task scheduled: "${prompt}" every ${intervalStr} (ID: ${task.id})`,
|
|
541
599
|
}]);
|
|
542
600
|
}
|
|
543
601
|
else {
|
|
@@ -559,7 +617,7 @@ async function cmdStats(ctx) {
|
|
|
559
617
|
const cost = metrics ? metrics.estimatedCostUSD(ctx.config.model) : 0;
|
|
560
618
|
let content = `**Session Statistics:**\n- Messages: ${ctx.messages.length}\n- MCP Tools: ${mcpTools}\n- Skills: ${skills}\n- Subagents: ${agents}\n- Scheduled Tasks: ${tasks}\n- Extensions: ${exts}\n- Theme: ${themeManager.theme.name}\n- Language: ${i18n.getLocale()}\n- Approval Mode: ${ctx.approvalMode}`;
|
|
561
619
|
if (usage && usage.total > 0) {
|
|
562
|
-
content += `\n\n**Token Usage:**\n- Input: ${usage.input.toLocaleString()} tokens\n- Output: ${usage.output.toLocaleString()} tokens\n- Total: ${usage.total.toLocaleString()} tokens\n- Est. Cost: $${cost.toFixed(4)}`;
|
|
620
|
+
content += `\n\n**Token Usage:**\n- Input: ${usage.input.toLocaleString()} tokens\n- Cache hit input: ${usage.cacheHitInput.toLocaleString()} tokens\n- Cache miss input: ${usage.cacheMissInput.toLocaleString()} tokens\n- Output: ${usage.output.toLocaleString()} tokens\n- Reasoning output: ${usage.reasoningOutput.toLocaleString()} tokens\n- Total: ${usage.total.toLocaleString()} tokens\n- Est. Cost: $${cost.toFixed(4)}`;
|
|
563
621
|
}
|
|
564
622
|
ctx.setMessages(prev => [...prev, { role: 'assistant', content }]);
|
|
565
623
|
return true;
|
|
@@ -582,11 +640,11 @@ async function cmdTheme(ctx, input) {
|
|
|
582
640
|
}
|
|
583
641
|
const success = themeManager.setTheme(themeName);
|
|
584
642
|
if (success) {
|
|
585
|
-
ctx.addServiceNotice?.(
|
|
643
|
+
ctx.addServiceNotice?.(`[theme] Тема изменена: ${themeName}`);
|
|
586
644
|
ctx.setStatusText(`Тема: ${themeName}`);
|
|
587
645
|
}
|
|
588
646
|
else {
|
|
589
|
-
ctx.addServiceNotice?.(
|
|
647
|
+
ctx.addServiceNotice?.(`[err] Тема "${themeName}" не найдена`);
|
|
590
648
|
}
|
|
591
649
|
return true;
|
|
592
650
|
}
|
|
@@ -597,7 +655,7 @@ async function cmdModel(ctx, input) {
|
|
|
597
655
|
ctx.onModelPicker();
|
|
598
656
|
}
|
|
599
657
|
else {
|
|
600
|
-
const list = DEEPSEEK_MODELS.map(m => `- **${m.label}** (\`${m.id}\`): ${m.description}${m.id === ctx.config.model ? '
|
|
658
|
+
const list = DEEPSEEK_MODELS.map(m => `- **${m.label}** (\`${m.id}\`): ${m.description}${m.id === ctx.config.model ? ' [current]' : ''}`).join('\n');
|
|
601
659
|
ctx.setMessages(prev => [...prev, {
|
|
602
660
|
role: 'assistant',
|
|
603
661
|
content: `**Available Models:**\n${list}\n\nCurrent: **${ctx.config.model}**\nUse \`/model <id>\` to switch.`,
|
|
@@ -611,21 +669,27 @@ async function cmdModel(ctx, input) {
|
|
|
611
669
|
const { saveConfig } = await import('../config/loader.js');
|
|
612
670
|
await saveConfig({ ...ctx.config, model: targetId });
|
|
613
671
|
const label = found?.label ?? targetId;
|
|
614
|
-
ctx.addServiceNotice?.(
|
|
672
|
+
ctx.addServiceNotice?.(`[model] Модель: ${label} (${targetId})`);
|
|
615
673
|
return true;
|
|
616
674
|
}
|
|
617
675
|
async function cmdLang(ctx, input) {
|
|
618
676
|
const code = input.split(/\s+/).pop()?.toLowerCase();
|
|
619
677
|
if (!code || !['en', 'ru', 'zh'].includes(code)) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
678
|
+
// Open interactive picker if available, fallback to usage text
|
|
679
|
+
if (ctx.onLangPicker) {
|
|
680
|
+
ctx.onLangPicker();
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
ctx.setMessages(prev => [...prev, {
|
|
684
|
+
role: 'assistant',
|
|
685
|
+
content: 'Usage: `/lang en|ru|zh`',
|
|
686
|
+
}]);
|
|
687
|
+
}
|
|
624
688
|
return true;
|
|
625
689
|
}
|
|
626
690
|
i18n.setLocale(code);
|
|
627
691
|
await saveConfig({ ...ctx.config, language: code });
|
|
628
|
-
ctx.addServiceNotice?.(
|
|
692
|
+
ctx.addServiceNotice?.(`[lang] Язык изменён: ${code}`);
|
|
629
693
|
return true;
|
|
630
694
|
}
|
|
631
695
|
async function cmdExtensions(ctx) {
|
|
@@ -651,7 +715,7 @@ async function cmdFollowup(ctx) {
|
|
|
651
715
|
const suggestions = generateFollowups(lastContent);
|
|
652
716
|
ctx.setMessages(prev => [...prev, {
|
|
653
717
|
role: 'assistant',
|
|
654
|
-
content:
|
|
718
|
+
content: `**Follow-up suggestions:**\n${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n')}`,
|
|
655
719
|
}]);
|
|
656
720
|
return true;
|
|
657
721
|
}
|
|
@@ -673,7 +737,7 @@ async function cmdLogs(ctx) {
|
|
|
673
737
|
const fileList = files.map((f, i) => `${i + 1}. ${f}`).join('\n');
|
|
674
738
|
ctx.setMessages(prev => [...prev, {
|
|
675
739
|
role: 'assistant',
|
|
676
|
-
content:
|
|
740
|
+
content: `**Recent Logs** (${files.length} files):\n\n${fileList}\n\n**Tail of ${files[0]}:**\n\`\`\`\n${latestLog}\n\`\`\``,
|
|
677
741
|
}]);
|
|
678
742
|
}
|
|
679
743
|
catch {
|
|
@@ -697,7 +761,7 @@ async function cmdTools(ctx) {
|
|
|
697
761
|
const modeToolNames = new Set(modeTools.map(t => t.tool.name));
|
|
698
762
|
const toolLines = allTools.map(def => {
|
|
699
763
|
const t = def.tool;
|
|
700
|
-
const inMode = modeToolNames.has(t.name) ? '
|
|
764
|
+
const inMode = modeToolNames.has(t.name) ? '[on]' : '[off]';
|
|
701
765
|
let approvalLabel;
|
|
702
766
|
if (def.approval === 'never') {
|
|
703
767
|
approvalLabel = 'read-only';
|
|
@@ -716,7 +780,7 @@ async function cmdTools(ctx) {
|
|
|
716
780
|
'',
|
|
717
781
|
`**Текущий режим:** \`${ctx.approvalMode}\``,
|
|
718
782
|
...(ctx.approvalMode === 'plan'
|
|
719
|
-
? ['>
|
|
783
|
+
? ['> [warn] В PLAN mode доступны только read-only инструменты. Для записи используйте `/setup` и смените режим на default/auto-edit/turbo.']
|
|
720
784
|
: []),
|
|
721
785
|
].filter(Boolean).join('\n');
|
|
722
786
|
ctx.setMessages(prev => [...prev, {
|
|
@@ -739,7 +803,7 @@ async function cmdBrowserTest(ctx, input) {
|
|
|
739
803
|
else {
|
|
740
804
|
headless = false; // default: headed (видимое окно)
|
|
741
805
|
}
|
|
742
|
-
ctx.setStatusText('
|
|
806
|
+
ctx.setStatusText('Запуск browser test...');
|
|
743
807
|
// Сохраняем текущий режим Chrome, чтобы восстановить после теста
|
|
744
808
|
const prevState = chromeManager.getState();
|
|
745
809
|
const prevHeadless = prevState.headless;
|
|
@@ -753,7 +817,7 @@ async function cmdBrowserTest(ctx, input) {
|
|
|
753
817
|
catch (err) {
|
|
754
818
|
ctx.setMessages(prev => [...prev, {
|
|
755
819
|
role: 'assistant',
|
|
756
|
-
content: `##
|
|
820
|
+
content: `## Browser Test Error\n\n\`\`\`\n${String(err)}\n\`\`\``,
|
|
757
821
|
}]);
|
|
758
822
|
}
|
|
759
823
|
finally {
|
|
@@ -771,21 +835,21 @@ async function cmdBrowserRealTest(ctx, input) {
|
|
|
771
835
|
const headless = parts.includes('--headless');
|
|
772
836
|
const siteArgs = parts.filter(p => !p.startsWith('--'));
|
|
773
837
|
const sites = siteArgs.length > 0 ? siteArgs : undefined;
|
|
774
|
-
ctx.setStatusText('
|
|
838
|
+
ctx.setStatusText('Real site smoke-test...');
|
|
775
839
|
const prevState = chromeManager.getState();
|
|
776
840
|
try {
|
|
777
841
|
const report = await browserRealTest({ sites, headless });
|
|
778
842
|
if (saveReport) {
|
|
779
843
|
const { writeFile } = await import('node:fs/promises');
|
|
780
844
|
await writeFile('BROWSER_REAL_TEST_REPORT.md', report, 'utf8');
|
|
781
|
-
ctx.addServiceNotice?.('
|
|
845
|
+
ctx.addServiceNotice?.('Report saved: BROWSER_REAL_TEST_REPORT.md');
|
|
782
846
|
}
|
|
783
847
|
ctx.setMessages(prev => [...prev, { role: 'assistant', content: report }]);
|
|
784
848
|
}
|
|
785
849
|
catch (err) {
|
|
786
850
|
ctx.setMessages(prev => [...prev, {
|
|
787
851
|
role: 'assistant',
|
|
788
|
-
content: `##
|
|
852
|
+
content: `## browser-real-test error\n\`\`\`\n${String(err)}\n\`\`\``,
|
|
789
853
|
}]);
|
|
790
854
|
}
|
|
791
855
|
finally {
|
|
@@ -805,7 +869,7 @@ async function cmdChrome(ctx, input) {
|
|
|
805
869
|
const modeStr = state.connected
|
|
806
870
|
? (state.headless ? 'headless (фоновый)' : 'headed (видимое окно)')
|
|
807
871
|
: 'не запущен';
|
|
808
|
-
ctx.addServiceNotice?.(
|
|
872
|
+
ctx.addServiceNotice?.(`Chrome: ${modeStr}${state.connected ? ` | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}` : ''}`);
|
|
809
873
|
return true;
|
|
810
874
|
}
|
|
811
875
|
// Determine desired mode
|
|
@@ -817,7 +881,7 @@ async function cmdChrome(ctx, input) {
|
|
|
817
881
|
desiredHeadless = false;
|
|
818
882
|
}
|
|
819
883
|
else {
|
|
820
|
-
ctx.addServiceNotice?.('
|
|
884
|
+
ctx.addServiceNotice?.('[err] /chrome: используйте --headed, --headless или без флага для статуса');
|
|
821
885
|
return true;
|
|
822
886
|
}
|
|
823
887
|
try {
|
|
@@ -827,10 +891,10 @@ async function cmdChrome(ctx, input) {
|
|
|
827
891
|
// Save to config
|
|
828
892
|
ctx.config.chromeHeadless = state.headless;
|
|
829
893
|
saveConfig({ chromeHeadless: state.headless }).catch(() => { });
|
|
830
|
-
ctx.addServiceNotice?.(
|
|
894
|
+
ctx.addServiceNotice?.(`Chrome: ${modeStr} | PID: ${state.managedProcessPid ?? '—'} | Порт: ${state.debugPort}`);
|
|
831
895
|
}
|
|
832
896
|
catch (err) {
|
|
833
|
-
ctx.addServiceNotice?.(
|
|
897
|
+
ctx.addServiceNotice?.(`[err] Chrome: ${String(err)}`);
|
|
834
898
|
}
|
|
835
899
|
return true;
|
|
836
900
|
}
|
|
@@ -839,12 +903,12 @@ async function cmdLastBrowserTest(ctx) {
|
|
|
839
903
|
if (!result) {
|
|
840
904
|
ctx.setMessages(prev => [...prev, {
|
|
841
905
|
role: 'assistant',
|
|
842
|
-
content: '##
|
|
906
|
+
content: '## Последний browser test\n\nНет сохранённого отчёта последнего теста. Запустите `/browser-test` сначала.',
|
|
843
907
|
}]);
|
|
844
908
|
return true;
|
|
845
909
|
}
|
|
846
910
|
const lines = [
|
|
847
|
-
'##
|
|
911
|
+
'## Последний browser test',
|
|
848
912
|
'',
|
|
849
913
|
`> **Timestamp:** ${result.timestamp}`,
|
|
850
914
|
'> **Источник:** сохранённый structured result (не LLM-реконструкция)',
|
|
@@ -853,7 +917,7 @@ async function cmdLastBrowserTest(ctx) {
|
|
|
853
917
|
'|-----|--------|-------------|',
|
|
854
918
|
];
|
|
855
919
|
for (const step of result.steps) {
|
|
856
|
-
const icon = step.status === 'passed' ? '
|
|
920
|
+
const icon = step.status === 'passed' ? '[ok]' : step.status === 'failed' ? '[err]' : '[skip]';
|
|
857
921
|
const dur = step.durationMs > 0 ? `${step.durationMs}ms` : '—';
|
|
858
922
|
lines.push(`| ${icon} ${step.name} | ${step.status} | ${dur} |`);
|
|
859
923
|
}
|
|
@@ -878,32 +942,76 @@ async function cmdCapabilities(ctx) {
|
|
|
878
942
|
`**Режим:** \`${ctx.approvalMode}\``,
|
|
879
943
|
'',
|
|
880
944
|
'### Чтение и поиск',
|
|
881
|
-
...modeReadTools.map(t => ` -
|
|
945
|
+
...modeReadTools.map(t => ` - [on] \`${t.tool.name}\` — ${t.tool.description}`),
|
|
882
946
|
...readTools
|
|
883
947
|
.filter(t => !modeReadTools.some(mt => mt.tool.name === t.tool.name))
|
|
884
|
-
.map(t => ` -
|
|
948
|
+
.map(t => ` - [off] \`${t.tool.name}\` — заблокирован в PLAN mode`),
|
|
885
949
|
'',
|
|
886
950
|
'### Запись и исполнение',
|
|
887
|
-
...modeWriteTools.map(t => ` -
|
|
951
|
+
...modeWriteTools.map(t => ` - [on] \`${t.tool.name}\` — ${t.tool.description} (${t.approval === 'auto' ? 'авто-подтверждение' : 'требует подтверждения'})`),
|
|
888
952
|
...writeTools
|
|
889
953
|
.filter(t => !modeWriteTools.some(mt => mt.tool.name === t.tool.name))
|
|
890
|
-
.map(t => ` -
|
|
954
|
+
.map(t => ` - [off] \`${t.tool.name}\` — заблокирован в PLAN mode`),
|
|
891
955
|
'',
|
|
892
956
|
];
|
|
893
957
|
if (ctx.approvalMode === 'plan') {
|
|
894
|
-
lines.push('>
|
|
958
|
+
lines.push('> [warn] **Вы в PLAN mode.**', '> У меня есть инструменты write_file и edit, но в этом режиме они отключены.', '> Я могу предложить изменения, но не могу применить их напрямую.', '> Используйте `/setup` и выберите другой режим (default, auto-edit, turbo) для включения записи.', '');
|
|
895
959
|
}
|
|
896
960
|
lines.push('### Дополнительно');
|
|
897
|
-
lines.push(' -
|
|
898
|
-
lines.push(' -
|
|
899
|
-
lines.push(' -
|
|
900
|
-
lines.push(' -
|
|
961
|
+
lines.push(' - **MCP серверы** — подключаемые внешние инструменты');
|
|
962
|
+
lines.push(' - **Расширения** — плагины, добавляющие функциональность');
|
|
963
|
+
lines.push(' - **Навыки (Skills)** — предустановленные сценарии работы');
|
|
964
|
+
lines.push(' - **Под-агенты** — дочерние агенты для параллельных задач');
|
|
901
965
|
ctx.setMessages(prev => [...prev, {
|
|
902
966
|
role: 'assistant',
|
|
903
967
|
content: lines.join('\n'),
|
|
904
968
|
}]);
|
|
905
969
|
return true;
|
|
906
970
|
}
|
|
971
|
+
// ─── Budget command handler ──────────────────────────────────────────────────
|
|
972
|
+
async function cmdBudget(ctx, input) {
|
|
973
|
+
const sub = input.slice('/budget'.length).trim();
|
|
974
|
+
const notice = ctx.addServiceNotice ?? ((text) => {
|
|
975
|
+
ctx.setMessages(prev => [...prev, { role: 'assistant', content: text }]);
|
|
976
|
+
});
|
|
977
|
+
if (sub === 'status') {
|
|
978
|
+
const budget = ctx.getBudget?.();
|
|
979
|
+
if (!budget) {
|
|
980
|
+
notice('Budget: off');
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
const prefix = 'Budget: active';
|
|
984
|
+
const lines = [
|
|
985
|
+
prefix,
|
|
986
|
+
`maxToolCalls: ${budget.maxToolCalls}`,
|
|
987
|
+
`maxApiCalls: ${budget.maxApiCalls}`,
|
|
988
|
+
`maxReadFiles: ${budget.maxReadFiles}`,
|
|
989
|
+
`maxShellCommands: ${budget.maxShellCommands}`,
|
|
990
|
+
];
|
|
991
|
+
notice(lines.join('\n'));
|
|
992
|
+
}
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
if (sub === 'off') {
|
|
996
|
+
ctx.setBudget?.(undefined);
|
|
997
|
+
notice('Budget: off');
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
if (sub === 'audit' || sub === 'small') {
|
|
1001
|
+
ctx.setBudget?.({ ...AUDIT_BUDGET_PRESET });
|
|
1002
|
+
const lines = [
|
|
1003
|
+
'Budget: audit enabled',
|
|
1004
|
+
`maxToolCalls: ${AUDIT_BUDGET_PRESET.maxToolCalls}`,
|
|
1005
|
+
`maxApiCalls: ${AUDIT_BUDGET_PRESET.maxApiCalls}`,
|
|
1006
|
+
`maxReadFiles: ${AUDIT_BUDGET_PRESET.maxReadFiles}`,
|
|
1007
|
+
`maxShellCommands: ${AUDIT_BUDGET_PRESET.maxShellCommands}`,
|
|
1008
|
+
];
|
|
1009
|
+
notice(lines.join('\n'));
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
notice('Usage: /budget status|off|audit|small');
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
907
1015
|
export const COMMANDS = [
|
|
908
1016
|
{ name: '/help', description: 'Show this help', handler: cmdHelp },
|
|
909
1017
|
{ name: '/setup', description: 'Settings: language, API key, theme, mode', handler: cmdSetup },
|
|
@@ -935,6 +1043,7 @@ export const COMMANDS = [
|
|
|
935
1043
|
{ name: '/browser-real-test', description: 'Smoke test on real websites', handler: cmdBrowserRealTest },
|
|
936
1044
|
{ name: '/last-browser-test', description: 'Show last browser test report', handler: cmdLastBrowserTest },
|
|
937
1045
|
{ name: '/chrome', description: 'Chrome mode: --headed|--headless|-s', handler: cmdChrome },
|
|
1046
|
+
{ name: '/budget', description: 'Budget: /budget status|off|audit|small', handler: cmdBudget },
|
|
938
1047
|
];
|
|
939
1048
|
export const COMMAND_NAMES = COMMANDS.map(c => c.name);
|
|
940
1049
|
export const COMMAND_MAP = new Map(COMMANDS.map(c => [c.name, c.description]));
|