micro-models-agent 0.22.0 → 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 +708 -246
  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
@@ -2021,7 +2021,7 @@ var init_security = __esm(() => {
2021
2021
  maxParallelTasks: 5
2022
2022
  },
2023
2023
  contentScan: {
2024
- enabled: true,
2024
+ enabled: false,
2025
2025
  dangerousPatterns: [
2026
2026
  /eval\(/,
2027
2027
  /new Function\(/,
@@ -2310,6 +2310,7 @@ Command: {command}`,
2310
2310
  "tool.friendly.process_kill": "Stopping process",
2311
2311
  "plan.no_steps": "No plan steps specified. Provide concrete steps with files and commands.",
2312
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.",
2313
2314
  "plan.step_done": "Step {n}/{total}: {description} ✓",
2314
2315
  "plan.complete": "Task complete: {summary}",
2315
2316
  "plan.title_steps": 'Plan "{title}" created with {count} steps',
@@ -2570,6 +2571,14 @@ Use this knowledge to answer the user's question.`,
2570
2571
  "exec.audit_fail": "[✗] Task incomplete: {done}/{total} steps done, {files} files missing",
2571
2572
  "exec.audit_fail_typecheck": "[✗] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
2572
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.",
2573
2582
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2574
2583
  "hall.short_response": "Response too short or empty",
2575
2584
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
@@ -2647,7 +2656,12 @@ Use this knowledge to answer the user's question.`,
2647
2656
  "tool.recall.empty": 'Nothing found for "{query}"',
2648
2657
  "tool.recall.no_memory": "Memory is empty",
2649
2658
  "tool.recall.search_results": `{category} results:
2650
- {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"
2651
2665
  };
2652
2666
  });
2653
2667
 
@@ -2807,6 +2821,7 @@ var init_ru = __esm(() => {
2807
2821
  "tool.friendly.process_kill": "Остановка процесса",
2808
2822
  "plan.no_steps": "Не указаны шаги плана. Укажите конкретные шаги с файлами и командами.",
2809
2823
  "plan.created": "План создан: {title} ({steps} шагов)",
2824
+ "plan.coverage_warning": "План может не покрывать требуемые файлы из постановки: {missing}. Добавьте шаги, покрывающие их.",
2810
2825
  "plan.step_done": "Шаг {n}/{total}: {description} ✓",
2811
2826
  "plan.complete": "Задача выполнена: {summary}",
2812
2827
  "plan.title_steps": 'План "{title}" создан с {count} шагами',
@@ -3067,6 +3082,14 @@ var init_ru = __esm(() => {
3067
3082
  "exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
3068
3083
  "exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
3069
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} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
3070
3093
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3071
3094
  "hall.short_response": "Слишком короткий или пустой ответ",
3072
3095
  "hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
@@ -3144,7 +3167,12 @@ var init_ru = __esm(() => {
3144
3167
  "tool.recall.empty": 'Ничего не найдено по "{query}"',
3145
3168
  "tool.recall.no_memory": "Память пуста",
3146
3169
  "tool.recall.search_results": `Результаты {category}:
3147
- {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"
3148
3176
  };
3149
3177
  });
3150
3178
 
@@ -8797,7 +8825,133 @@ var init_agent_moe = __esm(() => {
8797
8825
  init_verifier();
8798
8826
  });
8799
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
+
8800
8952
  // src/core/agent.ts
8953
+ import { join as join10 } from "path";
8954
+
8801
8955
  class Agent {
8802
8956
  deps;
8803
8957
  systemPromptAdded = false;
@@ -8865,7 +9019,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8865
9019
  pluginManager,
8866
9020
  contextManager,
8867
9021
  logger,
8868
- sessionManager
9022
+ sessionManager,
9023
+ baseDir
8869
9024
  } = this.deps;
8870
9025
  const slog = new SessionLogger(sessionManager);
8871
9026
  if (sessionManager && !sessionManager.getActive()) {
@@ -8907,7 +9062,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8907
9062
  contextManager,
8908
9063
  hallucinationDetector,
8909
9064
  logger,
8910
- sessionManager
9065
+ sessionManager,
9066
+ baseDir
8911
9067
  } = this.deps;
8912
9068
  const slog = new SessionLogger(sessionManager);
8913
9069
  let iteration = 0;
@@ -8919,6 +9075,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
8919
9075
  const MAX_HALLUCINATION_RETRIES = 3;
8920
9076
  let consecutiveToolFailures = 0;
8921
9077
  const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
9078
+ let auditRetries = 0;
9079
+ const MAX_AUDIT_RETRIES = 3;
8922
9080
  while (iteration < config.maxToolIterations && !this.shutdownRequested) {
8923
9081
  iteration++;
8924
9082
  pluginManager.runOnBeforeThink({
@@ -9055,6 +9213,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9055
9213
  pluginManager.runOnToolStart({ iteration, logger }, { id: call.id, name: call.name, arguments: call.arguments });
9056
9214
  onTool?.({ type: "start", tool: call.name, args: call.arguments });
9057
9215
  slog.logToolCall(call, iteration);
9216
+ const tokensBeforeTool = contextManager.getEstimatedTokens();
9058
9217
  const result = await toolExecutor.execute(call);
9059
9218
  const duration = Date.now() - startTime;
9060
9219
  if (!result.success)
@@ -9074,12 +9233,14 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9074
9233
  ` + result.diff + `
9075
9234
  `);
9076
9235
  }
9236
+ const tokensAfterTool = contextManager.getEstimatedTokens();
9077
9237
  onTool?.({
9078
9238
  type: "end",
9079
9239
  tool: call.name,
9080
9240
  args: call.arguments,
9081
9241
  duration,
9082
- error: !result.success
9242
+ error: !result.success,
9243
+ ctxDelta: tokensAfterTool - tokensBeforeTool
9083
9244
  });
9084
9245
  const currentTokens2 = contextManager.getEstimatedTokens();
9085
9246
  const budget3 = contextManager.getBudget();
@@ -9088,7 +9249,9 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9088
9249
  role: "tool",
9089
9250
  content: truncatedOutput,
9090
9251
  name: call.name,
9091
- tool_call_id: call.id
9252
+ tool_call_id: call.id,
9253
+ success: result.success,
9254
+ arguments: call.arguments
9092
9255
  });
9093
9256
  summaries.push(`[Tool: ${call.name} (${JSON.stringify(call.arguments)}) → ${truncatedOutput.slice(0, 200)}]`);
9094
9257
  if (config.session.autoSave) {
@@ -9116,6 +9279,14 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9116
9279
  content: `<system-summary>${recoveryMsg}
9117
9280
  ${taskReminder}</system-summary>`
9118
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
+ }
9119
9290
  }
9120
9291
  contextManager.addMessage({
9121
9292
  role: "user",
@@ -9129,8 +9300,11 @@ ${taskReminder}</system-summary>`
9129
9300
  const filled = Math.round(ctxPct / 100 * barLen);
9130
9301
  const ctxBar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
9131
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;
9132
9306
  onMeta?.(`
9133
- ${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}%`)}
9134
9308
  `);
9135
9309
  continue;
9136
9310
  }
@@ -9204,7 +9378,9 @@ ${taskReminder}</system-summary>`
9204
9378
  })}</system-summary>`
9205
9379
  });
9206
9380
  slog.logAudit(audit.summary, iteration);
9207
- 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`);
9208
9384
  break;
9209
9385
  }
9210
9386
  continue;
@@ -9225,7 +9401,9 @@ ${taskReminder}</system-summary>`
9225
9401
  contextLimit: budget.history,
9226
9402
  promptTokens: apiPromptTokens,
9227
9403
  completionTokens: apiCompletionTokens,
9228
- totalTokens: apiPromptTokens + apiCompletionTokens
9404
+ totalTokens: apiPromptTokens + apiCompletionTokens,
9405
+ compactionCount: contextManager.getCompactionCount(),
9406
+ contextQuality: contextManager.getQuality()
9229
9407
  };
9230
9408
  }
9231
9409
  return {
@@ -9236,7 +9414,9 @@ ${taskReminder}</system-summary>`
9236
9414
  contextLimit: budget.history,
9237
9415
  promptTokens: apiPromptTokens,
9238
9416
  completionTokens: apiCompletionTokens,
9239
- totalTokens: apiPromptTokens + apiCompletionTokens
9417
+ totalTokens: apiPromptTokens + apiCompletionTokens,
9418
+ compactionCount: contextManager.getCompactionCount(),
9419
+ contextQuality: contextManager.getQuality()
9240
9420
  };
9241
9421
  }
9242
9422
  clearContext() {
@@ -9280,14 +9460,56 @@ var init_agent = __esm(() => {
9280
9460
  init_prompt_builder();
9281
9461
  init_processes();
9282
9462
  init_agent_moe();
9463
+ init_store();
9283
9464
  });
9284
9465
 
9285
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
+
9286
9506
  class ContextManager {
9287
9507
  contextWindow;
9288
9508
  messages = [];
9289
9509
  compactedBlock = null;
9290
9510
  iterationsSinceCompaction = 0;
9511
+ compactionCount = 0;
9512
+ peakTokens = 0;
9291
9513
  budget;
9292
9514
  compactionThreshold;
9293
9515
  fileFacts = [];
@@ -9318,6 +9540,17 @@ class ContextManager {
9318
9540
  getBudget() {
9319
9541
  return { ...this.budget };
9320
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
+ }
9321
9554
  addMessage(msg) {
9322
9555
  if (msg.role === "user" && this.pendingImageParts.length > 0) {
9323
9556
  const textPart = {
@@ -9332,6 +9565,9 @@ class ContextManager {
9332
9565
  }
9333
9566
  this.messages.push(msg);
9334
9567
  this.iterationsSinceCompaction++;
9568
+ const tokens = this.getEstimatedTokens();
9569
+ if (tokens > this.peakTokens)
9570
+ this.peakTokens = tokens;
9335
9571
  }
9336
9572
  addPendingImage(part) {
9337
9573
  this.pendingImageParts.push(part);
@@ -9398,6 +9634,7 @@ class ContextManager {
9398
9634
  compact() {
9399
9635
  if (this.messages.length <= KEEP_LAST_N * 2)
9400
9636
  return;
9637
+ this.compactionCount++;
9401
9638
  const cutoff = this.messages.length - KEEP_LAST_N * 2;
9402
9639
  const oldTurns = this.messages.slice(0, cutoff);
9403
9640
  const recentTurns = this.messages.slice(cutoff);
@@ -9413,6 +9650,13 @@ class ContextManager {
9413
9650
  if (this.errorFacts.length > 0) {
9414
9651
  parts.push(`[Errors: ${this.errorFacts.slice(-3).join("; ")}]`);
9415
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
+ }
9416
9660
  this.compactedBlock = parts.join(" ");
9417
9661
  const summary = {
9418
9662
  role: "user",
@@ -9481,6 +9725,8 @@ class ContextManager {
9481
9725
  this.messages = [];
9482
9726
  this.compactedBlock = null;
9483
9727
  this.iterationsSinceCompaction = 0;
9728
+ this.compactionCount = 0;
9729
+ this.peakTokens = 0;
9484
9730
  }
9485
9731
  getEstimatedTokens() {
9486
9732
  return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
@@ -10999,7 +11245,7 @@ ${JSON.stringify(result, null, 2)}`
10999
11245
 
11000
11246
  // src/tools/search-history.ts
11001
11247
  import * as fs from "fs";
11002
- import { join as join8 } from "path";
11248
+ import { join as join11 } from "path";
11003
11249
  import { homedir as homedir5 } from "os";
11004
11250
  function searchFile(filePath, query, maxResults, results) {
11005
11251
  if (!fs.existsSync(filePath))
@@ -11037,7 +11283,7 @@ var init_search_history = __esm(() => {
11037
11283
  const query = String(args.query || "").toLowerCase();
11038
11284
  const maxResults = Number(args.maxResults) || 5;
11039
11285
  const sessionId = args.sessionId ? String(args.sessionId) : null;
11040
- const sessionDir = join8(homedir5(), ".mma", "sessions");
11286
+ const sessionDir = join11(homedir5(), ".mma", "sessions");
11041
11287
  const results = [];
11042
11288
  try {
11043
11289
  if (!fs.existsSync(sessionDir)) {
@@ -11049,7 +11295,7 @@ var init_search_history = __esm(() => {
11049
11295
  continue;
11050
11296
  if (sessionId && entry.name !== sessionId)
11051
11297
  continue;
11052
- const historyFile = join8(sessionDir, entry.name, "history.jsonl");
11298
+ const historyFile = join11(sessionDir, entry.name, "history.jsonl");
11053
11299
  searchFile(historyFile, query, maxResults, results);
11054
11300
  if (results.length >= maxResults)
11055
11301
  break;
@@ -11066,127 +11312,9 @@ var init_search_history = __esm(() => {
11066
11312
  };
11067
11313
  });
11068
11314
 
11069
- // src/modules/memory/search.ts
11070
- import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
11071
- import { join as join9 } from "path";
11072
-
11073
- class MemorySearch {
11074
- memoryDir;
11075
- constructor(memoryDir) {
11076
- this.memoryDir = memoryDir;
11077
- }
11078
- query(query) {
11079
- const results = [];
11080
- const lowerQuery = query.toLowerCase();
11081
- for (const name of MEMORY_FILES) {
11082
- const path = join9(this.memoryDir, `${name}.md`);
11083
- if (!existsSync19(path))
11084
- continue;
11085
- const content = readFileSync10(path, "utf-8");
11086
- const lines = content.split(`
11087
- `);
11088
- for (const line of lines) {
11089
- if (line.toLowerCase().includes(lowerQuery)) {
11090
- results.push({ file: name, match: line.trim() });
11091
- }
11092
- }
11093
- }
11094
- const prefsPath = join9(this.memoryDir, "preferences.json");
11095
- if (existsSync19(prefsPath)) {
11096
- try {
11097
- const prefs = JSON.parse(readFileSync10(prefsPath, "utf-8"));
11098
- for (const [key, value] of Object.entries(prefs)) {
11099
- const searchStr = `${key}=${value}`;
11100
- if (searchStr.toLowerCase().includes(lowerQuery)) {
11101
- results.push({ file: "preferences", match: `${key} = ${value}` });
11102
- }
11103
- }
11104
- } catch {}
11105
- }
11106
- return results;
11107
- }
11108
- }
11109
- var MEMORY_FILES;
11110
- var init_search = __esm(() => {
11111
- MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
11112
- });
11113
-
11114
- // src/modules/memory/store.ts
11115
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, appendFileSync as appendFileSync4, existsSync as existsSync20, mkdirSync as mkdirSync10 } from "fs";
11116
- import { join as join10 } from "path";
11117
-
11118
- class MemoryStore {
11119
- memoryDir;
11120
- constructor(memoryDir) {
11121
- this.memoryDir = memoryDir;
11122
- this.ensureDir();
11123
- for (const name of MEMORY_FILES2) {
11124
- const path = join10(this.memoryDir, `${name}.md`);
11125
- if (!existsSync20(path)) {
11126
- writeFileSync8(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
11127
-
11128
- `, "utf-8");
11129
- }
11130
- }
11131
- }
11132
- ensureDir() {
11133
- if (!existsSync20(this.memoryDir)) {
11134
- mkdirSync10(this.memoryDir, { recursive: true });
11135
- }
11136
- }
11137
- read(name) {
11138
- const path = join10(this.memoryDir, `${name}.md`);
11139
- if (!existsSync20(path))
11140
- return "";
11141
- return readFileSync11(path, "utf-8");
11142
- }
11143
- append(name, entry) {
11144
- const path = join10(this.memoryDir, `${name}.md`);
11145
- const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
11146
- const formatted = `- **${timestamp}** — ${entry}
11147
- `;
11148
- appendFileSync4(path, formatted, "utf-8");
11149
- }
11150
- search(query) {
11151
- const searchModule = new MemorySearch(this.memoryDir);
11152
- return searchModule.query(query);
11153
- }
11154
- prefsPath() {
11155
- return join10(this.memoryDir, "preferences.json");
11156
- }
11157
- getPreferences() {
11158
- const path = this.prefsPath();
11159
- if (!existsSync20(path))
11160
- return {};
11161
- try {
11162
- return JSON.parse(readFileSync11(path, "utf-8"));
11163
- } catch {
11164
- return {};
11165
- }
11166
- }
11167
- setPreference(key, value) {
11168
- const prefs = this.getPreferences();
11169
- prefs[key] = value;
11170
- writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
11171
- }
11172
- deletePreference(key) {
11173
- const prefs = this.getPreferences();
11174
- if (!(key in prefs))
11175
- return false;
11176
- delete prefs[key];
11177
- writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
11178
- return true;
11179
- }
11180
- }
11181
- var MEMORY_FILES2;
11182
- var init_store = __esm(() => {
11183
- init_search();
11184
- MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
11185
- });
11186
-
11187
11315
  // src/tools/remember.ts
11188
11316
  import { homedir as homedir6 } from "os";
11189
- import { join as join11 } from "path";
11317
+ import { join as join12 } from "path";
11190
11318
  var CATEGORIES, rememberTool;
11191
11319
  var init_remember = __esm(() => {
11192
11320
  init_i18n();
@@ -11224,7 +11352,7 @@ var init_remember = __esm(() => {
11224
11352
  if (!CATEGORIES.includes(category)) {
11225
11353
  return { success: false, output: t("tool.invalid_params") };
11226
11354
  }
11227
- const memoryDir = join11(homedir6(), ".mma", "memory");
11355
+ const memoryDir = join12(homedir6(), ".mma", "memory");
11228
11356
  const store = new MemoryStore(memoryDir);
11229
11357
  try {
11230
11358
  if (category === "preferences") {
@@ -11257,7 +11385,7 @@ var init_remember = __esm(() => {
11257
11385
 
11258
11386
  // src/tools/recall.ts
11259
11387
  import { homedir as homedir7 } from "os";
11260
- import { join as join12 } from "path";
11388
+ import { join as join13 } from "path";
11261
11389
  function formatAll(store) {
11262
11390
  const parts = [];
11263
11391
  const prefs = store.getPreferences();
@@ -11340,7 +11468,7 @@ var init_recall = __esm(() => {
11340
11468
  handler: async (_ctx, args) => {
11341
11469
  const query = args.query ? String(args.query) : "";
11342
11470
  const category = args.category ? String(args.category) : "";
11343
- const memoryDir = join12(homedir7(), ".mma", "memory");
11471
+ const memoryDir = join13(homedir7(), ".mma", "memory");
11344
11472
  const store = new MemoryStore(memoryDir);
11345
11473
  try {
11346
11474
  if (!query && !category) {
@@ -11536,15 +11664,15 @@ function buildIndexInjectionScript() {
11536
11664
 
11537
11665
  // src/modules/browser/cookie-store.ts
11538
11666
  import { readFile, writeFile, mkdir } from "fs/promises";
11539
- import { join as join13 } from "path";
11667
+ import { join as join14 } from "path";
11540
11668
 
11541
11669
  class CookieStore {
11542
11670
  filePath;
11543
11671
  constructor(cookieDir) {
11544
- this.filePath = join13(cookieDir, "cookies.json");
11672
+ this.filePath = join14(cookieDir, "cookies.json");
11545
11673
  }
11546
11674
  async save(cookies) {
11547
- await mkdir(join13(this.filePath, ".."), { recursive: true });
11675
+ await mkdir(join14(this.filePath, ".."), { recursive: true });
11548
11676
  await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
11549
11677
  }
11550
11678
  async load() {
@@ -11864,10 +11992,10 @@ var init_types = __esm(() => {
11864
11992
  });
11865
11993
 
11866
11994
  // src/tools/browser.ts
11867
- import { join as join14 } from "path";
11995
+ import { join as join15 } from "path";
11868
11996
  function getSession(ctx) {
11869
11997
  if (!session) {
11870
- const cookieDir = join14(ctx.baseDir, ".mma", "browser");
11998
+ const cookieDir = join15(ctx.baseDir, ".mma", "browser");
11871
11999
  session = new BrowserSession({
11872
12000
  ...DEFAULT_BROWSER_CONFIG,
11873
12001
  headless: ctx.config.browser?.headless ?? true,
@@ -11997,8 +12125,8 @@ async function readClipboardFallback() {
11997
12125
  const { platform: platform4 } = await import("os");
11998
12126
  const { execSync: execSync3 } = await import("child_process");
11999
12127
  const { readFileSync: readFileSync13, unlinkSync: unlinkSync3 } = await import("fs");
12000
- const { join: join15 } = await import("path");
12001
- 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`);
12002
12130
  try {
12003
12131
  if (platform4() === "linux") {
12004
12132
  execSync3(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
@@ -12276,7 +12404,7 @@ class ModuleRegistry {
12276
12404
 
12277
12405
  // src/modules/plugins/loader.ts
12278
12406
  import { readdirSync as readdirSync5, existsSync as existsSync22, statSync as statSync4 } from "fs";
12279
- import { join as join15 } from "path";
12407
+ import { join as join16 } from "path";
12280
12408
 
12281
12409
  class PluginLoader {
12282
12410
  loadFromDir(dirPath, pluginManager, logger) {
@@ -12284,7 +12412,7 @@ class PluginLoader {
12284
12412
  return;
12285
12413
  const entries = readdirSync5(dirPath);
12286
12414
  for (const entry of entries) {
12287
- const fullPath = join15(dirPath, entry);
12415
+ const fullPath = join16(dirPath, entry);
12288
12416
  if (!statSync4(fullPath).isFile())
12289
12417
  continue;
12290
12418
  if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
@@ -12309,7 +12437,7 @@ var init_loader = __esm(() => {
12309
12437
  // src/modules/plugins/builtin/lint-on-write.ts
12310
12438
  import { execSync as execSync3 } from "child_process";
12311
12439
  import { existsSync as existsSync23, readFileSync as readFileSync13 } from "fs";
12312
- 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";
12313
12441
 
12314
12442
  class LintOnWritePlugin {
12315
12443
  name = "lint-on-write";
@@ -12371,7 +12499,7 @@ class LintOnWritePlugin {
12371
12499
  }
12372
12500
  async runProjectLint(ctx, result) {
12373
12501
  try {
12374
- const packageJsonPath = join16(ctx.baseDir, "package.json");
12502
+ const packageJsonPath = join17(ctx.baseDir, "package.json");
12375
12503
  if (!existsSync23(packageJsonPath)) {
12376
12504
  return;
12377
12505
  }
@@ -12391,7 +12519,7 @@ class LintOnWritePlugin {
12391
12519
  }
12392
12520
  }
12393
12521
  async runProjectTypeCheck(ctx, result) {
12394
- const tsconfigPath = join16(ctx.baseDir, "tsconfig.json");
12522
+ const tsconfigPath = join17(ctx.baseDir, "tsconfig.json");
12395
12523
  if (!existsSync23(tsconfigPath)) {
12396
12524
  return;
12397
12525
  }
@@ -12580,6 +12708,11 @@ class StuckDetector {
12580
12708
  repetitionThreshold = 3;
12581
12709
  consecutiveFailures = 0;
12582
12710
  lastFailedTool = "";
12711
+ lastErrorOutput = "";
12712
+ escalationCount = 0;
12713
+ escalationThreshold = 3;
12714
+ fileRewriteCount = new Map;
12715
+ fileRewriteThreshold = 3;
12583
12716
  constructor(threshold = 8, errorThreshold = 3) {
12584
12717
  this.threshold = threshold;
12585
12718
  this.errorThreshold = errorThreshold;
@@ -12599,14 +12732,26 @@ class StuckDetector {
12599
12732
  this.recentToolCalls.shift();
12600
12733
  }
12601
12734
  }
12602
- recordToolError(toolName) {
12735
+ recordToolError(toolName, output) {
12603
12736
  this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
12604
12737
  this.consecutiveFailures++;
12605
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;
12606
12750
  }
12607
12751
  recordToolSuccess() {
12608
12752
  this.consecutiveFailures = 0;
12609
12753
  this.lastFailedTool = "";
12754
+ this.escalationCount = 0;
12610
12755
  }
12611
12756
  setCurrentStep(stepId, description) {
12612
12757
  if (stepId !== this.currentStepId) {
@@ -12644,6 +12789,117 @@ class StuckDetector {
12644
12789
  getConsecutiveFailuresCount() {
12645
12790
  return this.consecutiveFailures;
12646
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
+ }
12647
12903
  getRepetitiveToolMessage() {
12648
12904
  if (!this.hasRepetitiveToolCalls())
12649
12905
  return "";
@@ -12722,7 +12978,7 @@ var init_stuck_detector = __esm(() => {
12722
12978
  // src/modules/execution/auditor.ts
12723
12979
  import { execSync as execSync4 } from "child_process";
12724
12980
  import { existsSync as existsSync24 } from "fs";
12725
- import { resolve as resolve13, join as join17 } from "path";
12981
+ import { resolve as resolve13, join as join18 } from "path";
12726
12982
 
12727
12983
  class Auditor {
12728
12984
  baseDir;
@@ -12730,7 +12986,7 @@ class Auditor {
12730
12986
  this.baseDir = baseDir;
12731
12987
  }
12732
12988
  async audit(plan) {
12733
- const allStepText = plan.steps.map((s) => s.description).join(" ");
12989
+ const allStepText = plan.steps.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
12734
12990
  const fileMatches = allStepText.match(/\b[\w./-]+\.[a-z]+/gi) || [];
12735
12991
  const uniqueFiles = [...new Set(fileMatches)];
12736
12992
  const missingFiles = [];
@@ -12746,6 +13002,10 @@ class Auditor {
12746
13002
  const doneSteps = plan.steps.filter((s) => s.status === "done").length;
12747
13003
  const totalSteps = plan.steps.length;
12748
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
+ }
12749
13009
  const passed = missingFiles.length === 0 && !typeCheckError;
12750
13010
  let summary;
12751
13011
  if (passed) {
@@ -12764,11 +13024,12 @@ class Auditor {
12764
13024
  createdFiles: existingFiles,
12765
13025
  modifiedFiles: [],
12766
13026
  summary,
12767
- typeCheckError
13027
+ typeCheckError,
13028
+ massEditWarning
12768
13029
  };
12769
13030
  }
12770
13031
  async runProjectTypeCheck() {
12771
- const tsconfigPath = join17(this.baseDir, "tsconfig.json");
13032
+ const tsconfigPath = join18(this.baseDir, "tsconfig.json");
12772
13033
  if (!existsSync24(tsconfigPath)) {
12773
13034
  return null;
12774
13035
  }
@@ -12789,22 +13050,23 @@ class Auditor {
12789
13050
  }
12790
13051
  }
12791
13052
  }
13053
+ var MASS_EDIT_THRESHOLD = 10;
12792
13054
  var init_auditor = __esm(() => {
12793
13055
  init_i18n();
12794
13056
  });
12795
13057
 
12796
13058
  // src/modules/execution/plan-persister.ts
12797
13059
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11, existsSync as existsSync25 } from "fs";
12798
- import { join as join18 } from "path";
13060
+ import { join as join19 } from "path";
12799
13061
 
12800
13062
  class PlanPersister {
12801
13063
  filePath;
12802
13064
  constructor(baseDir) {
12803
- const mmaDir = join18(baseDir, ".mma");
13065
+ const mmaDir = join19(baseDir, ".mma");
12804
13066
  if (!existsSync25(mmaDir)) {
12805
13067
  mkdirSync11(mmaDir, { recursive: true });
12806
13068
  }
12807
- this.filePath = join18(mmaDir, "plan.json");
13069
+ this.filePath = join19(mmaDir, "plan.json");
12808
13070
  }
12809
13071
  save(plan) {
12810
13072
  const file = {
@@ -12842,6 +13104,92 @@ class PlanPersister {
12842
13104
  }
12843
13105
  var init_plan_persister = () => {};
12844
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
+
12845
13193
  // src/modules/execution/module.ts
12846
13194
  import { existsSync as existsSync26, readFileSync as readFileSync15 } from "fs";
12847
13195
  import { resolve as resolve14 } from "path";
@@ -12951,10 +13299,25 @@ Write CONCRETE steps with exact file paths and commands:
12951
13299
  const plan = PlanCreator.createPlan(title, steps, this.baseDir);
12952
13300
  this.setPlan(plan);
12953
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
+ }
12954
13317
  return {
12955
13318
  success: true,
12956
- output: t("plan.created", { title, steps: String(steps.length) }),
12957
- display
13319
+ output,
13320
+ display: displayOut
12958
13321
  };
12959
13322
  }
12960
13323
  if (action === "show") {
@@ -13100,12 +13463,47 @@ Sub-tasks: ${note}`
13100
13463
  if (currentIter - this.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
13101
13464
  const recovery = this.stuckDetector.getRecoveryMessage();
13102
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
+ `) });
13103
13486
  ctx.contextManager.addMessage({
13104
13487
  role: "user",
13105
- content: `<system-summary>${recovery}</system-summary>`
13488
+ content: `<system-summary>${hintMsg}</system-summary>`
13106
13489
  });
13107
13490
  }
13491
+ this.stuckDetector.recordEscalation();
13108
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
+ }
13109
13507
  }
13110
13508
  }
13111
13509
  },
@@ -13138,11 +13536,44 @@ Sub-tasks: ${note}`
13138
13536
  },
13139
13537
  onAfterTool: (ctx, call, result) => {
13140
13538
  if (!result.success) {
13141
- 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
+ }
13142
13553
  } else {
13143
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
+ }
13144
13564
  }
13145
- 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
+ }
13146
13577
  this.advancePlanIfStepComplete(ctx.contextManager);
13147
13578
  }
13148
13579
  }
@@ -13253,8 +13684,18 @@ Sub-tasks: ${note}`
13253
13684
  });
13254
13685
  }
13255
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
+ }
13256
13697
  }
13257
- 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;
13258
13699
  var init_module = __esm(() => {
13259
13700
  init_i18n();
13260
13701
  init_tracker();
@@ -13262,11 +13703,13 @@ var init_module = __esm(() => {
13262
13703
  init_stuck_detector();
13263
13704
  init_auditor();
13264
13705
  init_plan_persister();
13706
+ init_plan_coverage();
13707
+ init_error_skill_map();
13265
13708
  });
13266
13709
 
13267
13710
  // src/modules/security/session-encryption.ts
13268
13711
  import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync27, readdirSync as readdirSync6, unlinkSync as unlinkSync3 } from "fs";
13269
- import { join as join19 } from "path";
13712
+ import { join as join20 } from "path";
13270
13713
  import { homedir as homedir8 } from "os";
13271
13714
 
13272
13715
  class SessionFileEncryptor {
@@ -13275,7 +13718,7 @@ class SessionFileEncryptor {
13275
13718
  constructor(config) {
13276
13719
  this.config = { ...DEFAULT_SESSION_ENCRYPTION, ...config };
13277
13720
  this.encryptor = new ConfigEncryptor({
13278
- keyPath: config?.keyPath || join19(homedir8(), ".mma", ".session-encryption-key")
13721
+ keyPath: config?.keyPath || join20(homedir8(), ".mma", ".session-encryption-key")
13279
13722
  });
13280
13723
  }
13281
13724
  isEnabled() {
@@ -13347,7 +13790,7 @@ class SessionFileEncryptor {
13347
13790
  return;
13348
13791
  const files = readdirSync6(sessionDir);
13349
13792
  for (const file of files) {
13350
- const filePath = join19(sessionDir, file);
13793
+ const filePath = join20(sessionDir, file);
13351
13794
  if (existsSync27(filePath) && !file.endsWith(".enc")) {
13352
13795
  try {
13353
13796
  const content = readFileSync16(filePath, "utf8");
@@ -13364,7 +13807,7 @@ class SessionFileEncryptor {
13364
13807
  const files = readdirSync6(sessionDir);
13365
13808
  for (const file of files) {
13366
13809
  if (file.endsWith(".enc")) {
13367
- const encFilePath = join19(sessionDir, file);
13810
+ const encFilePath = join20(sessionDir, file);
13368
13811
  const decFilePath = encFilePath.slice(0, -4);
13369
13812
  try {
13370
13813
  const content = readFileSync16(encFilePath, "utf8");
@@ -13397,7 +13840,7 @@ import {
13397
13840
  writeFileSync as writeFileSync11,
13398
13841
  appendFileSync as appendFileSync5
13399
13842
  } from "fs";
13400
- import { join as join20 } from "path";
13843
+ import { join as join21 } from "path";
13401
13844
  import { gzipSync } from "zlib";
13402
13845
 
13403
13846
  class SessionStore {
@@ -13423,16 +13866,16 @@ class SessionStore {
13423
13866
  mkdirSync12(this.baseDir, { recursive: true });
13424
13867
  }
13425
13868
  sessionDir(id) {
13426
- return join20(this.baseDir, id);
13869
+ return join21(this.baseDir, id);
13427
13870
  }
13428
13871
  metaPath(id) {
13429
- return join20(this.sessionDir(id), "meta.json");
13872
+ return join21(this.sessionDir(id), "meta.json");
13430
13873
  }
13431
13874
  historyPath(id) {
13432
- return join20(this.sessionDir(id), "history.jsonl");
13875
+ return join21(this.sessionDir(id), "history.jsonl");
13433
13876
  }
13434
13877
  sessionLogPath(id) {
13435
- return join20(this.sessionDir(id), "session.jsonl");
13878
+ return join21(this.sessionDir(id), "session.jsonl");
13436
13879
  }
13437
13880
  sessionExists(id) {
13438
13881
  return existsSync28(this.metaPath(id));
@@ -13555,7 +13998,7 @@ class SessionStore {
13555
13998
  if (existsSync28(historyPath)) {
13556
13999
  const content = readFileSync17(historyPath, "utf-8");
13557
14000
  const compressed = gzipSync(content);
13558
- const gzPath = join20(this.baseDir, `${session2.id}.jsonl.gz`);
14001
+ const gzPath = join21(this.baseDir, `${session2.id}.jsonl.gz`);
13559
14002
  writeFileSync11(gzPath, compressed);
13560
14003
  rmSync(historyPath);
13561
14004
  }
@@ -13763,7 +14206,7 @@ class ProfileCompressor {
13763
14206
 
13764
14207
  // src/modules/user-profile/profile.ts
13765
14208
  import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync29, mkdirSync as mkdirSync13 } from "fs";
13766
- import { join as join21 } from "path";
14209
+ import { join as join22 } from "path";
13767
14210
  import { homedir as homedir9, hostname, platform as platform4, type } from "os";
13768
14211
  import { env } from "process";
13769
14212
 
@@ -13790,10 +14233,10 @@ class UserProfile {
13790
14233
  if (!existsSync29(this.profileDir)) {
13791
14234
  mkdirSync13(this.profileDir, { recursive: true });
13792
14235
  }
13793
- 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");
13794
14237
  }
13795
14238
  load() {
13796
- const path = join21(this.profileDir, "profile.json");
14239
+ const path = join22(this.profileDir, "profile.json");
13797
14240
  if (!existsSync29(path))
13798
14241
  return null;
13799
14242
  try {
@@ -13833,7 +14276,7 @@ var init_profile = () => {};
13833
14276
 
13834
14277
  // src/modules/skills/loader.ts
13835
14278
  import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync30, statSync as statSync5 } from "fs";
13836
- import { join as join22 } from "path";
14279
+ import { join as join23 } from "path";
13837
14280
 
13838
14281
  class SkillsLoader {
13839
14282
  loadFromDir(dirPath) {
@@ -13846,7 +14289,7 @@ class SkillsLoader {
13846
14289
  scanDir(dirPath, skills) {
13847
14290
  const entries = readdirSync8(dirPath);
13848
14291
  for (const entry of entries) {
13849
- const fullPath = join22(dirPath, entry);
14292
+ const fullPath = join23(dirPath, entry);
13850
14293
  const stat = statSync5(fullPath);
13851
14294
  if (stat.isDirectory()) {
13852
14295
  this.scanDir(fullPath, skills);
@@ -14127,7 +14570,7 @@ var init_browser2 = __esm(() => {
14127
14570
 
14128
14571
  // src/modules/indexer/walker.ts
14129
14572
  import { readdirSync as readdirSync9, readFileSync as readFileSync20, statSync as statSync6, existsSync as existsSync31, watch } from "fs";
14130
- import { join as join23, relative, extname as extname5 } from "path";
14573
+ import { join as join24, relative, extname as extname5 } from "path";
14131
14574
 
14132
14575
  class Indexer {
14133
14576
  baseDir;
@@ -14165,7 +14608,7 @@ class Indexer {
14165
14608
  for (const entry of entries) {
14166
14609
  if (count >= this.MAX_FILES)
14167
14610
  return;
14168
- const fullPath = join23(dir, entry);
14611
+ const fullPath = join24(dir, entry);
14169
14612
  const relPath = relative(this.baseDir, fullPath);
14170
14613
  const stat = statSync6(fullPath);
14171
14614
  if (stat.isDirectory()) {
@@ -14229,13 +14672,13 @@ var init_walker = __esm(() => {
14229
14672
 
14230
14673
  // src/modules/indexer/cache.ts
14231
14674
  import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as existsSync32, mkdirSync as mkdirSync14, rmSync as rmSync2 } from "fs";
14232
- import { join as join24 } from "path";
14675
+ import { join as join25 } from "path";
14233
14676
 
14234
14677
  class IndexCache {
14235
14678
  cachePath;
14236
14679
  cache = null;
14237
14680
  constructor(cacheDir) {
14238
- this.cachePath = join24(cacheDir, "index-cache.json");
14681
+ this.cachePath = join25(cacheDir, "index-cache.json");
14239
14682
  }
14240
14683
  load() {
14241
14684
  if (this.cache)
@@ -14251,7 +14694,7 @@ class IndexCache {
14251
14694
  }
14252
14695
  save(result) {
14253
14696
  this.cache = result;
14254
- const dir = join24(this.cachePath, "..");
14697
+ const dir = join25(this.cachePath, "..");
14255
14698
  if (!existsSync32(dir))
14256
14699
  mkdirSync14(dir, { recursive: true });
14257
14700
  writeFileSync13(this.cachePath, JSON.stringify(result), "utf-8");
@@ -14616,13 +15059,13 @@ var init_mcp = __esm(() => {
14616
15059
 
14617
15060
  // src/modules/memory/module.ts
14618
15061
  import { homedir as homedir10 } from "os";
14619
- import { join as join25 } from "path";
15062
+ import { join as join26 } from "path";
14620
15063
 
14621
15064
  class MemoryModule {
14622
15065
  name = "memory";
14623
15066
  store;
14624
15067
  constructor(memoryDir) {
14625
- const dir = memoryDir || join25(homedir10(), ".mma", "memory");
15068
+ const dir = memoryDir || join26(homedir10(), ".mma", "memory");
14626
15069
  this.store = new MemoryStore(dir);
14627
15070
  }
14628
15071
  getSystemPromptBlock() {
@@ -14669,7 +15112,7 @@ __export(exports_bootstrap, {
14669
15112
  bootstrap: () => bootstrap
14670
15113
  });
14671
15114
  import { homedir as homedir11 } from "os";
14672
- import { join as join26, resolve as resolve15 } from "path";
15115
+ import { join as join27, resolve as resolve15 } from "path";
14673
15116
  import { existsSync as existsSync33, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "fs";
14674
15117
  function buildSystemInfo(config, baseDir, profileCompressed) {
14675
15118
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
@@ -14704,8 +15147,8 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
14704
15147
  `);
14705
15148
  }
14706
15149
  async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14707
- const dir = configDir || join26(homedir11(), ".mma");
14708
- 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");
14709
15152
  const config = loadConfig({ configDir: dir, projectConfigPath });
14710
15153
  setLocale(config.locale);
14711
15154
  try {
@@ -14715,7 +15158,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14715
15158
  }
14716
15159
  } catch {}
14717
15160
  const logger = new Logger(config.logLevel);
14718
- logger.setLogDir(join26(dir, "logs"));
15161
+ logger.setLogDir(join27(dir, "logs"));
14719
15162
  logger.debug("MMA bootstrap", {
14720
15163
  version: config.version,
14721
15164
  model: config.model
@@ -14737,7 +15180,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14737
15180
  logger.info(`Model ${config.model} loaded in ${loadResult.loadTime}s`);
14738
15181
  }
14739
15182
  }
14740
- const profile = new UserProfile(join26(dir));
15183
+ const profile = new UserProfile(join27(dir));
14741
15184
  profile.load() || profile.collect();
14742
15185
  profile.save();
14743
15186
  const llmProvider = new OpenAICompatProvider({
@@ -14749,7 +15192,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14749
15192
  rateLimits: config.security?.rateLimits
14750
15193
  });
14751
15194
  const baseDir = projectDir ? resolve15(projectDir) : process.cwd();
14752
- const projectMapCacheDir = join26(baseDir, ".mma");
15195
+ const projectMapCacheDir = join27(baseDir, ".mma");
14753
15196
  const indexerModule = new IndexerModule({
14754
15197
  baseDir,
14755
15198
  cacheDir: projectMapCacheDir
@@ -14760,9 +15203,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14760
15203
  logger.warn(`Project indexing failed: ${err.message}`);
14761
15204
  }
14762
15205
  const skillsLoader = new SkillsLoader;
14763
- const builtinDir = join26(import.meta.dirname, "skills", "builtin");
14764
- const globalDir = join26(homedir11(), ".agents", "skills");
14765
- 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");
14766
15209
  const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
14767
15210
  const skillsMatcher = new SkillsMatcher;
14768
15211
  const skillsBudget = Math.floor(config.contextWindow * 0.1);
@@ -14776,11 +15219,11 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14776
15219
  essential: true,
14777
15220
  estimatedTokens: 250
14778
15221
  };
14779
- const agentsMdGlobal = join26(dir, "AGENTS.md");
15222
+ const agentsMdGlobal = join27(dir, "AGENTS.md");
14780
15223
  if (!existsSync33(agentsMdGlobal)) {
14781
15224
  writeFileSync14(agentsMdGlobal, "", "utf-8");
14782
15225
  }
14783
- const sessionDir = join26(dir, "sessions");
15226
+ const sessionDir = join27(dir, "sessions");
14784
15227
  const sessionStore = new SessionStore(sessionDir);
14785
15228
  sessionStore.init();
14786
15229
  const sessionManager = new SessionManager(sessionStore, {
@@ -14834,7 +15277,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14834
15277
  const mcpModule = new MCPModule(config);
14835
15278
  await mcpModule.initialize();
14836
15279
  moduleRegistry.register(mcpModule);
14837
- const memoryModule = new MemoryModule(join26(dir, "memory"));
15280
+ const memoryModule = new MemoryModule(join27(dir, "memory"));
14838
15281
  moduleRegistry.register(memoryModule);
14839
15282
  if (config.browser.enabled) {
14840
15283
  const browserModule = new BrowserModule;
@@ -14877,8 +15320,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14877
15320
  pluginManager.register(plugin);
14878
15321
  pluginManager.register(plugin2);
14879
15322
  const pluginLoader = new PluginLoader;
14880
- const globalPluginsDir = join26(homedir11(), ".mma", "plugins");
14881
- const projectPluginsDir = join26(dir, ".mma", "plugins");
15323
+ const globalPluginsDir = join27(homedir11(), ".mma", "plugins");
15324
+ const projectPluginsDir = join27(dir, ".mma", "plugins");
14882
15325
  pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
14883
15326
  pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
14884
15327
  contextManager.onCompact = (summary) => {
@@ -14896,9 +15339,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
14896
15339
  const skipAgentsMd = noAgentsMd === true;
14897
15340
  if (!skipAgentsMd) {
14898
15341
  const agentsMdCandidates = [
14899
- join26(baseDir, "AGENTS.md"),
14900
- join26(baseDir, ".mma", "AGENTS.md"),
14901
- join26(dir, "AGENTS.md")
15342
+ join27(baseDir, "AGENTS.md"),
15343
+ join27(baseDir, ".mma", "AGENTS.md"),
15344
+ join27(dir, "AGENTS.md")
14902
15345
  ];
14903
15346
  for (const p of agentsMdCandidates) {
14904
15347
  if (existsSync33(p)) {
@@ -15225,7 +15668,7 @@ function pad(text, width, align) {
15225
15668
  }
15226
15669
  return text + " ".repeat(gap);
15227
15670
  }
15228
- function truncate(text, width) {
15671
+ function truncate2(text, width) {
15229
15672
  if (stringWidth(text) <= width)
15230
15673
  return text;
15231
15674
  let acc = "";
@@ -15311,7 +15754,7 @@ function formatTable(tableLines, opts = {}) {
15311
15754
  const cells = [];
15312
15755
  for (let c = 0;c < columnCount; c++) {
15313
15756
  const raw = row[c] ?? "";
15314
- const cell = truncate(raw, widths[c]);
15757
+ const cell = truncate2(raw, widths[c]);
15315
15758
  const a = isHeader && align[c] === undefined ? "center" : align[c] ?? "left";
15316
15759
  cells.push(pad(cell, widths[c], a));
15317
15760
  }
@@ -15735,14 +16178,14 @@ async function runSetup() {
15735
16178
 
15736
16179
  // src/cli/commands.ts
15737
16180
  init_i18n();
15738
- import { join as join28, dirname as dirname9 } from "path";
16181
+ import { join as join29, dirname as dirname9 } from "path";
15739
16182
  import { homedir as homedir13 } from "os";
15740
16183
  import { existsSync as existsSync34, readFileSync as readFileSync23 } from "fs";
15741
16184
 
15742
16185
  // src/cli/security-commands.ts
15743
16186
  init_bootstrap();
15744
16187
  init_config();
15745
- import { join as join27 } from "path";
16188
+ import { join as join28 } from "path";
15746
16189
  import { homedir as homedir12 } from "os";
15747
16190
 
15748
16191
  // src/modules/security/security-policies.ts
@@ -16272,7 +16715,7 @@ function createSecurityCommand(program2) {
16272
16715
  }
16273
16716
  });
16274
16717
  securityCmd.command("set-policy").argument("<preset>", t("cli.security.preset")).description(t("cli.security.set_policy")).action(async (preset) => {
16275
- const configPath = join27(homedir12(), ".mma", "config.json");
16718
+ const configPath = join28(homedir12(), ".mma", "config.json");
16276
16719
  const { config: appConfig } = await bootstrap();
16277
16720
  const validPresets = ["strict", "balanced", "permissive"];
16278
16721
  if (!validPresets.includes(preset)) {
@@ -16287,7 +16730,7 @@ function createSecurityCommand(program2) {
16287
16730
  console.log(t("cli.security.policy_description", { description: policy.description }));
16288
16731
  });
16289
16732
  securityCmd.command("enable-encryption").description(t("cli.security.enable_encryption")).action(async () => {
16290
- const configPath = join27(homedir12(), ".mma", "config.json");
16733
+ const configPath = join28(homedir12(), ".mma", "config.json");
16291
16734
  const { config: appConfig } = await bootstrap();
16292
16735
  appConfig.security = appConfig.security || {};
16293
16736
  appConfig.security.sessionEncryption = {
@@ -16299,7 +16742,7 @@ function createSecurityCommand(program2) {
16299
16742
  console.log(t("cli.security.encryption_enabled"));
16300
16743
  });
16301
16744
  securityCmd.command("disable-encryption").description(t("cli.security.disable_encryption")).action(async () => {
16302
- const configPath = join27(homedir12(), ".mma", "config.json");
16745
+ const configPath = join28(homedir12(), ".mma", "config.json");
16303
16746
  const { config: appConfig } = await bootstrap();
16304
16747
  appConfig.security = appConfig.security || {};
16305
16748
  appConfig.security.sessionEncryption = {
@@ -16311,7 +16754,7 @@ function createSecurityCommand(program2) {
16311
16754
  console.log(t("cli.security.encryption_disabled"));
16312
16755
  });
16313
16756
  securityCmd.command("enable-audit").description(t("cli.security.enable_audit")).action(async () => {
16314
- const configPath = join27(homedir12(), ".mma", "config.json");
16757
+ const configPath = join28(homedir12(), ".mma", "config.json");
16315
16758
  const { config: appConfig } = await bootstrap();
16316
16759
  appConfig.security = appConfig.security || {};
16317
16760
  appConfig.security.auditNotifier = {
@@ -16325,7 +16768,7 @@ function createSecurityCommand(program2) {
16325
16768
  console.log(t("cli.security.audit_enabled"));
16326
16769
  });
16327
16770
  securityCmd.command("disable-audit").description(t("cli.security.disable_audit")).action(async () => {
16328
- const configPath = join27(homedir12(), ".mma", "config.json");
16771
+ const configPath = join28(homedir12(), ".mma", "config.json");
16329
16772
  const { config: appConfig } = await bootstrap();
16330
16773
  appConfig.security = appConfig.security || {};
16331
16774
  appConfig.security.auditNotifier = {
@@ -16361,8 +16804,8 @@ import { fileURLToPath } from "url";
16361
16804
  function readVersion() {
16362
16805
  const here = dirname9(fileURLToPath(import.meta.url));
16363
16806
  const candidates = [
16364
- join28(here, "..", "..", "package.json"),
16365
- join28(here, "..", "package.json")
16807
+ join29(here, "..", "..", "package.json"),
16808
+ join29(here, "..", "package.json")
16366
16809
  ];
16367
16810
  for (const p of candidates) {
16368
16811
  if (existsSync34(p)) {
@@ -16378,7 +16821,7 @@ function createProgram() {
16378
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"));
16379
16822
  program2.command("init").description(t("cli.init")).action(async () => {
16380
16823
  const answers = await runSetup();
16381
- const configPath = join28(homedir13(), ".mma", "config.json");
16824
+ const configPath = join29(homedir13(), ".mma", "config.json");
16382
16825
  const { config } = await bootstrap();
16383
16826
  config.provider.type = answers.provider;
16384
16827
  config.provider.baseUrl = answers.apiBase;
@@ -16423,7 +16866,7 @@ function createProgram() {
16423
16866
  });
16424
16867
  const configCmd = program2.command("config").description(t("cli.manage_config"));
16425
16868
  configCmd.command("set").argument("<key>", t("cli.config_key")).argument("<value>", "Config value").description(t("cli.set_value")).action(async (key, value) => {
16426
- const configPath = join28(homedir13(), ".mma", "config.json");
16869
+ const configPath = join29(homedir13(), ".mma", "config.json");
16427
16870
  const { config } = await bootstrap();
16428
16871
  const keys = key.split(".");
16429
16872
  let obj = config;
@@ -16483,14 +16926,14 @@ function createProgram() {
16483
16926
  console.log(t("cli.model_hint"));
16484
16927
  });
16485
16928
  model.command("use").argument("<name>", "Model name").description(t("cli.set_model")).action(async (name) => {
16486
- const configPath = join28(homedir13(), ".mma", "config.json");
16929
+ const configPath = join29(homedir13(), ".mma", "config.json");
16487
16930
  const { config } = await bootstrap();
16488
16931
  config.model = name;
16489
16932
  saveConfig(config, configPath);
16490
16933
  console.log(t("cli.model_set", { name }));
16491
16934
  });
16492
16935
  program2.command("context").description(t("cli.manage_context")).argument("<size>", "Context window size in tokens").action(async (size) => {
16493
- const configPath = join28(homedir13(), ".mma", "config.json");
16936
+ const configPath = join29(homedir13(), ".mma", "config.json");
16494
16937
  const { config } = await bootstrap();
16495
16938
  const contextWindow = parseInt(size, 10);
16496
16939
  if (isNaN(contextWindow) || contextWindow < 1024) {
@@ -16508,7 +16951,7 @@ function createProgram() {
16508
16951
  console.log(t("cli.base_url"), config.provider.baseUrl);
16509
16952
  });
16510
16953
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
16511
- const configPath = join28(homedir13(), ".mma", "config.json");
16954
+ const configPath = join29(homedir13(), ".mma", "config.json");
16512
16955
  const { config } = await bootstrap();
16513
16956
  config.provider.type = name;
16514
16957
  saveConfig(config, configPath);
@@ -16561,7 +17004,7 @@ init_bootstrap();
16561
17004
  init_colors();
16562
17005
  import * as readline2 from "readline";
16563
17006
  import { existsSync as existsSync35, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "fs";
16564
- import { join as join29, dirname as dirname10 } from "path";
17007
+ import { join as join30, dirname as dirname10 } from "path";
16565
17008
  import { homedir as homedir14 } from "os";
16566
17009
  import { fileURLToPath as fileURLToPath2 } from "url";
16567
17010
 
@@ -16877,7 +17320,7 @@ init_i18n();
16877
17320
  function isRichTerminal() {
16878
17321
  return Boolean(process.stdout.isTTY) && !process.env.CI;
16879
17322
  }
16880
- function summarizeArgs(args) {
17323
+ function summarizeArgs2(args) {
16881
17324
  const preferred = ["path", "file", "query", "url", "command", "name"];
16882
17325
  for (const key of preferred) {
16883
17326
  const value = args[key];
@@ -16943,7 +17386,7 @@ class Renderer {
16943
17386
  toolStart(tool, args) {
16944
17387
  this.endCard();
16945
17388
  this.spinner.stop();
16946
- const summary = summarizeArgs(args);
17389
+ const summary = summarizeArgs2(args);
16947
17390
  if (!this.rich) {
16948
17391
  this.out.write(`
16949
17392
  ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
@@ -16953,15 +17396,21 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
16953
17396
  this.card = { tool, args, body: [], start: Date.now() };
16954
17397
  this.spinner.start(`${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}`);
16955
17398
  }
16956
- toolEnd(_tool, duration, error) {
17399
+ toolEnd(_tool, duration, error, ctxDelta) {
16957
17400
  this.spinner.stop();
16958
- 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
+ }
16959
17407
  return;
17408
+ }
16960
17409
  if (!this.card)
16961
17410
  return;
16962
17411
  const { tool, args, body } = this.card;
16963
17412
  const lines = [];
16964
- const summary = summarizeArgs(args);
17413
+ const summary = summarizeArgs2(args);
16965
17414
  if (summary)
16966
17415
  lines.push(pc.dim(summary));
16967
17416
  for (const chunk of body) {
@@ -16972,7 +17421,12 @@ ${pc.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc.dim(summary)}` : ""}
16972
17421
  }
16973
17422
  }
16974
17423
  const marker = error ? pc.red("✗") : pc.green("✓");
16975
- 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);
16976
17430
  const title = `${marker} ${friendlyTool(tool)}`;
16977
17431
  for (const line of box(lines, { title, width: this.width })) {
16978
17432
  this.out.write(`${line}
@@ -17011,8 +17465,8 @@ init_config();
17011
17465
  function readVersion2() {
17012
17466
  const here = dirname10(fileURLToPath2(import.meta.url));
17013
17467
  const candidates = [
17014
- join29(here, "..", "..", "package.json"),
17015
- join29(here, "..", "package.json")
17468
+ join30(here, "..", "..", "package.json"),
17469
+ join30(here, "..", "package.json")
17016
17470
  ];
17017
17471
  for (const p of candidates) {
17018
17472
  if (existsSync35(p)) {
@@ -17045,13 +17499,21 @@ var COMMAND_GROUPS = {
17045
17499
  delete: "session",
17046
17500
  skill: "skill"
17047
17501
  };
17048
- function formatContextBar(used, limit) {
17502
+ function formatContextBar(used, limit, compactions, quality) {
17049
17503
  const pct = Math.min(100, Math.round(used / limit * 100));
17050
17504
  const barLen = 20;
17051
17505
  const filled = Math.round(pct / 100 * barLen);
17052
17506
  const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
17053
17507
  const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
17054
- 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;
17055
17517
  }
17056
17518
 
17057
17519
  class Repl {
@@ -17080,10 +17542,10 @@ class Repl {
17080
17542
  this.sessionManager = sessionManager;
17081
17543
  this.skillsModule = skillsModule;
17082
17544
  this.pluginManager = pluginManager;
17083
- this.configDir = configDir || join29(homedir14(), ".mma");
17545
+ this.configDir = configDir || join30(homedir14(), ".mma");
17084
17546
  this.baseDir = baseDir || process.cwd();
17085
17547
  this.noAgentsMd = noAgentsMd === true;
17086
- this.historyPath = join29(homedir14(), ".mma", "repl-history");
17548
+ this.historyPath = join30(homedir14(), ".mma", "repl-history");
17087
17549
  this.loadHistory();
17088
17550
  this.registerBuiltinCommands();
17089
17551
  this.registerMmaCommands();
@@ -17269,7 +17731,7 @@ class Repl {
17269
17731
  action: async () => {
17270
17732
  console.log(pc.yellow(t("repl.wizard_running")));
17271
17733
  const answers = await runSetup();
17272
- const configPath = join29(homedir14(), ".mma", "config.json");
17734
+ const configPath = join30(homedir14(), ".mma", "config.json");
17273
17735
  this.config.provider.type = answers.provider;
17274
17736
  this.config.provider.baseUrl = answers.apiBase;
17275
17737
  this.config.provider.apiKey = answers.apiKey;
@@ -17299,7 +17761,7 @@ class Repl {
17299
17761
  return;
17300
17762
  }
17301
17763
  this.config.provider.type = name;
17302
- const configPath = join29(homedir14(), ".mma", "config.json");
17764
+ const configPath = join30(homedir14(), ".mma", "config.json");
17303
17765
  saveConfig(this.config, configPath);
17304
17766
  console.log(pc.green(t("repl.provider_set", { name })));
17305
17767
  return;
@@ -17351,7 +17813,7 @@ class Repl {
17351
17813
  return;
17352
17814
  }
17353
17815
  this.config.model = name;
17354
- const configPath = join29(homedir14(), ".mma", "config.json");
17816
+ const configPath = join30(homedir14(), ".mma", "config.json");
17355
17817
  saveConfig(this.config, configPath);
17356
17818
  console.log(pc.green(t("repl.model_set", { name })));
17357
17819
  return;
@@ -17375,7 +17837,7 @@ class Repl {
17375
17837
  return;
17376
17838
  }
17377
17839
  this.config.contextWindow = size;
17378
- const configPath = join29(homedir14(), ".mma", "config.json");
17840
+ const configPath = join30(homedir14(), ".mma", "config.json");
17379
17841
  saveConfig(this.config, configPath);
17380
17842
  console.log(pc.green(t("cli.context_set", { size })));
17381
17843
  }
@@ -17393,9 +17855,9 @@ class Repl {
17393
17855
  this.agent.shutdown();
17394
17856
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
17395
17857
  const { homedir: homedir15 } = await import("os");
17396
- const { join: join30 } = await import("path");
17397
- const configDir = join30(homedir15(), ".mma");
17398
- 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");
17399
17861
  const freshConfig = loadConfig2({ configDir, projectConfigPath });
17400
17862
  Object.assign(this.config, freshConfig);
17401
17863
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
@@ -17805,7 +18267,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17805
18267
  if (ev.type === "start") {
17806
18268
  renderer.toolStart(ev.tool, ev.args);
17807
18269
  } else {
17808
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
18270
+ renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
17809
18271
  }
17810
18272
  }, (phase) => {
17811
18273
  if (phase === "thinking") {
@@ -17885,7 +18347,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17885
18347
  showContextBar(result) {
17886
18348
  if (result.contextUsed !== undefined && result.contextLimit !== undefined && result.contextLimit > 0) {
17887
18349
  console.log();
17888
- const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
18350
+ const ctxLine = formatContextBar(result.contextUsed, result.contextLimit, result.compactionCount, result.contextQuality);
17889
18351
  console.log(ctxLine);
17890
18352
  if (result.totalTokens !== undefined && result.totalTokens > 0) {
17891
18353
  const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
@@ -17929,9 +18391,9 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17929
18391
  row(t("repl.agents_label"), pc.red(t("repl.disabled")));
17930
18392
  } else {
17931
18393
  const agentsMdCandidates = [
17932
- join29(this.baseDir, "AGENTS.md"),
17933
- join29(this.baseDir, ".mma", "AGENTS.md"),
17934
- join29(this.configDir, "AGENTS.md")
18394
+ join30(this.baseDir, "AGENTS.md"),
18395
+ join30(this.baseDir, ".mma", "AGENTS.md"),
18396
+ join30(this.configDir, "AGENTS.md")
17935
18397
  ];
17936
18398
  const foundAgents = agentsMdCandidates.filter((p) => existsSync35(p));
17937
18399
  if (foundAgents.length > 0) {
@@ -17944,7 +18406,7 @@ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
17944
18406
  }
17945
18407
  const meta = this.sessionManager?.getActiveMeta();
17946
18408
  if (meta) {
17947
- const sessionPath = join29(this.configDir, "sessions", meta.id);
18409
+ const sessionPath = join30(this.configDir, "sessions", meta.id);
17948
18410
  row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
17949
18411
  }
17950
18412
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
@@ -17970,7 +18432,7 @@ init_config();
17970
18432
  init_i18n();
17971
18433
  init_colors();
17972
18434
  import { existsSync as existsSync36 } from "fs";
17973
- import { join as join30 } from "path";
18435
+ import { join as join31 } from "path";
17974
18436
  import { homedir as homedir15 } from "os";
17975
18437
  async function main() {
17976
18438
  const program2 = createProgram();
@@ -18011,7 +18473,7 @@ async function main() {
18011
18473
  if (ev.type === "start") {
18012
18474
  renderer.toolStart(ev.tool, ev.args);
18013
18475
  } else {
18014
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
18476
+ renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
18015
18477
  }
18016
18478
  }, (phase) => {
18017
18479
  if (phase === "thinking") {
@@ -18034,7 +18496,7 @@ async function main() {
18034
18496
  }
18035
18497
  agent.shutdown();
18036
18498
  } else {
18037
- const configPath = join30(homedir15(), ".mma", "config.json");
18499
+ const configPath = join31(homedir15(), ".mma", "config.json");
18038
18500
  if (!existsSync36(configPath)) {
18039
18501
  console.log(pc.yellow(`
18040
18502
  ` + t("cli.first_run") + `