micro-models-agent 0.39.0 → 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.
Files changed (96) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +116 -3
  3. package/dist/cli/main.js +35 -8
  4. package/dist/cli/repl-commands.js +633 -0
  5. package/dist/cli/repl.js +110 -611
  6. package/dist/cli/setup.js +32 -12
  7. package/dist/config/config.js +46 -30
  8. package/dist/config/defaults.js +10 -1
  9. package/dist/config/security.js +15 -8
  10. package/dist/core/agent-moe.js +24 -12
  11. package/dist/core/agent.js +281 -47
  12. package/dist/core/bootstrap.js +52 -36
  13. package/dist/core/session-logger.js +35 -2
  14. package/dist/core/workspace.js +76 -0
  15. package/dist/i18n/en.json +79 -15
  16. package/dist/i18n/index.js +12 -9
  17. package/dist/i18n/ru.json +79 -15
  18. package/dist/index.js +13 -13
  19. package/dist/llm/openai-compat.js +39 -10
  20. package/dist/logger/app-logger.js +83 -16
  21. package/dist/logger/file-log.js +151 -0
  22. package/dist/main.js +537 -284
  23. package/dist/modules/browser/bridge-server.mjs +113 -105
  24. package/dist/modules/browser/session.js +108 -60
  25. package/dist/modules/certification/cli.js +176 -0
  26. package/dist/modules/certification/fact-checker.js +84 -0
  27. package/dist/modules/certification/loader.js +111 -0
  28. package/dist/modules/certification/manifest.js +50 -0
  29. package/dist/modules/certification/runner.js +162 -0
  30. package/dist/modules/certification/scenarios.js +124 -0
  31. package/dist/modules/certification/types.js +1 -0
  32. package/dist/modules/context/manager.js +119 -10
  33. package/dist/modules/execution/auditor.js +33 -39
  34. package/dist/modules/execution/index.js +8 -6
  35. package/dist/modules/execution/module.js +474 -32
  36. package/dist/modules/execution/moe-executor.js +97 -40
  37. package/dist/modules/execution/plan-coverage.js +68 -0
  38. package/dist/modules/execution/plan-persister.js +46 -0
  39. package/dist/modules/execution/plan-store.js +159 -0
  40. package/dist/modules/execution/planner.js +63 -13
  41. package/dist/modules/execution/stuck-detector.js +252 -39
  42. package/dist/modules/execution/tracker.js +21 -7
  43. package/dist/modules/execution/verifier.js +46 -17
  44. package/dist/modules/hallucination/confidence.js +7 -2
  45. package/dist/modules/hallucination/consistency.js +8 -42
  46. package/dist/modules/hallucination/detector.js +26 -21
  47. package/dist/modules/hallucination/factual.js +170 -150
  48. package/dist/modules/hallucination/index.js +5 -4
  49. package/dist/modules/hallucination/js-identifiers.js +72 -0
  50. package/dist/modules/hallucination/llm-judge.js +103 -0
  51. package/dist/modules/index.js +5 -5
  52. package/dist/modules/lsp/client.js +235 -0
  53. package/dist/modules/lsp/config.js +81 -0
  54. package/dist/modules/lsp/index.js +3 -0
  55. package/dist/modules/lsp/module.js +68 -0
  56. package/dist/modules/lsp/types.js +1 -0
  57. package/dist/modules/mcp/client.js +8 -2
  58. package/dist/modules/memory/store.js +4 -0
  59. package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
  60. package/dist/modules/processes/index.js +1 -2
  61. package/dist/modules/processes/registry.js +125 -35
  62. package/dist/modules/processes/runner.js +9 -110
  63. package/dist/modules/security/audit-log.js +30 -10
  64. package/dist/modules/security/command-validator.js +42 -16
  65. package/dist/modules/security/content-scanner.js +9 -8
  66. package/dist/modules/security/network-validator.js +2 -2
  67. package/dist/modules/security/path-validator.js +64 -10
  68. package/dist/modules/security/security-policies.js +221 -67
  69. package/dist/modules/security/session-encryption.js +42 -25
  70. package/dist/modules/session/manager.js +15 -10
  71. package/dist/modules/session/store.js +62 -8
  72. package/dist/modules/skills/index.js +2 -3
  73. package/dist/modules/skills/module.js +10 -23
  74. package/dist/tools/bash.js +287 -90
  75. package/dist/tools/create-dir.js +0 -1
  76. package/dist/tools/delete-file.js +0 -1
  77. package/dist/tools/edit-file.js +10 -8
  78. package/dist/tools/executor.js +57 -7
  79. package/dist/tools/grep-tool.js +51 -29
  80. package/dist/tools/index.js +55 -40
  81. package/dist/tools/load-skill.js +14 -18
  82. package/dist/tools/move-file.js +3 -2
  83. package/dist/tools/pipeline-run.js +1 -1
  84. package/dist/tools/read-file.js +15 -5
  85. package/dist/tools/search-history.js +42 -22
  86. package/dist/tools/subagent.js +21 -12
  87. package/dist/tools/web-browse.js +54 -25
  88. package/dist/tools/web-fetch.js +60 -34
  89. package/dist/tools/web-search.js +39 -20
  90. package/dist/tools/write-file.js +13 -10
  91. package/dist/ui/diff.js +9 -16
  92. package/dist/ui/renderer.js +69 -6
  93. package/package.json +48 -45
  94. package/dist/modules/context/history.js +0 -15
  95. package/dist/modules/processes/detect.js +0 -34
  96. 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": "[Скилл загружен. Осталось {remaining} токенов в бюджете скиллов]",
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.detected_hint": "[Обнаружена длительная командазапущена в фоне]",
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.title": "MMA REPL v2",
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": "Застряли на шаге {stepId}. Текущий: {description}. Нет прогресса {iterations} итераций.",
375
- "exec.stuck_recovery": "[Обнаружено застревание: {iterations} итераций без прогресса. Прекратите повторять тот же подход. Попробуйте принципиально другую стратегию: используйте другой инструмент (например, 'browser' для сайтов с JavaScript вместо 'web_browse'), разбейте задачу на подзадачи или объясните пользователю что пошло не так и спросите совета.]",
376
- "exec.tool_errors": "Инструмент {tool} сбоил {count} раз. Предложите другой подход.",
377
- "exec.tool_errors_recovery": "[Инструмент {tool} сбоил {count} раз. Прекратите использовать этот инструмент и попробуйте другой подход или объясните проблему пользователю.]",
378
- "exec.repetitive_tool": "[Вы вызвали {tool} {count} раз подряд с одинаковыми аргументами и результатом. Прекратите повторятьпопробуйте другой подход: создайте файлы, измените аргументы или объясните пользователю проблему.]",
379
- "exec.consecutive_failures_recovery": "[{count} последовательных вызовов тулов завершились ошибкой. Вы ходите по кругу прекратите пытаться выполнить команды, которые постоянно падают. Объясните проблему пользователю или попробуйте совершенно другой подход: напишите файлы напрямую, используйте другой инструмент или разбейте задачу на части.]",
380
- "exec.task_reminder": "[Напоминание о задаче: {task}. Не переключайтесь на посторонние задачи. Если не можете выполнить задачу объясните почему и спросите совета.]",
381
- "exec.plan_warning": "[⚠ Внимание: вы работаете над шагом {step} — \"{description}\". Ваш вызов {tool} касается файлов, не упомянутых в текущем шаге. Сосредоточьтесь на текущем шаге или обновите план через plan update.]",
382
- "exec.off_track": "[⚠ Вне плана: шаг {stepId} — \"{description}\", но вы используете {tool} для другого пути. Объясните или вернитесь.]",
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.contradiction": "Противоречит предыдущему решению: \"{decision}\" в {location}",
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 './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, questionTool, approveTool, 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';
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(), '.mma');
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('migration.summary', { summary }));
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: controller.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) => setTimeout(resolve, ms));
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 'fs';
2
- import { join } from 'path';
3
- import { sanitizeLogMessage } from '../modules/security/data-sanitizer';
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
- constructor(level = 'info', prefix = '') {
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('debug', msg, meta);
85
+ this.log("debug", msg, meta);
30
86
  }
31
87
  info(msg, meta) {
32
- this.log('info', msg, meta);
88
+ this.log("info", msg, meta);
33
89
  }
34
90
  warn(msg, meta) {
35
- this.log('warn', msg, meta);
91
+ this.log("warn", msg, meta);
36
92
  }
37
93
  error(msg, meta) {
38
- this.log('error', msg, meta);
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
- if (this.logDir) {
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(this.logDir, 'app.jsonl'), JSON.stringify({ level, ts, prefix: this.prefix, msg: sanitizedMsg, meta: sanitizedMeta ?? null }) + '\n', 'utf-8');
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 === 'string') {
131
+ if (typeof value === "string") {
65
132
  sanitized[key] = sanitizeLogMessage(value);
66
133
  }
67
- else if (typeof value === 'object' && value !== null) {
134
+ else if (typeof value === "object" && value !== null) {
68
135
  sanitized[key] = this.sanitizeMeta(value);
69
136
  }
70
137
  else {