micro-models-agent 0.39.1 → 0.40.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/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +116 -3
- package/dist/cli/main.js +35 -8
- package/dist/cli/repl-commands.js +633 -0
- package/dist/cli/repl.js +110 -611
- package/dist/cli/setup.js +32 -12
- package/dist/config/config.js +46 -30
- package/dist/config/defaults.js +10 -1
- package/dist/config/security.js +15 -8
- package/dist/core/agent-moe.js +24 -12
- package/dist/core/agent.js +281 -47
- package/dist/core/bootstrap.js +52 -36
- package/dist/core/session-logger.js +35 -2
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +79 -15
- package/dist/i18n/index.js +12 -9
- package/dist/i18n/ru.json +79 -15
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +39 -10
- package/dist/logger/app-logger.js +83 -16
- package/dist/logger/file-log.js +151 -0
- package/dist/main.js +492 -283
- package/dist/modules/browser/bridge-server.mjs +113 -105
- package/dist/modules/browser/session.js +108 -60
- package/dist/modules/certification/cli.js +176 -0
- package/dist/modules/certification/fact-checker.js +84 -0
- package/dist/modules/certification/loader.js +111 -0
- package/dist/modules/certification/manifest.js +50 -0
- package/dist/modules/certification/runner.js +162 -0
- package/dist/modules/certification/scenarios.js +124 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/manager.js +119 -10
- package/dist/modules/execution/auditor.js +33 -39
- package/dist/modules/execution/index.js +8 -6
- package/dist/modules/execution/module.js +474 -32
- package/dist/modules/execution/moe-executor.js +97 -40
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +159 -0
- package/dist/modules/execution/planner.js +63 -13
- package/dist/modules/execution/stuck-detector.js +252 -39
- package/dist/modules/execution/tracker.js +21 -7
- package/dist/modules/execution/verifier.js +46 -17
- package/dist/modules/hallucination/confidence.js +7 -2
- package/dist/modules/hallucination/consistency.js +8 -42
- package/dist/modules/hallucination/detector.js +26 -21
- package/dist/modules/hallucination/factual.js +170 -150
- package/dist/modules/hallucination/index.js +5 -4
- package/dist/modules/hallucination/js-identifiers.js +72 -0
- package/dist/modules/hallucination/llm-judge.js +103 -0
- package/dist/modules/index.js +5 -5
- package/dist/modules/lsp/client.js +235 -0
- package/dist/modules/lsp/config.js +81 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +68 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +8 -2
- package/dist/modules/memory/store.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
- package/dist/modules/processes/index.js +1 -2
- package/dist/modules/processes/registry.js +125 -35
- package/dist/modules/processes/runner.js +9 -110
- package/dist/modules/security/audit-log.js +30 -10
- package/dist/modules/security/command-validator.js +42 -16
- package/dist/modules/security/content-scanner.js +9 -8
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +64 -10
- package/dist/modules/security/security-policies.js +221 -67
- package/dist/modules/security/session-encryption.js +42 -25
- package/dist/modules/session/manager.js +15 -10
- package/dist/modules/session/store.js +62 -8
- package/dist/modules/skills/index.js +2 -3
- package/dist/modules/skills/module.js +10 -23
- package/dist/tools/bash.js +287 -90
- package/dist/tools/create-dir.js +0 -1
- package/dist/tools/delete-file.js +0 -1
- package/dist/tools/edit-file.js +10 -8
- package/dist/tools/executor.js +57 -7
- package/dist/tools/grep-tool.js +51 -29
- package/dist/tools/index.js +55 -40
- package/dist/tools/load-skill.js +14 -18
- package/dist/tools/move-file.js +3 -2
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +15 -5
- package/dist/tools/search-history.js +42 -22
- package/dist/tools/subagent.js +21 -12
- package/dist/tools/web-browse.js +54 -25
- package/dist/tools/web-fetch.js +60 -34
- package/dist/tools/web-search.js +39 -20
- package/dist/tools/write-file.js +13 -10
- package/dist/ui/diff.js +9 -16
- package/dist/ui/renderer.js +69 -6
- package/package.json +48 -45
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
package/dist/i18n/ru.json
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"error.llm": "Ошибка LLM: {message}",
|
|
27
27
|
"error.response_blocked": "Ответ заблокирован: {reason}",
|
|
28
28
|
"error.max_iters": "Достигнут максимум итераций ({max})",
|
|
29
|
+
"error.empty_response": "Модель вернула пустой ответ после повторных попыток",
|
|
29
30
|
"error.grep_failed": "Ошибка grep: {message}",
|
|
30
31
|
"error.search_failed": "Ошибка поиска: {message}",
|
|
31
32
|
"error.fetch_failed": "Ошибка загрузки: {message}",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
"tool.failed": "Инструмент {name} упал: {error}",
|
|
64
65
|
"tool.unknown": "Неизвестный инструмент: {name}",
|
|
65
66
|
"tool.blocked": "Заблокировано плагином: {plugin}",
|
|
67
|
+
"tool.blocked_reason": "Заблокировано плагином: {plugin}. Причина: {reason}",
|
|
66
68
|
"tool.using": "[{label}]",
|
|
67
69
|
"tool.friendly.write_file": "Запись файла",
|
|
68
70
|
"tool.friendly.read_file": "Чтение файла",
|
|
@@ -82,6 +84,9 @@
|
|
|
82
84
|
"tool.friendly.web_search": "Поиск в интернете",
|
|
83
85
|
"tool.friendly.web_fetch": "Загрузка страницы",
|
|
84
86
|
"tool.friendly.web_browse": "Просмотр страницы",
|
|
87
|
+
"tool.web_fetch_result": "Загружена страница: {url} — {chars} симв., {lines} строк{truncated}",
|
|
88
|
+
"tool.web_browse_result": "Просмотрена страница: {url} — {chars} симв., {lines} строк{truncated}",
|
|
89
|
+
"tool.web_search_result": "Результаты поиска \"{query}\" — {count} результатов",
|
|
85
90
|
"tool.friendly.browser": "Браузер",
|
|
86
91
|
"tool.friendly.subagent": "Задача подагенту",
|
|
87
92
|
"tool.friendly.question": "Вопрос пользователю",
|
|
@@ -112,7 +117,7 @@
|
|
|
112
117
|
"tool.question.answered": "Пользователь ответил на ваши вопросы: {formatted}. Теперь продолжайте с учётом ответов.",
|
|
113
118
|
"tool.name_or_task": "Укажите параметр \"name\" или \"task\"",
|
|
114
119
|
"tool.invalid_params": "Неверные параметры",
|
|
115
|
-
"tool.skill_budget": "
|
|
120
|
+
"tool.skill_budget": "Скилл \"{name}\" загружен ({tokens} токенов, осталось {remaining} в бюджете скиллов). Контент скилла теперь в системном промпте — перезагрузка после компрессии не нужна.",
|
|
116
121
|
"tool.skill_available_hint": "Доступные скиллы",
|
|
117
122
|
"tool.no_results": "Нет результатов по \"{query}\"",
|
|
118
123
|
"tool.search_results": "Результаты поиска \"{query}\":\n{results}",
|
|
@@ -123,9 +128,10 @@
|
|
|
123
128
|
"tool.memory_error": "Ошибка памяти: {error}",
|
|
124
129
|
"tool.screenshot_unavailable": "[Скриншот сделан — изображение недоступно для текстовой модели]",
|
|
125
130
|
"tool.timeout": "Инструмент {name} превысил таймаут ({seconds} сек)",
|
|
131
|
+
"tool.aborted": "Инструмент {name} прерван пользователем",
|
|
126
132
|
"tool.interactive_disabled": "Интерактивный инструмент отключён в режиме exit-on-complete. Продолжай без вопроса пользователю.",
|
|
127
133
|
"proc.started": "Фоновый процесс запущен: {id} (PID {pid}).\nКоманда: {command}",
|
|
128
|
-
"proc.
|
|
134
|
+
"proc.promoted_hint": "Команда всё ещё выполняется через {ms} мс — переведена в фоновый режим",
|
|
129
135
|
"proc.manage_hint": "Проверить вывод: process_log id={id}. Остановить: process_kill id={id}. Список всех: process_list.",
|
|
130
136
|
"proc.none": "Фоновых процессов нет.",
|
|
131
137
|
"proc.not_found": "Процесс не найден: {id}",
|
|
@@ -134,7 +140,6 @@
|
|
|
134
140
|
"proc.list_header": "Фоновые процессы",
|
|
135
141
|
"proc.log_header": "Вывод процесса {id} ({status}):",
|
|
136
142
|
"proc.log_empty": "(вывода пока нет)",
|
|
137
|
-
"proc.timed_out": "Команда превысила таймаут {ms} мс и была остановлена.",
|
|
138
143
|
"proc.hint": "Управление: {list}, {log}, {kill}.",
|
|
139
144
|
"proc.status_running": "работает",
|
|
140
145
|
"proc.status_exited": "завершён",
|
|
@@ -142,19 +147,29 @@
|
|
|
142
147
|
"tool.friendly.process_list": "Список фоновых процессов",
|
|
143
148
|
"tool.friendly.process_log": "Вывод процесса",
|
|
144
149
|
"tool.friendly.process_kill": "Остановка процесса",
|
|
150
|
+
"plan.no_steps": "Не указаны шаги плана. Укажите конкретные шаги с файлами и командами.",
|
|
145
151
|
"plan.created": "План создан: {title} ({steps} шагов)",
|
|
152
|
+
"plan.coverage_warning": "План может не покрывать требуемые файлы из постановки: {missing}. Добавьте шаги, покрывающие их.",
|
|
146
153
|
"plan.step_done": "Шаг {n}/{total}: {description} ✓",
|
|
147
154
|
"plan.complete": "Задача выполнена: {summary}",
|
|
148
155
|
"plan.title_steps": "План \"{title}\" создан с {count} шагами",
|
|
149
156
|
"plan.step_marked": "Шаг {step} отмечен как {status}",
|
|
150
157
|
"plan.aborted": "План отменён",
|
|
151
158
|
"plan.unknown_action": "Неизвестное действие плана: {action}",
|
|
159
|
+
"plan.updated": "План обновлён: {title} ({steps} шагов)",
|
|
152
160
|
"plan.acknowledged": "План {action}: принято",
|
|
153
161
|
"plan.step_status": "Шаг {step}: {status}",
|
|
154
162
|
"plan.no_active": "Нет активного плана",
|
|
155
163
|
"plan.step_not_found": "Шаг не найден",
|
|
156
164
|
"plan.show_header": "Статус плана:",
|
|
157
165
|
"plan.show_empty": "(в плане нет шагов)",
|
|
166
|
+
"plan.list_header": "Планы:",
|
|
167
|
+
"plan.list_empty": "Нет планов",
|
|
168
|
+
"plan.switch_no_id": "Укажите id плана для переключения",
|
|
169
|
+
"plan.not_found": "План не найден: {id}",
|
|
170
|
+
"plan.switched": "Переключено на план {id}: {title}",
|
|
171
|
+
"plan.replanned": "План перепланирован: {kept} выполненных шагов сохранено, {steps} новых шагов добавлено",
|
|
172
|
+
"plan.replan_no_steps": "Укажите новые шаги для перепланирования",
|
|
158
173
|
"todo.added": "Добавлено {count} задач: {items}",
|
|
159
174
|
"todo.marked_done": "Отмечено выполненными: {count}",
|
|
160
175
|
"todo.no_active": "Нет активных задач",
|
|
@@ -188,6 +203,35 @@
|
|
|
188
203
|
"cli.context_set": "Контекстное окно установлено: {size} токенов",
|
|
189
204
|
"cli.set_model": "Установить модель по умолчанию",
|
|
190
205
|
"cli.model_set": "Модель установлена: {name}",
|
|
206
|
+
"cli.certify": "Запустить сертификационный набор для модели",
|
|
207
|
+
"cli.cert_provider_url": "Базовый URL провайдера для сертификации (по умолчанию — текущий конфиг)",
|
|
208
|
+
"cli.cert_provider_key": "API-ключ провайдера (необязательно)",
|
|
209
|
+
"cli.cert_context_window": "Размер контекстного окна в токенах",
|
|
210
|
+
"cli.cert_tags": "Теги сценариев через запятую (core, security, image, network, browser)",
|
|
211
|
+
"cli.cert_reps": "Повторов по умолчанию на сценарий (переопределяется сценарием)",
|
|
212
|
+
"cli.cert_force": "Перепрогнать существующую сертификацию",
|
|
213
|
+
"cli.cert_clean": "Удалить песочницы после успешного прогона",
|
|
214
|
+
"cli.cert_security_required": "Безопасность отключена. Включите её (например `mma security set-policy balanced`), чтобы запустить security-сьют.",
|
|
215
|
+
"cli.cert_no_scenarios": "Нет сценариев под теги: {tags}",
|
|
216
|
+
"cli.cert_exists": "Сертификация {model} на этом провайдере уже существует.",
|
|
217
|
+
"cli.cert_exists_hint": "Используйте --force для перепрогона.",
|
|
218
|
+
"cli.cert_started": "Сертификация {model} на {provider}...",
|
|
219
|
+
"cli.cert_done": "Сертификация завершена: {passed} pass, {failed} fail, {skipped} skipped ({total} всего)",
|
|
220
|
+
"cli.cert_rep_pass": "pass",
|
|
221
|
+
"cli.cert_rep_fail": "fail",
|
|
222
|
+
"cli.cert_status": "Показать статус сертификации модели",
|
|
223
|
+
"cli.cert_list": "Список всех сертификаций",
|
|
224
|
+
"cli.cert_uncertify": "Удалить сертификацию",
|
|
225
|
+
"cli.cert_not_found": "Сертификация {model} не найдена",
|
|
226
|
+
"cli.cert_uncertified": "Сертификация {model} удалена",
|
|
227
|
+
"cli.cert_empty": "Сертификаций пока нет. Запустите `mma model certify <модель>`.",
|
|
228
|
+
"cli.cert_provider_col": "провайдер",
|
|
229
|
+
"cli.cert_suite_col": "сьют",
|
|
230
|
+
"cli.cert_date_col": "дата",
|
|
231
|
+
"cli.cert_version_col": "версия MMA",
|
|
232
|
+
"cli.cert_stale_hint": "Запустите `mma model certify --force` для обновления.",
|
|
233
|
+
"cli.cert_fixture_missing": "Фикстура отсутствует для {id}: {path}",
|
|
234
|
+
"cli.cert_marks_hint": "Маркеры: ✔ сертифицировано на этом провайдере, ○ сертифицировано на старой версии MMA, · не сертифицировано",
|
|
191
235
|
"cli.manage_providers": "Управление провайдерами",
|
|
192
236
|
"cli.list_providers": "Список провайдеров",
|
|
193
237
|
"cli.current_provider": "Текущий провайдер:",
|
|
@@ -286,10 +330,14 @@
|
|
|
286
330
|
"repl.skill_unknown_sub": "Неизвестная подкоманда скилла: {subcmd}",
|
|
287
331
|
"repl.skill_usage": "Использование: /skill [list|loaded|load|unload|search]",
|
|
288
332
|
"repl.agent": "Агент: ",
|
|
289
|
-
"repl.
|
|
333
|
+
"repl.interrupt": "Прервано (Esc)",
|
|
334
|
+
"repl.title": "MMA REPL v{version}",
|
|
290
335
|
"repl.model": "Модель:",
|
|
291
336
|
"repl.provider": "Провайдер:",
|
|
292
337
|
"repl.context": "Контекст:",
|
|
338
|
+
"repl.sysprompt_label": "Системный промпт:",
|
|
339
|
+
"repl.sysprompt_size": "{used} / {budget} токенов",
|
|
340
|
+
"repl.sysprompt_desc": "Показать системный промпт",
|
|
293
341
|
"repl.max_iters": "Макс итераций:",
|
|
294
342
|
"repl.stuck_thresh": "Порог зависания:",
|
|
295
343
|
"repl.reasoning_label": "Рассуждения:",
|
|
@@ -371,25 +419,34 @@
|
|
|
371
419
|
"skill.prompt_hint": "НЕ загружайте скиллы автоматически. Загружайте скилл ТОЛЬКО когда пользователь явно об этом просит (например 'используй скилл X' или 'загрузи скилл Y'). Скиллы ниже перечислены только для справки — не угадывайте какой скилл подходит по описанию задачи.",
|
|
372
420
|
"skill.prompt_fallback": "Если load_skill не удался из-за большого размера скилла, продолжайте задачу без него — не останавливайтесь.",
|
|
373
421
|
"skill.loaded_content": "[Скилл загружен: {name}]\n{content}\n\nИспользуйте эти знания для ответа на вопрос пользователя.",
|
|
374
|
-
"exec.stuck": "
|
|
375
|
-
"exec.stuck_recovery": "
|
|
376
|
-
"exec.tool_errors": "Инструмент {tool}
|
|
377
|
-
"exec.tool_errors_recovery": "
|
|
378
|
-
"exec.repetitive_tool": "
|
|
379
|
-
"exec.consecutive_failures_recovery": "
|
|
380
|
-
"exec.
|
|
381
|
-
"exec.
|
|
382
|
-
"exec.off_track": "
|
|
422
|
+
"exec.stuck": "Нет прогресса на шаге {stepId} ({description}) — {iterations} итераций.",
|
|
423
|
+
"exec.stuck_recovery": "Шаг {stepId} — \"{description}\" — без прогресса уже {iterations} итераций. Попробуйте другой подход: проверьте что нужно для этого шага, установлены ли зависимости, или создайте файлы напрямую через write_file вместо команд терминала. После завершения шага вызовите plan update step={stepId} status=done.",
|
|
424
|
+
"exec.tool_errors": "Инструмент {tool} упал {count} раз. Попробуйте другой инструмент.",
|
|
425
|
+
"exec.tool_errors_recovery": "Инструмент {tool} упал {count} раз подряд. Попробуйте альтернативу: создайте файлы напрямую через write_file, используйте другую команду, или пропустите этот шаг через plan update step=N status=skipped с пометкой почему.",
|
|
426
|
+
"exec.repetitive_tool": "Инструмент {tool} вызван {count} раз с одинаковыми аргументами и результатом. Попробуйте другой подход — создайте файлы напрямую, измените аргументы или проверьте статус процесса через process_log.",
|
|
427
|
+
"exec.consecutive_failures_recovery": "{count} инструментов подряд упали. Создавайте файлы напрямую через write_file вместо команд терминала. Проверьте что зависимости установлены (npm install). Не запускайте сборку/тесты пока не созданы все файлы.",
|
|
428
|
+
"exec.plan_warning": "Текущий шаг плана {step} — \"{description}\", но вызывается {tool} для файлов вне этого шага. Завершите текущий шаг, вызовите plan update step={step} status=done, затем переходите к следующему.",
|
|
429
|
+
"exec.plan_blocked": "{max} вызовов подряд вне текущего шага. Завершите шаг {step} — остальные шаги ждут пока этот не будет выполнен.",
|
|
430
|
+
"exec.off_track": "Шаг {stepId} — \"{description}\", но используется {tool} для другого пути. Вернитесь к текущему шагу.",
|
|
431
|
+
"exec.step_gate_deps": "[⚠ Шаг {step} \"{description}\": зависимости не установлены. Сначала выполните команду установки (npm install, pip install и т.д.). Проверьте что lock-файл или директория зависимостей существует. Если этот шаг на самом деле не нужен (внешних зависимостей нет) — пропусти его: plan update step={step} status=skipped note=\"зависимости не нужны\".]",
|
|
432
|
+
"exec.step_gate_deps_force": "[⚠ Шаг {step}: зависимости всё ещё не установлены (нет lock-файла) — второе предупреждение. Если этот шаг НЕ нужен — НЕМЕДЛЕННО вызови: plan update step={step} status=skipped note=\"зависимости не нужны\". Если нужен — выполни установку прямо сейчас. Не делай других вызовов инструментов до обновления плана.]",
|
|
433
|
+
"exec.step_gate_empty": "[⚠ Шаг {step}: файлы существуют, но выглядят пустыми: {files}. Добавьте реальный код в эти файлы перед тем как переходить к следующему шагу.]",
|
|
434
|
+
"exec.step_gate_ok": "[✓] Шаг {step} завершён и проверен. ПЕРЕХОДИМ к шагу {nextStep}: \"{nextDesc}\". Работайте ТОЛЬКО над этим шагом.",
|
|
435
|
+
"exec.step_gate_last": "[✓] Шаг {step} завершён — это был последний шаг. Проверьте всё вместе и предоставьте финальный ответ.]",
|
|
383
436
|
"exec.audit_pass": "[✓] Задача выполнена: {done}/{total} шагов, {files} файлов проверено",
|
|
384
437
|
"exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
|
|
385
438
|
"exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
|
|
386
439
|
"exec.audit_incomplete": "[⚠ Финальная проверка не пройдена: {summary}. Задача НЕ завершена — продолжайте работу. Оставшиеся шаги: {steps}]",
|
|
440
|
+
"exec.mass_edit_warning": "⚠️ План затрагивает {count} файлов — проверьте полный список перед продолжением.",
|
|
441
|
+
"exec.escalation": "\n\n⚠️ Агент застрял на шаге {stepId} ({description}). Эскалация к пользователю — пожалуйста, подскажите как действовать.",
|
|
442
|
+
"exec.hints": "\n[Подсказки]\n{hints}",
|
|
443
|
+
"exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
|
|
387
444
|
"hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
|
|
388
445
|
"hall.short_response": "Слишком короткий или пустой ответ",
|
|
389
446
|
"hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
|
|
390
447
|
"hall.uncertainty": "Маркеры неопределённости: {markers}",
|
|
391
448
|
"hall.unknown_paths": "Упомянутые файлы не найдены: {paths}",
|
|
392
|
-
"hall.
|
|
449
|
+
"hall.contradiction_llm": "Противоречит ранее принятому решению{reason}",
|
|
393
450
|
"hall.uncertainty_prefix": "\n\n[⚠️ Неопределённость] ",
|
|
394
451
|
"browser.repeated_action": "Вы повторили то же действие ({action}) {threshold} раз. Попробуйте другой подход: используйте \"snapshot\" для перечитки страницы, другие номера элементов или другой URL.",
|
|
395
452
|
"browser.unknown_action": "Неизвестное действие: {action}",
|
|
@@ -457,5 +514,12 @@
|
|
|
457
514
|
"tool.remember.entry_required": "Требуется текст записи",
|
|
458
515
|
"tool.recall.empty": "Ничего не найдено по \"{query}\"",
|
|
459
516
|
"tool.recall.no_memory": "Память пуста",
|
|
460
|
-
"tool.recall.search_results": "Результаты {category}:\n{results}"
|
|
517
|
+
"tool.recall.search_results": "Результаты {category}:\n{results}",
|
|
518
|
+
"ctx.compactions": "сжатий: {count}",
|
|
519
|
+
"ctx.quality": "качество: {percent}%",
|
|
520
|
+
"ctx.delta_pos": "контекст +{tokens}",
|
|
521
|
+
"ctx.delta_neg": "контекст -{tokens} ↓",
|
|
522
|
+
"ctx.delta_zero": "контекст ±0",
|
|
523
|
+
"file.notfound_resolved": "Файл не найден: {path} (резолвится в {resolved})",
|
|
524
|
+
"bash.echo_write_blocked": "Запись файлов через echo/printf ненадёжна в Windows cmd.exe (кавычки и многострочность ломаются). Используй инструмент write_file вместо этого (цель: {path})."
|
|
461
525
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
export { DEFAULTS, loadConfig } from
|
|
2
|
-
export { Logger } from
|
|
3
|
-
export { PromptBuilder } from
|
|
4
|
-
export { OpenAICompatProvider, TokenCounter, parseChunks, OrchestratorClient } from
|
|
5
|
-
export { ToolRegistry, ToolExecutor, readFileTool, writeFileTool, editFileTool, globTool, grepTool, listDirTool, createDirTool, deleteFileTool, moveFileTool, fileInfoTool, bashTool, subagentTool, webSearchTool, webFetchTool, webBrowseTool,
|
|
6
|
-
export { MigrationDetector, BackupManager } from
|
|
7
|
-
import { MigrationDetector } from
|
|
8
|
-
import { BackupManager } from
|
|
9
|
-
import { t } from
|
|
10
|
-
import { homedir } from
|
|
11
|
-
import { join } from
|
|
1
|
+
export { DEFAULTS, loadConfig } from "./config/index";
|
|
2
|
+
export { Logger } from "./logger/index";
|
|
3
|
+
export { PromptBuilder } from "./core/index";
|
|
4
|
+
export { OpenAICompatProvider, TokenCounter, parseChunks, OrchestratorClient, } from "./llm/index";
|
|
5
|
+
export { ToolRegistry, ToolExecutor, readFileTool, writeFileTool, editFileTool, globTool, grepTool, listDirTool, createDirTool, deleteFileTool, moveFileTool, fileInfoTool, bashTool, subagentTool, webSearchTool, webFetchTool, webBrowseTool, createLoadSkillTool, pipelineRunTool, mcpCallTool, searchHistoryTool, registerAllTools, } from "./tools/index";
|
|
6
|
+
export { MigrationDetector, BackupManager } from "./migration/index";
|
|
7
|
+
import { MigrationDetector } from "./migration/detect";
|
|
8
|
+
import { BackupManager } from "./migration/backup";
|
|
9
|
+
import { t } from "./i18n/index";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
12
|
export function checkMigration(configDir) {
|
|
13
|
-
const dir = configDir || join(homedir(),
|
|
13
|
+
const dir = configDir || join(homedir(), ".mma");
|
|
14
14
|
const detector = new MigrationDetector(dir);
|
|
15
15
|
if (detector.needsMigration()) {
|
|
16
16
|
const backup = new BackupManager(dir);
|
|
17
17
|
backup.backupConfig();
|
|
18
18
|
backup.backupAll();
|
|
19
19
|
const summary = backup.getBackupSummary();
|
|
20
|
-
console.log(t(
|
|
20
|
+
console.log(t("migration.summary", { summary }));
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TokenCounter } from "./token-counter";
|
|
2
2
|
import { t } from "../i18n/index";
|
|
3
|
-
import { createRateLimiter } from "../modules/security/rate-limiter";
|
|
3
|
+
import { createRateLimiter, } from "../modules/security/rate-limiter";
|
|
4
4
|
export class OpenAICompatProvider {
|
|
5
5
|
model;
|
|
6
6
|
contextWindow;
|
|
@@ -20,14 +20,14 @@ export class OpenAICompatProvider {
|
|
|
20
20
|
};
|
|
21
21
|
this.rateLimiter = createRateLimiter(config.rateLimits);
|
|
22
22
|
}
|
|
23
|
-
async *chat(messages, tools) {
|
|
23
|
+
async *chat(messages, tools, signal) {
|
|
24
24
|
// Check rate limit before making request
|
|
25
25
|
if (!this.rateLimiter.canMakeRequest()) {
|
|
26
26
|
throw new Error(`Rate limit exceeded: ${this.rateLimiter.getConfig().maxRequestsPerMinute} requests per minute`);
|
|
27
27
|
}
|
|
28
28
|
// Record this request
|
|
29
29
|
this.rateLimiter.recordRequest();
|
|
30
|
-
const streamResult = this.doStream(messages, tools);
|
|
30
|
+
const streamResult = this.doStream(messages, tools, signal);
|
|
31
31
|
let hasToolCall = false;
|
|
32
32
|
let hasText = false;
|
|
33
33
|
let reasoningAcc = "";
|
|
@@ -42,13 +42,13 @@ export class OpenAICompatProvider {
|
|
|
42
42
|
yield chunk;
|
|
43
43
|
}
|
|
44
44
|
if (!hasToolCall && !hasText) {
|
|
45
|
-
const fallback = await this.doNonStreaming(messages, tools);
|
|
45
|
+
const fallback = await this.doNonStreaming(messages, tools, signal);
|
|
46
46
|
for (const chunk of fallback) {
|
|
47
47
|
yield chunk;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
async *doStream(messages, tools) {
|
|
51
|
+
async *doStream(messages, tools, signal) {
|
|
52
52
|
const body = {
|
|
53
53
|
model: this.model,
|
|
54
54
|
messages,
|
|
@@ -75,11 +75,24 @@ export class OpenAICompatProvider {
|
|
|
75
75
|
const controller = new AbortController();
|
|
76
76
|
const totalTimeoutMs = 120000;
|
|
77
77
|
const timeoutId = setTimeout(() => controller.abort(), totalTimeoutMs);
|
|
78
|
+
const abortSignal = (() => {
|
|
79
|
+
if (!signal)
|
|
80
|
+
return controller.signal;
|
|
81
|
+
try {
|
|
82
|
+
return AbortSignal.any([controller.signal, signal]);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
signal.addEventListener("abort", () => controller.abort(), {
|
|
86
|
+
once: true,
|
|
87
|
+
});
|
|
88
|
+
return controller.signal;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
78
91
|
const response = await this.fetchWithRetry(`${this.config.baseUrl}/chat/completions`, {
|
|
79
92
|
method: "POST",
|
|
80
93
|
headers,
|
|
81
94
|
body: JSON.stringify(body),
|
|
82
|
-
signal:
|
|
95
|
+
signal: abortSignal,
|
|
83
96
|
});
|
|
84
97
|
if (!response.ok) {
|
|
85
98
|
clearTimeout(timeoutId);
|
|
@@ -182,7 +195,7 @@ export class OpenAICompatProvider {
|
|
|
182
195
|
reader.releaseLock();
|
|
183
196
|
}
|
|
184
197
|
}
|
|
185
|
-
async doNonStreaming(messages, tools) {
|
|
198
|
+
async doNonStreaming(messages, tools, signal) {
|
|
186
199
|
const body = {
|
|
187
200
|
model: this.model,
|
|
188
201
|
messages,
|
|
@@ -211,6 +224,7 @@ export class OpenAICompatProvider {
|
|
|
211
224
|
method: "POST",
|
|
212
225
|
headers,
|
|
213
226
|
body: JSON.stringify(body),
|
|
227
|
+
signal,
|
|
214
228
|
});
|
|
215
229
|
if (!response.ok) {
|
|
216
230
|
const errorText = await response.text();
|
|
@@ -310,7 +324,7 @@ export class OpenAICompatProvider {
|
|
|
310
324
|
if (attempt < maxRetries) {
|
|
311
325
|
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
312
326
|
const jitter = Math.random() * baseDelay * 0.1;
|
|
313
|
-
await this.sleep(delay + jitter);
|
|
327
|
+
await this.sleep(delay + jitter, init.signal ?? undefined);
|
|
314
328
|
}
|
|
315
329
|
}
|
|
316
330
|
throw lastError ?? new Error(t("error.llm_retries"));
|
|
@@ -318,7 +332,22 @@ export class OpenAICompatProvider {
|
|
|
318
332
|
isRetryable(status) {
|
|
319
333
|
return status === 429 || status >= 500;
|
|
320
334
|
}
|
|
321
|
-
sleep(ms) {
|
|
322
|
-
return new Promise((resolve) =>
|
|
335
|
+
sleep(ms, signal) {
|
|
336
|
+
return new Promise((resolve, reject) => {
|
|
337
|
+
if (signal?.aborted) {
|
|
338
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
let timer;
|
|
342
|
+
const onAbort = () => {
|
|
343
|
+
clearTimeout(timer);
|
|
344
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
345
|
+
};
|
|
346
|
+
timer = setTimeout(() => {
|
|
347
|
+
signal?.removeEventListener("abort", onAbort);
|
|
348
|
+
resolve();
|
|
349
|
+
}, ms);
|
|
350
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
351
|
+
});
|
|
323
352
|
}
|
|
324
353
|
}
|
|
@@ -1,14 +1,28 @@
|
|
|
1
|
-
import { appendFileSync, mkdirSync, existsSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { sanitizeLogMessage } from
|
|
1
|
+
import { appendFileSync, mkdirSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { sanitizeLogMessage } from "../modules/security/data-sanitizer";
|
|
4
|
+
import { FileLogWriter } from "./file-log";
|
|
5
|
+
import pc from "picocolors";
|
|
4
6
|
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
7
|
+
const LEVEL_COLORS = {
|
|
8
|
+
debug: (s) => pc.dim(s),
|
|
9
|
+
info: (s) => s,
|
|
10
|
+
warn: (s) => pc.yellow(s),
|
|
11
|
+
error: (s) => pc.red(s),
|
|
12
|
+
};
|
|
13
|
+
function isColorEnabled() {
|
|
14
|
+
return (!process.env.NO_COLOR && !process.env.CI && process.stdout.isTTY === true);
|
|
15
|
+
}
|
|
5
16
|
export class Logger {
|
|
6
17
|
level;
|
|
7
18
|
prefix;
|
|
8
19
|
logDir = null;
|
|
9
|
-
|
|
20
|
+
sessionDir = null;
|
|
21
|
+
fileLog;
|
|
22
|
+
constructor(level = "info", prefix = "") {
|
|
10
23
|
this.level = level;
|
|
11
24
|
this.prefix = prefix;
|
|
25
|
+
this.fileLog = new FileLogWriter();
|
|
12
26
|
}
|
|
13
27
|
setLevel(level) {
|
|
14
28
|
this.level = level;
|
|
@@ -18,24 +32,66 @@ export class Logger {
|
|
|
18
32
|
if (!existsSync(dir)) {
|
|
19
33
|
mkdirSync(dir, { recursive: true });
|
|
20
34
|
}
|
|
35
|
+
this.fileLog.setLogDir(dir);
|
|
36
|
+
}
|
|
37
|
+
setSessionDir(dir) {
|
|
38
|
+
this.sessionDir = dir;
|
|
39
|
+
if (!existsSync(dir)) {
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
clearSessionDir() {
|
|
44
|
+
this.sessionDir = null;
|
|
45
|
+
}
|
|
46
|
+
/** Open a per-session `.log` file (legacy v1 behavior). */
|
|
47
|
+
initSessionLog(sessionId) {
|
|
48
|
+
this.fileLog.initSessionLog(sessionId);
|
|
49
|
+
}
|
|
50
|
+
/** Fall back to daily log files when no session is active. */
|
|
51
|
+
closeSessionLog() {
|
|
52
|
+
this.fileLog.closeSessionLog();
|
|
53
|
+
}
|
|
54
|
+
getLogPath() {
|
|
55
|
+
return this.fileLog.getLogPath();
|
|
56
|
+
}
|
|
57
|
+
cleanupOldLogs(maxDays, maxFiles) {
|
|
58
|
+
this.fileLog.cleanupOldLogs(maxDays, maxFiles);
|
|
59
|
+
}
|
|
60
|
+
// --- Tagged helpers (legacy v1 API) ---
|
|
61
|
+
logLLMRequest(model, messagesCount, promptPreview, caller) {
|
|
62
|
+
this.fileLog.logLLMRequest(model, messagesCount, promptPreview, caller);
|
|
63
|
+
}
|
|
64
|
+
logLLMResponse(model, responseLength, genTimeMs, error, caller) {
|
|
65
|
+
this.fileLog.logLLMResponse(model, responseLength, genTimeMs, error, caller);
|
|
66
|
+
}
|
|
67
|
+
logToolCall(tool, preview, result) {
|
|
68
|
+
this.fileLog.logToolCall(tool, preview, result);
|
|
69
|
+
}
|
|
70
|
+
logToolOutput(tool, output, exitCode) {
|
|
71
|
+
this.fileLog.logToolOutput(tool, output, exitCode);
|
|
72
|
+
}
|
|
73
|
+
logREPL(tag, content) {
|
|
74
|
+
this.fileLog.logREPL(tag, content);
|
|
21
75
|
}
|
|
22
76
|
child(prefix) {
|
|
23
77
|
const childLogger = new Logger(this.level, this.prefix ? `${this.prefix}:${prefix}` : prefix);
|
|
24
78
|
if (this.logDir)
|
|
25
79
|
childLogger.setLogDir(this.logDir);
|
|
80
|
+
if (this.sessionDir)
|
|
81
|
+
childLogger.setSessionDir(this.sessionDir);
|
|
26
82
|
return childLogger;
|
|
27
83
|
}
|
|
28
84
|
debug(msg, meta) {
|
|
29
|
-
this.log(
|
|
85
|
+
this.log("debug", msg, meta);
|
|
30
86
|
}
|
|
31
87
|
info(msg, meta) {
|
|
32
|
-
this.log(
|
|
88
|
+
this.log("info", msg, meta);
|
|
33
89
|
}
|
|
34
90
|
warn(msg, meta) {
|
|
35
|
-
this.log(
|
|
91
|
+
this.log("warn", msg, meta);
|
|
36
92
|
}
|
|
37
93
|
error(msg, meta) {
|
|
38
|
-
this.log(
|
|
94
|
+
this.log("error", msg, meta);
|
|
39
95
|
}
|
|
40
96
|
log(level, msg, meta) {
|
|
41
97
|
if (LEVELS[level] < LEVELS[this.level])
|
|
@@ -44,15 +100,26 @@ export class Logger {
|
|
|
44
100
|
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
45
101
|
const sanitizedMeta = meta ? this.sanitizeMeta(meta) : undefined;
|
|
46
102
|
const ts = new Date().toISOString();
|
|
47
|
-
const prefix = this.prefix ? ` [${this.prefix}]` :
|
|
48
|
-
const metaStr = sanitizedMeta ? ` ${JSON.stringify(sanitizedMeta)}` :
|
|
103
|
+
const prefix = this.prefix ? ` [${this.prefix}]` : "";
|
|
104
|
+
const metaStr = sanitizedMeta ? ` ${JSON.stringify(sanitizedMeta)}` : "";
|
|
49
105
|
const line = `[${level.toUpperCase()}]${prefix} ${ts} — ${sanitizedMsg}${metaStr}`;
|
|
50
|
-
console.log(line);
|
|
51
|
-
|
|
106
|
+
console.log(isColorEnabled() ? LEVEL_COLORS[level](line) : line);
|
|
107
|
+
// Legacy v1 behavior: also append to the tagged `.log` file (session or daily).
|
|
108
|
+
this.fileLog.log(level.toUpperCase(), this.prefix || "MMA", `${ts} — ${sanitizedMsg}${metaStr}`);
|
|
109
|
+
const logTarget = this.sessionDir ?? this.logDir;
|
|
110
|
+
if (logTarget) {
|
|
52
111
|
try {
|
|
53
|
-
appendFileSync(join(
|
|
112
|
+
appendFileSync(join(logTarget, "app.jsonl"), JSON.stringify({
|
|
113
|
+
level,
|
|
114
|
+
ts,
|
|
115
|
+
prefix: this.prefix,
|
|
116
|
+
msg: sanitizedMsg,
|
|
117
|
+
meta: sanitizedMeta ?? null,
|
|
118
|
+
}) + "\n", "utf-8");
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* file logging is best-effort */
|
|
54
122
|
}
|
|
55
|
-
catch { /* file logging is best-effort */ }
|
|
56
123
|
}
|
|
57
124
|
}
|
|
58
125
|
/**
|
|
@@ -61,10 +128,10 @@ export class Logger {
|
|
|
61
128
|
sanitizeMeta(meta) {
|
|
62
129
|
const sanitized = {};
|
|
63
130
|
for (const [key, value] of Object.entries(meta)) {
|
|
64
|
-
if (typeof value ===
|
|
131
|
+
if (typeof value === "string") {
|
|
65
132
|
sanitized[key] = sanitizeLogMessage(value);
|
|
66
133
|
}
|
|
67
|
-
else if (typeof value ===
|
|
134
|
+
else if (typeof value === "object" && value !== null) {
|
|
68
135
|
sanitized[key] = this.sanitizeMeta(value);
|
|
69
136
|
}
|
|
70
137
|
else {
|