micro-models-agent 0.21.1 → 0.23.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 (169) hide show
  1. package/README.md +312 -312
  2. package/bin/mma.mjs +8 -8
  3. package/dist/cli/commands.js +220 -0
  4. package/dist/cli/completer.js +168 -0
  5. package/dist/cli/index.js +2 -0
  6. package/dist/cli/main.js +113 -0
  7. package/dist/cli/repl.js +987 -0
  8. package/dist/cli/security-commands.js +166 -0
  9. package/dist/cli/setup.js +229 -0
  10. package/dist/config/config.js +186 -0
  11. package/dist/config/defaults.js +91 -0
  12. package/dist/config/experts.js +15 -0
  13. package/dist/config/index.js +3 -0
  14. package/dist/config/security.js +193 -0
  15. package/dist/config/types.js +1 -0
  16. package/dist/core/agent-moe.js +98 -0
  17. package/dist/core/agent.js +461 -0
  18. package/dist/core/bootstrap.js +321 -0
  19. package/dist/core/index.js +2 -0
  20. package/dist/core/prompt-builder.js +55 -0
  21. package/dist/core/session-logger.js +122 -0
  22. package/dist/core/types.js +1 -0
  23. package/dist/i18n/en.json +461 -0
  24. package/dist/i18n/index.js +43 -0
  25. package/dist/i18n/ru.json +461 -0
  26. package/dist/index.js +22 -0
  27. package/dist/llm/image-utils.js +144 -0
  28. package/dist/llm/index.js +4 -0
  29. package/dist/llm/model-loader.js +78 -0
  30. package/dist/llm/openai-compat.js +324 -0
  31. package/dist/llm/orchestrator.js +194 -0
  32. package/dist/llm/provider.js +10 -0
  33. package/dist/llm/response.js +39 -0
  34. package/dist/llm/token-counter.js +39 -0
  35. package/dist/llm/types.js +1 -0
  36. package/dist/logger/app-logger.js +76 -0
  37. package/dist/logger/index.js +1 -0
  38. package/dist/main.js +883 -293
  39. package/dist/migration/backup.js +45 -0
  40. package/dist/migration/detect.js +50 -0
  41. package/dist/migration/index.js +2 -0
  42. package/dist/modules/browser/actions.js +46 -0
  43. package/dist/modules/browser/cookie-store.js +24 -0
  44. package/dist/modules/browser/index.js +5 -0
  45. package/dist/modules/browser/module.js +28 -0
  46. package/dist/modules/browser/session.js +287 -0
  47. package/dist/modules/browser/snapshot.js +114 -0
  48. package/dist/modules/browser/types.js +9 -0
  49. package/dist/modules/context/history.js +15 -0
  50. package/dist/modules/context/index.js +1 -0
  51. package/dist/modules/context/manager.js +240 -0
  52. package/dist/modules/execution/auditor.js +72 -0
  53. package/dist/modules/execution/index.js +6 -0
  54. package/dist/modules/execution/module.js +337 -0
  55. package/dist/modules/execution/moe-executor.js +209 -0
  56. package/dist/modules/execution/plan-validator.js +153 -0
  57. package/dist/modules/execution/planner.js +35 -0
  58. package/dist/modules/execution/stuck-detector.js +134 -0
  59. package/dist/modules/execution/tracker.js +53 -0
  60. package/dist/modules/execution/types.js +1 -0
  61. package/dist/modules/execution/verifier.js +149 -0
  62. package/dist/modules/hallucination/confidence.js +54 -0
  63. package/dist/modules/hallucination/consistency.js +60 -0
  64. package/dist/modules/hallucination/detector.js +41 -0
  65. package/dist/modules/hallucination/factual.js +170 -0
  66. package/dist/modules/hallucination/index.js +4 -0
  67. package/dist/modules/index.js +5 -0
  68. package/dist/modules/indexer/cache.js +38 -0
  69. package/dist/modules/indexer/index.js +3 -0
  70. package/dist/modules/indexer/module.js +192 -0
  71. package/dist/modules/indexer/walker.js +101 -0
  72. package/dist/modules/mcp/client.js +393 -0
  73. package/dist/modules/mcp/index.js +3 -0
  74. package/dist/modules/mcp/module.js +146 -0
  75. package/dist/modules/mcp/registry.js +15 -0
  76. package/dist/modules/memory/index.js +1 -0
  77. package/dist/modules/memory/module.js +48 -0
  78. package/dist/modules/memory/search.js +40 -0
  79. package/dist/modules/memory/store.js +65 -0
  80. package/dist/modules/pipelines/engine.js +60 -0
  81. package/dist/modules/pipelines/index.js +3 -0
  82. package/dist/modules/pipelines/parser.js +53 -0
  83. package/dist/modules/pipelines/template.js +14 -0
  84. package/dist/modules/plugins/builtin/lint-on-write.js +121 -0
  85. package/dist/modules/plugins/builtin/notify.js +8 -0
  86. package/dist/modules/plugins/index.js +1 -0
  87. package/dist/modules/plugins/loader.js +28 -0
  88. package/dist/modules/plugins/manager.js +161 -0
  89. package/dist/modules/plugins/types.js +1 -0
  90. package/dist/modules/processes/detect.js +34 -0
  91. package/dist/modules/processes/index.js +3 -0
  92. package/dist/modules/processes/registry.js +148 -0
  93. package/dist/modules/processes/runner.js +124 -0
  94. package/dist/modules/registry.js +45 -0
  95. package/dist/modules/security/audit-log.js +116 -0
  96. package/dist/modules/security/audit-notifier.js +292 -0
  97. package/dist/modules/security/command-validator.js +185 -0
  98. package/dist/modules/security/content-scanner.js +52 -0
  99. package/dist/modules/security/data-sanitizer.js +97 -0
  100. package/dist/modules/security/encryption.js +240 -0
  101. package/dist/modules/security/index.js +14 -0
  102. package/dist/modules/security/network-validator.js +79 -0
  103. package/dist/modules/security/path-validator.js +155 -0
  104. package/dist/modules/security/rate-limiter.js +119 -0
  105. package/dist/modules/security/security-policies.js +393 -0
  106. package/dist/modules/security/session-encryption.js +193 -0
  107. package/dist/modules/security/session-isolation.js +95 -0
  108. package/dist/modules/session/index.js +3 -0
  109. package/dist/modules/session/manager.js +167 -0
  110. package/dist/modules/session/module.js +24 -0
  111. package/dist/modules/session/store.js +174 -0
  112. package/dist/modules/session/types.js +1 -0
  113. package/dist/modules/skills/index.js +3 -0
  114. package/dist/modules/skills/loader.js +72 -0
  115. package/dist/modules/skills/matcher.js +27 -0
  116. package/dist/modules/skills/module.js +143 -0
  117. package/dist/modules/types.js +1 -0
  118. package/dist/modules/updater/checker.js +32 -0
  119. package/dist/modules/updater/index.js +1 -0
  120. package/dist/modules/user-profile/compressor.js +16 -0
  121. package/dist/modules/user-profile/index.js +1 -0
  122. package/dist/modules/user-profile/profile.js +68 -0
  123. package/dist/tools/approve.js +32 -0
  124. package/dist/tools/attach-image.js +89 -0
  125. package/dist/tools/bash.js +140 -0
  126. package/dist/tools/browser.js +97 -0
  127. package/dist/tools/create-dir.js +56 -0
  128. package/dist/tools/delete-file.js +63 -0
  129. package/dist/tools/edit-file.js +77 -0
  130. package/dist/tools/executor.js +95 -0
  131. package/dist/tools/file-info.js +45 -0
  132. package/dist/tools/filter-tools.js +10 -0
  133. package/dist/tools/glob-tool.js +26 -0
  134. package/dist/tools/grep-tool.js +64 -0
  135. package/dist/tools/index.js +52 -0
  136. package/dist/tools/list-dir.js +47 -0
  137. package/dist/tools/load-skill.js +48 -0
  138. package/dist/tools/mcp-call.js +68 -0
  139. package/dist/tools/move-file.js +84 -0
  140. package/dist/tools/path-utils.js +51 -0
  141. package/dist/tools/pipeline-run.js +144 -0
  142. package/dist/tools/preview.js +2 -0
  143. package/dist/tools/process-kill.js +29 -0
  144. package/dist/tools/process-list.js +38 -0
  145. package/dist/tools/process-log.js +41 -0
  146. package/dist/tools/question.js +142 -0
  147. package/dist/tools/read-file.js +73 -0
  148. package/dist/tools/recall.js +110 -0
  149. package/dist/tools/registry.js +36 -0
  150. package/dist/tools/remember.js +67 -0
  151. package/dist/tools/scope-check.js +30 -0
  152. package/dist/tools/search-history.js +64 -0
  153. package/dist/tools/subagent.js +142 -0
  154. package/dist/tools/types.js +1 -0
  155. package/dist/tools/user-input.js +123 -0
  156. package/dist/tools/web-browse.js +57 -0
  157. package/dist/tools/web-fetch.js +72 -0
  158. package/dist/tools/web-search.js +59 -0
  159. package/dist/tools/write-file.js +80 -0
  160. package/dist/ui/box.js +81 -0
  161. package/dist/ui/colors.js +4 -0
  162. package/dist/ui/diff.js +185 -0
  163. package/dist/ui/index.js +6 -0
  164. package/dist/ui/md-formatter.js +212 -0
  165. package/dist/ui/output.js +13 -0
  166. package/dist/ui/renderer.js +141 -0
  167. package/dist/ui/spinner.js +70 -0
  168. package/dist/ui/table.js +144 -0
  169. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1888,6 +1888,7 @@ var require_commander = __commonJS((exports) => {
1888
1888
  // src/config/security.ts
1889
1889
  function mergeSecurityConfig(userConfig) {
1890
1890
  return {
1891
+ enabled: userConfig?.enabled ?? DEFAULT_SECURITY_CONFIG.enabled,
1891
1892
  bash: {
1892
1893
  ...DEFAULT_SECURITY_CONFIG.bash,
1893
1894
  ...userConfig?.bash
@@ -1923,7 +1924,9 @@ function mergeSecurityConfig(userConfig) {
1923
1924
  var DEFAULT_SECURITY_CONFIG;
1924
1925
  var init_security = __esm(() => {
1925
1926
  DEFAULT_SECURITY_CONFIG = {
1927
+ enabled: false,
1926
1928
  bash: {
1929
+ enabled: false,
1927
1930
  blacklist: [
1928
1931
  "rm",
1929
1932
  "dd",
@@ -1985,6 +1988,7 @@ var init_security = __esm(() => {
1985
1988
  dangerousOperators: [">", ">>", "2>", "2>>", "`"]
1986
1989
  },
1987
1990
  paths: {
1991
+ enabled: false,
1988
1992
  denied: [
1989
1993
  ".git/",
1990
1994
  "node_modules/",
@@ -2004,6 +2008,7 @@ var init_security = __esm(() => {
2004
2008
  allowed: []
2005
2009
  },
2006
2010
  network: {
2011
+ enabled: false,
2007
2012
  deniedDomains: ["localhost", "127.0.0.1", "::1"],
2008
2013
  allowedDomains: [],
2009
2014
  requestTimeout: 15000,
@@ -2016,7 +2021,7 @@ var init_security = __esm(() => {
2016
2021
  maxParallelTasks: 5
2017
2022
  },
2018
2023
  contentScan: {
2019
- enabled: true,
2024
+ enabled: false,
2020
2025
  dangerousPatterns: [
2021
2026
  /eval\(/,
2022
2027
  /new Function\(/,
@@ -2305,6 +2310,7 @@ Command: {command}`,
2305
2310
  "tool.friendly.process_kill": "Stopping process",
2306
2311
  "plan.no_steps": "No plan steps specified. Provide concrete steps with files and commands.",
2307
2312
  "plan.created": "Plan created: {title} ({steps} steps)",
2313
+ "plan.coverage_warning": "Plan may be missing required files from the task: {missing}. Add steps covering them.",
2308
2314
  "plan.step_done": "Step {n}/{total}: {description} ✓",
2309
2315
  "plan.complete": "Task complete: {summary}",
2310
2316
  "plan.title_steps": 'Plan "{title}" created with {count} steps',
@@ -2548,16 +2554,15 @@ Available commands:`,
2548
2554
  {content}
2549
2555
 
2550
2556
  Use this knowledge to answer the user's question.`,
2551
- "exec.stuck": "Stuck on step {stepId}. Current: {description}. No progress for {iterations} iterations.",
2552
- "exec.stuck_recovery": '[STUCK: {iterations} iterations on step {stepId} — "{description}". You MUST change approach NOW. Concrete actions: (1) Check what this step needs are dependencies installed? (2) Use plan show to see the plan. (3) If commands fail — create files directly via write_file. (4) After completing the step, call plan update step={stepId} status=done. Do NOT work on other steps until this one is finished.]',
2553
- "exec.tool_errors": "Tool {tool} failed {count} times. Suggest alternative approach.",
2554
- "exec.tool_errors_recovery": "[Tool {tool} failed {count} consecutive times. DO NOT use it again. Try a different approach: (1) create files directly via write_file, (2) use a different tool for the same task, (3) if nothing works — explain the problem and skip this step via plan update step=N status=skipped with a note why.]",
2555
- "exec.repetitive_tool": "[REPETITION: you called {tool} {count} times with identical arguments and result. This is pointless. IMMEDIATELY change approach: create files directly, change arguments, or if it's a process — check its status via process_log.]",
2556
- "exec.consecutive_failures_recovery": "[{count} CONSECUTIVE TOOLS FAILED. Stop running commands. Here is what to do: (1) Create files directly via write_file instead of terminal commands. (2) Check if dependencies are installed (npm install). (3) Use list_dir to see what's in the project. (4) Do not attempt build/tests until all files are created.]",
2557
- "exec.task_reminder": "[ORIGINAL TASK: {task}. You MUST work on this task only. Do not create files for other tasks. If you cannot complete it explain why and request help.]",
2558
- "exec.plan_warning": '[⚠ PLAN: you are on step {step} — "{description}", but calling {tool} with files outside this step. This is FORBIDDEN — work STRICTLY on the current step. First complete it and call plan update step={step} status=done, only then move to the next.]',
2559
- "exec.plan_blocked": "[⛔ BLOCKED: {max} consecutive calls outside the current step. You are violating the plan. Return to step {step} and complete it. Other steps are blocked until this one is done.]",
2560
- "exec.off_track": `[⚠ OFF PLAN: step {stepId} is "{description}", but you're using {tool} on a different path. Return to the current step immediately.]`,
2557
+ "exec.stuck": "No progress on step {stepId} ({description}) for {iterations} iterations.",
2558
+ "exec.stuck_recovery": 'Step {stepId} — "{description}" has had no progress for {iterations} iterations. Try a different approach: review what this step needs, check if dependencies are installed, or create files directly via write_file instead of shell commands. After completing the step call plan update step={stepId} status=done.',
2559
+ "exec.tool_errors": "Tool {tool} failed {count} times. Consider using a different tool.",
2560
+ "exec.tool_errors_recovery": "Tool {tool} has failed {count} times in a row. Try an alternative: create files directly via write_file, use a different command, or if nothing works — skip this step via plan update step=N status=skipped with a note explaining why.",
2561
+ "exec.repetitive_tool": "Called {tool} {count} times with identical arguments and result. Try a different approach create files directly, change arguments, or check process status via process_log.",
2562
+ "exec.consecutive_failures_recovery": "{count} consecutive tool failures. Create files directly via write_file instead of terminal commands. Check that dependencies are installed (npm install). Do not run build/tests until all files are created.",
2563
+ "exec.plan_warning": 'Current plan step {step} is "{description}", but {tool} is being called for files outside this step. Complete the current step first, then call plan update step={step} status=done before moving to the next step.',
2564
+ "exec.plan_blocked": "{max} consecutive calls outside the current step. Finish step {step} before proceeding other steps should wait until this one is complete.",
2565
+ "exec.off_track": 'Step {stepId} "{description}" but you are using {tool} on a different path. Return to the current step.',
2561
2566
  "exec.step_gate_deps": '[⚠ Step {step} "{description}": dependencies are not installed. First run the install command (npm install, pip install, etc.). Verify the lock file or dependency directory exists.]',
2562
2567
  "exec.step_gate_empty": "[⚠ Step {step}: files exist but appear empty: {files}. Add real code to these files before advancing to the next step.]",
2563
2568
  "exec.step_gate_ok": '[✓] Step {step} completed and verified. MOVING to step {nextStep}: "{nextDesc}". Work ONLY on this step.',
@@ -2566,6 +2571,14 @@ Use this knowledge to answer the user's question.`,
2566
2571
  "exec.audit_fail": "[✗] Task incomplete: {done}/{total} steps done, {files} files missing",
2567
2572
  "exec.audit_fail_typecheck": "[✗] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
2568
2573
  "exec.audit_incomplete": "[⚠ Final audit incomplete: {summary}. Task is NOT finished — continue working. Remaining steps: {steps}]",
2574
+ "exec.mass_edit_warning": "⚠️ Plan affects {count} files — review the full list before proceeding.",
2575
+ "exec.escalation": `
2576
+
2577
+ ⚠️ Agent stuck on step {stepId} ({description}). Escalating to user — please provide guidance.`,
2578
+ "exec.hints": `
2579
+ [Hints]
2580
+ {hints}`,
2581
+ "exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
2569
2582
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2570
2583
  "hall.short_response": "Response too short or empty",
2571
2584
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
@@ -2643,7 +2656,12 @@ Use this knowledge to answer the user's question.`,
2643
2656
  "tool.recall.empty": 'Nothing found for "{query}"',
2644
2657
  "tool.recall.no_memory": "Memory is empty",
2645
2658
  "tool.recall.search_results": `{category} results:
2646
- {results}`
2659
+ {results}`,
2660
+ "ctx.compactions": "compactions: {count}",
2661
+ "ctx.quality": "quality: {percent}%",
2662
+ "ctx.delta_pos": "ctx +{tokens}",
2663
+ "ctx.delta_neg": "ctx -{tokens} ↓",
2664
+ "ctx.delta_zero": "ctx ±0"
2647
2665
  };
2648
2666
  });
2649
2667
 
@@ -2803,6 +2821,7 @@ var init_ru = __esm(() => {
2803
2821
  "tool.friendly.process_kill": "Остановка процесса",
2804
2822
  "plan.no_steps": "Не указаны шаги плана. Укажите конкретные шаги с файлами и командами.",
2805
2823
  "plan.created": "План создан: {title} ({steps} шагов)",
2824
+ "plan.coverage_warning": "План может не покрывать требуемые файлы из постановки: {missing}. Добавьте шаги, покрывающие их.",
2806
2825
  "plan.step_done": "Шаг {n}/{total}: {description} ✓",
2807
2826
  "plan.complete": "Задача выполнена: {summary}",
2808
2827
  "plan.title_steps": 'План "{title}" создан с {count} шагами',
@@ -3046,16 +3065,15 @@ var init_ru = __esm(() => {
3046
3065
  {content}
3047
3066
 
3048
3067
  Используйте эти знания для ответа на вопрос пользователя.`,
3049
- "exec.stuck": "Застряли на шаге {stepId}. Текущий: {description}. Нет прогресса {iterations} итераций.",
3050
- "exec.stuck_recovery": '[ЗАСТРЕВАНИЕ: {iterations} итераций на шаге {stepId} — "{description}". ВЫ ДОЛЖНЫ сменить подход прямо сейчас. Конкретные действия: (1) Проверьте что нужно для этого шага установлены ли зависимости? (2) Используйте plan show чтобы увидеть план. (3) Если команды не работают — создайте файлы напрямую через write_file. (4) После завершения шага вызовите plan update step={stepId} status=done. НЕ переходите к другим шагам пока этот не завершён.]',
3051
- "exec.tool_errors": "Инструмент {tool} сбоил {count} раз. Предложите другой подход.",
3052
- "exec.tool_errors_recovery": "[Инструмент {tool} сбоил {count} раз подряд. НЕ ИСПОЛЬЗУЙТЕ его снова. Используйте другой подход: (1) создайте нужные файлы напрямую через write_file, (2) используйте другой инструмент для той же задачи, (3) если ничего не работает — объясните проблему и переходите к следующему шагу плана через plan update step=N status=skipped с пометкой почему.]",
3053
- "exec.repetitive_tool": "[ПОВТОРЕНИЕ: вы вызвали {tool} {count} раз с одинаковыми аргументами и результатом. Это бессмысленно. НЕМЕДЛЕННО смените подход: создайте файлы напрямую, измените аргументы или, если это процесс — проверьте его статус через process_log.]",
3054
- "exec.consecutive_failures_recovery": "[{count} ИНСТРУМЕНТОВ ПОДРЯД УПАЛИ. Хватит пытаться запускать команды. Вот что делать: (1) Создавайте файлы напрямую через write_file вместо команд терминала. (2) Проверьте, установлены ли зависимости (npm install). (3) Используйте list_dir чтобы увидеть что есть в проекте. (4) Не пытайтесь запустить сборку/тесты пока не созданы все файлы.]",
3055
- "exec.task_reminder": "[ИСХОДНАЯ ЗАДАЧА: {task}. ВЫ ДОЛЖНЫ работать только над этой задачей. Не создавайте файлы для других задач. Если не можете выполнить объясните почему и запросите помощь.]",
3056
- "exec.plan_warning": '[⚠ ПЛАН: вы на шаге {step} — "{description}", но вызываете {tool} для файлов вне этого шага. Это ЗАПРЕЩЕНОработайте СТРОГО над текущим шагом. Сначала завершите его и вызовите plan update step={step} status=done, только потом переходите к следующему.]',
3057
- "exec.plan_blocked": "[⛔ ЗАБЛОКИРОВАНО: {max} вызова подряд вне текущего шага. Вы нарушаете план. Вернитесь к шагу {step} и завершите его. Другие шаги будут заблокированы пока текущий не выполнен.]",
3058
- "exec.off_track": '[⚠ ВНЕ ПЛАНА: шаг {stepId} — "{description}", но вы используете {tool} для другого пути. Вернитесь к текущему шагу немедленно.]',
3068
+ "exec.stuck": "Нет прогресса на шаге {stepId} ({description}) {iterations} итераций.",
3069
+ "exec.stuck_recovery": 'Шаг {stepId} — "{description}" без прогресса уже {iterations} итераций. Попробуйте другой подход: проверьте что нужно для этого шага, установлены ли зависимости, или создайте файлы напрямую через write_file вместо команд терминала. После завершения шага вызовите plan update step={stepId} status=done.',
3070
+ "exec.tool_errors": "Инструмент {tool} упал {count} раз. Попробуйте другой инструмент.",
3071
+ "exec.tool_errors_recovery": "Инструмент {tool} упал {count} раз подряд. Попробуйте альтернативу: создайте файлы напрямую через write_file, используйте другую команду, или пропустите этот шаг через plan update step=N status=skipped с пометкой почему.",
3072
+ "exec.repetitive_tool": "Инструмент {tool} вызван {count} раз с одинаковыми аргументами и результатом. Попробуйте другой подход создайте файлы напрямую, измените аргументы или проверьте статус процесса через process_log.",
3073
+ "exec.consecutive_failures_recovery": "{count} инструментов подряд упали. Создавайте файлы напрямую через write_file вместо команд терминала. Проверьте что зависимости установлены (npm install). Не запускайте сборку/тесты пока не созданы все файлы.",
3074
+ "exec.plan_warning": 'Текущий шаг плана {step} "{description}", но вызывается {tool} для файлов вне этого шага. Завершите текущий шаг, вызовите plan update step={step} status=done, затем переходите к следующему.',
3075
+ "exec.plan_blocked": "{max} вызовов подряд вне текущего шага. Завершите шаг {step} остальные шаги ждут пока этот не будет выполнен.",
3076
+ "exec.off_track": 'Шаг {stepId} "{description}", но используется {tool} для другого пути. Вернитесь к текущему шагу.',
3059
3077
  "exec.step_gate_deps": '[⚠ Шаг {step} "{description}": зависимости не установлены. Сначала выполните команду установки (npm install, pip install и т.д.). Проверьте что lock-файл или директория зависимостей существует.]',
3060
3078
  "exec.step_gate_empty": "[⚠ Шаг {step}: файлы существуют, но выглядят пустыми: {files}. Добавьте реальный код в эти файлы перед тем как переходить к следующему шагу.]",
3061
3079
  "exec.step_gate_ok": '[✓] Шаг {step} завершён и проверен. ПЕРЕХОДИМ к шагу {nextStep}: "{nextDesc}". Работайте ТОЛЬКО над этим шагом.',
@@ -3064,6 +3082,14 @@ var init_ru = __esm(() => {
3064
3082
  "exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
3065
3083
  "exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
3066
3084
  "exec.audit_incomplete": "[⚠ Финальная проверка не пройдена: {summary}. Задача НЕ завершена — продолжайте работу. Оставшиеся шаги: {steps}]",
3085
+ "exec.mass_edit_warning": "⚠️ План затрагивает {count} файлов — проверьте полный список перед продолжением.",
3086
+ "exec.escalation": `
3087
+
3088
+ ⚠️ Агент застрял на шаге {stepId} ({description}). Эскалация к пользователю — пожалуйста, подскажите как действовать.`,
3089
+ "exec.hints": `
3090
+ [Подсказки]
3091
+ {hints}`,
3092
+ "exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
3067
3093
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3068
3094
  "hall.short_response": "Слишком короткий или пустой ответ",
3069
3095
  "hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
@@ -3141,7 +3167,12 @@ var init_ru = __esm(() => {
3141
3167
  "tool.recall.empty": 'Ничего не найдено по "{query}"',
3142
3168
  "tool.recall.no_memory": "Память пуста",
3143
3169
  "tool.recall.search_results": `Результаты {category}:
3144
- {results}`
3170
+ {results}`,
3171
+ "ctx.compactions": "сжатий: {count}",
3172
+ "ctx.quality": "качество: {percent}%",
3173
+ "ctx.delta_pos": "контекст +{tokens}",
3174
+ "ctx.delta_neg": "контекст -{tokens} ↓",
3175
+ "ctx.delta_zero": "контекст ±0"
3145
3176
  };
3146
3177
  });
3147
3178
 
@@ -4936,12 +4967,33 @@ function isPathInScope(baseDir, targetPath, scope, securityConfig) {
4936
4967
  const baseResolved = resolve(baseDir);
4937
4968
  const targetResolved = resolve(baseDir, normalize(targetPath));
4938
4969
  if (!targetResolved.startsWith(baseResolved)) {
4939
- return { allowed: false, reason: "Path is outside the base working directory" };
4970
+ return {
4971
+ allowed: false,
4972
+ reason: "Path is outside the base working directory"
4973
+ };
4974
+ }
4975
+ if (!securityConfig?.enabled) {
4976
+ if (scope) {
4977
+ const isReadScope = scope.read_only_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
4978
+ const isWriteScope = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
4979
+ if (isWriteScope)
4980
+ return { allowed: true };
4981
+ if (isReadScope)
4982
+ return { allowed: true, reason: "Read-only file" };
4983
+ return {
4984
+ allowed: false,
4985
+ reason: "Path is not within the allowed scope for this sub-agent"
4986
+ };
4987
+ }
4988
+ return { allowed: true };
4940
4989
  }
4941
4990
  if (securityConfig?.denied?.length) {
4942
4991
  for (const pattern of securityConfig.denied) {
4943
4992
  if (matchesGlobPattern(targetResolved, baseDir, pattern)) {
4944
- return { allowed: false, reason: `Path matches denied pattern: ${pattern}` };
4993
+ return {
4994
+ allowed: false,
4995
+ reason: `Path matches denied pattern: ${pattern}`
4996
+ };
4945
4997
  }
4946
4998
  }
4947
4999
  }
@@ -4954,7 +5006,10 @@ function isPathInScope(baseDir, targetPath, scope, securityConfig) {
4954
5006
  }
4955
5007
  }
4956
5008
  if (!isAllowed) {
4957
- return { allowed: false, reason: "Path does not match any allowed pattern" };
5009
+ return {
5010
+ allowed: false,
5011
+ reason: "Path does not match any allowed pattern"
5012
+ };
4958
5013
  }
4959
5014
  }
4960
5015
  if (!scope) {
@@ -4975,12 +5030,30 @@ function isPathWritable(baseDir, targetPath, scope, securityConfig) {
4975
5030
  const baseResolved = resolve(baseDir);
4976
5031
  const targetResolved = resolve(baseDir, normalize(targetPath));
4977
5032
  if (!targetResolved.startsWith(baseResolved)) {
4978
- return { allowed: false, reason: "Path is outside the base working directory" };
5033
+ return {
5034
+ allowed: false,
5035
+ reason: "Path is outside the base working directory"
5036
+ };
5037
+ }
5038
+ if (!securityConfig?.enabled) {
5039
+ if (scope) {
5040
+ const isWriteScope = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
5041
+ if (isWriteScope)
5042
+ return { allowed: true };
5043
+ return {
5044
+ allowed: false,
5045
+ reason: "Path is not within the allowed scope for this sub-agent"
5046
+ };
5047
+ }
5048
+ return { allowed: true };
4979
5049
  }
4980
5050
  if (securityConfig?.denied?.length) {
4981
5051
  for (const pattern of securityConfig.denied) {
4982
5052
  if (matchesGlobPattern(targetResolved, baseDir, pattern)) {
4983
- return { allowed: false, reason: `Path matches denied pattern: ${pattern}` };
5053
+ return {
5054
+ allowed: false,
5055
+ reason: `Path matches denied pattern: ${pattern}`
5056
+ };
4984
5057
  }
4985
5058
  }
4986
5059
  }
@@ -4993,7 +5066,10 @@ function isPathWritable(baseDir, targetPath, scope, securityConfig) {
4993
5066
  }
4994
5067
  }
4995
5068
  if (!isAllowed) {
4996
- return { allowed: false, reason: "Path does not match any allowed pattern" };
5069
+ return {
5070
+ allowed: false,
5071
+ reason: "Path does not match any allowed pattern"
5072
+ };
4997
5073
  }
4998
5074
  }
4999
5075
  if (!scope) {
@@ -6417,9 +6493,15 @@ function containsOperator(command, op) {
6417
6493
  return new RegExp(escaped).test(command);
6418
6494
  }
6419
6495
  function isCommandAllowed(command, securityConfig) {
6420
- let config = securityConfig || DEFAULT_BASH_CONFIG;
6496
+ if (!securityConfig?.enabled) {
6497
+ return { allowed: true, sanitizedCommand: command };
6498
+ }
6499
+ let config = securityConfig;
6421
6500
  if (!config.blacklist || !Array.isArray(config.blacklist)) {
6422
- config = FALLBACK_BASH_CONFIG;
6501
+ config = {
6502
+ ...FALLBACK_BASH_CONFIG,
6503
+ enabled: config.enabled ?? FALLBACK_BASH_CONFIG.enabled
6504
+ };
6423
6505
  }
6424
6506
  const trimmedCommand = command.trim();
6425
6507
  if (!trimmedCommand) {
@@ -6508,7 +6590,21 @@ var FALLBACK_BASH_CONFIG, DEFAULT_BASH_CONFIG;
6508
6590
  var init_command_validator = __esm(() => {
6509
6591
  init_security();
6510
6592
  FALLBACK_BASH_CONFIG = {
6511
- blacklist: ["rm", "dd", "chmod", "wget", "curl", "scp", "ssh", "nc", "netcat", "powershell", "pwsh", "cmd"],
6593
+ enabled: false,
6594
+ blacklist: [
6595
+ "rm",
6596
+ "dd",
6597
+ "chmod",
6598
+ "wget",
6599
+ "curl",
6600
+ "scp",
6601
+ "ssh",
6602
+ "nc",
6603
+ "netcat",
6604
+ "powershell",
6605
+ "pwsh",
6606
+ "cmd"
6607
+ ],
6512
6608
  whitelist: [],
6513
6609
  blockDangerousFlags: false,
6514
6610
  dangerousFlags: ["--force", "-rf", "--no-preserve-root"],
@@ -8729,7 +8825,133 @@ var init_agent_moe = __esm(() => {
8729
8825
  init_verifier();
8730
8826
  });
8731
8827
 
8828
+ // src/modules/memory/search.ts
8829
+ import { readFileSync as readFileSync9, existsSync as existsSync18 } from "fs";
8830
+ import { join as join8 } from "path";
8831
+
8832
+ class MemorySearch {
8833
+ memoryDir;
8834
+ constructor(memoryDir) {
8835
+ this.memoryDir = memoryDir;
8836
+ }
8837
+ query(query) {
8838
+ const results = [];
8839
+ const lowerQuery = query.toLowerCase();
8840
+ for (const name of MEMORY_FILES) {
8841
+ const path = join8(this.memoryDir, `${name}.md`);
8842
+ if (!existsSync18(path))
8843
+ continue;
8844
+ const content = readFileSync9(path, "utf-8");
8845
+ const lines = content.split(`
8846
+ `);
8847
+ for (const line of lines) {
8848
+ if (line.toLowerCase().includes(lowerQuery)) {
8849
+ results.push({ file: name, match: line.trim() });
8850
+ }
8851
+ }
8852
+ }
8853
+ const prefsPath = join8(this.memoryDir, "preferences.json");
8854
+ if (existsSync18(prefsPath)) {
8855
+ try {
8856
+ const prefs = JSON.parse(readFileSync9(prefsPath, "utf-8"));
8857
+ for (const [key, value] of Object.entries(prefs)) {
8858
+ const searchStr = `${key}=${value}`;
8859
+ if (searchStr.toLowerCase().includes(lowerQuery)) {
8860
+ results.push({ file: "preferences", match: `${key} = ${value}` });
8861
+ }
8862
+ }
8863
+ } catch {}
8864
+ }
8865
+ return results;
8866
+ }
8867
+ }
8868
+ var MEMORY_FILES;
8869
+ var init_search = __esm(() => {
8870
+ MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
8871
+ });
8872
+
8873
+ // src/modules/memory/store.ts
8874
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, appendFileSync as appendFileSync4, existsSync as existsSync19, mkdirSync as mkdirSync10 } from "fs";
8875
+ import { join as join9 } from "path";
8876
+
8877
+ class MemoryStore {
8878
+ memoryDir;
8879
+ constructor(memoryDir) {
8880
+ this.memoryDir = memoryDir;
8881
+ this.ensureDir();
8882
+ for (const name of MEMORY_FILES2) {
8883
+ const path = join9(this.memoryDir, `${name}.md`);
8884
+ if (!existsSync19(path)) {
8885
+ writeFileSync8(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
8886
+
8887
+ `, "utf-8");
8888
+ }
8889
+ }
8890
+ }
8891
+ ensureDir() {
8892
+ if (!existsSync19(this.memoryDir)) {
8893
+ mkdirSync10(this.memoryDir, { recursive: true });
8894
+ }
8895
+ }
8896
+ read(name) {
8897
+ const path = join9(this.memoryDir, `${name}.md`);
8898
+ if (!existsSync19(path))
8899
+ return "";
8900
+ return readFileSync10(path, "utf-8");
8901
+ }
8902
+ append(name, entry) {
8903
+ const path = join9(this.memoryDir, `${name}.md`);
8904
+ const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
8905
+ const formatted = `- **${timestamp}** — ${entry}
8906
+ `;
8907
+ appendFileSync4(path, formatted, "utf-8");
8908
+ }
8909
+ search(query) {
8910
+ const searchModule = new MemorySearch(this.memoryDir);
8911
+ return searchModule.query(query);
8912
+ }
8913
+ prefsPath() {
8914
+ return join9(this.memoryDir, "preferences.json");
8915
+ }
8916
+ getPreferences() {
8917
+ const path = this.prefsPath();
8918
+ if (!existsSync19(path))
8919
+ return {};
8920
+ try {
8921
+ return JSON.parse(readFileSync10(path, "utf-8"));
8922
+ } catch {
8923
+ return {};
8924
+ }
8925
+ }
8926
+ setPreference(key, value) {
8927
+ const prefs = this.getPreferences();
8928
+ prefs[key] = value;
8929
+ writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
8930
+ }
8931
+ deletePreference(key) {
8932
+ const prefs = this.getPreferences();
8933
+ if (!(key in prefs))
8934
+ return false;
8935
+ delete prefs[key];
8936
+ writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
8937
+ return true;
8938
+ }
8939
+ appendRule(category, pattern, cause, solution) {
8940
+ const entry = `**pattern:** ${pattern}
8941
+ **cause:** ${cause}
8942
+ **solution:** ${solution}`;
8943
+ this.append(category, entry);
8944
+ }
8945
+ }
8946
+ var MEMORY_FILES2;
8947
+ var init_store = __esm(() => {
8948
+ init_search();
8949
+ MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
8950
+ });
8951
+
8732
8952
  // src/core/agent.ts
8953
+ import { join as join10 } from "path";
8954
+
8733
8955
  class Agent {
8734
8956
  deps;
8735
8957
  systemPromptAdded = false;
@@ -8797,7 +9019,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8797
9019
  pluginManager,
8798
9020
  contextManager,
8799
9021
  logger,
8800
- sessionManager
9022
+ sessionManager,
9023
+ baseDir
8801
9024
  } = this.deps;
8802
9025
  const slog = new SessionLogger(sessionManager);
8803
9026
  if (sessionManager && !sessionManager.getActive()) {
@@ -8839,7 +9062,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8839
9062
  contextManager,
8840
9063
  hallucinationDetector,
8841
9064
  logger,
8842
- sessionManager
9065
+ sessionManager,
9066
+ baseDir
8843
9067
  } = this.deps;
8844
9068
  const slog = new SessionLogger(sessionManager);
8845
9069
  let iteration = 0;
@@ -8851,6 +9075,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8851
9075
  const MAX_HALLUCINATION_RETRIES = 3;
8852
9076
  let consecutiveToolFailures = 0;
8853
9077
  const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
9078
+ let auditRetries = 0;
9079
+ const MAX_AUDIT_RETRIES = 3;
8854
9080
  while (iteration < config.maxToolIterations && !this.shutdownRequested) {
8855
9081
  iteration++;
8856
9082
  pluginManager.runOnBeforeThink({
@@ -8987,6 +9213,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8987
9213
  pluginManager.runOnToolStart({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments });
8988
9214
  onTool?.({ type: "start", tool: call.name, args: call.arguments });
8989
9215
  slog.logToolCall(call, iteration);
9216
+ const tokensBeforeTool = contextManager.getEstimatedTokens();
8990
9217
  const result = await toolExecutor.execute(call);
8991
9218
  const duration = Date.now() - startTime;
8992
9219
  if (!result.success)
@@ -9006,12 +9233,14 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9006
9233
  ` + result.diff + `
9007
9234
  `);
9008
9235
  }
9236
+ const tokensAfterTool = contextManager.getEstimatedTokens();
9009
9237
  onTool?.({
9010
9238
  type: "end",
9011
9239
  tool: call.name,
9012
9240
  args: call.arguments,
9013
9241
  duration,
9014
- error: !result.success
9242
+ error: !result.success,
9243
+ ctxDelta: tokensAfterTool - tokensBeforeTool
9015
9244
  });
9016
9245
  const currentTokens2 = contextManager.getEstimatedTokens();
9017
9246
  const budget3 = contextManager.getBudget();
@@ -9020,7 +9249,9 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9020
9249
  role: "tool",
9021
9250
  content: truncatedOutput,
9022
9251
  name: call.name,
9023
- tool_call_id: call.id
9252
+ tool_call_id: call.id,
9253
+ success: result.success,
9254
+ arguments: call.arguments
9024
9255
  });
9025
9256
  summaries.push(`[Tool: ${call.name} (${JSON.stringify(call.arguments)}) → ${truncatedOutput.slice(0, 200)}]`);
9026
9257
  if (config.session.autoSave) {
@@ -9048,6 +9279,14 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9048
9279
  content: `<system-summary>${recoveryMsg}
9049
9280
  ${taskReminder}</system-summary>`
9050
9281
  });
9282
+ if (sessionManager) {
9283
+ const activeSession = sessionManager.getActiveMeta();
9284
+ if (activeSession) {
9285
+ const memDir = join10(baseDir, ".mma", "memory");
9286
+ const memStore = new MemoryStore(memDir);
9287
+ memStore.appendRule("errors", `${consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
9288
+ }
9289
+ }
9051
9290
  }
9052
9291
  contextManager.addMessage({
9053
9292
  role: "user",
@@ -9061,8 +9300,11 @@ ${taskReminder}</system-summary>`
9061
9300
  const filled = Math.round(ctxPct / 100 * barLen);
9062
9301
  const ctxBar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
9063
9302
  const pctColor = ctxPct >= 75 ? pc.yellow : pc.dim;
9303
+ const compCount = contextManager.getCompactionCount();
9304
+ const quality = contextManager.getQuality();
9305
+ const qualityColor = quality >= 70 ? pc.green : quality >= 40 ? pc.yellow : pc.red;
9064
9306
  onMeta?.(`
9065
- ${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)}
9307
+ ${ctxBar} ${pctColor(`${ctxPct}%`)} ${pc.dim(`ctx: ${ctxTokens}/${ctxBudget.history}`)} ${pc.dim(`compactions: ${compCount}`)} ${qualityColor(`quality: ${quality}%`)}
9066
9308
  `);
9067
9309
  continue;
9068
9310
  }
@@ -9136,7 +9378,9 @@ ${taskReminder}</system-summary>`
9136
9378
  })}</system-summary>`
9137
9379
  });
9138
9380
  slog.logAudit(audit.summary, iteration);
9139
- if (iteration >= config.maxToolIterations - 1) {
9381
+ auditRetries++;
9382
+ if (auditRetries >= MAX_AUDIT_RETRIES || iteration >= config.maxToolIterations - 1) {
9383
+ logger.warn(`Final audit still incomplete after ${auditRetries} retries — finishing anyway`);
9140
9384
  break;
9141
9385
  }
9142
9386
  continue;
@@ -9157,7 +9401,9 @@ ${taskReminder}</system-summary>`
9157
9401
  contextLimit: budget.history,
9158
9402
  promptTokens: apiPromptTokens,
9159
9403
  completionTokens: apiCompletionTokens,
9160
- totalTokens: apiPromptTokens + apiCompletionTokens
9404
+ totalTokens: apiPromptTokens + apiCompletionTokens,
9405
+ compactionCount: contextManager.getCompactionCount(),
9406
+ contextQuality: contextManager.getQuality()
9161
9407
  };
9162
9408
  }
9163
9409
  return {
@@ -9168,7 +9414,9 @@ ${taskReminder}</system-summary>`
9168
9414
  contextLimit: budget.history,
9169
9415
  promptTokens: apiPromptTokens,
9170
9416
  completionTokens: apiCompletionTokens,
9171
- totalTokens: apiPromptTokens + apiCompletionTokens
9417
+ totalTokens: apiPromptTokens + apiCompletionTokens,
9418
+ compactionCount: contextManager.getCompactionCount(),
9419
+ contextQuality: contextManager.getQuality()
9172
9420
  };
9173
9421
  }
9174
9422
  clearContext() {
@@ -9212,14 +9460,56 @@ var init_agent = __esm(() => {
9212
9460
  init_prompt_builder();
9213
9461
  init_processes();
9214
9462
  init_agent_moe();
9463
+ init_store();
9215
9464
  });
9216
9465
 
9217
9466
  // src/modules/context/manager.ts
9467
+ function summarizeArgs(args) {
9468
+ if (!args)
9469
+ return "";
9470
+ if (typeof args === "string")
9471
+ return args.slice(0, 80);
9472
+ try {
9473
+ const keys = Object.keys(args);
9474
+ return keys.slice(0, 3).join(", ");
9475
+ } catch {
9476
+ return String(args).slice(0, 80);
9477
+ }
9478
+ }
9479
+ function truncate(s, max) {
9480
+ if (s.length <= max)
9481
+ return s;
9482
+ return s.slice(0, max - 3) + "...";
9483
+ }
9484
+ function extractTriedAndFailed(messages) {
9485
+ const failures = new Map;
9486
+ for (const msg of messages) {
9487
+ if (msg.role === "tool" && msg.name && msg.success === false) {
9488
+ const key = `${msg.name}:${summarizeArgs(msg.arguments)}`;
9489
+ const existing = failures.get(key);
9490
+ const errorText = truncate(typeof msg.content === "string" ? msg.content : getMessageText(msg.content), 100);
9491
+ if (existing) {
9492
+ existing.count++;
9493
+ } else {
9494
+ failures.set(key, {
9495
+ tool: msg.name,
9496
+ args: summarizeArgs(msg.arguments),
9497
+ error: errorText,
9498
+ count: 1
9499
+ });
9500
+ }
9501
+ }
9502
+ }
9503
+ return Array.from(failures.values()).filter((f) => f.count >= 2);
9504
+ }
9505
+
9218
9506
  class ContextManager {
9219
9507
  contextWindow;
9220
9508
  messages = [];
9221
9509
  compactedBlock = null;
9222
9510
  iterationsSinceCompaction = 0;
9511
+ compactionCount = 0;
9512
+ peakTokens = 0;
9223
9513
  budget;
9224
9514
  compactionThreshold;
9225
9515
  fileFacts = [];
@@ -9250,6 +9540,17 @@ class ContextManager {
9250
9540
  getBudget() {
9251
9541
  return { ...this.budget };
9252
9542
  }
9543
+ getCompactionCount() {
9544
+ return this.compactionCount;
9545
+ }
9546
+ getIterationsSinceCompaction() {
9547
+ return this.iterationsSinceCompaction;
9548
+ }
9549
+ getQuality() {
9550
+ const freshness = 1 - this.iterationsSinceCompaction / COMPACTION_INTERVAL;
9551
+ const depth = 1 / (1 + this.compactionCount);
9552
+ return Math.round(freshness * depth * 100);
9553
+ }
9253
9554
  addMessage(msg) {
9254
9555
  if (msg.role === "user" && this.pendingImageParts.length > 0) {
9255
9556
  const textPart = {
@@ -9264,6 +9565,9 @@ class ContextManager {
9264
9565
  }
9265
9566
  this.messages.push(msg);
9266
9567
  this.iterationsSinceCompaction++;
9568
+ const tokens = this.getEstimatedTokens();
9569
+ if (tokens > this.peakTokens)
9570
+ this.peakTokens = tokens;
9267
9571
  }
9268
9572
  addPendingImage(part) {
9269
9573
  this.pendingImageParts.push(part);
@@ -9330,6 +9634,7 @@ class ContextManager {
9330
9634
  compact() {
9331
9635
  if (this.messages.length <= KEEP_LAST_N * 2)
9332
9636
  return;
9637
+ this.compactionCount++;
9333
9638
  const cutoff = this.messages.length - KEEP_LAST_N * 2;
9334
9639
  const oldTurns = this.messages.slice(0, cutoff);
9335
9640
  const recentTurns = this.messages.slice(cutoff);
@@ -9345,6 +9650,13 @@ class ContextManager {
9345
9650
  if (this.errorFacts.length > 0) {
9346
9651
  parts.push(`[Errors: ${this.errorFacts.slice(-3).join("; ")}]`);
9347
9652
  }
9653
+ const triedAndFailed = extractTriedAndFailed(this.messages);
9654
+ if (triedAndFailed.length > 0) {
9655
+ const lines = triedAndFailed.map((t2) => `- ${t2.tool}(${t2.args}): ${t2.error} (failed ${t2.count}x)`);
9656
+ parts.push(`[Already tried & failed — do NOT repeat:]
9657
+ ${lines.join(`
9658
+ `)}`);
9659
+ }
9348
9660
  this.compactedBlock = parts.join(" ");
9349
9661
  const summary = {
9350
9662
  role: "user",
@@ -9413,6 +9725,8 @@ class ContextManager {
9413
9725
  this.messages = [];
9414
9726
  this.compactedBlock = null;
9415
9727
  this.iterationsSinceCompaction = 0;
9728
+ this.compactionCount = 0;
9729
+ this.peakTokens = 0;
9416
9730
  }
9417
9731
  getEstimatedTokens() {
9418
9732
  return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
@@ -9868,7 +10182,7 @@ Partial output: ${result.text}`
9868
10182
 
9869
10183
  // src/modules/security/network-validator.ts
9870
10184
  function isUrlAllowed(url, securityConfig) {
9871
- if (!securityConfig) {
10185
+ if (!securityConfig?.enabled) {
9872
10186
  return { allowed: true };
9873
10187
  }
9874
10188
  try {
@@ -10931,7 +11245,7 @@ ${JSON.stringify(result, null, 2)}`
10931
11245
 
10932
11246
  // src/tools/search-history.ts
10933
11247
  import * as fs from "fs";
10934
- import { join as join8 } from "path";
11248
+ import { join as join11 } from "path";
10935
11249
  import { homedir as homedir5 } from "os";
10936
11250
  function searchFile(filePath, query, maxResults, results) {
10937
11251
  if (!fs.existsSync(filePath))
@@ -10969,7 +11283,7 @@ var init_search_history = __esm(() => {
10969
11283
  const query = String(args.query || "").toLowerCase();
10970
11284
  const maxResults = Number(args.maxResults) || 5;
10971
11285
  const sessionId = args.sessionId ? String(args.sessionId) : null;
10972
- const sessionDir = join8(homedir5(), ".mma", "sessions");
11286
+ const sessionDir = join11(homedir5(), ".mma", "sessions");
10973
11287
  const results = [];
10974
11288
  try {
10975
11289
  if (!fs.existsSync(sessionDir)) {
@@ -10981,7 +11295,7 @@ var init_search_history = __esm(() => {
10981
11295
  continue;
10982
11296
  if (sessionId && entry.name !== sessionId)
10983
11297
  continue;
10984
- const historyFile = join8(sessionDir, entry.name, "history.jsonl");
11298
+ const historyFile = join11(sessionDir, entry.name, "history.jsonl");
10985
11299
  searchFile(historyFile, query, maxResults, results);
10986
11300
  if (results.length >= maxResults)
10987
11301
  break;
@@ -10998,127 +11312,9 @@ var init_search_history = __esm(() => {
10998
11312
  };
10999
11313
  });
11000
11314
 
11001
- // src/modules/memory/search.ts
11002
- import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
11003
- import { join as join9 } from "path";
11004
-
11005
- class MemorySearch {
11006
- memoryDir;
11007
- constructor(memoryDir) {
11008
- this.memoryDir = memoryDir;
11009
- }
11010
- query(query) {
11011
- const results = [];
11012
- const lowerQuery = query.toLowerCase();
11013
- for (const name of MEMORY_FILES) {
11014
- const path = join9(this.memoryDir, `${name}.md`);
11015
- if (!existsSync19(path))
11016
- continue;
11017
- const content = readFileSync10(path, "utf-8");
11018
- const lines = content.split(`
11019
- `);
11020
- for (const line of lines) {
11021
- if (line.toLowerCase().includes(lowerQuery)) {
11022
- results.push({ file: name, match: line.trim() });
11023
- }
11024
- }
11025
- }
11026
- const prefsPath = join9(this.memoryDir, "preferences.json");
11027
- if (existsSync19(prefsPath)) {
11028
- try {
11029
- const prefs = JSON.parse(readFileSync10(prefsPath, "utf-8"));
11030
- for (const [key, value] of Object.entries(prefs)) {
11031
- const searchStr = `${key}=${value}`;
11032
- if (searchStr.toLowerCase().includes(lowerQuery)) {
11033
- results.push({ file: "preferences", match: `${key} = ${value}` });
11034
- }
11035
- }
11036
- } catch {}
11037
- }
11038
- return results;
11039
- }
11040
- }
11041
- var MEMORY_FILES;
11042
- var init_search = __esm(() => {
11043
- MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
11044
- });
11045
-
11046
- // src/modules/memory/store.ts
11047
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, appendFileSync as appendFileSync4, existsSync as existsSync20, mkdirSync as mkdirSync10 } from "fs";
11048
- import { join as join10 } from "path";
11049
-
11050
- class MemoryStore {
11051
- memoryDir;
11052
- constructor(memoryDir) {
11053
- this.memoryDir = memoryDir;
11054
- this.ensureDir();
11055
- for (const name of MEMORY_FILES2) {
11056
- const path = join10(this.memoryDir, `${name}.md`);
11057
- if (!existsSync20(path)) {
11058
- writeFileSync8(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
11059
-
11060
- `, "utf-8");
11061
- }
11062
- }
11063
- }
11064
- ensureDir() {
11065
- if (!existsSync20(this.memoryDir)) {
11066
- mkdirSync10(this.memoryDir, { recursive: true });
11067
- }
11068
- }
11069
- read(name) {
11070
- const path = join10(this.memoryDir, `${name}.md`);
11071
- if (!existsSync20(path))
11072
- return "";
11073
- return readFileSync11(path, "utf-8");
11074
- }
11075
- append(name, entry) {
11076
- const path = join10(this.memoryDir, `${name}.md`);
11077
- const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
11078
- const formatted = `- **${timestamp}** — ${entry}
11079
- `;
11080
- appendFileSync4(path, formatted, "utf-8");
11081
- }
11082
- search(query) {
11083
- const searchModule = new MemorySearch(this.memoryDir);
11084
- return searchModule.query(query);
11085
- }
11086
- prefsPath() {
11087
- return join10(this.memoryDir, "preferences.json");
11088
- }
11089
- getPreferences() {
11090
- const path = this.prefsPath();
11091
- if (!existsSync20(path))
11092
- return {};
11093
- try {
11094
- return JSON.parse(readFileSync11(path, "utf-8"));
11095
- } catch {
11096
- return {};
11097
- }
11098
- }
11099
- setPreference(key, value) {
11100
- const prefs = this.getPreferences();
11101
- prefs[key] = value;
11102
- writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
11103
- }
11104
- deletePreference(key) {
11105
- const prefs = this.getPreferences();
11106
- if (!(key in prefs))
11107
- return false;
11108
- delete prefs[key];
11109
- writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
11110
- return true;
11111
- }
11112
- }
11113
- var MEMORY_FILES2;
11114
- var init_store = __esm(() => {
11115
- init_search();
11116
- MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
11117
- });
11118
-
11119
11315
  // src/tools/remember.ts
11120
11316
  import { homedir as homedir6 } from "os";
11121
- import { join as join11 } from "path";
11317
+ import { join as join12 } from "path";
11122
11318
  var CATEGORIES, rememberTool;
11123
11319
  var init_remember = __esm(() => {
11124
11320
  init_i18n();
@@ -11156,7 +11352,7 @@ var init_remember = __esm(() => {
11156
11352
  if (!CATEGORIES.includes(category)) {
11157
11353
  return { success: false, output: t("tool.invalid_params") };
11158
11354
  }
11159
- const memoryDir = join11(homedir6(), ".mma", "memory");
11355
+ const memoryDir = join12(homedir6(), ".mma", "memory");
11160
11356
  const store = new MemoryStore(memoryDir);
11161
11357
  try {
11162
11358
  if (category === "preferences") {
@@ -11189,7 +11385,7 @@ var init_remember = __esm(() => {
11189
11385
 
11190
11386
  // src/tools/recall.ts
11191
11387
  import { homedir as homedir7 } from "os";
11192
- import { join as join12 } from "path";
11388
+ import { join as join13 } from "path";
11193
11389
  function formatAll(store) {
11194
11390
  const parts = [];
11195
11391
  const prefs = store.getPreferences();
@@ -11272,7 +11468,7 @@ var init_recall = __esm(() => {
11272
11468
  handler: async (_ctx, args) => {
11273
11469
  const query = args.query ? String(args.query) : "";
11274
11470
  const category = args.category ? String(args.category) : "";
11275
- const memoryDir = join12(homedir7(), ".mma", "memory");
11471
+ const memoryDir = join13(homedir7(), ".mma", "memory");
11276
11472
  const store = new MemoryStore(memoryDir);
11277
11473
  try {
11278
11474
  if (!query && !category) {
@@ -11468,15 +11664,15 @@ function buildIndexInjectionScript() {
11468
11664
 
11469
11665
  // src/modules/browser/cookie-store.ts
11470
11666
  import { readFile, writeFile, mkdir } from "fs/promises";
11471
- import { join as join13 } from "path";
11667
+ import { join as join14 } from "path";
11472
11668
 
11473
11669
  class CookieStore {
11474
11670
  filePath;
11475
11671
  constructor(cookieDir) {
11476
- this.filePath = join13(cookieDir, "cookies.json");
11672
+ this.filePath = join14(cookieDir, "cookies.json");
11477
11673
  }
11478
11674
  async save(cookies) {
11479
- await mkdir(join13(this.filePath, ".."), { recursive: true });
11675
+ await mkdir(join14(this.filePath, ".."), { recursive: true });
11480
11676
  await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
11481
11677
  }
11482
11678
  async load() {
@@ -11796,10 +11992,10 @@ var init_types = __esm(() => {
11796
11992
  });
11797
11993
 
11798
11994
  // src/tools/browser.ts
11799
- import { join as join14 } from "path";
11995
+ import { join as join15 } from "path";
11800
11996
  function getSession(ctx) {
11801
11997
  if (!session) {
11802
- const cookieDir = join14(ctx.baseDir, ".mma", "browser");
11998
+ const cookieDir = join15(ctx.baseDir, ".mma", "browser");
11803
11999
  session = new BrowserSession({
11804
12000
  ...DEFAULT_BROWSER_CONFIG,
11805
12001
  headless: ctx.config.browser?.headless ?? true,
@@ -11929,8 +12125,8 @@ async function readClipboardFallback() {
11929
12125
  const { platform: platform4 } = await import("os");
11930
12126
  const { execSync: execSync3 } = await import("child_process");
11931
12127
  const { readFileSync: readFileSync13, unlinkSync: unlinkSync3 } = await import("fs");
11932
- const { join: join15 } = await import("path");
11933
- const tmpPath = join15(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
12128
+ const { join: join16 } = await import("path");
12129
+ const tmpPath = join16(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
11934
12130
  try {
11935
12131
  if (platform4() === "linux") {
11936
12132
  execSync3(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
@@ -12208,7 +12404,7 @@ class ModuleRegistry {
12208
12404
 
12209
12405
  // src/modules/plugins/loader.ts
12210
12406
  import { readdirSync as readdirSync5, existsSync as existsSync22, statSync as statSync4 } from "fs";
12211
- import { join as join15 } from "path";
12407
+ import { join as join16 } from "path";
12212
12408
 
12213
12409
  class PluginLoader {
12214
12410
  loadFromDir(dirPath, pluginManager, logger) {
@@ -12216,7 +12412,7 @@ class PluginLoader {
12216
12412
  return;
12217
12413
  const entries = readdirSync5(dirPath);
12218
12414
  for (const entry of entries) {
12219
- const fullPath = join15(dirPath, entry);
12415
+ const fullPath = join16(dirPath, entry);
12220
12416
  if (!statSync4(fullPath).isFile())
12221
12417
  continue;
12222
12418
  if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
@@ -12241,7 +12437,7 @@ var init_loader = __esm(() => {
12241
12437
  // src/modules/plugins/builtin/lint-on-write.ts
12242
12438
  import { execSync as execSync3 } from "child_process";
12243
12439
  import { existsSync as existsSync23, readFileSync as readFileSync13 } from "fs";
12244
- import { resolve as resolve12, extname as extname4, join as join16 } from "path";
12440
+ import { resolve as resolve12, extname as extname4, join as join17 } from "path";
12245
12441
 
12246
12442
  class LintOnWritePlugin {
12247
12443
  name = "lint-on-write";
@@ -12303,7 +12499,7 @@ class LintOnWritePlugin {
12303
12499
  }
12304
12500
  async runProjectLint(ctx, result) {
12305
12501
  try {
12306
- const packageJsonPath = join16(ctx.baseDir, "package.json");
12502
+ const packageJsonPath = join17(ctx.baseDir, "package.json");
12307
12503
  if (!existsSync23(packageJsonPath)) {
12308
12504
  return;
12309
12505
  }
@@ -12323,7 +12519,7 @@ class LintOnWritePlugin {
12323
12519
  }
12324
12520
  }
12325
12521
  async runProjectTypeCheck(ctx, result) {
12326
- const tsconfigPath = join16(ctx.baseDir, "tsconfig.json");
12522
+ const tsconfigPath = join17(ctx.baseDir, "tsconfig.json");
12327
12523
  if (!existsSync23(tsconfigPath)) {
12328
12524
  return;
12329
12525
  }
@@ -12512,6 +12708,11 @@ class StuckDetector {
12512
12708
  repetitionThreshold = 3;
12513
12709
  consecutiveFailures = 0;
12514
12710
  lastFailedTool = "";
12711
+ lastErrorOutput = "";
12712
+ escalationCount = 0;
12713
+ escalationThreshold = 3;
12714
+ fileRewriteCount = new Map;
12715
+ fileRewriteThreshold = 3;
12515
12716
  constructor(threshold = 8, errorThreshold = 3) {
12516
12717
  this.threshold = threshold;
12517
12718
  this.errorThreshold = errorThreshold;
@@ -12531,14 +12732,26 @@ class StuckDetector {
12531
12732
  this.recentToolCalls.shift();
12532
12733
  }
12533
12734
  }
12534
- recordToolError(toolName) {
12735
+ recordToolError(toolName, output) {
12535
12736
  this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
12536
12737
  this.consecutiveFailures++;
12537
12738
  this.lastFailedTool = toolName;
12739
+ if (output)
12740
+ this.lastErrorOutput = output;
12741
+ }
12742
+ getLastErrorOutput() {
12743
+ return this.lastErrorOutput;
12744
+ }
12745
+ getLastFailedTool() {
12746
+ return this.lastFailedTool;
12747
+ }
12748
+ getIterationsOnCurrentStep() {
12749
+ return this.iterationsOnCurrentStep;
12538
12750
  }
12539
12751
  recordToolSuccess() {
12540
12752
  this.consecutiveFailures = 0;
12541
12753
  this.lastFailedTool = "";
12754
+ this.escalationCount = 0;
12542
12755
  }
12543
12756
  setCurrentStep(stepId, description) {
12544
12757
  if (stepId !== this.currentStepId) {
@@ -12576,6 +12789,117 @@ class StuckDetector {
12576
12789
  getConsecutiveFailuresCount() {
12577
12790
  return this.consecutiveFailures;
12578
12791
  }
12792
+ recordEscalation() {
12793
+ this.escalationCount++;
12794
+ }
12795
+ shouldEscalate() {
12796
+ return this.escalationCount >= this.escalationThreshold;
12797
+ }
12798
+ resetEscalation() {
12799
+ this.escalationCount = 0;
12800
+ }
12801
+ getEscalationCount() {
12802
+ return this.escalationCount;
12803
+ }
12804
+ getHints() {
12805
+ const hints = [];
12806
+ const desc = this.currentStepDescription.toLowerCase();
12807
+ if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
12808
+ hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
12809
+ }
12810
+ if (desc.includes("test") || desc.includes("spec")) {
12811
+ hints.push("Make sure the source files exist and have real code before running tests.");
12812
+ }
12813
+ if (desc.includes("build") || desc.includes("compile")) {
12814
+ hints.push("Check that all dependencies are installed and source files are not empty.");
12815
+ }
12816
+ if (desc.includes("deploy") || desc.includes("publish")) {
12817
+ hints.push("Verify credentials and network access before deploying.");
12818
+ }
12819
+ if (this.hasRepetitiveToolCalls()) {
12820
+ hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
12821
+ }
12822
+ if (this.hasConsecutiveFailures()) {
12823
+ hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
12824
+ }
12825
+ return hints;
12826
+ }
12827
+ getActionableHints() {
12828
+ const hints = [];
12829
+ const error = this.lastErrorOutput;
12830
+ if (!error)
12831
+ return hints;
12832
+ if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
12833
+ if (/node_modules/.test(error)) {
12834
+ hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
12835
+ }
12836
+ }
12837
+ if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
12838
+ hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
12839
+ }
12840
+ if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
12841
+ hints.push("TypeScript type mismatch. Check the type signature of the function/API you are using. If a package lacks type declarations, install @types/<package> or use skipLibCheck.");
12842
+ }
12843
+ if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
12844
+ hints.push("Module not found. Run npm install or check if the import path is correct.");
12845
+ }
12846
+ if (/EACCES|EPERM|permission denied/i.test(error)) {
12847
+ hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
12848
+ }
12849
+ if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
12850
+ hints.push("Network error. Check if the server is running and accessible.");
12851
+ }
12852
+ if (/is not recognized|command not found|not found in path/i.test(error)) {
12853
+ hints.push("Command not found. Check if the tool is installed and in PATH.");
12854
+ }
12855
+ if (this.hasExcessiveRewrites()) {
12856
+ const file = this.getExcessiveRewriteFile();
12857
+ hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
12858
+ }
12859
+ return hints;
12860
+ }
12861
+ getToolAlternative() {
12862
+ const tool = this.lastFailedTool;
12863
+ const error = this.lastErrorOutput;
12864
+ if (!tool)
12865
+ return null;
12866
+ if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
12867
+ const alternatives = {
12868
+ "ts-node": "tsx",
12869
+ jest: "vitest",
12870
+ mocha: "vitest",
12871
+ webpack: "vite",
12872
+ rollup: "vite",
12873
+ parcel: "vite",
12874
+ babel: "tsc",
12875
+ eslint: "biome",
12876
+ prettier: "biome"
12877
+ };
12878
+ for (const [failed, alt] of Object.entries(alternatives)) {
12879
+ if (tool.includes(failed) || error.includes(failed)) {
12880
+ return alt;
12881
+ }
12882
+ }
12883
+ }
12884
+ return null;
12885
+ }
12886
+ recordFileRewrite(filePath) {
12887
+ const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
12888
+ this.fileRewriteCount.set(filePath, count);
12889
+ }
12890
+ getFileRewriteCount(filePath) {
12891
+ return this.fileRewriteCount.get(filePath) || 0;
12892
+ }
12893
+ hasExcessiveRewrites() {
12894
+ return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
12895
+ }
12896
+ getExcessiveRewriteFile() {
12897
+ for (const [file, count] of this.fileRewriteCount) {
12898
+ if (count >= this.fileRewriteThreshold)
12899
+ return file;
12900
+ }
12901
+ return null;
12902
+ }
12579
12903
  getRepetitiveToolMessage() {
12580
12904
  if (!this.hasRepetitiveToolCalls())
12581
12905
  return "";
@@ -12654,7 +12978,7 @@ var init_stuck_detector = __esm(() => {
12654
12978
  // src/modules/execution/auditor.ts
12655
12979
  import { execSync as execSync4 } from "child_process";
12656
12980
  import { existsSync as existsSync24 } from "fs";
12657
- import { resolve as resolve13, join as join17 } from "path";
12981
+ import { resolve as resolve13, join as join18 } from "path";
12658
12982
 
12659
12983
  class Auditor {
12660
12984
  baseDir;
@@ -12662,7 +12986,7 @@ class Auditor {
12662
12986
  this.baseDir = baseDir;
12663
12987
  }
12664
12988
  async audit(plan) {
12665
- const allStepText = plan.steps.map((s) => s.description).join(" ");
12989
+ const allStepText = plan.steps.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
12666
12990
  const fileMatches = allStepText.match(/\b[\w./-]+\.[a-z]+/gi) || [];
12667
12991
  const uniqueFiles = [...new Set(fileMatches)];
12668
12992
  const missingFiles = [];
@@ -12678,6 +13002,10 @@ class Auditor {
12678
13002
  const doneSteps = plan.steps.filter((s) => s.status === "done").length;
12679
13003
  const totalSteps = plan.steps.length;
12680
13004
  const typeCheckError = await this.runProjectTypeCheck();
13005
+ let massEditWarning = null;
13006
+ if (uniqueFiles.length > MASS_EDIT_THRESHOLD) {
13007
+ massEditWarning = t("exec.mass_edit_warning", { count: String(uniqueFiles.length) });
13008
+ }
12681
13009
  const passed = missingFiles.length === 0 && !typeCheckError;
12682
13010
  let summary;
12683
13011
  if (passed) {
@@ -12696,11 +13024,12 @@ class Auditor {
12696
13024
  createdFiles: existingFiles,
12697
13025
  modifiedFiles: [],
12698
13026
  summary,
12699
- typeCheckError
13027
+ typeCheckError,
13028
+ massEditWarning
12700
13029
  };
12701
13030
  }
12702
13031
  async runProjectTypeCheck() {
12703
- const tsconfigPath = join17(this.baseDir, "tsconfig.json");
13032
+ const tsconfigPath = join18(this.baseDir, "tsconfig.json");
12704
13033
  if (!existsSync24(tsconfigPath)) {
12705
13034
  return null;
12706
13035
  }
@@ -12721,22 +13050,23 @@ class Auditor {
12721
13050
  }
12722
13051
  }
12723
13052
  }
13053
+ var MASS_EDIT_THRESHOLD = 10;
12724
13054
  var init_auditor = __esm(() => {
12725
13055
  init_i18n();
12726
13056
  });
12727
13057
 
12728
13058
  // src/modules/execution/plan-persister.ts
12729
13059
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11, existsSync as existsSync25 } from "fs";
12730
- import { join as join18 } from "path";
13060
+ import { join as join19 } from "path";
12731
13061
 
12732
13062
  class PlanPersister {
12733
13063
  filePath;
12734
13064
  constructor(baseDir) {
12735
- const mmaDir = join18(baseDir, ".mma");
13065
+ const mmaDir = join19(baseDir, ".mma");
12736
13066
  if (!existsSync25(mmaDir)) {
12737
13067
  mkdirSync11(mmaDir, { recursive: true });
12738
13068
  }
12739
- this.filePath = join18(mmaDir, "plan.json");
13069
+ this.filePath = join19(mmaDir, "plan.json");
12740
13070
  }
12741
13071
  save(plan) {
12742
13072
  const file = {
@@ -12774,6 +13104,92 @@ class PlanPersister {
12774
13104
  }
12775
13105
  var init_plan_persister = () => {};
12776
13106
 
13107
+ // src/modules/execution/plan-coverage.ts
13108
+ function extractFilePaths(text) {
13109
+ if (!text)
13110
+ return [];
13111
+ const seen = new Set;
13112
+ const result = [];
13113
+ for (const m of text.matchAll(FILE_PATH_RE)) {
13114
+ let p = m[0];
13115
+ p = p.replace(/[.,;:)\]>]+$/g, "");
13116
+ const ext = p.split(".").pop()?.toLowerCase() ?? "";
13117
+ if (!p || IGNORED_EXT.has(ext))
13118
+ continue;
13119
+ const key = p.toLowerCase();
13120
+ if (seen.has(key))
13121
+ continue;
13122
+ seen.add(key);
13123
+ result.push(p);
13124
+ }
13125
+ return result;
13126
+ }
13127
+ function basename2(p) {
13128
+ const parts = p.split(/[/\\]/);
13129
+ return parts[parts.length - 1] ?? p;
13130
+ }
13131
+ function checkPlanCoverage(taskText, stepDescriptions) {
13132
+ const taskPaths = extractFilePaths(taskText);
13133
+ if (taskPaths.length === 0)
13134
+ return { missing: [] };
13135
+ const stepText = stepDescriptions.map((s) => s.toLowerCase()).join(`
13136
+ `);
13137
+ const missing = taskPaths.filter((p) => {
13138
+ const lower = p.toLowerCase();
13139
+ const base = basename2(lower);
13140
+ return !stepText.includes(lower) && !stepText.includes(base);
13141
+ });
13142
+ return { missing };
13143
+ }
13144
+ var FILE_PATH_RE, IGNORED_EXT;
13145
+ var init_plan_coverage = __esm(() => {
13146
+ FILE_PATH_RE = /\b[\w./\\-]+\.[a-z]+\b/gi;
13147
+ IGNORED_EXT = new Set([
13148
+ "tsx",
13149
+ "jsx",
13150
+ "json5",
13151
+ "mdx",
13152
+ "yml",
13153
+ "yaml",
13154
+ "toml",
13155
+ "lock",
13156
+ "svg",
13157
+ "png",
13158
+ "jpg",
13159
+ "jpeg",
13160
+ "gif",
13161
+ "webp",
13162
+ "ico",
13163
+ "css",
13164
+ "scss",
13165
+ "less",
13166
+ "html",
13167
+ "jsx"
13168
+ ]);
13169
+ });
13170
+
13171
+ // src/modules/skills/error-skill-map.ts
13172
+ function suggestSkill(errorOutput) {
13173
+ for (const { pattern, skill } of ERROR_SKILL_MAP) {
13174
+ if (pattern.test(errorOutput))
13175
+ return skill;
13176
+ }
13177
+ return null;
13178
+ }
13179
+ var ERROR_SKILL_MAP;
13180
+ var init_error_skill_map = __esm(() => {
13181
+ ERROR_SKILL_MAP = [
13182
+ { pattern: /TS\d{4}:/i, skill: "typescript-expert" },
13183
+ { pattern: /Cannot find name|No overload matches/i, skill: "typescript-type-expert" },
13184
+ { pattern: /ts-node|tsx.*error/i, skill: "typescript-expert" },
13185
+ { pattern: /Module not found|Cannot resolve module/i, skill: "typescript-expert" },
13186
+ { pattern: /ECONNREFUSED|ETIMEDOUT|fetch failed/i, skill: "devops-expert" },
13187
+ { pattern: /permission denied|EACCES/i, skill: "linux-server-expert" },
13188
+ { pattern: /docker|container/i, skill: "docker-expert" },
13189
+ { pattern: /jest|vitest|test.*fail/i, skill: "vitest-testing-expert" }
13190
+ ];
13191
+ });
13192
+
12777
13193
  // src/modules/execution/module.ts
12778
13194
  import { existsSync as existsSync26, readFileSync as readFileSync15 } from "fs";
12779
13195
  import { resolve as resolve14 } from "path";
@@ -12850,13 +13266,13 @@ class ExecutionModule {
12850
13266
  name: "plan",
12851
13267
  description: `Create, update, show, or abort a multi-step plan. Use "create" at the start of complex tasks. Use "update" after completing each step to track progress. Use "show" to re-print the current plan checklist.
12852
13268
 
12853
- IMPORTANT each step MUST be detailed and concrete (≥50 chars):
13269
+ Write CONCRETE steps with exact file paths and commands:
12854
13270
  - Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")
12855
- - Specify WHICH packages to install with exact versions (e.g. "run npm install react@18.3 react-dom@18.3")
13271
+ - Specify WHICH packages to install (e.g. "run npm install react react-dom")
12856
13272
  - Specify WHICH CLI commands to run with exact arguments
12857
- - Must contain at least one file extension (.ts, .tsx, .json, etc.) OR a command verb (install, create, build, run, add, init)
12858
- - BAD: "Настройка проекта и установка зависимостей" (too vague, no files, no versions)
12859
- - GOOD: "Создать package.json с зависимостями react@18.3 и vite@5.4, затем выполнить npm install в корне проекта"`,
13273
+ - Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)
13274
+ - Good: "Создать src/components/Header.tsx с навигацией и логотипом"
13275
+ - Bad: "Настройка проекта" (too vague what exactly needs to be configured?)`,
12860
13276
  parameters: {
12861
13277
  type: "object",
12862
13278
  properties: {
@@ -12883,10 +13299,25 @@ IMPORTANT — each step MUST be detailed and concrete (≥50 chars):
12883
13299
  const plan = PlanCreator.createPlan(title, steps, this.baseDir);
12884
13300
  this.setPlan(plan);
12885
13301
  const display = PlanCreator.toPromptBlock(plan, 0);
13302
+ let output = t("plan.created", { title, steps: String(steps.length) });
13303
+ let displayOut = display;
13304
+ const taskText = this.getTaskText(_ctx);
13305
+ if (taskText) {
13306
+ const { missing } = checkPlanCoverage(taskText, steps);
13307
+ if (missing.length > 0) {
13308
+ const warn = t("plan.coverage_warning", {
13309
+ missing: missing.join(", ")
13310
+ });
13311
+ output += `
13312
+ ⚠️ ${warn}`;
13313
+ displayOut = `${display}
13314
+ ⚠️ ${warn}`;
13315
+ }
13316
+ }
12886
13317
  return {
12887
13318
  success: true,
12888
- output: t("plan.created", { title, steps: String(steps.length) }),
12889
- display
13319
+ output,
13320
+ display: displayOut
12890
13321
  };
12891
13322
  }
12892
13323
  if (action === "show") {
@@ -13032,12 +13463,47 @@ Sub-tasks: ${note}`
13032
13463
  if (currentIter - this.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
13033
13464
  const recovery = this.stuckDetector.getRecoveryMessage();
13034
13465
  if (recovery && ctx.contextManager) {
13466
+ const lastError = this.stuckDetector.getLastErrorOutput();
13467
+ const suggestedSkill = suggestSkill(lastError);
13468
+ const skillHint = suggestedSkill ? `
13469
+ Consider loading the "${suggestedSkill}" skill for expert guidance on this error.` : "";
13470
+ const actionableHints = this.stuckDetector.getActionableHints();
13471
+ const actionableHintStr = actionableHints.length > 0 ? `
13472
+ ${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
13473
+ `) })}` : "";
13474
+ const alternative = this.stuckDetector.getToolAlternative();
13475
+ const altHint = alternative ? `
13476
+ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
13477
+ ctx.contextManager.addMessage({
13478
+ role: "user",
13479
+ content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
13480
+ });
13481
+ }
13482
+ const hints = this.stuckDetector.getHints();
13483
+ if (hints.length > 0 && ctx.contextManager) {
13484
+ const hintMsg = t("exec.hints", { hints: hints.map((h) => `- ${h}`).join(`
13485
+ `) });
13035
13486
  ctx.contextManager.addMessage({
13036
13487
  role: "user",
13037
- content: `<system-summary>${recovery}</system-summary>`
13488
+ content: `<system-summary>${hintMsg}</system-summary>`
13038
13489
  });
13039
13490
  }
13491
+ this.stuckDetector.recordEscalation();
13040
13492
  this.lastRecoveryIteration = currentIter;
13493
+ if (this.stuckDetector.shouldEscalate() && ctx.onMeta) {
13494
+ const escalation = t("exec.escalation", {
13495
+ stepId: String(this.tracker?.getCurrentStep()?.id ?? "?"),
13496
+ description: this.tracker?.getCurrentStep()?.description ?? ""
13497
+ });
13498
+ ctx.onMeta(escalation);
13499
+ }
13500
+ if (this.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
13501
+ const step2 = this.tracker?.getCurrentStep();
13502
+ ctx.contextManager.addMessage({
13503
+ role: "user",
13504
+ content: `<system-summary>CRITICAL: You have been stuck on step ${step2?.id ?? "?"} ("${step2?.description ?? ""}") for ${this.stuckDetector.getIterationsOnCurrentStep()} iterations. You MUST stop trying the same approach. Options: 1) Skip this step via plan update step=${step2?.id ?? "?"} status=skipped with a note explaining why. 2) Mark the step as done if the code runs correctly, even if there are type errors. 3) Try a completely different approach. Do NOT continue with the current approach.</system-summary>`
13505
+ });
13506
+ }
13041
13507
  }
13042
13508
  }
13043
13509
  },
@@ -13070,11 +13536,44 @@ Sub-tasks: ${note}`
13070
13536
  },
13071
13537
  onAfterTool: (ctx, call, result) => {
13072
13538
  if (!result.success) {
13073
- this.stuckDetector.recordToolError(call.name);
13539
+ this.stuckDetector.recordToolError(call.name, result.output);
13540
+ const actionableHints = this.stuckDetector.getActionableHints();
13541
+ const alternative = this.stuckDetector.getToolAlternative();
13542
+ if ((actionableHints.length > 0 || alternative) && ctx.contextManager) {
13543
+ const parts = [...actionableHints];
13544
+ if (alternative) {
13545
+ parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
13546
+ }
13547
+ ctx.contextManager.addMessage({
13548
+ role: "user",
13549
+ content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
13550
+ `) })}</system-summary>`
13551
+ });
13552
+ }
13074
13553
  } else {
13075
13554
  this.stuckDetector.recordToolSuccess();
13555
+ if (call.name === "bash" && result.success && ctx.contextManager) {
13556
+ const cmd = String(call.arguments?.command ?? "");
13557
+ if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
13558
+ ctx.contextManager.addMessage({
13559
+ role: "user",
13560
+ content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`
13561
+ });
13562
+ }
13563
+ }
13076
13564
  }
13077
- if (this.tracker && result.success && (call.name === "write_file" || call.name === "edit_file")) {
13565
+ if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
13566
+ const filePath = call.arguments?.path;
13567
+ if (filePath) {
13568
+ this.stuckDetector.recordFileRewrite(filePath);
13569
+ if (this.stuckDetector.hasExcessiveRewrites()) {
13570
+ const file = this.stuckDetector.getExcessiveRewriteFile();
13571
+ const count = this.stuckDetector.getFileRewriteCount(file);
13572
+ if (ctx.onMeta) {
13573
+ ctx.onMeta(t("exec.file_rewrite_warning", { file, count: String(count) }));
13574
+ }
13575
+ }
13576
+ }
13078
13577
  this.advancePlanIfStepComplete(ctx.contextManager);
13079
13578
  }
13080
13579
  }
@@ -13185,8 +13684,18 @@ Sub-tasks: ${note}`
13185
13684
  });
13186
13685
  }
13187
13686
  }
13687
+ getTaskText(ctx) {
13688
+ const manager = ctx?.contextManager;
13689
+ if (!manager)
13690
+ return "";
13691
+ const history = manager.getActiveHistory();
13692
+ const firstUser = history.find((m) => m.role === "user");
13693
+ if (!firstUser)
13694
+ return "";
13695
+ return getMessageText(firstUser.content).trim();
13696
+ }
13188
13697
  }
13189
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3;
13698
+ var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 15;
13190
13699
  var init_module = __esm(() => {
13191
13700
  init_i18n();
13192
13701
  init_tracker();
@@ -13194,11 +13703,13 @@ var init_module = __esm(() => {
13194
13703
  init_stuck_detector();
13195
13704
  init_auditor();
13196
13705
  init_plan_persister();
13706
+ init_plan_coverage();
13707
+ init_error_skill_map();
13197
13708
  });
13198
13709
 
13199
13710
  // src/modules/security/session-encryption.ts
13200
13711
  import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync27, readdirSync as readdirSync6, unlinkSync as unlinkSync3 } from "fs";
13201
- import { join as join19 } from "path";
13712
+ import { join as join20 } from "path";
13202
13713
  import { homedir as homedir8 } from "os";
13203
13714
 
13204
13715
  class SessionFileEncryptor {
@@ -13207,7 +13718,7 @@ class SessionFileEncryptor {
13207
13718
  constructor(config) {
13208
13719
  this.config = { ...DEFAULT_SESSION_ENCRYPTION, ...config };
13209
13720
  this.encryptor = new ConfigEncryptor({
13210
- keyPath: config?.keyPath || join19(homedir8(), ".mma", ".session-encryption-key")
13721
+ keyPath: config?.keyPath || join20(homedir8(), ".mma", ".session-encryption-key")
13211
13722
  });
13212
13723
  }
13213
13724
  isEnabled() {
@@ -13279,7 +13790,7 @@ class SessionFileEncryptor {
13279
13790
  return;
13280
13791
  const files = readdirSync6(sessionDir);
13281
13792
  for (const file of files) {
13282
- const filePath = join19(sessionDir, file);
13793
+ const filePath = join20(sessionDir, file);
13283
13794
  if (existsSync27(filePath) && !file.endsWith(".enc")) {
13284
13795
  try {
13285
13796
  const content = readFileSync16(filePath, "utf8");
@@ -13296,7 +13807,7 @@ class SessionFileEncryptor {
13296
13807
  const files = readdirSync6(sessionDir);
13297
13808
  for (const file of files) {
13298
13809
  if (file.endsWith(".enc")) {
13299
- const encFilePath = join19(sessionDir, file);
13810
+ const encFilePath = join20(sessionDir, file);
13300
13811
  const decFilePath = encFilePath.slice(0, -4);
13301
13812
  try {
13302
13813
  const content = readFileSync16(encFilePath, "utf8");
@@ -13329,7 +13840,7 @@ import {
13329
13840
  writeFileSync as writeFileSync11,
13330
13841
  appendFileSync as appendFileSync5
13331
13842
  } from "fs";
13332
- import { join as join20 } from "path";
13843
+ import { join as join21 } from "path";
13333
13844
  import { gzipSync } from "zlib";
13334
13845
 
13335
13846
  class SessionStore {
@@ -13355,16 +13866,16 @@ class SessionStore {
13355
13866
  mkdirSync12(this.baseDir, { recursive: true });
13356
13867
  }
13357
13868
  sessionDir(id) {
13358
- return join20(this.baseDir, id);
13869
+ return join21(this.baseDir, id);
13359
13870
  }
13360
13871
  metaPath(id) {
13361
- return join20(this.sessionDir(id), "meta.json");
13872
+ return join21(this.sessionDir(id), "meta.json");
13362
13873
  }
13363
13874
  historyPath(id) {
13364
- return join20(this.sessionDir(id), "history.jsonl");
13875
+ return join21(this.sessionDir(id), "history.jsonl");
13365
13876
  }
13366
13877
  sessionLogPath(id) {
13367
- return join20(this.sessionDir(id), "session.jsonl");
13878
+ return join21(this.sessionDir(id), "session.jsonl");
13368
13879
  }
13369
13880
  sessionExists(id) {
13370
13881
  return existsSync28(this.metaPath(id));
@@ -13487,7 +13998,7 @@ class SessionStore {
13487
13998
  if (existsSync28(historyPath)) {
13488
13999
  const content = readFileSync17(historyPath, "utf-8");
13489
14000
  const compressed = gzipSync(content);
13490
- const gzPath = join20(this.baseDir, `${session2.id}.jsonl.gz`);
14001
+ const gzPath = join21(this.baseDir, `${session2.id}.jsonl.gz`);
13491
14002
  writeFileSync11(gzPath, compressed);
13492
14003
  rmSync(historyPath);
13493
14004
  }
@@ -13695,7 +14206,7 @@ class ProfileCompressor {
13695
14206
 
13696
14207
  // src/modules/user-profile/profile.ts
13697
14208
  import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync29, mkdirSync as mkdirSync13 } from "fs";
13698
- import { join as join21 } from "path";
14209
+ import { join as join22 } from "path";
13699
14210
  import { homedir as homedir9, hostname, platform as platform4, type } from "os";
13700
14211
  import { env } from "process";
13701
14212
 
@@ -13722,10 +14233,10 @@ class UserProfile {
13722
14233
  if (!existsSync29(this.profileDir)) {
13723
14234
  mkdirSync13(this.profileDir, { recursive: true });
13724
14235
  }
13725
- writeFileSync12(join21(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
14236
+ writeFileSync12(join22(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
13726
14237
  }
13727
14238
  load() {
13728
- const path = join21(this.profileDir, "profile.json");
14239
+ const path = join22(this.profileDir, "profile.json");
13729
14240
  if (!existsSync29(path))
13730
14241
  return null;
13731
14242
  try {
@@ -13765,7 +14276,7 @@ var init_profile = () => {};
13765
14276
 
13766
14277
  // src/modules/skills/loader.ts
13767
14278
  import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync30, statSync as statSync5 } from "fs";
13768
- import { join as join22 } from "path";
14279
+ import { join as join23 } from "path";
13769
14280
 
13770
14281
  class SkillsLoader {
13771
14282
  loadFromDir(dirPath) {
@@ -13778,7 +14289,7 @@ class SkillsLoader {
13778
14289
  scanDir(dirPath, skills) {
13779
14290
  const entries = readdirSync8(dirPath);
13780
14291
  for (const entry of entries) {
13781
- const fullPath = join22(dirPath, entry);
14292
+ const fullPath = join23(dirPath, entry);
13782
14293
  const stat = statSync5(fullPath);
13783
14294
  if (stat.isDirectory()) {
13784
14295
  this.scanDir(fullPath, skills);
@@ -14059,7 +14570,7 @@ var init_browser2 = __esm(() => {
14059
14570
 
14060
14571
  // src/modules/indexer/walker.ts
14061
14572
  import { readdirSync as readdirSync9, readFileSync as readFileSync20, statSync as statSync6, existsSync as existsSync31, watch } from "fs";
14062
- import { join as join23, relative, extname as extname5 } from "path";
14573
+ import { join as join24, relative, extname as extname5 } from "path";
14063
14574
 
14064
14575
  class Indexer {
14065
14576
  baseDir;
@@ -14097,7 +14608,7 @@ class Indexer {
14097
14608
  for (const entry of entries) {
14098
14609
  if (count >= this.MAX_FILES)
14099
14610
  return;
14100
- const fullPath = join23(dir, entry);
14611
+ const fullPath = join24(dir, entry);
14101
14612
  const relPath = relative(this.baseDir, fullPath);
14102
14613
  const stat = statSync6(fullPath);
14103
14614
  if (stat.isDirectory()) {
@@ -14161,13 +14672,13 @@ var init_walker = __esm(() => {
14161
14672
 
14162
14673
  // src/modules/indexer/cache.ts
14163
14674
  import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as existsSync32, mkdirSync as mkdirSync14, rmSync as rmSync2 } from "fs";
14164
- import { join as join24 } from "path";
14675
+ import { join as join25 } from "path";
14165
14676
 
14166
14677
  class IndexCache {
14167
14678
  cachePath;
14168
14679
  cache = null;
14169
14680
  constructor(cacheDir) {
14170
- this.cachePath = join24(cacheDir, "index-cache.json");
14681
+ this.cachePath = join25(cacheDir, "index-cache.json");
14171
14682
  }
14172
14683
  load() {
14173
14684
  if (this.cache)
@@ -14183,7 +14694,7 @@ class IndexCache {
14183
14694
  }
14184
14695
  save(result) {
14185
14696
  this.cache = result;
14186
- const dir = join24(this.cachePath, "..");
14697
+ const dir = join25(this.cachePath, "..");
14187
14698
  if (!existsSync32(dir))
14188
14699
  mkdirSync14(dir, { recursive: true });
14189
14700
  writeFileSync13(this.cachePath, JSON.stringify(result), "utf-8");
@@ -14548,13 +15059,13 @@ var init_mcp = __esm(() => {
14548
15059
 
14549
15060
  // src/modules/memory/module.ts
14550
15061
  import { homedir as homedir10 } from "os";
14551
- import { join as join25 } from "path";
15062
+ import { join as join26 } from "path";
14552
15063
 
14553
15064
  class MemoryModule {
14554
15065
  name = "memory";
14555
15066
  store;
14556
15067
  constructor(memoryDir) {
14557
- const dir = memoryDir || join25(homedir10(), ".mma", "memory");
15068
+ const dir = memoryDir || join26(homedir10(), ".mma", "memory");
14558
15069
  this.store = new MemoryStore(dir);
14559
15070
  }
14560
15071
  getSystemPromptBlock() {
@@ -14601,7 +15112,7 @@ __export(exports_bootstrap, {
14601
15112
  bootstrap: () => bootstrap
14602
15113
  });
14603
15114
  import { homedir as homedir11 } from "os";
14604
- import { join as join26, resolve as resolve15 } from "path";
15115
+ import { join as join27, resolve as resolve15 } from "path";
14605
15116
  import { existsSync as existsSync33, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
14606
15117
  function buildSystemInfo(config, baseDir, profileCompressed) {
14607
15118
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
@@ -14611,21 +15122,20 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
14611
15122
  `Date: ${now}`,
14612
15123
  `Workspace: ${baseDir}`,
14613
15124
  `${profileCompressed}`,
14614
- `Reply in the user's language. Use tools for filesystem, bash, web access.`,
14615
- `Use tools when needed. Explain briefly what you're doing if it's not obvious.`,
14616
- `Answer the user's question directly. Only take action when the user explicitly asks you to change, create, or fix something.`,
14617
- `If a tool call fails, report the error and ask the user how to proceed.`,
15125
+ `Reply in the user's language. Use tools for: read/write/edit/delete files, search (glob/grep), shell commands (bash), web access (web_search, web_fetch), subagents, browser, MCP.`,
15126
+ `Use tools when needed explain briefly what you're doing if it's not obvious.`,
15127
+ `If a tool call fails, analyze the error and correct the call try up to 2 times with different approaches before asking the user for help.`,
14618
15128
  ``,
14619
- `Design principles — apply them automatically without naming them:`,
14620
- `- Do not add code, files, or abstractions that are not needed right now (YAGNI — You Ain't Gonna Need It). If something is not required by the current task, omit it.`,
14621
- `- Prefer simple, straightforward solutions over clever or complex ones (KISS — Keep It Simple, Stupid). Flat code beats nested, minimal config beats flexible, explicit beats generic.`,
14622
- `- Do not duplicate code, logic, or configuration (DRY Don't Repeat Yourself). Extract shared logic into a single place, reuse existing utilities and patterns before writing new ones.`
15129
+ `Design principles:`,
15130
+ `- YAGNI: Do not add code, files, or abstractions not needed right now.`,
15131
+ `- KISS: Prefer simple, straightforward solutions over clever or complex ones.`,
15132
+ `- DRY: Do not duplicate code, logic, or configuration — reuse existing utilities and patterns.`
14623
15133
  ];
14624
15134
  if (isWin) {
14625
15135
  lines.push(``, `Windows environment — use Windows-compatible commands:`, `- Use "dir" instead of "ls". Use "dir /b" for bare listing.`, `- Use "type" or "Get-Content" instead of "cat".`, `- Use "cd" instead of "pwd". Use "echo %cd%" to print working directory.`, `- Use "copy" instead of "cp", "move" instead of "mv", "del" instead of "rm".`, `- Do not use "mkdir -p" — Windows mkdir creates intermediate dirs by default. Use the create_dir tool instead.`, `- Use forward slashes (/) or escaped backslashes (\\\\) in file paths.`, `- Your working directory is: ${baseDir}`);
14626
15136
  }
14627
- lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Do NOT chain "cd dir && cmd" — the security module blocks the "&&" operator.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`, `- Background processes (dev servers, watchers, long-running npm install): use the "background: true" parameter or the tool will auto-detect and background them. Check output with process_log.`);
14628
- lines.push(``, `=== DEVELOPMENT RULES — follow these strictly ===`, ``, `1. DEPENDENCIES FIRST: Before writing any source code, ALWAYS install project dependencies (e.g., "npm install", "pip install -r requirements.txt", "cargo build", "go mod tidy"). Verify the package manager's lock file or dependency directory exists. Never write code that imports/uses packages that aren't installed yet.`, `2. TOOLKIT/FWK FIRST: If the task specifies a framework or UI library, initialize and configure it BEFORE writing application code. Run its project init command first, then add components/modules. Never write your own version of what the framework already provides.`, `3. ONE STEP AT A TIME: Follow the plan sequentially. Complete step N before starting step N+1. When a step is done: verify the deliverables exist and have real content (not empty), then call "plan update step=N status=done". Do not redo completed work.`, `4. VERIFY YOUR WORK: After creating/modifying files, verify they exist on disk. After installing dependencies, verify the package manager completed successfully. After any command, check its output for errors. Don't assume operations succeeded.`, `5. NO PREMATURE WORK: Do not create files for future steps. Do not add imports/references to packages or modules that haven't been installed yet. Do not reference files or components that don't exist yet. Build incrementally — one layer at a time.`, `6. WHEN STUCK: If a command fails 2+ times, STOP and try a different approach. Write files directly instead of using commands. Ask the user for help. Never repeat the same failing command more than twice.`, ``, `=== PLAN QUALITY RULES — your plan MUST follow these ===`, ``, `- Each step must describe CONCRETE deliverables: exact filenames with paths, exact packages to install, exact CLI commands to run. Steps shorter than 50 characters or without file paths/commands are REJECTED.`, `- A step like "Настройка проекта" or "Setup the project" is USELESS and will be REJECTED.`, `- A step like "Создать src/components/Header.tsx с навигацией и логотипом, добавить в src/App.tsx импорт <Header />" is GOOD.`, `- Include file extensions (.tsx, .css, .json) and directory paths. Every step must mention at least one file or command.`, `- The plan must cover EVERYTHING needed: init → deps → framework setup → code → verification.`, `- Number of steps: 5-8 for a typical task. Too few means you're being vague. Too many means you're over-splitting.`);
15137
+ lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Prefer workdir over "cd dir && cmd" chaining.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`, `- Background processes (dev servers, watchers, long-running npm install): use the "background: true" parameter or the tool will auto-detect and background them. Check output with process_log.`);
15138
+ lines.push(``, `=== DEVELOPMENT RULES — follow these strictly ===`, ``, `1. DEPENDENCIES FIRST: Before writing any source code, ALWAYS install project dependencies (e.g., "npm install", "pip install -r requirements.txt", "cargo build", "go mod tidy"). Verify the package manager's lock file or dependency directory exists. Never write code that imports/uses packages that aren't installed yet.`, `2. TOOLKIT/FWK FIRST: If the task specifies a framework or UI library, initialize and configure it BEFORE writing application code. Run its project init command first, then add components/modules. Never write your own version of what the framework already provides.`, `3. ONE STEP AT A TIME: Follow the plan sequentially. Complete step N before starting step N+1. When a step is done: verify the deliverables exist and have real content (not empty), then call "plan update step=N status=done". Do not redo completed work.`, `4. VERIFY YOUR WORK: After creating/modifying files, verify they exist on disk. After installing dependencies, verify the package manager completed successfully. After any command, check its output for errors. Don't assume operations succeeded.`, `5. NO PREMATURE WORK: Do not create files for future steps. Do not add imports/references to packages or modules that haven't been installed yet. Do not reference files or components that don't exist yet. Build incrementally — one layer at a time.`, `6. WHEN STUCK: If a command fails 2+ times, STOP and try a different approach. Write files directly instead of using commands. Ask the user for help. Never repeat the same failing command more than twice.`, ``, `=== PLAN QUALITY RULES — your plan MUST follow these ===`, ``, `- Each step must describe CONCRETE deliverables: exact filenames with paths, exact packages to install, exact CLI commands to run. Avoid vague steps be specific.`, `- A step like "Настройка проекта" or "Setup the project" is too vague describe what exactly needs to be configured or set up.`, `- A step like "Создать src/components/Header.tsx с навигацией и логотипом, добавить в src/App.tsx импорт <Header />" is GOOD.`, `- Include file extensions (.tsx, .css, .json) and directory paths. Every step must mention at least one file or command.`, `- The plan must cover EVERYTHING needed: init → deps → framework setup → code → verification.`, `- Number of steps: 5-8 for a typical task. Too few means you're being vague. Too many means you're over-splitting.`);
14629
15139
  if (config.autoPlan) {
14630
15140
  lines.push(``, `Plan rule (MANDATORY): For ANY task that requires creating files, installing packages, or multiple actions — you MUST create a plan using the "plan" tool BEFORE starting work. Each step must describe a concrete deliverable (specific files to create, packages to install, commands to run). Do not combine unrelated work into one step.`);
14631
15141
  }
@@ -14637,8 +15147,8 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
14637
15147
  `);
14638
15148
  }
14639
15149
  async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14640
- const dir = configDir || join26(homedir11(), ".mma");
14641
- const projectConfigPath = projectDir ? join26(projectDir, ".mmrc") : join26(process.cwd(), ".mmrc");
15150
+ const dir = configDir || join27(homedir11(), ".mma");
15151
+ const projectConfigPath = projectDir ? join27(projectDir, ".mmrc") : join27(process.cwd(), ".mmrc");
14642
15152
  const config = loadConfig({ configDir: dir, projectConfigPath });
14643
15153
  setLocale(config.locale);
14644
15154
  try {
@@ -14648,7 +15158,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14648
15158
  }
14649
15159
  } catch {}
14650
15160
  const logger = new Logger(config.logLevel);
14651
- logger.setLogDir(join26(dir, "logs"));
15161
+ logger.setLogDir(join27(dir, "logs"));
14652
15162
  logger.debug("MMA bootstrap", {
14653
15163
  version: config.version,
14654
15164
  model: config.model
@@ -14670,7 +15180,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14670
15180
  logger.info(`Model ${config.model} loaded in ${loadResult.loadTime}s`);
14671
15181
  }
14672
15182
  }
14673
- const profile = new UserProfile(join26(dir));
15183
+ const profile = new UserProfile(join27(dir));
14674
15184
  profile.load() || profile.collect();
14675
15185
  profile.save();
14676
15186
  const llmProvider = new OpenAICompatProvider({
@@ -14682,7 +15192,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14682
15192
  rateLimits: config.security?.rateLimits
14683
15193
  });
14684
15194
  const baseDir = projectDir ? resolve15(projectDir) : process.cwd();
14685
- const projectMapCacheDir = join26(baseDir, ".mma");
15195
+ const projectMapCacheDir = join27(baseDir, ".mma");
14686
15196
  const indexerModule = new IndexerModule({
14687
15197
  baseDir,
14688
15198
  cacheDir: projectMapCacheDir
@@ -14693,9 +15203,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14693
15203
  logger.warn(`Project indexing failed: ${err.message}`);
14694
15204
  }
14695
15205
  const skillsLoader = new SkillsLoader;
14696
- const builtinDir = join26(import.meta.dirname, "skills", "builtin");
14697
- const globalDir = join26(homedir11(), ".agents", "skills");
14698
- const projectSkillsDir = join26(baseDir, ".mma", "skills");
15206
+ const builtinDir = join27(import.meta.dirname, "skills", "builtin");
15207
+ const globalDir = join27(homedir11(), ".agents", "skills");
15208
+ const projectSkillsDir = join27(baseDir, ".mma", "skills");
14699
15209
  const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
14700
15210
  const skillsMatcher = new SkillsMatcher;
14701
15211
  const skillsBudget = Math.floor(config.contextWindow * 0.1);
@@ -14709,11 +15219,11 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14709
15219
  essential: true,
14710
15220
  estimatedTokens: 250
14711
15221
  };
14712
- const agentsMdGlobal = join26(dir, "AGENTS.md");
15222
+ const agentsMdGlobal = join27(dir, "AGENTS.md");
14713
15223
  if (!existsSync33(agentsMdGlobal)) {
14714
15224
  writeFileSync14(agentsMdGlobal, "", "utf-8");
14715
15225
  }
14716
- const sessionDir = join26(dir, "sessions");
15226
+ const sessionDir = join27(dir, "sessions");
14717
15227
  const sessionStore = new SessionStore(sessionDir);
14718
15228
  sessionStore.init();
14719
15229
  const sessionManager = new SessionManager(sessionStore, {
@@ -14767,7 +15277,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14767
15277
  const mcpModule = new MCPModule(config);
14768
15278
  await mcpModule.initialize();
14769
15279
  moduleRegistry.register(mcpModule);
14770
- const memoryModule = new MemoryModule(join26(dir, "memory"));
15280
+ const memoryModule = new MemoryModule(join27(dir, "memory"));
14771
15281
  moduleRegistry.register(memoryModule);
14772
15282
  if (config.browser.enabled) {
14773
15283
  const browserModule = new BrowserModule;
@@ -14810,8 +15320,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14810
15320
  pluginManager.register(plugin);
14811
15321
  pluginManager.register(plugin2);
14812
15322
  const pluginLoader = new PluginLoader;
14813
- const globalPluginsDir = join26(homedir11(), ".mma", "plugins");
14814
- const projectPluginsDir = join26(dir, ".mma", "plugins");
15323
+ const globalPluginsDir = join27(homedir11(), ".mma", "plugins");
15324
+ const projectPluginsDir = join27(dir, ".mma", "plugins");
14815
15325
  pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
14816
15326
  pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
14817
15327
  contextManager.onCompact = (summary) => {
@@ -14829,9 +15339,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14829
15339
  const skipAgentsMd = noAgentsMd === true;
14830
15340
  if (!skipAgentsMd) {
14831
15341
  const agentsMdCandidates = [
14832
- join26(baseDir, "AGENTS.md"),
14833
- join26(baseDir, ".mma", "AGENTS.md"),
14834
- join26(dir, "AGENTS.md")
15342
+ join27(baseDir, "AGENTS.md"),
15343
+ join27(baseDir, ".mma", "AGENTS.md"),
15344
+ join27(dir, "AGENTS.md")
14835
15345
  ];
14836
15346
  for (const p of agentsMdCandidates) {
14837
15347
  if (existsSync33(p)) {
@@ -15158,7 +15668,7 @@ function pad(text, width, align) {
15158
15668
  }
15159
15669
  return text + " ".repeat(gap);
15160
15670
  }
15161
- function truncate(text, width) {
15671
+ function truncate2(text, width) {
15162
15672
  if (stringWidth(text) <= width)
15163
15673
  return text;
15164
15674
  let acc = "";
@@ -15244,7 +15754,7 @@ function formatTable(tableLines, opts = {}) {
15244
15754
  const cells = [];
15245
15755
  for (let c = 0;c < columnCount; c++) {
15246
15756
  const raw = row[c] ?? "";
15247
- const cell = truncate(raw, widths[c]);
15757
+ const cell = truncate2(raw, widths[c]);
15248
15758
  const a = isHeader && align[c] === undefined ? "center" : align[c] ?? "left";
15249
15759
  cells.push(pad(cell, widths[c], a));
15250
15760
  }
@@ -15668,14 +16178,14 @@ async function runSetup() {
15668
16178
 
15669
16179
  // src/cli/commands.ts
15670
16180
  init_i18n();
15671
- import { join as join28, dirname as dirname9 } from "path";
16181
+ import { join as join29, dirname as dirname9 } from "path";
15672
16182
  import { homedir as homedir13 } from "os";
15673
16183
  import { existsSync as existsSync34, readFileSync as readFileSync23 } from "fs";
15674
16184
 
15675
16185
  // src/cli/security-commands.ts
15676
16186
  init_bootstrap();
15677
16187
  init_config();
15678
- import { join as join27 } from "path";
16188
+ import { join as join28 } from "path";
15679
16189
  import { homedir as homedir12 } from "os";
15680
16190
 
15681
16191
  // src/modules/security/security-policies.ts
@@ -15686,7 +16196,9 @@ var STRICT_POLICY = {
15686
16196
  preset: "strict",
15687
16197
  recommendedFor: ["production", "sensitive-data", "multi-user", "enterprise"],
15688
16198
  config: {
16199
+ enabled: true,
15689
16200
  bash: {
16201
+ enabled: true,
15690
16202
  blacklist: [
15691
16203
  "rm",
15692
16204
  "dd",
@@ -15745,10 +16257,22 @@ var STRICT_POLICY = {
15745
16257
  "-R",
15746
16258
  "--no-clobber"
15747
16259
  ],
15748
- dangerousOperators: [">", ">>", "2>", "2>>", "|", "&&", "||", ";", "&", "`"],
16260
+ dangerousOperators: [
16261
+ ">",
16262
+ ">>",
16263
+ "2>",
16264
+ "2>>",
16265
+ "|",
16266
+ "&&",
16267
+ "||",
16268
+ ";",
16269
+ "&",
16270
+ "`"
16271
+ ],
15749
16272
  logCommands: true
15750
16273
  },
15751
16274
  paths: {
16275
+ enabled: true,
15752
16276
  denied: [
15753
16277
  ".git/",
15754
16278
  "node_modules/",
@@ -15781,6 +16305,7 @@ var STRICT_POLICY = {
15781
16305
  allowed: []
15782
16306
  },
15783
16307
  network: {
16308
+ enabled: true,
15784
16309
  deniedDomains: [
15785
16310
  "localhost",
15786
16311
  "127.0.0.1",
@@ -15852,7 +16377,9 @@ var BALANCED_POLICY = {
15852
16377
  preset: "balanced",
15853
16378
  recommendedFor: ["development", "single-user", "general-use", "testing"],
15854
16379
  config: {
16380
+ enabled: true,
15855
16381
  bash: {
16382
+ enabled: true,
15856
16383
  blacklist: [
15857
16384
  "rm",
15858
16385
  "dd",
@@ -15898,6 +16425,7 @@ var BALANCED_POLICY = {
15898
16425
  logCommands: true
15899
16426
  },
15900
16427
  paths: {
16428
+ enabled: true,
15901
16429
  denied: [
15902
16430
  ".git/",
15903
16431
  "node_modules/",
@@ -15918,6 +16446,7 @@ var BALANCED_POLICY = {
15918
16446
  allowed: []
15919
16447
  },
15920
16448
  network: {
16449
+ enabled: true,
15921
16450
  deniedDomains: [],
15922
16451
  allowedDomains: [],
15923
16452
  requestTimeout: 15000,
@@ -15953,7 +16482,12 @@ var BALANCED_POLICY = {
15953
16482
  auditNotifier: {
15954
16483
  enabled: false,
15955
16484
  minSeverity: "medium",
15956
- eventTypes: ["security_block", "bash_command", "file_operation", "network_request"],
16485
+ eventTypes: [
16486
+ "security_block",
16487
+ "bash_command",
16488
+ "file_operation",
16489
+ "network_request"
16490
+ ],
15957
16491
  maxRetries: 3
15958
16492
  }
15959
16493
  }
@@ -15964,7 +16498,9 @@ var PERMISSIVE_POLICY = {
15964
16498
  preset: "permissive",
15965
16499
  recommendedFor: ["trusted-environment", "local-dev", "testing", "sandbox"],
15966
16500
  config: {
16501
+ enabled: true,
15967
16502
  bash: {
16503
+ enabled: true,
15968
16504
  blacklist: [
15969
16505
  "rm",
15970
16506
  "dd",
@@ -15984,10 +16520,12 @@ var PERMISSIVE_POLICY = {
15984
16520
  logCommands: true
15985
16521
  },
15986
16522
  paths: {
16523
+ enabled: true,
15987
16524
  denied: [".env", "*.key", "*.pem", "*.crt", "*.p12", "*.pfx"],
15988
16525
  allowed: []
15989
16526
  },
15990
16527
  network: {
16528
+ enabled: true,
15991
16529
  deniedDomains: [],
15992
16530
  allowedDomains: [],
15993
16531
  requestTimeout: 30000,
@@ -16177,7 +16715,7 @@ function createSecurityCommand(program2) {
16177
16715
  }
16178
16716
  });
16179
16717
  securityCmd.command("set-policy").argument("<preset>", t("cli.security.preset")).description(t("cli.security.set_policy")).action(async (preset) => {
16180
- const configPath = join27(homedir12(), ".mma", "config.json");
16718
+ const configPath = join28(homedir12(), ".mma", "config.json");
16181
16719
  const { config: appConfig } = await bootstrap();
16182
16720
  const validPresets = ["strict", "balanced", "permissive"];
16183
16721
  if (!validPresets.includes(preset)) {
@@ -16192,7 +16730,7 @@ function createSecurityCommand(program2) {
16192
16730
  console.log(t("cli.security.policy_description", { description: policy.description }));
16193
16731
  });
16194
16732
  securityCmd.command("enable-encryption").description(t("cli.security.enable_encryption")).action(async () => {
16195
- const configPath = join27(homedir12(), ".mma", "config.json");
16733
+ const configPath = join28(homedir12(), ".mma", "config.json");
16196
16734
  const { config: appConfig } = await bootstrap();
16197
16735
  appConfig.security = appConfig.security || {};
16198
16736
  appConfig.security.sessionEncryption = {
@@ -16204,7 +16742,7 @@ function createSecurityCommand(program2) {
16204
16742
  console.log(t("cli.security.encryption_enabled"));
16205
16743
  });
16206
16744
  securityCmd.command("disable-encryption").description(t("cli.security.disable_encryption")).action(async () => {
16207
- const configPath = join27(homedir12(), ".mma", "config.json");
16745
+ const configPath = join28(homedir12(), ".mma", "config.json");
16208
16746
  const { config: appConfig } = await bootstrap();
16209
16747
  appConfig.security = appConfig.security || {};
16210
16748
  appConfig.security.sessionEncryption = {
@@ -16216,7 +16754,7 @@ function createSecurityCommand(program2) {
16216
16754
  console.log(t("cli.security.encryption_disabled"));
16217
16755
  });
16218
16756
  securityCmd.command("enable-audit").description(t("cli.security.enable_audit")).action(async () => {
16219
- const configPath = join27(homedir12(), ".mma", "config.json");
16757
+ const configPath = join28(homedir12(), ".mma", "config.json");
16220
16758
  const { config: appConfig } = await bootstrap();
16221
16759
  appConfig.security = appConfig.security || {};
16222
16760
  appConfig.security.auditNotifier = {
@@ -16230,7 +16768,7 @@ function createSecurityCommand(program2) {
16230
16768
  console.log(t("cli.security.audit_enabled"));
16231
16769
  });
16232
16770
  securityCmd.command("disable-audit").description(t("cli.security.disable_audit")).action(async () => {
16233
- const configPath = join27(homedir12(), ".mma", "config.json");
16771
+ const configPath = join28(homedir12(), ".mma", "config.json");
16234
16772
  const { config: appConfig } = await bootstrap();
16235
16773
  appConfig.security = appConfig.security || {};
16236
16774
  appConfig.security.auditNotifier = {
@@ -16266,8 +16804,8 @@ import { fileURLToPath } from "url";
16266
16804
  function readVersion() {
16267
16805
  const here = dirname9(fileURLToPath(import.meta.url));
16268
16806
  const candidates = [
16269
- join28(here, "..", "..", "package.json"),
16270
- join28(here, "..", "package.json")
16807
+ join29(here, "..", "..", "package.json"),
16808
+ join29(here, "..", "package.json")
16271
16809
  ];
16272
16810
  for (const p of candidates) {
16273
16811
  if (existsSync34(p)) {
@@ -16283,7 +16821,7 @@ function createProgram() {
16283
16821
  const program2 = new Command().name("mma").description(t("cli.description")).version(version).option("--no-agents-md", t("cli.no_agents_md")).option("-d, --dir <path>", t("cli.dir")).option("-e, --exit-on-complete", t("cli.exit_on_complete")).option("-j, --json", t("cli.json"));
16284
16822
  program2.command("init").description(t("cli.init")).action(async () => {
16285
16823
  const answers = await runSetup();
16286
- const configPath = join28(homedir13(), ".mma", "config.json");
16824
+ const configPath = join29(homedir13(), ".mma", "config.json");
16287
16825
  const { config } = await bootstrap();
16288
16826
  config.provider.type = answers.provider;
16289
16827
  config.provider.baseUrl = answers.apiBase;
@@ -16292,12 +16830,43 @@ function createProgram() {
16292
16830
  config.contextWindow = answers.contextWindow;
16293
16831
  config.maxToolIterations = answers.maxToolIterations;
16294
16832
  config.locale = answers.locale;
16833
+ if (config.security) {
16834
+ config.security.enabled = answers.securityBashBlock || answers.securityFlagsBlock || !answers.securityPathsDeny;
16835
+ config.security.bash.enabled = config.security.enabled;
16836
+ config.security.bash.blockDangerousFlags = answers.securityFlagsBlock;
16837
+ if (answers.securityBashBlock) {
16838
+ config.security.bash.blacklist = [
16839
+ ...new Set([
16840
+ ...config.security.bash.blacklist,
16841
+ "rm",
16842
+ "dd",
16843
+ "chmod",
16844
+ "wget",
16845
+ "curl",
16846
+ "scp",
16847
+ "ssh",
16848
+ "nc",
16849
+ "netcat",
16850
+ "sudo",
16851
+ "su",
16852
+ "kill",
16853
+ "pkill",
16854
+ "killall",
16855
+ "shutdown",
16856
+ "reboot"
16857
+ ])
16858
+ ];
16859
+ }
16860
+ if (!answers.securityPathsDeny) {
16861
+ config.security.paths.denied = [];
16862
+ }
16863
+ }
16295
16864
  saveConfig(config, configPath);
16296
16865
  console.log(t("cli.config_saved"));
16297
16866
  });
16298
16867
  const configCmd = program2.command("config").description(t("cli.manage_config"));
16299
16868
  configCmd.command("set").argument("<key>", t("cli.config_key")).argument("<value>", "Config value").description(t("cli.set_value")).action(async (key, value) => {
16300
- const configPath = join28(homedir13(), ".mma", "config.json");
16869
+ const configPath = join29(homedir13(), ".mma", "config.json");
16301
16870
  const { config } = await bootstrap();
16302
16871
  const keys = key.split(".");
16303
16872
  let obj = config;
@@ -16357,14 +16926,14 @@ function createProgram() {
16357
16926
  console.log(t("cli.model_hint"));
16358
16927
  });
16359
16928
  model.command("use").argument("<name>", "Model name").description(t("cli.set_model")).action(async (name) => {
16360
- const configPath = join28(homedir13(), ".mma", "config.json");
16929
+ const configPath = join29(homedir13(), ".mma", "config.json");
16361
16930
  const { config } = await bootstrap();
16362
16931
  config.model = name;
16363
16932
  saveConfig(config, configPath);
16364
16933
  console.log(t("cli.model_set", { name }));
16365
16934
  });
16366
16935
  program2.command("context").description(t("cli.manage_context")).argument("<size>", "Context window size in tokens").action(async (size) => {
16367
- const configPath = join28(homedir13(), ".mma", "config.json");
16936
+ const configPath = join29(homedir13(), ".mma", "config.json");
16368
16937
  const { config } = await bootstrap();
16369
16938
  const contextWindow = parseInt(size, 10);
16370
16939
  if (isNaN(contextWindow) || contextWindow < 1024) {
@@ -16382,7 +16951,7 @@ function createProgram() {
16382
16951
  console.log(t("cli.base_url"), config.provider.baseUrl);
16383
16952
  });
16384
16953
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
16385
- const configPath = join28(homedir13(), ".mma", "config.json");
16954
+ const configPath = join29(homedir13(), ".mma", "config.json");
16386
16955
  const { config } = await bootstrap();
16387
16956
  config.provider.type = name;
16388
16957
  saveConfig(config, configPath);
@@ -16435,7 +17004,7 @@ init_bootstrap();
16435
17004
  init_colors();
16436
17005
  import * as readline2 from "readline";
16437
17006
  import { existsSync as existsSync35, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "fs";
16438
- import { join as join29, dirname as dirname10 } from "path";
17007
+ import { join as join30, dirname as dirname10 } from "path";
16439
17008
  import { homedir as homedir14 } from "os";
16440
17009
  import { fileURLToPath as fileURLToPath2 } from "url";
16441
17010
 
@@ -16751,7 +17320,7 @@ init_i18n();
16751
17320
  function isRichTerminal() {
16752
17321
  return Boolean(process.stdout.isTTY) && !process.env.CI;
16753
17322
  }
16754
- function summarizeArgs(args) {
17323
+ function summarizeArgs2(args) {
16755
17324
  const preferred = ["path", "file", "query", "url", "command", "name"];
16756
17325
  for (const key of preferred) {
16757
17326
  const value = args[key];
@@ -16817,7 +17386,7 @@ class Renderer {
16817
17386
  toolStart(tool, args) {
16818
17387
  this.endCard();
16819
17388
  this.spinner.stop();
16820
- const summary = summarizeArgs(args);
17389
+ const summary = summarizeArgs2(args);
16821
17390
  if (!this.rich) {
16822
17391
  this.out.write(`
16823
17392
  ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
@@ -16827,15 +17396,21 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
16827
17396
  this.card = { tool, args, body: [], start: Date.now() };
16828
17397
  this.spinner.start(`${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}`);
16829
17398
  }
16830
- toolEnd(_tool, duration, error) {
17399
+ toolEnd(_tool, duration, error, ctxDelta) {
16831
17400
  this.spinner.stop();
16832
- if (!this.rich)
17401
+ if (!this.rich) {
17402
+ if (ctxDelta !== undefined && ctxDelta !== 0) {
17403
+ const deltaStr = ctxDelta > 0 ? pc.green(`+${ctxDelta}`) : pc.yellow(`${ctxDelta} ↓`);
17404
+ this.out.write(`${pc.dim("ctx")} ${deltaStr}
17405
+ `);
17406
+ }
16833
17407
  return;
17408
+ }
16834
17409
  if (!this.card)
16835
17410
  return;
16836
17411
  const { tool, args, body } = this.card;
16837
17412
  const lines = [];
16838
- const summary = summarizeArgs(args);
17413
+ const summary = summarizeArgs2(args);
16839
17414
  if (summary)
16840
17415
  lines.push(pc.dim(summary));
16841
17416
  for (const chunk of body) {
@@ -16846,7 +17421,12 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
16846
17421
  }
16847
17422
  }
16848
17423
  const marker = error ? pc.red("✗") : pc.green("✓");
16849
- lines.push(`${marker} ${pc.dim(`${duration}ms`)}`);
17424
+ let footer = `${marker} ${pc.dim(`${duration}ms`)}`;
17425
+ if (ctxDelta !== undefined && ctxDelta !== 0) {
17426
+ const deltaStr = ctxDelta > 0 ? pc.green(`+${ctxDelta}`) : pc.yellow(`${ctxDelta} ↓`);
17427
+ footer += ` ${pc.dim("ctx")} ${deltaStr}`;
17428
+ }
17429
+ lines.push(footer);
16850
17430
  const title = `${marker} ${friendlyTool(tool)}`;
16851
17431
  for (const line of box(lines, { title, width: this.width })) {
16852
17432
  this.out.write(`${line}
@@ -16885,8 +17465,8 @@ init_config();
16885
17465
  function readVersion2() {
16886
17466
  const here = dirname10(fileURLToPath2(import.meta.url));
16887
17467
  const candidates = [
16888
- join29(here, "..", "..", "package.json"),
16889
- join29(here, "..", "package.json")
17468
+ join30(here, "..", "..", "package.json"),
17469
+ join30(here, "..", "package.json")
16890
17470
  ];
16891
17471
  for (const p of candidates) {
16892
17472
  if (existsSync35(p)) {
@@ -16919,13 +17499,21 @@ var COMMAND_GROUPS = {
16919
17499
  delete: "session",
16920
17500
  skill: "skill"
16921
17501
  };
16922
- function formatContextBar(used, limit) {
17502
+ function formatContextBar(used, limit, compactions, quality) {
16923
17503
  const pct = Math.min(100, Math.round(used / limit * 100));
16924
17504
  const barLen = 20;
16925
17505
  const filled = Math.round(pct / 100 * barLen);
16926
17506
  const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
16927
17507
  const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
16928
- return ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
17508
+ let line = ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
17509
+ if (compactions !== undefined) {
17510
+ line += pc.dim(` compactions: ${compactions}`);
17511
+ }
17512
+ if (quality !== undefined) {
17513
+ const qColor = quality >= 70 ? pc.green : quality >= 40 ? pc.yellow : pc.red;
17514
+ line += ` ${qColor(`quality: ${quality}%`)}`;
17515
+ }
17516
+ return line;
16929
17517
  }
16930
17518
 
16931
17519
  class Repl {
@@ -16954,10 +17542,10 @@ class Repl {
16954
17542
  this.sessionManager = sessionManager;
16955
17543
  this.skillsModule = skillsModule;
16956
17544
  this.pluginManager = pluginManager;
16957
- this.configDir = configDir || join29(homedir14(), ".mma");
17545
+ this.configDir = configDir || join30(homedir14(), ".mma");
16958
17546
  this.baseDir = baseDir || process.cwd();
16959
17547
  this.noAgentsMd = noAgentsMd === true;
16960
- this.historyPath = join29(homedir14(), ".mma", "repl-history");
17548
+ this.historyPath = join30(homedir14(), ".mma", "repl-history");
16961
17549
  this.loadHistory();
16962
17550
  this.registerBuiltinCommands();
16963
17551
  this.registerMmaCommands();
@@ -17143,7 +17731,7 @@ class Repl {
17143
17731
  action: async () => {
17144
17732
  console.log(pc.yellow(t("repl.wizard_running")));
17145
17733
  const answers = await runSetup();
17146
- const configPath = join29(homedir14(), ".mma", "config.json");
17734
+ const configPath = join30(homedir14(), ".mma", "config.json");
17147
17735
  this.config.provider.type = answers.provider;
17148
17736
  this.config.provider.baseUrl = answers.apiBase;
17149
17737
  this.config.provider.apiKey = answers.apiKey;
@@ -17173,7 +17761,7 @@ class Repl {
17173
17761
  return;
17174
17762
  }
17175
17763
  this.config.provider.type = name;
17176
- const configPath = join29(homedir14(), ".mma", "config.json");
17764
+ const configPath = join30(homedir14(), ".mma", "config.json");
17177
17765
  saveConfig(this.config, configPath);
17178
17766
  console.log(pc.green(t("repl.provider_set", { name })));
17179
17767
  return;
@@ -17225,7 +17813,7 @@ class Repl {
17225
17813
  return;
17226
17814
  }
17227
17815
  this.config.model = name;
17228
- const configPath = join29(homedir14(), ".mma", "config.json");
17816
+ const configPath = join30(homedir14(), ".mma", "config.json");
17229
17817
  saveConfig(this.config, configPath);
17230
17818
  console.log(pc.green(t("repl.model_set", { name })));
17231
17819
  return;
@@ -17249,7 +17837,7 @@ class Repl {
17249
17837
  return;
17250
17838
  }
17251
17839
  this.config.contextWindow = size;
17252
- const configPath = join29(homedir14(), ".mma", "config.json");
17840
+ const configPath = join30(homedir14(), ".mma", "config.json");
17253
17841
  saveConfig(this.config, configPath);
17254
17842
  console.log(pc.green(t("cli.context_set", { size })));
17255
17843
  }
@@ -17267,9 +17855,9 @@ class Repl {
17267
17855
  this.agent.shutdown();
17268
17856
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
17269
17857
  const { homedir: homedir15 } = await import("os");
17270
- const { join: join30 } = await import("path");
17271
- const configDir = join30(homedir15(), ".mma");
17272
- const projectConfigPath = join30(process.cwd(), ".mmrc");
17858
+ const { join: join31 } = await import("path");
17859
+ const configDir = join31(homedir15(), ".mma");
17860
+ const projectConfigPath = join31(process.cwd(), ".mmrc");
17273
17861
  const freshConfig = loadConfig2({ configDir, projectConfigPath });
17274
17862
  Object.assign(this.config, freshConfig);
17275
17863
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
@@ -17679,7 +18267,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17679
18267
  if (ev.type === "start") {
17680
18268
  renderer.toolStart(ev.tool, ev.args);
17681
18269
  } else {
17682
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
18270
+ renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
17683
18271
  }
17684
18272
  }, (phase) => {
17685
18273
  if (phase === "thinking") {
@@ -17759,7 +18347,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17759
18347
  showContextBar(result) {
17760
18348
  if (result.contextUsed !== undefined && result.contextLimit !== undefined && result.contextLimit > 0) {
17761
18349
  console.log();
17762
- const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
18350
+ const ctxLine = formatContextBar(result.contextUsed, result.contextLimit, result.compactionCount, result.contextQuality);
17763
18351
  console.log(ctxLine);
17764
18352
  if (result.totalTokens !== undefined && result.totalTokens > 0) {
17765
18353
  const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
@@ -17803,9 +18391,9 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17803
18391
  row(t("repl.agents_label"), pc.red(t("repl.disabled")));
17804
18392
  } else {
17805
18393
  const agentsMdCandidates = [
17806
- join29(this.baseDir, "AGENTS.md"),
17807
- join29(this.baseDir, ".mma", "AGENTS.md"),
17808
- join29(this.configDir, "AGENTS.md")
18394
+ join30(this.baseDir, "AGENTS.md"),
18395
+ join30(this.baseDir, ".mma", "AGENTS.md"),
18396
+ join30(this.configDir, "AGENTS.md")
17809
18397
  ];
17810
18398
  const foundAgents = agentsMdCandidates.filter((p) => existsSync35(p));
17811
18399
  if (foundAgents.length > 0) {
@@ -17818,7 +18406,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17818
18406
  }
17819
18407
  const meta = this.sessionManager?.getActiveMeta();
17820
18408
  if (meta) {
17821
- const sessionPath = join29(this.configDir, "sessions", meta.id);
18409
+ const sessionPath = join30(this.configDir, "sessions", meta.id);
17822
18410
  row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
17823
18411
  }
17824
18412
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
@@ -17844,7 +18432,7 @@ init_config();
17844
18432
  init_i18n();
17845
18433
  init_colors();
17846
18434
  import { existsSync as existsSync36 } from "fs";
17847
- import { join as join30 } from "path";
18435
+ import { join as join31 } from "path";
17848
18436
  import { homedir as homedir15 } from "os";
17849
18437
  async function main() {
17850
18438
  const program2 = createProgram();
@@ -17885,7 +18473,7 @@ async function main() {
17885
18473
  if (ev.type === "start") {
17886
18474
  renderer.toolStart(ev.tool, ev.args);
17887
18475
  } else {
17888
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
18476
+ renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
17889
18477
  }
17890
18478
  }, (phase) => {
17891
18479
  if (phase === "thinking") {
@@ -17908,7 +18496,7 @@ async function main() {
17908
18496
  }
17909
18497
  agent.shutdown();
17910
18498
  } else {
17911
- const configPath = join30(homedir15(), ".mma", "config.json");
18499
+ const configPath = join31(homedir15(), ".mma", "config.json");
17912
18500
  if (!existsSync36(configPath)) {
17913
18501
  console.log(pc.yellow(`
17914
18502
  ` + t("cli.first_run") + `
@@ -17923,6 +18511,8 @@ async function main() {
17923
18511
  config2.maxToolIterations = answers.maxToolIterations;
17924
18512
  config2.locale = answers.locale;
17925
18513
  if (config2.security) {
18514
+ config2.security.enabled = answers.securityBashBlock || answers.securityFlagsBlock || !answers.securityPathsDeny;
18515
+ config2.security.bash.enabled = config2.security.enabled;
17926
18516
  config2.security.bash.blockDangerousFlags = answers.securityFlagsBlock;
17927
18517
  if (answers.securityBashBlock) {
17928
18518
  config2.security.bash.blacklist = [