micro-models-agent 0.28.9 → 0.28.17

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 (184) hide show
  1. package/dist/cli/commands.js +333 -0
  2. package/dist/cli/completer.js +168 -0
  3. package/dist/cli/index.js +2 -0
  4. package/dist/cli/main.js +140 -0
  5. package/dist/cli/repl-commands.js +633 -0
  6. package/dist/cli/repl.js +486 -0
  7. package/dist/cli/security-commands.js +166 -0
  8. package/dist/cli/setup.js +249 -0
  9. package/dist/config/config.js +202 -0
  10. package/dist/config/defaults.js +100 -0
  11. package/dist/config/experts.js +15 -0
  12. package/dist/config/index.js +3 -0
  13. package/dist/config/security.js +200 -0
  14. package/dist/config/types.js +1 -0
  15. package/dist/core/agent-moe.js +110 -0
  16. package/dist/core/agent.js +695 -0
  17. package/dist/core/bootstrap.js +337 -0
  18. package/dist/core/index.js +2 -0
  19. package/dist/core/prompt-builder.js +55 -0
  20. package/dist/core/session-logger.js +155 -0
  21. package/dist/core/types.js +1 -0
  22. package/dist/core/workspace.js +76 -0
  23. package/dist/i18n/en.json +525 -0
  24. package/dist/i18n/index.js +46 -0
  25. package/dist/i18n/ru.json +525 -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 +353 -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 +143 -0
  37. package/dist/logger/file-log.js +151 -0
  38. package/dist/logger/index.js +1 -0
  39. package/dist/main.js +1758 -612
  40. package/dist/migration/backup.js +45 -0
  41. package/dist/migration/detect.js +50 -0
  42. package/dist/migration/index.js +2 -0
  43. package/dist/modules/browser/actions.js +46 -0
  44. package/dist/modules/browser/cookie-store.js +24 -0
  45. package/dist/modules/browser/index.js +5 -0
  46. package/dist/modules/browser/module.js +28 -0
  47. package/dist/modules/browser/session.js +335 -0
  48. package/dist/modules/browser/snapshot.js +114 -0
  49. package/dist/modules/browser/types.js +9 -0
  50. package/dist/modules/certification/cli.js +176 -0
  51. package/dist/modules/certification/fact-checker.js +84 -0
  52. package/dist/modules/certification/loader.js +111 -0
  53. package/dist/modules/certification/manifest.js +50 -0
  54. package/dist/modules/certification/runner.js +162 -0
  55. package/dist/modules/certification/scenarios.js +124 -0
  56. package/dist/modules/certification/types.js +1 -0
  57. package/dist/modules/context/index.js +1 -0
  58. package/dist/modules/context/manager.js +349 -0
  59. package/dist/modules/execution/auditor.js +66 -0
  60. package/dist/modules/execution/index.js +8 -0
  61. package/dist/modules/execution/module.js +779 -0
  62. package/dist/modules/execution/moe-executor.js +266 -0
  63. package/dist/modules/execution/plan-coverage.js +68 -0
  64. package/dist/modules/execution/plan-persister.js +46 -0
  65. package/dist/modules/execution/plan-store.js +159 -0
  66. package/dist/modules/execution/plan-validator.js +153 -0
  67. package/dist/modules/execution/planner.js +85 -0
  68. package/dist/modules/execution/stuck-detector.js +347 -0
  69. package/dist/modules/execution/tracker.js +67 -0
  70. package/dist/modules/execution/types.js +1 -0
  71. package/dist/modules/execution/verifier.js +178 -0
  72. package/dist/modules/hallucination/confidence.js +59 -0
  73. package/dist/modules/hallucination/consistency.js +26 -0
  74. package/dist/modules/hallucination/detector.js +46 -0
  75. package/dist/modules/hallucination/factual.js +190 -0
  76. package/dist/modules/hallucination/index.js +5 -0
  77. package/dist/modules/hallucination/js-identifiers.js +72 -0
  78. package/dist/modules/hallucination/llm-judge.js +103 -0
  79. package/dist/modules/index.js +5 -0
  80. package/dist/modules/indexer/cache.js +38 -0
  81. package/dist/modules/indexer/index.js +3 -0
  82. package/dist/modules/indexer/module.js +192 -0
  83. package/dist/modules/indexer/walker.js +101 -0
  84. package/dist/modules/lsp/client.js +235 -0
  85. package/dist/modules/lsp/config.js +81 -0
  86. package/dist/modules/lsp/index.js +3 -0
  87. package/dist/modules/lsp/module.js +68 -0
  88. package/dist/modules/lsp/types.js +1 -0
  89. package/dist/modules/mcp/client.js +399 -0
  90. package/dist/modules/mcp/index.js +3 -0
  91. package/dist/modules/mcp/module.js +146 -0
  92. package/dist/modules/mcp/registry.js +15 -0
  93. package/dist/modules/memory/index.js +1 -0
  94. package/dist/modules/memory/module.js +48 -0
  95. package/dist/modules/memory/search.js +40 -0
  96. package/dist/modules/memory/store.js +69 -0
  97. package/dist/modules/pipelines/engine.js +60 -0
  98. package/dist/modules/pipelines/index.js +3 -0
  99. package/dist/modules/pipelines/parser.js +53 -0
  100. package/dist/modules/pipelines/template.js +14 -0
  101. package/dist/modules/plugins/builtin/lint-on-write.js +226 -0
  102. package/dist/modules/plugins/builtin/notify.js +8 -0
  103. package/dist/modules/plugins/index.js +1 -0
  104. package/dist/modules/plugins/loader.js +28 -0
  105. package/dist/modules/plugins/manager.js +161 -0
  106. package/dist/modules/plugins/types.js +1 -0
  107. package/dist/modules/processes/index.js +2 -0
  108. package/dist/modules/processes/registry.js +238 -0
  109. package/dist/modules/processes/runner.js +23 -0
  110. package/dist/modules/registry.js +45 -0
  111. package/dist/modules/security/audit-log.js +136 -0
  112. package/dist/modules/security/audit-notifier.js +292 -0
  113. package/dist/modules/security/command-validator.js +211 -0
  114. package/dist/modules/security/content-scanner.js +53 -0
  115. package/dist/modules/security/data-sanitizer.js +97 -0
  116. package/dist/modules/security/encryption.js +240 -0
  117. package/dist/modules/security/index.js +14 -0
  118. package/dist/modules/security/network-validator.js +79 -0
  119. package/dist/modules/security/path-validator.js +209 -0
  120. package/dist/modules/security/rate-limiter.js +119 -0
  121. package/dist/modules/security/security-policies.js +547 -0
  122. package/dist/modules/security/session-encryption.js +210 -0
  123. package/dist/modules/security/session-isolation.js +95 -0
  124. package/dist/modules/session/index.js +3 -0
  125. package/dist/modules/session/manager.js +172 -0
  126. package/dist/modules/session/module.js +24 -0
  127. package/dist/modules/session/store.js +228 -0
  128. package/dist/modules/session/types.js +1 -0
  129. package/dist/modules/skills/index.js +2 -0
  130. package/dist/modules/skills/loader.js +72 -0
  131. package/dist/modules/skills/module.js +130 -0
  132. package/dist/modules/types.js +1 -0
  133. package/dist/modules/updater/checker.js +32 -0
  134. package/dist/modules/updater/index.js +1 -0
  135. package/dist/modules/user-profile/compressor.js +16 -0
  136. package/dist/modules/user-profile/index.js +1 -0
  137. package/dist/modules/user-profile/profile.js +68 -0
  138. package/dist/tools/approve.js +32 -0
  139. package/dist/tools/attach-image.js +89 -0
  140. package/dist/tools/bash.js +337 -0
  141. package/dist/tools/browser.js +97 -0
  142. package/dist/tools/create-dir.js +55 -0
  143. package/dist/tools/delete-file.js +62 -0
  144. package/dist/tools/edit-file.js +79 -0
  145. package/dist/tools/executor.js +145 -0
  146. package/dist/tools/file-info.js +45 -0
  147. package/dist/tools/filter-tools.js +10 -0
  148. package/dist/tools/glob-tool.js +26 -0
  149. package/dist/tools/grep-tool.js +86 -0
  150. package/dist/tools/index.js +67 -0
  151. package/dist/tools/list-dir.js +47 -0
  152. package/dist/tools/load-skill.js +44 -0
  153. package/dist/tools/mcp-call.js +68 -0
  154. package/dist/tools/move-file.js +85 -0
  155. package/dist/tools/path-utils.js +51 -0
  156. package/dist/tools/pipeline-run.js +144 -0
  157. package/dist/tools/preview.js +2 -0
  158. package/dist/tools/process-kill.js +29 -0
  159. package/dist/tools/process-list.js +38 -0
  160. package/dist/tools/process-log.js +41 -0
  161. package/dist/tools/question.js +142 -0
  162. package/dist/tools/read-file.js +83 -0
  163. package/dist/tools/recall.js +110 -0
  164. package/dist/tools/registry.js +36 -0
  165. package/dist/tools/remember.js +67 -0
  166. package/dist/tools/scope-check.js +30 -0
  167. package/dist/tools/search-history.js +84 -0
  168. package/dist/tools/subagent.js +151 -0
  169. package/dist/tools/types.js +1 -0
  170. package/dist/tools/user-input.js +123 -0
  171. package/dist/tools/web-browse.js +86 -0
  172. package/dist/tools/web-fetch.js +98 -0
  173. package/dist/tools/web-search.js +78 -0
  174. package/dist/tools/write-file.js +83 -0
  175. package/dist/ui/box.js +81 -0
  176. package/dist/ui/colors.js +4 -0
  177. package/dist/ui/diff.js +178 -0
  178. package/dist/ui/index.js +6 -0
  179. package/dist/ui/md-formatter.js +212 -0
  180. package/dist/ui/output.js +13 -0
  181. package/dist/ui/renderer.js +204 -0
  182. package/dist/ui/spinner.js +70 -0
  183. package/dist/ui/table.js +144 -0
  184. package/package.json +4 -4
package/dist/main.js CHANGED
@@ -2278,6 +2278,7 @@ var init_en = __esm(() => {
2278
2278
  {str}`,
2279
2279
  "file.moved": "Moved {from} → {to}",
2280
2280
  "file.not_found_short": "Not found: {path}",
2281
+ "file.notfound_resolved": "File not found: {path} (resolved to {resolved})",
2281
2282
  "file.empty": "(empty)",
2282
2283
  "file.no_matches": "No matches",
2283
2284
  "file.truncated": `
@@ -2295,6 +2296,7 @@ var init_en = __esm(() => {
2295
2296
  "error.llm": "LLM error: {message}",
2296
2297
  "error.response_blocked": "Response blocked: {reason}",
2297
2298
  "error.max_iters": "Max iterations ({max}) reached",
2299
+ "error.empty_response": "Model returned an empty response after retries",
2298
2300
  "error.grep_failed": "Grep failed: {message}",
2299
2301
  "error.search_failed": "Search failed: {message}",
2300
2302
  "error.fetch_failed": "Fetch failed: {message}",
@@ -2332,6 +2334,7 @@ var init_en = __esm(() => {
2332
2334
  "tool.failed": "Tool {name} failed: {error}",
2333
2335
  "tool.unknown": "Unknown tool: {name}",
2334
2336
  "tool.blocked": "Blocked by plugin: {plugin}",
2337
+ "tool.blocked_reason": "Blocked by plugin: {plugin}. Reason: {reason}",
2335
2338
  "tool.using": "[{label}]",
2336
2339
  "tool.friendly.write_file": "Writing file",
2337
2340
  "tool.friendly.read_file": "Reading file",
@@ -2344,6 +2347,7 @@ var init_en = __esm(() => {
2344
2347
  "tool.friendly.glob": "Searching files",
2345
2348
  "tool.friendly.grep": "Searching content",
2346
2349
  "tool.friendly.bash": "Running command",
2350
+ "bash.echo_write_blocked": "Writing files via echo/printf is unreliable in Windows cmd.exe (quotes and multi-line break). Use the write_file tool instead (target: {path}).",
2347
2351
  "tool.friendly.load_skill": "Loading skill",
2348
2352
  "tool.friendly.plan": "Planning",
2349
2353
  "tool.friendly.todo": "Updating tasks",
@@ -2433,6 +2437,13 @@ Command: {command}`,
2433
2437
  "plan.step_not_found": "Step not found",
2434
2438
  "plan.show_header": "Plan status:",
2435
2439
  "plan.show_empty": "(plan has no steps)",
2440
+ "plan.list_header": "Plans:",
2441
+ "plan.list_empty": "No plans",
2442
+ "plan.switch_no_id": "Provide a plan id to switch to",
2443
+ "plan.not_found": "Plan not found: {id}",
2444
+ "plan.switched": "Switched to plan {id}: {title}",
2445
+ "plan.replanned": "Plan re-planned: {kept} completed steps kept, {steps} new steps added",
2446
+ "plan.replan_no_steps": "Provide new steps for re-planning",
2436
2447
  "todo.added": "Added {count} todo(s): {items}",
2437
2448
  "todo.marked_done": "Marked {count} item(s) as done",
2438
2449
  "todo.no_active": "No active todos",
@@ -2706,12 +2717,14 @@ Use this knowledge to answer the user's question.`,
2706
2717
  "exec.plan_warning": 'Current plan step {step} is "{description}", but {tool} is being called for files outside this step. Complete the current step first, then call plan update step={step} status=done before moving to the next step.',
2707
2718
  "exec.plan_blocked": "{max} consecutive calls outside the current step. Finish step {step} before proceeding — other steps should wait until this one is complete.",
2708
2719
  "exec.off_track": 'Step {stepId} — "{description}" — but you are using {tool} on a different path. Return to the current step.',
2709
- "exec.step_gate_deps": '[⚠ Step {step} "{description}": dependencies are not installed. First run the install command (npm install, pip install, etc.). Verify the lock file or dependency directory exists.]',
2720
+ "exec.step_gate_deps": '[⚠ Step {step} "{description}": dependencies are not installed. First run the install command (npm install, pip install, etc.). Verify the lock file or dependency directory exists. If this step is NOT actually needed (no external dependencies), skip it: plan update step={step} status=skipped note="deps not needed".]',
2721
+ "exec.step_gate_deps_force": '[⚠ Step {step}: dependencies still not installed (no lock file) — 2nd warning. If this step is NOT needed, IMMEDIATELY call: plan update step={step} status=skipped note="deps not needed". If it IS needed, run the install command right now. Do NOT make other tool calls before updating the plan.]',
2710
2722
  "exec.step_gate_empty": "[⚠ Step {step}: files exist but appear empty: {files}. Add real code to these files before advancing to the next step.]",
2711
2723
  "exec.step_gate_ok": '[✓] Step {step} completed and verified. MOVING to step {nextStep}: "{nextDesc}". Work ONLY on this step.',
2712
2724
  "exec.step_gate_last": "[✓] Step {step} completed — that was the final step. Verify everything together and provide the final answer.]",
2713
2725
  "exec.audit_pass": "[✓] Task complete: {done}/{total} steps done, {files} files verified",
2714
2726
  "exec.audit_fail": "[✗] Task incomplete: {done}/{total} steps done, {files} files missing",
2727
+ "exec.audit_fail_tests": "[✗] Task incomplete: {done}/{total} steps done, tests FAILING: {failed} failed / {passed} passed — {detail}",
2715
2728
  "exec.audit_fail_typecheck": "[✗] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
2716
2729
  "exec.audit_incomplete": "[⚠ Final audit incomplete: {summary}. Task is NOT finished — continue working. Remaining steps: {steps}]",
2717
2730
  "exec.mass_edit_warning": "⚠️ Plan affects {count} files — review the full list before proceeding.",
@@ -2843,6 +2856,7 @@ var init_ru = __esm(() => {
2843
2856
  "error.llm": "Ошибка LLM: {message}",
2844
2857
  "error.response_blocked": "Ответ заблокирован: {reason}",
2845
2858
  "error.max_iters": "Достигнут максимум итераций ({max})",
2859
+ "error.empty_response": "Модель вернула пустой ответ после повторных попыток",
2846
2860
  "error.grep_failed": "Ошибка grep: {message}",
2847
2861
  "error.search_failed": "Ошибка поиска: {message}",
2848
2862
  "error.fetch_failed": "Ошибка загрузки: {message}",
@@ -2880,6 +2894,7 @@ var init_ru = __esm(() => {
2880
2894
  "tool.failed": "Инструмент {name} упал: {error}",
2881
2895
  "tool.unknown": "Неизвестный инструмент: {name}",
2882
2896
  "tool.blocked": "Заблокировано плагином: {plugin}",
2897
+ "tool.blocked_reason": "Заблокировано плагином: {plugin}. Причина: {reason}",
2883
2898
  "tool.using": "[{label}]",
2884
2899
  "tool.friendly.write_file": "Запись файла",
2885
2900
  "tool.friendly.read_file": "Чтение файла",
@@ -2981,6 +2996,13 @@ var init_ru = __esm(() => {
2981
2996
  "plan.step_not_found": "Шаг не найден",
2982
2997
  "plan.show_header": "Статус плана:",
2983
2998
  "plan.show_empty": "(в плане нет шагов)",
2999
+ "plan.list_header": "Планы:",
3000
+ "plan.list_empty": "Нет планов",
3001
+ "plan.switch_no_id": "Укажите id плана для переключения",
3002
+ "plan.not_found": "План не найден: {id}",
3003
+ "plan.switched": "Переключено на план {id}: {title}",
3004
+ "plan.replanned": "План перепланирован: {kept} выполненных шагов сохранено, {steps} новых шагов добавлено",
3005
+ "plan.replan_no_steps": "Укажите новые шаги для перепланирования",
2984
3006
  "todo.added": "Добавлено {count} задач: {items}",
2985
3007
  "todo.marked_done": "Отмечено выполненными: {count}",
2986
3008
  "todo.no_active": "Нет активных задач",
@@ -3254,12 +3276,14 @@ var init_ru = __esm(() => {
3254
3276
  "exec.plan_warning": 'Текущий шаг плана {step} — "{description}", но вызывается {tool} для файлов вне этого шага. Завершите текущий шаг, вызовите plan update step={step} status=done, затем переходите к следующему.',
3255
3277
  "exec.plan_blocked": "{max} вызовов подряд вне текущего шага. Завершите шаг {step} — остальные шаги ждут пока этот не будет выполнен.",
3256
3278
  "exec.off_track": 'Шаг {stepId} — "{description}", но используется {tool} для другого пути. Вернитесь к текущему шагу.',
3257
- "exec.step_gate_deps": '[⚠ Шаг {step} "{description}": зависимости не установлены. Сначала выполните команду установки (npm install, pip install и т.д.). Проверьте что lock-файл или директория зависимостей существует.]',
3279
+ "exec.step_gate_deps": '[⚠ Шаг {step} "{description}": зависимости не установлены. Сначала выполните команду установки (npm install, pip install и т.д.). Проверьте что lock-файл или директория зависимостей существует. Если этот шаг на самом деле не нужен (внешних зависимостей нет) — пропусти его: plan update step={step} status=skipped note="зависимости не нужны".]',
3280
+ "exec.step_gate_deps_force": '[⚠ Шаг {step}: зависимости всё ещё не установлены (нет lock-файла) — второе предупреждение. Если этот шаг НЕ нужен — НЕМЕДЛЕННО вызови: plan update step={step} status=skipped note="зависимости не нужны". Если нужен — выполни установку прямо сейчас. Не делай других вызовов инструментов до обновления плана.]',
3258
3281
  "exec.step_gate_empty": "[⚠ Шаг {step}: файлы существуют, но выглядят пустыми: {files}. Добавьте реальный код в эти файлы перед тем как переходить к следующему шагу.]",
3259
3282
  "exec.step_gate_ok": '[✓] Шаг {step} завершён и проверен. ПЕРЕХОДИМ к шагу {nextStep}: "{nextDesc}". Работайте ТОЛЬКО над этим шагом.',
3260
3283
  "exec.step_gate_last": "[✓] Шаг {step} завершён — это был последний шаг. Проверьте всё вместе и предоставьте финальный ответ.]",
3261
3284
  "exec.audit_pass": "[✓] Задача выполнена: {done}/{total} шагов, {files} файлов проверено",
3262
3285
  "exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
3286
+ "exec.audit_fail_tests": "[✗] Задача не выполнена: {done}/{total} шагов, тесты ПАДАЮТ: {failed} failed / {passed} passed — {detail}",
3263
3287
  "exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
3264
3288
  "exec.audit_incomplete": "[⚠ Финальная проверка не пройдена: {summary}. Задача НЕ завершена — продолжайте работу. Оставшиеся шаги: {steps}]",
3265
3289
  "exec.mass_edit_warning": "⚠️ План затрагивает {count} файлов — проверьте полный список перед продолжением.",
@@ -3352,7 +3376,9 @@ var init_ru = __esm(() => {
3352
3376
  "ctx.quality": "качество: {percent}%",
3353
3377
  "ctx.delta_pos": "контекст +{tokens}",
3354
3378
  "ctx.delta_neg": "контекст -{tokens} ↓",
3355
- "ctx.delta_zero": "контекст ±0"
3379
+ "ctx.delta_zero": "контекст ±0",
3380
+ "file.notfound_resolved": "Файл не найден: {path} (резолвится в {resolved})",
3381
+ "bash.echo_write_blocked": "Запись файлов через echo/printf ненадёжна в Windows cmd.exe (кавычки и многострочность ломаются). Используй инструмент write_file вместо этого (цель: {path})."
3356
3382
  };
3357
3383
  });
3358
3384
 
@@ -3932,6 +3958,132 @@ var init_data_sanitizer = __esm(() => {
3932
3958
  ];
3933
3959
  });
3934
3960
 
3961
+ // src/logger/file-log.ts
3962
+ import {
3963
+ appendFileSync,
3964
+ mkdirSync as mkdirSync3,
3965
+ existsSync as existsSync5,
3966
+ readdirSync as readdirSync3,
3967
+ renameSync,
3968
+ statSync,
3969
+ unlinkSync as unlinkSync2
3970
+ } from "node:fs";
3971
+ import { join as join5 } from "node:path";
3972
+
3973
+ class FileLogWriter {
3974
+ logDir = null;
3975
+ sessionLogPath = null;
3976
+ setLogDir(dir) {
3977
+ this.logDir = dir;
3978
+ if (!existsSync5(dir)) {
3979
+ mkdirSync3(dir, { recursive: true });
3980
+ }
3981
+ }
3982
+ initSessionLog(sessionId) {
3983
+ if (!this.logDir)
3984
+ return;
3985
+ if (!existsSync5(this.logDir)) {
3986
+ mkdirSync3(this.logDir, { recursive: true });
3987
+ }
3988
+ this.sessionLogPath = join5(this.logDir, `${sessionId}.log`);
3989
+ this.cleanupOldLogs();
3990
+ }
3991
+ closeSessionLog() {
3992
+ this.sessionLogPath = null;
3993
+ }
3994
+ getLogPath() {
3995
+ if (this.sessionLogPath)
3996
+ return this.sessionLogPath;
3997
+ if (!this.logDir)
3998
+ return "";
3999
+ const date = new Date().toISOString().slice(0, 10);
4000
+ return join5(this.logDir, `${date}.log`);
4001
+ }
4002
+ rotateIfNeeded(filePath) {
4003
+ if (!existsSync5(filePath))
4004
+ return;
4005
+ const size = statSync(filePath).size;
4006
+ if (size < MAX_LOG_SIZE)
4007
+ return;
4008
+ let idx = 1;
4009
+ while (existsSync5(`${filePath}.${idx}`))
4010
+ idx++;
4011
+ renameSync(filePath, `${filePath}.${idx}`);
4012
+ }
4013
+ log(level, tag, message) {
4014
+ const fp = this.getLogPath();
4015
+ if (!fp)
4016
+ return;
4017
+ if (this.logDir && !existsSync5(this.logDir)) {
4018
+ mkdirSync3(this.logDir, { recursive: true });
4019
+ }
4020
+ this.rotateIfNeeded(fp);
4021
+ const line = `[${new Date().toISOString()}] [${level}] [${tag}] ${message}
4022
+ `;
4023
+ try {
4024
+ appendFileSync(fp, line, "utf-8");
4025
+ } catch {}
4026
+ }
4027
+ logLLMRequest(model, messagesCount, promptPreview, caller) {
4028
+ const tag = caller ? `LLM/${caller}` : "LLM";
4029
+ this.log("INFO", tag, `→ ${model} | ${messagesCount} messages | "${promptPreview.slice(0, 100)}"`);
4030
+ }
4031
+ logLLMResponse(model, responseLength, genTimeMs, error, caller) {
4032
+ const tag = caller ? `LLM/${caller}` : "LLM";
4033
+ if (error) {
4034
+ this.log("ERROR", tag, `← ${model} | ${responseLength} chars | ${genTimeMs}ms | ERROR: ${error}`);
4035
+ } else {
4036
+ this.log("INFO", tag, `← ${model} | ${responseLength} chars | ${genTimeMs}ms`);
4037
+ }
4038
+ }
4039
+ logToolCall(tool, preview, result) {
4040
+ const previewTrimmed = preview.length > 200 ? preview.slice(0, 200) + "..." : preview;
4041
+ if (result !== undefined) {
4042
+ const resultTrimmed = result.length > 200 ? result.slice(0, 200) + "..." : result;
4043
+ this.log("INFO", "TOOL", `${tool} | ${previewTrimmed} → ${resultTrimmed}`);
4044
+ } else {
4045
+ this.log("INFO", "TOOL", `${tool} | ${previewTrimmed}`);
4046
+ }
4047
+ }
4048
+ logToolOutput(tool, output, exitCode) {
4049
+ const trimmed = output.length > TOOL_OUTPUT_LIMIT ? output.slice(0, TOOL_OUTPUT_LIMIT) + `
4050
+ ... (truncated ${output.length - TOOL_OUTPUT_LIMIT} chars)` : output;
4051
+ this.log("DEBUG", "TOOL_OUTPUT", `${tool} exit:${exitCode}
4052
+ ${trimmed}`);
4053
+ }
4054
+ logREPL(tag, content) {
4055
+ const trimmed = content.length > 400 ? content.slice(0, 400) + "..." : content;
4056
+ this.log("INFO", `REPL/${tag}`, trimmed);
4057
+ }
4058
+ cleanupOldLogs(maxDays = DEFAULT_MAX_DAYS, maxFiles = MAX_LOG_FILES) {
4059
+ if (!this.logDir || !existsSync5(this.logDir))
4060
+ return;
4061
+ const now = Date.now();
4062
+ const list = () => readdirSync3(this.logDir).filter((f) => f.endsWith(".log") || /\.log\.\d+$/.test(f)).map((f) => ({
4063
+ name: f,
4064
+ path: join5(this.logDir, f),
4065
+ mtime: statSync(join5(this.logDir, f)).mtimeMs
4066
+ })).sort((a, b) => b.mtime - a.mtime);
4067
+ for (const f of list()) {
4068
+ if (now - f.mtime > maxDays * 24 * 60 * 60 * 1000) {
4069
+ try {
4070
+ unlinkSync2(f.path);
4071
+ } catch {}
4072
+ }
4073
+ }
4074
+ const remaining = list();
4075
+ for (let i = maxFiles;i < remaining.length; i++) {
4076
+ try {
4077
+ unlinkSync2(remaining[i].path);
4078
+ } catch {}
4079
+ }
4080
+ }
4081
+ }
4082
+ var MAX_LOG_SIZE, MAX_LOG_FILES = 30, DEFAULT_MAX_DAYS = 30, TOOL_OUTPUT_LIMIT = 2000;
4083
+ var init_file_log = __esm(() => {
4084
+ MAX_LOG_SIZE = 5 * 1024 * 1024;
4085
+ });
4086
+
3935
4087
  // node_modules/picocolors/picocolors.js
3936
4088
  var require_picocolors = __commonJS((exports, module) => {
3937
4089
  var p = process || {};
@@ -4003,8 +4155,8 @@ var require_picocolors = __commonJS((exports, module) => {
4003
4155
  });
4004
4156
 
4005
4157
  // src/logger/app-logger.ts
4006
- import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
4007
- import { join as join5 } from "path";
4158
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
4159
+ import { join as join6 } from "path";
4008
4160
  function isColorEnabled() {
4009
4161
  return !process.env.NO_COLOR && !process.env.CI && process.stdout.isTTY === true;
4010
4162
  }
@@ -4014,28 +4166,58 @@ class Logger {
4014
4166
  prefix;
4015
4167
  logDir = null;
4016
4168
  sessionDir = null;
4169
+ fileLog;
4017
4170
  constructor(level = "info", prefix = "") {
4018
4171
  this.level = level;
4019
4172
  this.prefix = prefix;
4173
+ this.fileLog = new FileLogWriter;
4020
4174
  }
4021
4175
  setLevel(level) {
4022
4176
  this.level = level;
4023
4177
  }
4024
4178
  setLogDir(dir) {
4025
4179
  this.logDir = dir;
4026
- if (!existsSync5(dir)) {
4027
- mkdirSync3(dir, { recursive: true });
4180
+ if (!existsSync6(dir)) {
4181
+ mkdirSync4(dir, { recursive: true });
4028
4182
  }
4183
+ this.fileLog.setLogDir(dir);
4029
4184
  }
4030
4185
  setSessionDir(dir) {
4031
4186
  this.sessionDir = dir;
4032
- if (!existsSync5(dir)) {
4033
- mkdirSync3(dir, { recursive: true });
4187
+ if (!existsSync6(dir)) {
4188
+ mkdirSync4(dir, { recursive: true });
4034
4189
  }
4035
4190
  }
4036
4191
  clearSessionDir() {
4037
4192
  this.sessionDir = null;
4038
4193
  }
4194
+ initSessionLog(sessionId) {
4195
+ this.fileLog.initSessionLog(sessionId);
4196
+ }
4197
+ closeSessionLog() {
4198
+ this.fileLog.closeSessionLog();
4199
+ }
4200
+ getLogPath() {
4201
+ return this.fileLog.getLogPath();
4202
+ }
4203
+ cleanupOldLogs(maxDays, maxFiles) {
4204
+ this.fileLog.cleanupOldLogs(maxDays, maxFiles);
4205
+ }
4206
+ logLLMRequest(model, messagesCount, promptPreview, caller) {
4207
+ this.fileLog.logLLMRequest(model, messagesCount, promptPreview, caller);
4208
+ }
4209
+ logLLMResponse(model, responseLength, genTimeMs, error, caller) {
4210
+ this.fileLog.logLLMResponse(model, responseLength, genTimeMs, error, caller);
4211
+ }
4212
+ logToolCall(tool, preview, result) {
4213
+ this.fileLog.logToolCall(tool, preview, result);
4214
+ }
4215
+ logToolOutput(tool, output, exitCode) {
4216
+ this.fileLog.logToolOutput(tool, output, exitCode);
4217
+ }
4218
+ logREPL(tag, content) {
4219
+ this.fileLog.logREPL(tag, content);
4220
+ }
4039
4221
  child(prefix) {
4040
4222
  const childLogger = new Logger(this.level, this.prefix ? `${this.prefix}:${prefix}` : prefix);
4041
4223
  if (this.logDir)
@@ -4066,10 +4248,11 @@ class Logger {
4066
4248
  const metaStr = sanitizedMeta ? ` ${JSON.stringify(sanitizedMeta)}` : "";
4067
4249
  const line = `[${level.toUpperCase()}]${prefix} ${ts} — ${sanitizedMsg}${metaStr}`;
4068
4250
  console.log(isColorEnabled() ? LEVEL_COLORS[level](line) : line);
4251
+ this.fileLog.log(level.toUpperCase(), this.prefix || "MMA", `${ts} — ${sanitizedMsg}${metaStr}`);
4069
4252
  const logTarget = this.sessionDir ?? this.logDir;
4070
4253
  if (logTarget) {
4071
4254
  try {
4072
- appendFileSync(join5(logTarget, "app.jsonl"), JSON.stringify({
4255
+ appendFileSync2(join6(logTarget, "app.jsonl"), JSON.stringify({
4073
4256
  level,
4074
4257
  ts,
4075
4258
  prefix: this.prefix,
@@ -4097,6 +4280,7 @@ class Logger {
4097
4280
  var import_picocolors, LEVELS, LEVEL_COLORS;
4098
4281
  var init_app_logger = __esm(() => {
4099
4282
  init_data_sanitizer();
4283
+ init_file_log();
4100
4284
  import_picocolors = __toESM(require_picocolors(), 1);
4101
4285
  LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
4102
4286
  LEVEL_COLORS = {
@@ -5125,11 +5309,18 @@ class ToolExecutor {
5125
5309
  if (ctx)
5126
5310
  this.ctx = ctx;
5127
5311
  try {
5128
- return await this.execute({ id: `redirect_${Date.now()}`, name, arguments: args });
5312
+ return await this.execute({
5313
+ id: `redirect_${Date.now()}`,
5314
+ name,
5315
+ arguments: args
5316
+ });
5129
5317
  } finally {
5130
5318
  this.ctx = prevCtx;
5131
5319
  }
5132
5320
  }
5321
+ hasTool(name) {
5322
+ return this.registry.has(name);
5323
+ }
5133
5324
  async execute(call, signal) {
5134
5325
  const tool = this.registry.get(call.name);
5135
5326
  if (!tool) {
@@ -5143,12 +5334,12 @@ class ToolExecutor {
5143
5334
  if (plugin.onBeforeTool) {
5144
5335
  try {
5145
5336
  const proceed = await plugin.onBeforeTool(this.ctx, call);
5146
- if (!proceed) {
5337
+ if (proceed === false || typeof proceed === "string") {
5338
+ const pluginName = plugin.name || plugin.constructor?.name || "unknown";
5339
+ const reason = typeof proceed === "string" ? proceed : undefined;
5147
5340
  return {
5148
5341
  success: false,
5149
- output: t("tool.blocked", {
5150
- plugin: plugin.constructor?.name || "unknown"
5151
- }),
5342
+ output: reason ? t("tool.blocked_reason", { plugin: pluginName, reason }) : t("tool.blocked", { plugin: pluginName }),
5152
5343
  toolCallId: call.id
5153
5344
  };
5154
5345
  }
@@ -5209,6 +5400,8 @@ class ToolExecutor {
5209
5400
  }
5210
5401
  }
5211
5402
  }
5403
+ this.ctx.logger.logToolCall(call.name, JSON.stringify(call.arguments)?.slice(0, 200) ?? "", result.success ? "OK" : `FAIL: ${result.output?.slice(0, 200)}`);
5404
+ this.ctx.logger.logToolOutput(call.name, result.output ?? "", result.success ? 0 : 1);
5212
5405
  this.ctx.logger.debug(`Tool ${call.name}: ${result.success ? "OK" : "FAIL"}`);
5213
5406
  return result;
5214
5407
  }
@@ -5417,8 +5610,8 @@ __export(exports_audit_notifier, {
5417
5610
  DEFAULT_AUDIT_NOTIFIER_CONFIG: () => DEFAULT_AUDIT_NOTIFIER_CONFIG,
5418
5611
  AuditNotifier: () => AuditNotifier
5419
5612
  });
5420
- import { writeFileSync as writeFileSync4, appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
5421
- import { join as join6, dirname as dirname2 } from "path";
5613
+ import { writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
5614
+ import { join as join7, dirname as dirname2 } from "path";
5422
5615
  import { homedir as homedir2 } from "os";
5423
5616
  import { readFileSync as readFileSync4 } from "fs";
5424
5617
 
@@ -5446,7 +5639,7 @@ class AuditNotifier {
5446
5639
  ensureLogDirectory() {
5447
5640
  if (this.config.filePath) {
5448
5641
  const dir = dirname2(this.config.filePath);
5449
- mkdirSync4(dir, { recursive: true });
5642
+ mkdirSync5(dir, { recursive: true });
5450
5643
  }
5451
5644
  }
5452
5645
  shouldNotify(eventType) {
@@ -5512,7 +5705,7 @@ class AuditNotifier {
5512
5705
  return;
5513
5706
  try {
5514
5707
  const line = JSON.stringify(notification);
5515
- appendFileSync2(this.config.filePath, line + `
5708
+ appendFileSync3(this.config.filePath, line + `
5516
5709
  `, "utf8");
5517
5710
  } catch (error) {
5518
5711
  console.error("[AuditNotifier] Failed to write to file:", error);
@@ -5568,7 +5761,7 @@ class AuditNotifier {
5568
5761
  this.isProcessing = false;
5569
5762
  }
5570
5763
  readNotifications(limit = 100) {
5571
- if (!this.config.filePath || !existsSync6(this.config.filePath)) {
5764
+ if (!this.config.filePath || !existsSync7(this.config.filePath)) {
5572
5765
  return [];
5573
5766
  }
5574
5767
  try {
@@ -5625,7 +5818,7 @@ var init_audit_notifier = __esm(() => {
5625
5818
  };
5626
5819
  DEFAULT_AUDIT_NOTIFIER_CONFIG = {
5627
5820
  enabled: false,
5628
- filePath: join6(homedir2(), ".mma", "logs", "audit-notifications.jsonl"),
5821
+ filePath: join7(homedir2(), ".mma", "logs", "audit-notifications.jsonl"),
5629
5822
  webhookTimeout: 5000,
5630
5823
  minSeverity: "medium",
5631
5824
  eventTypes: [
@@ -5642,26 +5835,26 @@ var init_audit_notifier = __esm(() => {
5642
5835
  });
5643
5836
 
5644
5837
  // src/modules/security/audit-log.ts
5645
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, appendFileSync as appendFileSync3 } from "fs";
5646
- import { resolve as resolve2, join as join7 } from "path";
5838
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, appendFileSync as appendFileSync4 } from "fs";
5839
+ import { resolve as resolve2, join as join8 } from "path";
5647
5840
  import { homedir as homedir3 } from "os";
5648
5841
  function getAuditDir() {
5649
5842
  return _sessionAuditDir ?? _globalAuditDir;
5650
5843
  }
5651
5844
  function setAuditSessionDir(dir) {
5652
5845
  _sessionAuditDir = dir;
5653
- if (!existsSync7(dir)) {
5654
- mkdirSync5(dir, { recursive: true, mode: 448 });
5846
+ if (!existsSync8(dir)) {
5847
+ mkdirSync6(dir, { recursive: true, mode: 448 });
5655
5848
  }
5656
5849
  }
5657
5850
  function logAudit(entry) {
5658
5851
  const dir = getAuditDir();
5659
- if (!existsSync7(dir)) {
5660
- mkdirSync5(dir, { recursive: true, mode: 448 });
5852
+ if (!existsSync8(dir)) {
5853
+ mkdirSync6(dir, { recursive: true, mode: 448 });
5661
5854
  }
5662
5855
  try {
5663
5856
  const logEntry = JSON.stringify(entry);
5664
- appendFileSync3(join7(dir, "audit.jsonl"), logEntry + `
5857
+ appendFileSync4(join8(dir, "audit.jsonl"), logEntry + `
5665
5858
  `, "utf8");
5666
5859
  } catch {}
5667
5860
  try {
@@ -5726,12 +5919,12 @@ var init_audit_log = __esm(() => {
5726
5919
 
5727
5920
  // src/tools/path-utils.ts
5728
5921
  import { resolve as resolve3, normalize as normalize2, dirname as dirname3, basename, sep } from "path";
5729
- import { existsSync as existsSync8 } from "fs";
5922
+ import { existsSync as existsSync9 } from "fs";
5730
5923
  function safeResolvePath(baseDir, userPath) {
5731
5924
  const norm = normalize2(userPath);
5732
5925
  const stripped = norm.replace(/^[/\\]/, "");
5733
5926
  const resolved = resolve3(baseDir, stripped);
5734
- if (existsSync8(resolved) || existsSync8(dirname3(resolved)))
5927
+ if (existsSync9(resolved) || existsSync9(dirname3(resolved)))
5735
5928
  return resolved;
5736
5929
  const baseNorm = normalize2(baseDir);
5737
5930
  let cur = baseNorm;
@@ -5748,11 +5941,11 @@ function safeResolvePath(baseDir, userPath) {
5748
5941
  if (afterChar && afterChar !== "\\" && afterChar !== "/") {
5749
5942
  const fixed = stripped.slice(0, afterIdx) + sep + stripped.slice(afterIdx);
5750
5943
  const fixedResolved = resolve3(baseDir, normalize2(fixed));
5751
- if (existsSync8(fixedResolved) || existsSync8(dirname3(fixedResolved))) {
5944
+ if (existsSync9(fixedResolved) || existsSync9(dirname3(fixedResolved))) {
5752
5945
  return fixedResolved;
5753
5946
  }
5754
5947
  const fromParent = resolve3(dirname3(cur), normalize2(fixed));
5755
- if (existsSync8(fromParent) || existsSync8(dirname3(fromParent))) {
5948
+ if (existsSync9(fromParent) || existsSync9(dirname3(fromParent))) {
5756
5949
  return fromParent;
5757
5950
  }
5758
5951
  }
@@ -5768,7 +5961,7 @@ var init_path_utils = () => {};
5768
5961
  var MAX_PREVIEW_LINES = 15;
5769
5962
 
5770
5963
  // src/tools/read-file.ts
5771
- import { readFileSync as readFileSync5, existsSync as existsSync9 } from "fs";
5964
+ import { readFileSync as readFileSync5, existsSync as existsSync10 } from "fs";
5772
5965
  import { extname } from "path";
5773
5966
  var DEFAULT_LIMIT, readFileTool;
5774
5967
  var init_read_file = __esm(() => {
@@ -5812,8 +6005,12 @@ var init_read_file = __esm(() => {
5812
6005
  })
5813
6006
  };
5814
6007
  }
5815
- if (!existsSync9(resolved)) {
5816
- return { success: false, output: t("file.notfound", { path }) };
6008
+ if (!existsSync10(resolved)) {
6009
+ const output = resolved !== path ? t("file.notfound_resolved", {
6010
+ path,
6011
+ resolved
6012
+ }) : t("file.notfound", { path });
6013
+ return { success: false, output };
5817
6014
  }
5818
6015
  const content = readFileSync5(resolved, "utf-8");
5819
6016
  const lines = content.split(`
@@ -5863,7 +6060,7 @@ function scanContent(content, filePath, config) {
5863
6060
  if (matches.length > 0) {
5864
6061
  return {
5865
6062
  allowed: false,
5866
- reason: `Content contains dangerous patterns: ${matches.slice(0, 3).join(", ")}`,
6063
+ reason: `Content contains dangerous patterns in ${filePath}: ${matches.slice(0, 3).join(", ")}`,
5867
6064
  matches
5868
6065
  };
5869
6066
  }
@@ -5881,32 +6078,27 @@ var init_content_scanner = __esm(() => {
5881
6078
  /require\('net'\).createServer/gi,
5882
6079
  /require\('fs'\).writeFileSync/gi,
5883
6080
  /require\('fs'\).unlinkSync/gi,
5884
- /process\.exit\(/gi,
5885
- /setInterval\(/gi,
5886
- /setTimeout\(/gi,
5887
6081
  /while\(true\)/gi,
5888
6082
  /for\(;;\)/gi,
5889
6083
  /rm -rf/gi,
5890
6084
  /dd if=/gi,
5891
6085
  /chmod 777/gi,
5892
6086
  /wget.*\|.*sh/gi,
5893
- /curl.*\|.*sh/gi,
5894
- /\$\{/gi,
5895
- /`[^`]+`/gi
6087
+ /curl.*\|.*sh/gi
5896
6088
  ];
5897
6089
  });
5898
6090
 
5899
6091
  // src/modules/security/session-isolation.ts
5900
- import { join as join8, resolve as resolve5 } from "path";
6092
+ import { join as join9, resolve as resolve5 } from "path";
5901
6093
  import { homedir as homedir4 } from "os";
5902
- import { mkdirSync as mkdirSync6, existsSync as existsSync10 } from "fs";
6094
+ import { mkdirSync as mkdirSync7, existsSync as existsSync11 } from "fs";
5903
6095
  function createSessionContext(sessionId, projectDir, isolationConfig, securityOverrides) {
5904
6096
  const config = { ...DEFAULT_SESSION_ISOLATION, ...isolationConfig };
5905
- const baseDir = config.baseDir || join8(homedir4(), ".mma", "sessions", sessionId);
5906
- const tempDir = join8(baseDir, "temp");
5907
- if (config.isolateTempFiles && !existsSync10(tempDir)) {
6097
+ const baseDir = config.baseDir || join9(homedir4(), ".mma", "sessions", sessionId);
6098
+ const tempDir = join9(baseDir, "temp");
6099
+ if (config.isolateTempFiles && !existsSync11(tempDir)) {
5908
6100
  try {
5909
- mkdirSync6(tempDir, { recursive: true, mode: 448 });
6101
+ mkdirSync7(tempDir, { recursive: true, mode: 448 });
5910
6102
  } catch {}
5911
6103
  }
5912
6104
  return {
@@ -6136,7 +6328,7 @@ var init_diff = __esm(() => {
6136
6328
  });
6137
6329
 
6138
6330
  // src/tools/write-file.ts
6139
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
6331
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8, existsSync as existsSync12, readFileSync as readFileSync6 } from "fs";
6140
6332
  import { dirname as dirname4 } from "path";
6141
6333
  var writeFileTool;
6142
6334
  var init_write_file = __esm(() => {
@@ -6183,19 +6375,21 @@ var init_write_file = __esm(() => {
6183
6375
  };
6184
6376
  }
6185
6377
  const content = String(args.content);
6186
- const scanResult = scanContent(content, path, ctx.config.security?.contentScan);
6187
- if (!scanResult.allowed) {
6188
- logSecurityBlock(ctx.sessionId, "file_write", scanResult.reason || "Content contains dangerous patterns", path);
6189
- return {
6190
- success: false,
6191
- output: `[SECURITY BLOCKED] ${scanResult.reason}`
6192
- };
6378
+ if (securityConfig?.enabled && securityConfig?.contentScan?.enabled) {
6379
+ const scanResult = scanContent(content, path, securityConfig.contentScan);
6380
+ if (!scanResult.allowed) {
6381
+ logSecurityBlock(ctx.sessionId, "file_write", scanResult.reason || "Content contains dangerous patterns", path);
6382
+ return {
6383
+ success: false,
6384
+ output: `[SECURITY BLOCKED] ${scanResult.reason}`
6385
+ };
6386
+ }
6193
6387
  }
6194
6388
  const dir = dirname4(resolved);
6195
- if (!existsSync11(dir)) {
6196
- mkdirSync7(dir, { recursive: true });
6389
+ if (!existsSync12(dir)) {
6390
+ mkdirSync8(dir, { recursive: true });
6197
6391
  }
6198
- const fileExists = existsSync11(resolved);
6392
+ const fileExists = existsSync12(resolved);
6199
6393
  let oldContent = "";
6200
6394
  if (fileExists) {
6201
6395
  oldContent = readFileSync6(resolved, "utf-8");
@@ -6266,13 +6460,15 @@ var init_edit_file = __esm(() => {
6266
6460
  };
6267
6461
  }
6268
6462
  const updated = content.replace(oldStr, newStr);
6269
- const scanResult = scanContent(updated, path, ctx.config.security?.contentScan);
6270
- if (!scanResult.allowed) {
6271
- logSecurityBlock(ctx.sessionId, "file_write", scanResult.reason || "Content contains dangerous patterns", path);
6272
- return {
6273
- success: false,
6274
- output: `[SECURITY BLOCKED] ${scanResult.reason}`
6275
- };
6463
+ if (securityConfig?.enabled && securityConfig?.contentScan?.enabled) {
6464
+ const scanResult = scanContent(updated, path, securityConfig.contentScan);
6465
+ if (!scanResult.allowed) {
6466
+ logSecurityBlock(ctx.sessionId, "file_write", scanResult.reason || "Content contains dangerous patterns", path);
6467
+ return {
6468
+ success: false,
6469
+ output: `[SECURITY BLOCKED] ${scanResult.reason}`
6470
+ };
6471
+ }
6276
6472
  }
6277
6473
  writeFileSync6(resolved, updated, "utf-8");
6278
6474
  const diff = generateDiff(content, updated);
@@ -6397,7 +6593,7 @@ var init_grep_tool = __esm(() => {
6397
6593
  });
6398
6594
 
6399
6595
  // src/tools/list-dir.ts
6400
- import { readdirSync as readdirSync3, statSync, existsSync as existsSync12 } from "fs";
6596
+ import { readdirSync as readdirSync4, statSync as statSync2, existsSync as existsSync13 } from "fs";
6401
6597
  import { resolve as resolve8 } from "path";
6402
6598
  var listDirTool;
6403
6599
  var init_list_dir = __esm(() => {
@@ -6427,13 +6623,13 @@ var init_list_dir = __esm(() => {
6427
6623
  })
6428
6624
  };
6429
6625
  }
6430
- if (!existsSync12(resolved)) {
6626
+ if (!existsSync13(resolved)) {
6431
6627
  return { success: false, output: t("file.dir_notfound", { path }) };
6432
6628
  }
6433
- const entries = readdirSync3(resolved);
6629
+ const entries = readdirSync4(resolved);
6434
6630
  const lines = entries.map((e) => {
6435
6631
  const full = resolve8(resolved, e);
6436
- return statSync(full).isDirectory() ? `${e}/` : e;
6632
+ return statSync2(full).isDirectory() ? `${e}/` : e;
6437
6633
  });
6438
6634
  if (lines.length === 0) {
6439
6635
  return { success: true, output: t("file.empty") };
@@ -6447,7 +6643,7 @@ var init_list_dir = __esm(() => {
6447
6643
  });
6448
6644
 
6449
6645
  // src/tools/create-dir.ts
6450
- import { mkdirSync as mkdirSync8, existsSync as existsSync13 } from "fs";
6646
+ import { mkdirSync as mkdirSync9, existsSync as existsSync14 } from "fs";
6451
6647
  var createDirTool;
6452
6648
  var init_create_dir = __esm(() => {
6453
6649
  init_i18n();
@@ -6489,8 +6685,8 @@ var init_create_dir = __esm(() => {
6489
6685
  })
6490
6686
  };
6491
6687
  }
6492
- if (!existsSync13(resolved)) {
6493
- mkdirSync8(resolved, { recursive: true });
6688
+ if (!existsSync14(resolved)) {
6689
+ mkdirSync9(resolved, { recursive: true });
6494
6690
  }
6495
6691
  ctx.fileOperationsCount = currentCount + 1;
6496
6692
  logFileWrite(ctx.sessionId, path, true, "Directory created");
@@ -6500,7 +6696,7 @@ var init_create_dir = __esm(() => {
6500
6696
  });
6501
6697
 
6502
6698
  // src/tools/delete-file.ts
6503
- import { unlinkSync as unlinkSync2, existsSync as existsSync14, statSync as statSync2, readFileSync as readFileSync8 } from "fs";
6699
+ import { unlinkSync as unlinkSync3, existsSync as existsSync15, statSync as statSync3, readFileSync as readFileSync8 } from "fs";
6504
6700
  var deleteFileTool;
6505
6701
  var init_delete_file = __esm(() => {
6506
6702
  init_i18n();
@@ -6543,14 +6739,14 @@ var init_delete_file = __esm(() => {
6543
6739
  })
6544
6740
  };
6545
6741
  }
6546
- if (!existsSync14(resolved)) {
6742
+ if (!existsSync15(resolved)) {
6547
6743
  return { success: false, output: t("file.notfound", { path }) };
6548
6744
  }
6549
- if (statSync2(resolved).isDirectory()) {
6745
+ if (statSync3(resolved).isDirectory()) {
6550
6746
  return { success: false, output: t("file.is_directory", { path }) };
6551
6747
  }
6552
6748
  const content = readFileSync8(resolved, "utf-8");
6553
- unlinkSync2(resolved);
6749
+ unlinkSync3(resolved);
6554
6750
  const diff = generateDeleteDiff(content);
6555
6751
  ctx.fileOperationsCount = currentCount + 1;
6556
6752
  logFileDelete(ctx.sessionId, path, true);
@@ -6560,7 +6756,7 @@ var init_delete_file = __esm(() => {
6560
6756
  });
6561
6757
 
6562
6758
  // src/tools/move-file.ts
6563
- import { renameSync, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
6759
+ import { renameSync as renameSync2, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
6564
6760
  import { resolve as resolve9, normalize as normalize3, dirname as dirname5 } from "path";
6565
6761
  var moveFileTool;
6566
6762
  var init_move_file = __esm(() => {
@@ -6618,17 +6814,17 @@ var init_move_file = __esm(() => {
6618
6814
  })
6619
6815
  };
6620
6816
  }
6621
- if (!existsSync15(fromResolved)) {
6817
+ if (!existsSync16(fromResolved)) {
6622
6818
  return {
6623
6819
  success: false,
6624
6820
  output: t("file.not_found_short", { path: fromPath })
6625
6821
  };
6626
6822
  }
6627
6823
  const toDir = dirname5(toResolved);
6628
- if (!existsSync15(toDir)) {
6629
- mkdirSync9(toDir, { recursive: true });
6824
+ if (!existsSync16(toDir)) {
6825
+ mkdirSync10(toDir, { recursive: true });
6630
6826
  }
6631
- renameSync(fromResolved, toResolved);
6827
+ renameSync2(fromResolved, toResolved);
6632
6828
  const diff = generateMoveDiff(fromPath, toPath);
6633
6829
  ctx.fileOperationsCount = currentCount + 1;
6634
6830
  logFileWrite(ctx.sessionId, `${fromPath} -> ${toPath}`, true, "File moved");
@@ -6642,7 +6838,7 @@ var init_move_file = __esm(() => {
6642
6838
  });
6643
6839
 
6644
6840
  // src/tools/file-info.ts
6645
- import { statSync as statSync3, existsSync as existsSync16 } from "fs";
6841
+ import { statSync as statSync4, existsSync as existsSync17 } from "fs";
6646
6842
  var fileInfoTool;
6647
6843
  var init_file_info = __esm(() => {
6648
6844
  init_i18n();
@@ -6671,10 +6867,10 @@ var init_file_info = __esm(() => {
6671
6867
  })
6672
6868
  };
6673
6869
  }
6674
- if (!existsSync16(resolved)) {
6870
+ if (!existsSync17(resolved)) {
6675
6871
  return { success: false, output: t("file.not_found_short", { path }) };
6676
6872
  }
6677
- const stat = statSync3(resolved);
6873
+ const stat = statSync4(resolved);
6678
6874
  return {
6679
6875
  success: true,
6680
6876
  output: JSON.stringify({
@@ -6846,17 +7042,60 @@ var init_command_validator = __esm(() => {
6846
7042
  });
6847
7043
 
6848
7044
  // src/modules/processes/registry.ts
6849
- import { spawn } from "child_process";
7045
+ import { spawn, spawnSync } from "child_process";
6850
7046
  import { platform } from "os";
7047
+ function getOemDecoder() {
7048
+ if (oemDecoder !== undefined)
7049
+ return oemDecoder;
7050
+ oemDecoder = null;
7051
+ try {
7052
+ const cpOut = spawnSync("chcp.com", {
7053
+ encoding: "buffer",
7054
+ timeout: 2000,
7055
+ windowsHide: true
7056
+ }).stdout;
7057
+ const m = cpOut ? cpOut.toString("latin1").match(/(\d+)/) : null;
7058
+ if (m) {
7059
+ try {
7060
+ oemDecoder = new TextDecoder("ibm" + m[1]);
7061
+ } catch {
7062
+ oemDecoder = null;
7063
+ }
7064
+ }
7065
+ } catch {}
7066
+ if (!oemDecoder) {
7067
+ try {
7068
+ oemDecoder = new TextDecoder("ibm866");
7069
+ } catch {
7070
+ oemDecoder = null;
7071
+ }
7072
+ }
7073
+ return oemDecoder;
7074
+ }
7075
+ function decodeLineToUtf8(buf) {
7076
+ try {
7077
+ return new TextDecoder("utf-8", { fatal: true }).decode(buf);
7078
+ } catch {}
7079
+ const oem = getOemDecoder();
7080
+ if (oem) {
7081
+ try {
7082
+ return oem.decode(buf);
7083
+ } catch {}
7084
+ }
7085
+ return buf.toString("latin1");
7086
+ }
6851
7087
  function killTree(child) {
6852
7088
  const pid = child.pid;
6853
7089
  if (!pid)
6854
7090
  return;
6855
7091
  if (platform() === "win32") {
6856
- spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
6857
- windowsHide: true,
6858
- stdio: "ignore"
6859
- });
7092
+ try {
7093
+ spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], {
7094
+ windowsHide: true,
7095
+ stdio: "ignore",
7096
+ timeout: 3000
7097
+ });
7098
+ } catch {}
6860
7099
  return;
6861
7100
  }
6862
7101
  try {
@@ -6904,20 +7143,10 @@ class ProcessRegistry {
6904
7143
  }
6905
7144
  this.children.set(id, child);
6906
7145
  entry.pid = child.pid ?? 0;
6907
- const decoder = platform() === "win32" ? (() => {
6908
- try {
6909
- const cpOut = __require("child_process").execSync("chcp.com", {
6910
- encoding: "buffer",
6911
- timeout: 2000,
6912
- windowsHide: true
6913
- }).toString("latin1");
6914
- const m = cpOut.match(/(\d+)/);
6915
- if (m)
6916
- return new TextDecoder("ibm" + m[1]);
6917
- } catch {}
6918
- return new TextDecoder("ibm866");
6919
- })() : new TextDecoder("utf-8");
6920
7146
  let buf = "";
7147
+ let byteBuf = Buffer.alloc(0);
7148
+ const isWin = platform() === "win32";
7149
+ const unixDecoder = new TextDecoder("utf-8");
6921
7150
  const pushLine = (line) => {
6922
7151
  if (entry.log.length >= MAX_LOG_LINES) {
6923
7152
  entry.log.shift();
@@ -6925,19 +7154,36 @@ class ProcessRegistry {
6925
7154
  entry.log.push(line);
6926
7155
  };
6927
7156
  const append = (chunk) => {
6928
- const decoded = decoder.decode(chunk, { stream: true });
6929
- buf += decoded;
6930
- const lines = buf.split(/\r?\n/);
6931
- buf = lines.pop() ?? "";
6932
- for (const line of lines) {
6933
- pushLine(line);
7157
+ if (!isWin) {
7158
+ const decoded = unixDecoder.decode(chunk, { stream: true });
7159
+ buf += decoded;
7160
+ const lines = buf.split(/\r?\n/);
7161
+ buf = lines.pop() ?? "";
7162
+ for (const line of lines) {
7163
+ pushLine(line);
7164
+ }
7165
+ return;
7166
+ }
7167
+ byteBuf = byteBuf.length ? Buffer.concat([byteBuf, chunk]) : Buffer.from(chunk);
7168
+ let nl;
7169
+ while ((nl = byteBuf.indexOf(10)) !== -1) {
7170
+ const raw = byteBuf.subarray(0, nl);
7171
+ byteBuf = byteBuf.subarray(nl + 1);
7172
+ pushLine(decodeLineToUtf8(raw).replace(/\r$/, ""));
6934
7173
  }
6935
7174
  };
6936
7175
  const flush = () => {
6937
- buf += decoder.decode();
6938
- if (buf) {
6939
- pushLine(buf);
6940
- buf = "";
7176
+ if (!isWin) {
7177
+ buf += unixDecoder.decode();
7178
+ if (buf) {
7179
+ pushLine(buf);
7180
+ buf = "";
7181
+ }
7182
+ return;
7183
+ }
7184
+ if (byteBuf.length) {
7185
+ pushLine(decodeLineToUtf8(byteBuf));
7186
+ byteBuf = Buffer.alloc(0);
6941
7187
  }
6942
7188
  };
6943
7189
  const resolveExit = () => {
@@ -7046,7 +7292,7 @@ class ProcessRegistry {
7046
7292
  }
7047
7293
  }
7048
7294
  }
7049
- var MAX_LOG_LINES = 2000, MAX_KEPT_PROCESSES = 20, seq = 0, processRegistry;
7295
+ var MAX_LOG_LINES = 2000, MAX_KEPT_PROCESSES = 20, seq = 0, oemDecoder, processRegistry;
7050
7296
  var init_registry = __esm(() => {
7051
7297
  processRegistry = new ProcessRegistry;
7052
7298
  });
@@ -7059,6 +7305,28 @@ var init_processes = __esm(() => {
7059
7305
 
7060
7306
  // src/tools/bash.ts
7061
7307
  import { platform as platform2 } from "os";
7308
+ function extractEchoFileWrite(command) {
7309
+ if (!/^\s*(?:echo|printf)\b/i.test(command))
7310
+ return null;
7311
+ const m = command.match(/[>»]{1,2}\s*"?([^"'\s&|]+)"?/i);
7312
+ if (!m)
7313
+ return null;
7314
+ return m[1].replace(/["'']$/g, "");
7315
+ }
7316
+ function translateSemicolonsForCmd(command) {
7317
+ let out = "";
7318
+ let inQuotes = false;
7319
+ for (let i = 0;i < command.length; i++) {
7320
+ const ch = command[i];
7321
+ if (ch === '"') {
7322
+ inQuotes = !inQuotes;
7323
+ out += ch;
7324
+ continue;
7325
+ }
7326
+ out += ch === ";" && !inQuotes ? "&" : ch;
7327
+ }
7328
+ return out;
7329
+ }
7062
7330
  function adaptCommandForWindows(command) {
7063
7331
  if (platform2() !== "win32")
7064
7332
  return command;
@@ -7077,13 +7345,15 @@ function adaptCommandForWindows(command) {
7077
7345
  if (translated && !trimmed.includes("|") && !trimmed.includes(">") && !trimmed.includes("&&") && !trimmed.includes(";")) {
7078
7346
  return trimmed.replace(firstWord, translated);
7079
7347
  }
7080
- return command;
7348
+ return translateSemicolonsForCmd(command);
7081
7349
  }
7082
7350
  function detectToolCallInBash(command) {
7083
7351
  const match = command.trim().match(/^([\w-]+)\s+(.+)$/s);
7084
7352
  if (!match)
7085
7353
  return null;
7086
7354
  const tool = match[1];
7355
+ if (NEVER_TOOL_CALLS.has(tool))
7356
+ return null;
7087
7357
  const rest = match[2].trim();
7088
7358
  if (!/^[a-z][a-z0-9_]+$/.test(tool))
7089
7359
  return null;
@@ -7110,7 +7380,60 @@ function parseToolArgs(raw) {
7110
7380
  }
7111
7381
  return args;
7112
7382
  }
7113
- var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, bashGraceMs, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, bashTool;
7383
+ function detectTestResults(output) {
7384
+ if (!output)
7385
+ return null;
7386
+ const jest = output.match(/Tests:\s+(\d+)\s+passed,\s*(\d+)\s+failed/i);
7387
+ if (jest) {
7388
+ return { framework: "jest", passed: +jest[1], failed: +jest[2] };
7389
+ }
7390
+ const mochaPass = output.match(/(\d+)\s+passing/i);
7391
+ const mochaFail = output.match(/(\d+)\s+failing/i);
7392
+ if (mochaPass || mochaFail) {
7393
+ return {
7394
+ framework: "mocha",
7395
+ passed: mochaPass ? +mochaPass[1] : 0,
7396
+ failed: mochaFail ? +mochaFail[1] : 0
7397
+ };
7398
+ }
7399
+ const pytest = output.match(/(\d+)\s+passed[^\n]*?(?:,\s*(\d+)\s+failed)?/i);
7400
+ if (pytest && /pytest|passed|failed/i.test(output) && /(=====|short test summary|tests\s+ok)/i.test(output) === false && /pytest|collect/i.test(output)) {
7401
+ return {
7402
+ framework: "pytest",
7403
+ passed: +pytest[1],
7404
+ failed: pytest[2] ? +pytest[2] : 0
7405
+ };
7406
+ }
7407
+ const failMarkers = output.match(/\(fail\)/g)?.length ?? 0;
7408
+ const passMarkers = output.match(/\(pass\)/g)?.length ?? 0;
7409
+ const xMarkers = output.match(/\s×\s/g)?.length ?? 0;
7410
+ const summaryMatch = output.match(/^\s*(\d+)\s+pass[^\n]*$/m);
7411
+ const failSummary = output.match(/^\s*(\d+)\s+fail[^\n]*$/m);
7412
+ const ran = output.match(/(?:Ran|ran)\s+\d+\s+tests/i);
7413
+ if (failMarkers > 0 || passMarkers > 0 || xMarkers > 0 || ran) {
7414
+ const failed = Math.max(failMarkers + xMarkers, failSummary ? +failSummary[1] : 0);
7415
+ const passed = Math.max(passMarkers, summaryMatch ? +summaryMatch[1] : 0);
7416
+ return {
7417
+ framework: ran ? "bun/vitest" : "vitest",
7418
+ passed,
7419
+ failed,
7420
+ summary: summaryMatch?.[0] ?? failSummary?.[0]
7421
+ };
7422
+ }
7423
+ return null;
7424
+ }
7425
+ function emptyCliRunHint(command, output, code) {
7426
+ if (code !== 0 || output.trim())
7427
+ return null;
7428
+ if (/[>|]/.test(command))
7429
+ return null;
7430
+ if (/\b(bun test|vitest|pytest|jest|mocha|--test)\b/i.test(command))
7431
+ return null;
7432
+ if (!CLI_FILE_RUN_RE.test(command))
7433
+ return null;
7434
+ return "the command exited 0 but printed NOTHING to stdout. If this should run a CLI program, the file probably has no entry point: read it with read_file and check the code actually calls its main function with command-line arguments (e.g. main(process.argv[2])) and prints results with console.log.";
7435
+ }
7436
+ var BASH_GRACE_MS = 5000, SPAWN_SETTLE_MS = 100, bashGraceMs, UNIX_TO_WIN_HINTS, UNIX_TO_WIN_TRANSLATE, NEVER_TOOL_CALLS, CLI_FILE_RUN_RE, bashTool;
7114
7437
  var init_bash = __esm(() => {
7115
7438
  init_command_validator();
7116
7439
  init_audit_log();
@@ -7137,17 +7460,48 @@ var init_bash = __esm(() => {
7137
7460
  which: 'Use "where" instead.',
7138
7461
  echo: "echo works on Windows, but avoid pipes (|).",
7139
7462
  "Get-Content": "Use the read_file tool instead.",
7140
- "Select-Object": "Use the read_file tool with offset/limit instead."
7463
+ "Select-Object": "Use the read_file tool with offset/limit instead.",
7464
+ "Write-Host": "PowerShell cmdlet — this shell is cmd.exe. Print with plain echo instead.",
7465
+ "Select-String": "Use the grep tool instead.",
7466
+ "Out-File": "Write files with the write_file tool instead.",
7467
+ "Set-Content": "Write files with the write_file tool instead.",
7468
+ "Get-ChildItem": "Use the list_dir tool instead.",
7469
+ "Remove-Item": "Use the delete_file tool instead."
7141
7470
  };
7142
7471
  UNIX_TO_WIN_TRANSLATE = {
7143
- ls: "Get-ChildItem",
7144
- pwd: "Get-Location",
7145
- cat: "Get-Content",
7146
- wc: "@(Get-Content).Count"
7472
+ ls: "dir",
7473
+ pwd: "cd",
7474
+ cat: "type"
7147
7475
  };
7476
+ NEVER_TOOL_CALLS = new Set([
7477
+ "echo",
7478
+ "cat",
7479
+ "type",
7480
+ "printf",
7481
+ "touch",
7482
+ "mkdir",
7483
+ "cp",
7484
+ "mv",
7485
+ "rm",
7486
+ "ls",
7487
+ "dir",
7488
+ "cd",
7489
+ "pwd",
7490
+ "grep",
7491
+ "find",
7492
+ "head",
7493
+ "tail",
7494
+ "wc",
7495
+ "chmod",
7496
+ "sed",
7497
+ "awk"
7498
+ ]);
7499
+ CLI_FILE_RUN_RE = /\b(bun|node|deno|python|python3|tsx|ts-node|php|ruby|go\s+run)\S*\s+(run\s+)?["']?[\w./\\-]+\.(ts|js|tsx|jsx|mjs|cjs|py)\b/;
7148
7500
  bashTool = {
7149
7501
  name: "bash",
7150
- description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).",
7502
+ description: `Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).
7503
+
7504
+ Windows notes: the shell is cmd.exe — PowerShell cmdlets (Write-Host, Get-Content, Select-String, Out-File) and bash heredocs (cat << EOF) do NOT work there. Use the read_file/write_file tools instead of cat/echo redirection. Sequential commands: use && (a leading ; is auto-converted to &).`,
7151
7505
  tags: ["shell", "code"],
7152
7506
  parameters: {
7153
7507
  type: "object",
@@ -7167,7 +7521,7 @@ var init_bash = __esm(() => {
7167
7521
  handler: async (ctx, args) => {
7168
7522
  const originalCommand = String(args.command);
7169
7523
  const toolCall = detectToolCallInBash(originalCommand);
7170
- if (toolCall && toolCall.tool !== "bash" && ctx.toolExecutor) {
7524
+ if (toolCall && toolCall.tool !== "bash" && ctx.toolExecutor && ctx.toolExecutor.hasTool(toolCall.tool)) {
7171
7525
  const redirected = await ctx.toolExecutor.executeByName(toolCall.tool, parseToolArgs(toolCall.args), ctx);
7172
7526
  return {
7173
7527
  success: redirected.success,
@@ -7176,6 +7530,15 @@ ${redirected.output}`
7176
7530
  };
7177
7531
  }
7178
7532
  const command = adaptCommandForWindows(originalCommand);
7533
+ if (platform2() === "win32") {
7534
+ const echoWrite = extractEchoFileWrite(originalCommand);
7535
+ if (echoWrite) {
7536
+ return {
7537
+ success: false,
7538
+ output: t("bash.echo_write_blocked", { path: echoWrite })
7539
+ };
7540
+ }
7541
+ }
7179
7542
  const workdir = args.workdir ? String(args.workdir) : ctx.baseDir;
7180
7543
  const appConfig = ctx.config || {};
7181
7544
  const fullSecurityConfig = ctx.sessionContext ? getSessionSecurityConfig(appConfig, ctx.sessionContext) : appConfig.security || DEFAULT_SECURITY_CONFIG;
@@ -7211,6 +7574,22 @@ Hint: check the "workdir" path exists and the command is valid for this OS.`
7211
7574
  let output2 = entry.log.join(`
7212
7575
  `);
7213
7576
  processRegistry.remove(entry.id);
7577
+ const cliHint = emptyCliRunHint(command, output2, code);
7578
+ if (cliHint) {
7579
+ output2 = `(exit code 0, no output)
7580
+
7581
+ Hint: ${cliHint}`;
7582
+ }
7583
+ const testRun = detectTestResults(output2);
7584
+ if (testRun && testRun.failed > 0) {
7585
+ output2 = `[test-runner] ${testRun.framework}: ${testRun.failed} test(s) FAILING, ${testRun.passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.
7586
+
7587
+ ` + output2;
7588
+ } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
7589
+ output2 = `[test-runner] ${testRun.framework}: all ${testRun.passed} test(s) passing.
7590
+
7591
+ ${output2}`;
7592
+ }
7214
7593
  if (!output2 && code !== 0) {
7215
7594
  output2 = `(exit code ${code})`;
7216
7595
  }
@@ -7228,7 +7607,14 @@ Hint: "${firstWord}" may not work on Windows. ${hint}`;
7228
7607
  }
7229
7608
  const lines = output2.split(`
7230
7609
  `);
7231
- if (lines.length > MAX_PREVIEW_LINES) {
7610
+ if (testRun && testRun.failed > 0) {
7611
+ const TEST_TAIL_LINES = 400;
7612
+ const kept = lines.slice(-TEST_TAIL_LINES);
7613
+ const skipped = lines.length - kept.length;
7614
+ output2 = (skipped > 0 ? `[... ${skipped} earlier lines omitted — failing tests below]
7615
+ ` : "") + kept.join(`
7616
+ `);
7617
+ } else if (lines.length > MAX_PREVIEW_LINES) {
7232
7618
  output2 = lines.slice(0, MAX_PREVIEW_LINES).join(`
7233
7619
  `) + `
7234
7620
  ... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
@@ -7453,6 +7839,7 @@ class SessionLogger {
7453
7839
  const dir = this.session.getSessionDirectory(meta.id);
7454
7840
  if (this.logger) {
7455
7841
  this.logger.setSessionDir(dir);
7842
+ this.logger.initSessionLog(meta.id);
7456
7843
  }
7457
7844
  setAuditSessionDir(dir);
7458
7845
  }
@@ -8726,6 +9113,9 @@ class StuckDetector {
8726
9113
  escalationThreshold = 3;
8727
9114
  fileRewriteCount = new Map;
8728
9115
  fileRewriteThreshold = 3;
9116
+ lastBashCommand = "";
9117
+ lastBashOutput = "";
9118
+ emptyBashRunCount = 0;
8729
9119
  constructor(threshold = 6, errorThreshold = 3) {
8730
9120
  this.threshold = threshold;
8731
9121
  this.errorThreshold = errorThreshold;
@@ -8754,6 +9144,22 @@ class StuckDetector {
8754
9144
  getLastErrorOutput() {
8755
9145
  return this.lastErrorOutput;
8756
9146
  }
9147
+ recordBashOutput(command, output) {
9148
+ const trimmed = output.trim();
9149
+ if (command === this.lastBashCommand && !trimmed) {
9150
+ this.emptyBashRunCount++;
9151
+ } else {
9152
+ this.emptyBashRunCount = 0;
9153
+ }
9154
+ this.lastBashCommand = command;
9155
+ this.lastBashOutput = output;
9156
+ }
9157
+ getLastBashCommand() {
9158
+ return this.lastBashCommand;
9159
+ }
9160
+ getLastBashOutput() {
9161
+ return this.lastBashOutput;
9162
+ }
8757
9163
  getLastFailedTool() {
8758
9164
  return this.lastFailedTool;
8759
9165
  }
@@ -8784,15 +9190,26 @@ class StuckDetector {
8784
9190
  if (this.recentToolCalls.length < this.repetitionThreshold)
8785
9191
  return false;
8786
9192
  const last = this.recentToolCalls[this.recentToolCalls.length - 1];
8787
- let count = 1;
9193
+ let consecutiveCount = 1;
8788
9194
  for (let i = this.recentToolCalls.length - 2;i >= 0; i--) {
8789
9195
  if (this.recentToolCalls[i].name === last.name && this.recentToolCalls[i].argsKey === last.argsKey) {
8790
- count++;
9196
+ consecutiveCount++;
8791
9197
  } else {
8792
9198
  break;
8793
9199
  }
8794
9200
  }
8795
- return count >= this.repetitionThreshold;
9201
+ if (consecutiveCount >= this.repetitionThreshold)
9202
+ return true;
9203
+ const counts = new Map;
9204
+ for (const call of this.recentToolCalls) {
9205
+ const key = `${call.name}::${call.argsKey}`;
9206
+ counts.set(key, (counts.get(key) || 0) + 1);
9207
+ }
9208
+ for (const count of counts.values()) {
9209
+ if (count >= this.repetitionThreshold)
9210
+ return true;
9211
+ }
9212
+ return false;
8796
9213
  }
8797
9214
  hasConsecutiveFailures() {
8798
9215
  return this.consecutiveFailures >= this.errorThreshold;
@@ -8838,6 +9255,10 @@ class StuckDetector {
8838
9255
  getActionableHints() {
8839
9256
  const hints = [];
8840
9257
  const error = this.lastErrorOutput;
9258
+ const isScriptRun = /(^|[\s&])(bun|node|tsx|ts-node|deno|python|python3)\S*\s+[^|&]+\s+[^\s]+$/.test(this.lastBashCommand);
9259
+ if (isScriptRun && !this.lastBashOutput.trim() && this.emptyBashRunCount >= 2) {
9260
+ hints.push(`The command "${this.lastBashCommand}" ran ${this.emptyBashRunCount} times with EMPTY output. The program likely has no entry point — read the file with read_file and check that it actually calls its main function with process.argv / CLI arguments and prints results (console.log). Then run it again.`);
9261
+ }
8841
9262
  if (!error)
8842
9263
  return hints;
8843
9264
  if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
@@ -8867,6 +9288,18 @@ class StuckDetector {
8867
9288
  const file = this.getExcessiveRewriteFile();
8868
9289
  hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
8869
9290
  }
9291
+ if (/Write-Host|Get-Content|Select-String|Out-File|Set-Content/i.test(error)) {
9292
+ hints.push("That looks like a PowerShell cmdlet — the shell here is cmd.exe. Use echo/type for output or the read_file/write_file tools instead.");
9293
+ }
9294
+ if (/Bun\.\w+ is not a function|Bun\.\w+ is not defined|Bun\.\w+ is not a constructor/i.test(error)) {
9295
+ hints.push("That Bun API does not exist. Verify the API name in the Bun docs — common ones are Bun.file, Bun.write, Bun.spawn, Bun.serve. For file checks use fs.existsSync from node:fs.");
9296
+ }
9297
+ if (/error TS\d+|typecheck failed|syntax check failed/i.test(error)) {
9298
+ hints.push("The file still has a type/syntax error (see the error line in the output). Read the file with read_file around the reported line, fix the actual error, then re-run — rewriting the whole file blindly usually makes it worse.");
9299
+ }
9300
+ if (/unexpected.*<<|Непредвиденное появление/i.test(error)) {
9301
+ hints.push("Heredoc (<< EOF) is a bash feature — this shell is cmd.exe and does not support it. Write the file with write_file instead.");
9302
+ }
8870
9303
  return hints;
8871
9304
  }
8872
9305
  getToolAlternative() {
@@ -8969,6 +9402,9 @@ class StuckDetector {
8969
9402
  this.recentToolCalls = [];
8970
9403
  this.fileRewriteCount.clear();
8971
9404
  this.escalationCount = 0;
9405
+ this.lastBashCommand = "";
9406
+ this.lastBashOutput = "";
9407
+ this.emptyBashRunCount = 0;
8972
9408
  }
8973
9409
  resetStepState(stepId) {
8974
9410
  this.currentStepId = stepId;
@@ -8979,7 +9415,9 @@ class StuckDetector {
8979
9415
  this.lastErrorOutput = "";
8980
9416
  this.recentToolCalls = [];
8981
9417
  this.fileRewriteCount.clear();
8982
- this.escalationCount = 0;
9418
+ this.lastBashCommand = "";
9419
+ this.lastBashOutput = "";
9420
+ this.emptyBashRunCount = 0;
8983
9421
  }
8984
9422
  }
8985
9423
  var init_stuck_detector = __esm(() => {
@@ -9263,7 +9701,7 @@ var init_moe_executor = __esm(() => {
9263
9701
  });
9264
9702
 
9265
9703
  // src/modules/execution/verifier.ts
9266
- import { existsSync as existsSync17 } from "fs";
9704
+ import { existsSync as existsSync18 } from "fs";
9267
9705
  import { resolve as resolve10, extname as extname2 } from "path";
9268
9706
  import { spawn as spawn2 } from "child_process";
9269
9707
 
@@ -9274,7 +9712,7 @@ class StepVerifier {
9274
9712
  }
9275
9713
  async checkFileExists(path) {
9276
9714
  const resolved = resolve10(this.baseDir, path);
9277
- const exists = existsSync17(resolved);
9715
+ const exists = existsSync18(resolved);
9278
9716
  return {
9279
9717
  passed: exists,
9280
9718
  message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path })
@@ -9290,7 +9728,7 @@ class StepVerifier {
9290
9728
  }
9291
9729
  async runTypeCheck() {
9292
9730
  const tsconfigPath = resolve10(this.baseDir, "tsconfig.json");
9293
- if (!existsSync17(tsconfigPath)) {
9731
+ if (!existsSync18(tsconfigPath)) {
9294
9732
  return { passed: true, message: "No tsconfig.json found — skipping type check" };
9295
9733
  }
9296
9734
  try {
@@ -9303,7 +9741,7 @@ class StepVerifier {
9303
9741
  }
9304
9742
  async runTests() {
9305
9743
  const pkgPath = resolve10(this.baseDir, "package.json");
9306
- if (!existsSync17(pkgPath)) {
9744
+ if (!existsSync18(pkgPath)) {
9307
9745
  return { passed: true, message: "No package.json found — skipping tests" };
9308
9746
  }
9309
9747
  try {
@@ -9553,8 +9991,8 @@ var init_agent_moe = __esm(() => {
9553
9991
  });
9554
9992
 
9555
9993
  // src/modules/memory/search.ts
9556
- import { readFileSync as readFileSync9, existsSync as existsSync18 } from "fs";
9557
- import { join as join9 } from "path";
9994
+ import { readFileSync as readFileSync9, existsSync as existsSync19 } from "fs";
9995
+ import { join as join10 } from "path";
9558
9996
 
9559
9997
  class MemorySearch {
9560
9998
  memoryDir;
@@ -9565,8 +10003,8 @@ class MemorySearch {
9565
10003
  const results = [];
9566
10004
  const lowerQuery = query.toLowerCase();
9567
10005
  for (const name of MEMORY_FILES) {
9568
- const path = join9(this.memoryDir, `${name}.md`);
9569
- if (!existsSync18(path))
10006
+ const path = join10(this.memoryDir, `${name}.md`);
10007
+ if (!existsSync19(path))
9570
10008
  continue;
9571
10009
  const content = readFileSync9(path, "utf-8");
9572
10010
  const lines = content.split(`
@@ -9577,8 +10015,8 @@ class MemorySearch {
9577
10015
  }
9578
10016
  }
9579
10017
  }
9580
- const prefsPath = join9(this.memoryDir, "preferences.json");
9581
- if (existsSync18(prefsPath)) {
10018
+ const prefsPath = join10(this.memoryDir, "preferences.json");
10019
+ if (existsSync19(prefsPath)) {
9582
10020
  try {
9583
10021
  const prefs = JSON.parse(readFileSync9(prefsPath, "utf-8"));
9584
10022
  for (const [key, value] of Object.entries(prefs)) {
@@ -9598,8 +10036,8 @@ var init_search = __esm(() => {
9598
10036
  });
9599
10037
 
9600
10038
  // src/modules/memory/store.ts
9601
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, appendFileSync as appendFileSync4, existsSync as existsSync19, mkdirSync as mkdirSync10 } from "fs";
9602
- import { join as join10 } from "path";
10039
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, appendFileSync as appendFileSync5, existsSync as existsSync20, mkdirSync as mkdirSync11 } from "fs";
10040
+ import { join as join11 } from "path";
9603
10041
 
9604
10042
  class MemoryStore {
9605
10043
  memoryDir;
@@ -9607,8 +10045,8 @@ class MemoryStore {
9607
10045
  this.memoryDir = memoryDir;
9608
10046
  this.ensureDir();
9609
10047
  for (const name of MEMORY_FILES2) {
9610
- const path = join10(this.memoryDir, `${name}.md`);
9611
- if (!existsSync19(path)) {
10048
+ const path = join11(this.memoryDir, `${name}.md`);
10049
+ if (!existsSync20(path)) {
9612
10050
  writeFileSync7(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
9613
10051
 
9614
10052
  `, "utf-8");
@@ -9616,33 +10054,33 @@ class MemoryStore {
9616
10054
  }
9617
10055
  }
9618
10056
  ensureDir() {
9619
- if (!existsSync19(this.memoryDir)) {
9620
- mkdirSync10(this.memoryDir, { recursive: true });
10057
+ if (!existsSync20(this.memoryDir)) {
10058
+ mkdirSync11(this.memoryDir, { recursive: true });
9621
10059
  }
9622
10060
  }
9623
10061
  read(name) {
9624
- const path = join10(this.memoryDir, `${name}.md`);
9625
- if (!existsSync19(path))
10062
+ const path = join11(this.memoryDir, `${name}.md`);
10063
+ if (!existsSync20(path))
9626
10064
  return "";
9627
10065
  return readFileSync10(path, "utf-8");
9628
10066
  }
9629
10067
  append(name, entry) {
9630
- const path = join10(this.memoryDir, `${name}.md`);
10068
+ const path = join11(this.memoryDir, `${name}.md`);
9631
10069
  const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
9632
10070
  const formatted = `- **${timestamp}** — ${entry}
9633
10071
  `;
9634
- appendFileSync4(path, formatted, "utf-8");
10072
+ appendFileSync5(path, formatted, "utf-8");
9635
10073
  }
9636
10074
  search(query) {
9637
10075
  const searchModule = new MemorySearch(this.memoryDir);
9638
10076
  return searchModule.query(query);
9639
10077
  }
9640
10078
  prefsPath() {
9641
- return join10(this.memoryDir, "preferences.json");
10079
+ return join11(this.memoryDir, "preferences.json");
9642
10080
  }
9643
10081
  getPreferences() {
9644
10082
  const path = this.prefsPath();
9645
- if (!existsSync19(path))
10083
+ if (!existsSync20(path))
9646
10084
  return {};
9647
10085
  try {
9648
10086
  return JSON.parse(readFileSync10(path, "utf-8"));
@@ -9677,7 +10115,7 @@ var init_store = __esm(() => {
9677
10115
  });
9678
10116
 
9679
10117
  // src/core/agent.ts
9680
- import { join as join11 } from "path";
10118
+ import { join as join12 } from "path";
9681
10119
  function isToolCallJson(text) {
9682
10120
  const trimmed = text.trim();
9683
10121
  if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
@@ -9716,12 +10154,14 @@ class Agent {
9716
10154
  if (dynamic.length > 0) {
9717
10155
  builder.addBlocks(dynamic);
9718
10156
  }
9719
- const pluginBlocks = (this.deps.pluginManager.runOnBuildPrompt?.() ?? []).filter((s) => Boolean(s && s.trim() !== "")).map((content) => ({
9720
- content,
9721
- priority: "low",
9722
- essential: false,
9723
- estimatedTokens: this.deps.llmProvider.countTokens(content)
9724
- }));
10157
+ const pluginBlocks = (this.deps.pluginManager.runOnBuildPrompt?.() ?? []).flatMap((content) => content && content.trim() !== "" ? [
10158
+ {
10159
+ content,
10160
+ priority: "low",
10161
+ essential: false,
10162
+ estimatedTokens: this.deps.llmProvider.countTokens(content)
10163
+ }
10164
+ ] : []);
9725
10165
  if (pluginBlocks.length > 0) {
9726
10166
  builder.addBlocks(pluginBlocks);
9727
10167
  }
@@ -9732,6 +10172,18 @@ class Agent {
9732
10172
  const tokenCount = this.deps.llmProvider.countTokens(prompt);
9733
10173
  return { text: prompt, tokenCount, excluded };
9734
10174
  }
10175
+ resolveUsageTokens(apiPromptTokens, apiCompletionTokens, estimatedPromptTokens, completionChars) {
10176
+ if (apiPromptTokens > 0 || apiCompletionTokens > 0) {
10177
+ return {
10178
+ prompt: apiPromptTokens,
10179
+ completion: apiCompletionTokens,
10180
+ total: apiPromptTokens + apiCompletionTokens
10181
+ };
10182
+ }
10183
+ const prompt = Math.max(1, estimatedPromptTokens);
10184
+ const completion = Math.max(0, Math.ceil(completionChars / 4));
10185
+ return { prompt, completion, total: prompt + completion };
10186
+ }
9735
10187
  refreshSystemPrompt() {
9736
10188
  const { prompt } = this.buildSystemPrompt();
9737
10189
  const current = this.deps.contextManager.getActiveHistory().find((m) => m.role === "system");
@@ -9830,11 +10282,20 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9830
10282
  let lastToolSignature = "";
9831
10283
  let apiPromptTokens = 0;
9832
10284
  let apiCompletionTokens = 0;
10285
+ let apiCompletionChars = 0;
9833
10286
  const MAX_HALLUCINATION_RETRIES = 3;
9834
10287
  let consecutiveToolFailures = 0;
9835
10288
  const MAX_CONSECUTIVE_TOOL_FAILURES = 5;
9836
10289
  let auditRetries = 0;
9837
10290
  const MAX_AUDIT_RETRIES = 3;
10291
+ let emptyResponseRetries = 0;
10292
+ const MAX_EMPTY_RESPONSE_RETRIES = 2;
10293
+ let emptyResponseExhausted = false;
10294
+ let repeatedToolCount = 0;
10295
+ const MAX_REPEATED_TOOL_CALLS = 2;
10296
+ const allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
10297
+ const toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
10298
+ contextManager.setToolTokens(toolTokenEstimate);
9838
10299
  while (iteration < config.maxToolIterations && !this.shutdownRequested) {
9839
10300
  iteration++;
9840
10301
  contextManager.noteIteration();
@@ -9868,8 +10329,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9868
10329
  }
9869
10330
  this.refreshSystemPrompt();
9870
10331
  const history = contextManager.getActiveHistory();
9871
- const allTools = toolExecutor.getToolDefinitions(this.deps.toolTags);
9872
- slog.logToolDefs(allTools.length, allTools.map((t2) => t2.name), iteration);
10332
+ slog.logToolDefs(allToolsForBudget.length, allToolsForBudget.map((t2) => t2.name), iteration);
9873
10333
  let textContent = "";
9874
10334
  let reasoningContent = "";
9875
10335
  const toolCalls = [];
@@ -9877,8 +10337,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9877
10337
  let emittedReasoning = false;
9878
10338
  const textChunks = [];
9879
10339
  this.emitPhase(iteration, "thinking", onPhase);
10340
+ const llmStart = Date.now();
10341
+ logger.logLLMRequest(config.model, history.length, input, "agent");
9880
10342
  try {
9881
- for await (const chunk of llmProvider.chat(history, allTools, this.abortController?.signal)) {
10343
+ for await (const chunk of llmProvider.chat(history, allToolsForBudget, this.abortController?.signal)) {
9882
10344
  if (this.shutdownRequested)
9883
10345
  break;
9884
10346
  if (chunk.type === "text" && chunk.content) {
@@ -9924,6 +10386,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9924
10386
  logger.info("LLM call aborted (interrupt)");
9925
10387
  break;
9926
10388
  }
10389
+ logger.logLLMResponse(config.model, textContent.length, Date.now() - llmStart, err.message, "agent");
9927
10390
  logger.error(`LLM call failed: ${err.message}`);
9928
10391
  slog.logError(err.message);
9929
10392
  pluginManager.runOnError({ iteration, logger }, err);
@@ -9936,6 +10399,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9936
10399
  } finally {
9937
10400
  this.emitPhase(iteration, "done", onPhase);
9938
10401
  }
10402
+ apiCompletionChars += (textContent || reasoningContent).length;
10403
+ logger.logLLMResponse(config.model, (textContent || reasoningContent).length, Date.now() - llmStart, undefined, "agent");
9939
10404
  if (this.shutdownRequested) {
9940
10405
  break;
9941
10406
  }
@@ -9959,8 +10424,15 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
9959
10424
  if (this.deps.exitOnComplete && sawToolCall) {
9960
10425
  const signature = toolCalls.map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`).join("|");
9961
10426
  if (signature && signature === lastToolSignature) {
9962
- logger.debug("Exit-on-complete: repeated identical tool call, stopping");
9963
- break;
10427
+ repeatedToolCount++;
10428
+ if (repeatedToolCount >= MAX_REPEATED_TOOL_CALLS) {
10429
+ logger.debug("Exit-on-complete: repeated identical tool call, stopping");
10430
+ break;
10431
+ }
10432
+ contextManager.addMessage({
10433
+ role: "user",
10434
+ content: `<system-summary>You just called the same tool with identical arguments. If the task is done, answer with a final text response NOW. If the command failed, try a different approach.</system-summary>`
10435
+ });
9964
10436
  }
9965
10437
  lastToolSignature = signature;
9966
10438
  }
@@ -10070,7 +10542,7 @@ ${taskReminder}</system-summary>`
10070
10542
  if (sessionManager) {
10071
10543
  const activeSession = sessionManager.getActiveMeta();
10072
10544
  if (activeSession) {
10073
- const memDir = join11(baseDir, ".mma", "memory");
10545
+ const memDir = join12(baseDir, ".mma", "memory");
10074
10546
  const memStore = new MemoryStore(memDir);
10075
10547
  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");
10076
10548
  }
@@ -10134,8 +10606,9 @@ ${warnLine}
10134
10606
  }
10135
10607
  }
10136
10608
  if (hallucinationResult.status === "retry") {
10137
- if (this.deps.exitOnComplete) {
10609
+ if (this.deps.exitOnComplete && textContent?.trim()) {
10138
10610
  logger.debug("Exit-on-complete: stopping on first response");
10611
+ lastText = textContent;
10139
10612
  break;
10140
10613
  }
10141
10614
  if (hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
@@ -10165,7 +10638,9 @@ ${warnLine}
10165
10638
  }
10166
10639
  if (textContent) {
10167
10640
  contextManager.addMessage({ role: "assistant", content: textContent });
10168
- slog.saveAssistantMessage(textContent);
10641
+ if (config.session.autoSave) {
10642
+ slog.saveAssistantMessage(textContent);
10643
+ }
10169
10644
  slog.logAssistant(textContent, reasoningContent, undefined, iteration);
10170
10645
  }
10171
10646
  lastText = textContent;
@@ -10178,6 +10653,19 @@ ${warnLine}
10178
10653
  }
10179
10654
  }
10180
10655
  if (!sawToolCall) {
10656
+ if (!textContent?.trim()) {
10657
+ if (emptyResponseRetries < MAX_EMPTY_RESPONSE_RETRIES) {
10658
+ emptyResponseRetries++;
10659
+ logger.warn(`Empty response on iteration ${iteration} (retry ${emptyResponseRetries}/${MAX_EMPTY_RESPONSE_RETRIES})`);
10660
+ contextManager.addMessage({
10661
+ role: "user",
10662
+ content: `<system-summary>Your previous response was empty. Answer the user's task now with a final text response or call a tool. Do not reply with reasoning only.</system-summary>`
10663
+ });
10664
+ continue;
10665
+ }
10666
+ emptyResponseExhausted = true;
10667
+ logger.warn(`Empty response retries exhausted after ${MAX_EMPTY_RESPONSE_RETRIES} attempts`);
10668
+ }
10181
10669
  if (this.deps.finalAudit) {
10182
10670
  const audit = await this.deps.finalAudit();
10183
10671
  if (audit && !audit.passed) {
@@ -10204,6 +10692,7 @@ ${warnLine}
10204
10692
  }
10205
10693
  const tokensUsed = contextManager.getEstimatedTokens();
10206
10694
  const budget = contextManager.getBudget();
10695
+ const usageTokens = this.resolveUsageTokens(apiPromptTokens, apiCompletionTokens, tokensUsed, apiCompletionChars);
10207
10696
  if (iteration >= config.maxToolIterations) {
10208
10697
  return {
10209
10698
  success: false,
@@ -10212,22 +10701,23 @@ ${warnLine}
10212
10701
  iterationCount: iteration,
10213
10702
  contextUsed: tokensUsed,
10214
10703
  contextLimit: budget.history,
10215
- promptTokens: apiPromptTokens,
10216
- completionTokens: apiCompletionTokens,
10217
- totalTokens: apiPromptTokens + apiCompletionTokens,
10704
+ promptTokens: usageTokens.prompt,
10705
+ completionTokens: usageTokens.completion,
10706
+ totalTokens: usageTokens.total,
10218
10707
  compactionCount: contextManager.getCompactionCount(),
10219
10708
  contextQuality: contextManager.getQuality()
10220
10709
  };
10221
10710
  }
10222
10711
  return {
10223
- success: true,
10712
+ success: emptyResponseExhausted ? false : true,
10224
10713
  text: lastText,
10714
+ error: emptyResponseExhausted ? t("error.empty_response") : undefined,
10225
10715
  iterationCount: iteration,
10226
10716
  contextUsed: tokensUsed,
10227
10717
  contextLimit: budget.history,
10228
- promptTokens: apiPromptTokens,
10229
- completionTokens: apiCompletionTokens,
10230
- totalTokens: apiPromptTokens + apiCompletionTokens,
10718
+ promptTokens: usageTokens.prompt,
10719
+ completionTokens: usageTokens.completion,
10720
+ totalTokens: usageTokens.total,
10231
10721
  compactionCount: contextManager.getCompactionCount(),
10232
10722
  contextQuality: contextManager.getQuality()
10233
10723
  };
@@ -10279,6 +10769,7 @@ ${warnLine}
10279
10769
  if (killed > 0) {
10280
10770
  logger.info(`Killed ${killed} background process(es) on shutdown`);
10281
10771
  }
10772
+ logger.closeSessionLog();
10282
10773
  pluginManager.runOnSessionEnd({
10283
10774
  logger,
10284
10775
  sessionManager: sessionManager?.getActiveMeta()
@@ -10349,8 +10840,10 @@ class ContextManager {
10349
10840
  fileFacts = [];
10350
10841
  decisionFacts = [];
10351
10842
  errorFacts = [];
10843
+ static MAX_FACTS = 20;
10352
10844
  tokenCounter;
10353
10845
  pendingImageParts = [];
10846
+ toolTokens = 0;
10354
10847
  onCompact = null;
10355
10848
  constructor(contextWindow, contextBudget, tokenCounter) {
10356
10849
  this.contextWindow = contextWindow;
@@ -10505,10 +10998,16 @@ ${lines.join(`
10505
10998
  content: `<system-summary>${this.compactedBlock}</system-summary>`
10506
10999
  };
10507
11000
  const firstSystem = this.messages.find((m) => m.role === "system");
11001
+ const freshRecent = recentTurns.filter((m) => {
11002
+ if (m.role !== "user")
11003
+ return true;
11004
+ const text = getMessageText(m.content);
11005
+ return !text.startsWith("<system-summary>");
11006
+ });
10508
11007
  this.messages = [
10509
11008
  ...firstSystem ? [firstSystem] : [],
10510
11009
  summary,
10511
- ...recentTurns
11010
+ ...freshRecent
10512
11011
  ];
10513
11012
  this.iterationsSinceCompaction = 0;
10514
11013
  if (this.onCompact) {
@@ -10521,19 +11020,22 @@ ${lines.join(`
10521
11020
  /Файл (?:создан|обновлён|записан|удалён|перемещён):? ([\w./\\-]+\.[a-z]+)/gi,
10522
11021
  /file (?:created|updated|written|deleted|moved):? ([\w./\\-]+\.[a-z]+)/gi
10523
11022
  ];
11023
+ const newFiles = [];
11024
+ const newDecisions = [];
11025
+ const newErrors = [];
10524
11026
  for (const msg of turns) {
10525
11027
  const content = getMessageText(msg.content);
10526
11028
  if (msg.role === "tool") {
10527
11029
  for (const pattern of filePatterns) {
10528
11030
  for (const match of content.matchAll(pattern)) {
10529
- this.fileFacts.push(match[1]);
11031
+ newFiles.push(match[1]);
10530
11032
  }
10531
11033
  }
10532
11034
  if (content.includes("Plan:") && content.includes("[")) {
10533
11035
  const planLine = content.split(`
10534
11036
  `).find((line) => line.includes("Plan:"));
10535
11037
  if (planLine)
10536
- this.decisionFacts.push(planLine.trim());
11038
+ newDecisions.push(planLine.trim());
10537
11039
  }
10538
11040
  }
10539
11041
  if (msg.role === "assistant") {
@@ -10541,16 +11043,20 @@ ${lines.join(`
10541
11043
  const line = content.split(`
10542
11044
  `).find((l) => l.includes("decided:") || l.includes("decision:"));
10543
11045
  if (line)
10544
- this.decisionFacts.push(line.trim().slice(0, 200));
11046
+ newDecisions.push(line.trim().slice(0, 200));
10545
11047
  }
10546
11048
  }
10547
11049
  if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
10548
11050
  const line = content.split(`
10549
11051
  `).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
10550
11052
  if (line)
10551
- this.errorFacts.push(line.trim().slice(0, 250));
11053
+ newErrors.push(line.trim().slice(0, 250));
10552
11054
  }
10553
11055
  }
11056
+ const dedup = (arr) => [...new Set(arr)];
11057
+ this.fileFacts = dedup([...this.fileFacts, ...newFiles]).slice(-ContextManager.MAX_FACTS);
11058
+ this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-ContextManager.MAX_FACTS);
11059
+ this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-ContextManager.MAX_FACTS);
10554
11060
  }
10555
11061
  updateSystemPrompt(content) {
10556
11062
  const idx = this.messages.findIndex((m) => m.role === "system");
@@ -10571,7 +11077,10 @@ ${lines.join(`
10571
11077
  this.peakTokens = 0;
10572
11078
  }
10573
11079
  getEstimatedTokens() {
10574
- return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0);
11080
+ return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0) + this.toolTokens;
11081
+ }
11082
+ setToolTokens(tokens) {
11083
+ this.toolTokens = tokens;
10575
11084
  }
10576
11085
  resize(contextWindow, contextBudget, tokenCounter) {
10577
11086
  this.contextWindow = contextWindow;
@@ -10668,15 +11177,83 @@ var init_confidence = __esm(() => {
10668
11177
  init_i18n();
10669
11178
  });
10670
11179
 
11180
+ // src/modules/hallucination/js-identifiers.ts
11181
+ function isJsMemberAccess(candidate) {
11182
+ const lower = candidate.toLowerCase();
11183
+ const m = lower.match(/^([\w-]+)\./);
11184
+ return m !== null && JS_GLOBALS.has(m[1]);
11185
+ }
11186
+ var JS_GLOBALS;
11187
+ var init_js_identifiers = __esm(() => {
11188
+ JS_GLOBALS = new Set([
11189
+ "process",
11190
+ "console",
11191
+ "math",
11192
+ "json",
11193
+ "global",
11194
+ "globalthis",
11195
+ "window",
11196
+ "document",
11197
+ "buffer",
11198
+ "module",
11199
+ "require",
11200
+ "exports",
11201
+ "url",
11202
+ "promise",
11203
+ "array",
11204
+ "object",
11205
+ "string",
11206
+ "number",
11207
+ "boolean",
11208
+ "symbol",
11209
+ "date",
11210
+ "regexp",
11211
+ "error",
11212
+ "map",
11213
+ "set",
11214
+ "weakmap",
11215
+ "weakset",
11216
+ "proxy",
11217
+ "reflect",
11218
+ "intl",
11219
+ "crypto",
11220
+ "performance",
11221
+ "fetch",
11222
+ "navigator",
11223
+ "location",
11224
+ "settimeout",
11225
+ "setinterval",
11226
+ "cleartimeout",
11227
+ "clearinterval",
11228
+ "queuemicrotask",
11229
+ "structuredclone",
11230
+ "textencoder",
11231
+ "textdecoder",
11232
+ "atomics",
11233
+ "sharedarraybuffer",
11234
+ "dataview",
11235
+ "arraybuffer",
11236
+ "bigint",
11237
+ "infinity",
11238
+ "nan",
11239
+ "undefined",
11240
+ "bun",
11241
+ "deno",
11242
+ "node"
11243
+ ]);
11244
+ });
11245
+
10671
11246
  // src/modules/hallucination/factual.ts
10672
- import { existsSync as existsSync20, readdirSync as readdirSync4 } from "fs";
10673
- import { resolve as resolve11, isAbsolute, join as join12 } from "path";
11247
+ import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
11248
+ import { resolve as resolve11, isAbsolute, join as join13 } from "path";
10674
11249
  function looksLikeFilePath(s) {
10675
11250
  const lower = s.toLowerCase();
10676
11251
  if (TECH_NAMES.has(lower))
10677
11252
  return false;
10678
11253
  if (!s.includes("/") && !s.includes("\\") && !s.match(/^\w+\.[a-z]{2,4}$/i))
10679
11254
  return false;
11255
+ if (isJsMemberAccess(s))
11256
+ return false;
10680
11257
  const ext = s.split(".").pop()?.toLowerCase() || "";
10681
11258
  return !NON_FILE_EXTENSIONS.has(ext);
10682
11259
  }
@@ -10714,14 +11291,14 @@ class FactualCheck {
10714
11291
  }
10715
11292
  pathExists(fp) {
10716
11293
  if (isAbsolute(fp))
10717
- return existsSync20(fp);
11294
+ return existsSync21(fp);
10718
11295
  if (!fp.includes("/") && !fp.includes("\\")) {
10719
11296
  return this.bareNameExists(fp);
10720
11297
  }
10721
- return existsSync20(resolve11(this.baseDir, fp));
11298
+ return existsSync21(resolve11(this.baseDir, fp));
10722
11299
  }
10723
11300
  bareNameExists(name) {
10724
- if (existsSync20(resolve11(this.baseDir, name)))
11301
+ if (existsSync21(resolve11(this.baseDir, name)))
10725
11302
  return true;
10726
11303
  if (this.indexHas(name))
10727
11304
  return true;
@@ -10754,14 +11331,14 @@ class FactualCheck {
10754
11331
  return count;
10755
11332
  let entries;
10756
11333
  try {
10757
- entries = readdirSync4(dir, { withFileTypes: true });
11334
+ entries = readdirSync5(dir, { withFileTypes: true });
10758
11335
  } catch {
10759
11336
  return count;
10760
11337
  }
10761
11338
  for (const entry of entries) {
10762
11339
  if (count >= FactualCheck.MAX_INDEXED_FILES)
10763
11340
  break;
10764
- const full = join12(dir, entry.name);
11341
+ const full = join13(dir, entry.name);
10765
11342
  if (entry.isDirectory()) {
10766
11343
  if (!IGNORED_DIRS.has(entry.name)) {
10767
11344
  count = this.scanDir(full, index, count);
@@ -10786,6 +11363,7 @@ class FactualCheck {
10786
11363
  var IGNORED_DIRS, NON_FILE_EXTENSIONS, TECH_NAMES;
10787
11364
  var init_factual = __esm(() => {
10788
11365
  init_i18n();
11366
+ init_js_identifiers();
10789
11367
  IGNORED_DIRS = new Set([
10790
11368
  "node_modules",
10791
11369
  ".git",
@@ -12388,7 +12966,7 @@ ${JSON.stringify(result, null, 2)}`
12388
12966
 
12389
12967
  // src/tools/search-history.ts
12390
12968
  import * as fs from "fs";
12391
- import { join as join13 } from "path";
12969
+ import { join as join14 } from "path";
12392
12970
  import { homedir as homedir5 } from "os";
12393
12971
  function searchFile(filePath, query, maxResults, results) {
12394
12972
  if (!fs.existsSync(filePath))
@@ -12432,7 +13010,7 @@ var init_search_history = __esm(() => {
12432
13010
  const query = String(args.query || "").toLowerCase();
12433
13011
  const maxResults = Number(args.maxResults) || 5;
12434
13012
  const sessionId = args.sessionId ? String(args.sessionId) : null;
12435
- const sessionDir = join13(homedir5(), ".mma", "sessions");
13013
+ const sessionDir = join14(homedir5(), ".mma", "sessions");
12436
13014
  const results = [];
12437
13015
  try {
12438
13016
  if (!fs.existsSync(sessionDir)) {
@@ -12447,7 +13025,7 @@ var init_search_history = __esm(() => {
12447
13025
  continue;
12448
13026
  if (sessionId && entry.name !== sessionId)
12449
13027
  continue;
12450
- const historyFile = join13(sessionDir, entry.name, "history.jsonl");
13028
+ const historyFile = join14(sessionDir, entry.name, "history.jsonl");
12451
13029
  searchFile(historyFile, query, maxResults, results);
12452
13030
  if (results.length >= maxResults)
12453
13031
  break;
@@ -12475,7 +13053,7 @@ var init_search_history = __esm(() => {
12475
13053
 
12476
13054
  // src/tools/remember.ts
12477
13055
  import { homedir as homedir6 } from "os";
12478
- import { join as join14 } from "path";
13056
+ import { join as join15 } from "path";
12479
13057
  var CATEGORIES, rememberTool;
12480
13058
  var init_remember = __esm(() => {
12481
13059
  init_i18n();
@@ -12513,7 +13091,7 @@ var init_remember = __esm(() => {
12513
13091
  if (!CATEGORIES.includes(category)) {
12514
13092
  return { success: false, output: t("tool.invalid_params") };
12515
13093
  }
12516
- const memoryDir = join14(homedir6(), ".mma", "memory");
13094
+ const memoryDir = join15(homedir6(), ".mma", "memory");
12517
13095
  const store = new MemoryStore(memoryDir);
12518
13096
  try {
12519
13097
  if (category === "preferences") {
@@ -12546,7 +13124,7 @@ var init_remember = __esm(() => {
12546
13124
 
12547
13125
  // src/tools/recall.ts
12548
13126
  import { homedir as homedir7 } from "os";
12549
- import { join as join15 } from "path";
13127
+ import { join as join16 } from "path";
12550
13128
  function formatAll(store) {
12551
13129
  const parts = [];
12552
13130
  const prefs = store.getPreferences();
@@ -12629,7 +13207,7 @@ var init_recall = __esm(() => {
12629
13207
  handler: async (_ctx, args) => {
12630
13208
  const query = args.query ? String(args.query) : "";
12631
13209
  const category = args.category ? String(args.category) : "";
12632
- const memoryDir = join15(homedir7(), ".mma", "memory");
13210
+ const memoryDir = join16(homedir7(), ".mma", "memory");
12633
13211
  const store = new MemoryStore(memoryDir);
12634
13212
  try {
12635
13213
  if (!query && !category) {
@@ -12825,15 +13403,15 @@ function buildIndexInjectionScript() {
12825
13403
 
12826
13404
  // src/modules/browser/cookie-store.ts
12827
13405
  import { readFile, writeFile, mkdir } from "fs/promises";
12828
- import { join as join16 } from "path";
13406
+ import { join as join17 } from "path";
12829
13407
 
12830
13408
  class CookieStore {
12831
13409
  filePath;
12832
13410
  constructor(cookieDir) {
12833
- this.filePath = join16(cookieDir, "cookies.json");
13411
+ this.filePath = join17(cookieDir, "cookies.json");
12834
13412
  }
12835
13413
  async save(cookies) {
12836
- await mkdir(join16(this.filePath, ".."), { recursive: true });
13414
+ await mkdir(join17(this.filePath, ".."), { recursive: true });
12837
13415
  await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
12838
13416
  }
12839
13417
  async load() {
@@ -12851,7 +13429,9 @@ class CookieStore {
12851
13429
  var init_cookie_store = () => {};
12852
13430
 
12853
13431
  // src/modules/browser/session.ts
12854
- import { chromium } from "playwright";
13432
+ import {
13433
+ chromium
13434
+ } from "playwright";
12855
13435
 
12856
13436
  class BrowserActionTracker {
12857
13437
  history = [];
@@ -12866,7 +13446,10 @@ class BrowserActionTracker {
12866
13446
  const recent = this.history.slice(-this.threshold);
12867
13447
  const allSame = recent.every((h) => h.action === recent[0].action && h.url === recent[0].url && JSON.stringify(h.args) === JSON.stringify(recent[0].args));
12868
13448
  if (allSame) {
12869
- return t("browser.repeated_action", { action, threshold: this.threshold });
13449
+ return t("browser.repeated_action", {
13450
+ action,
13451
+ threshold: this.threshold
13452
+ });
12870
13453
  }
12871
13454
  return null;
12872
13455
  }
@@ -12881,7 +13464,12 @@ class BrowserSession {
12881
13464
  page = null;
12882
13465
  config;
12883
13466
  cookieStore;
12884
- state = { isOpen: false, url: null, title: null, elementCount: 0 };
13467
+ state = {
13468
+ isOpen: false,
13469
+ url: null,
13470
+ title: null,
13471
+ elementCount: 0
13472
+ };
12885
13473
  actionTracker = new BrowserActionTracker(3);
12886
13474
  constructor(config) {
12887
13475
  this.config = config;
@@ -12925,7 +13513,10 @@ class BrowserSession {
12925
13513
  result = await this.wait(Number(args.ms || 1000));
12926
13514
  break;
12927
13515
  default:
12928
- return { success: false, output: t("browser.unknown_action", { action }) };
13516
+ return {
13517
+ success: false,
13518
+ output: t("browser.unknown_action", { action })
13519
+ };
12929
13520
  }
12930
13521
  if (action !== "open") {
12931
13522
  const warning = this.actionTracker.record(action, args, this.page?.url() || "");
@@ -12937,7 +13528,10 @@ class BrowserSession {
12937
13528
  }
12938
13529
  return result;
12939
13530
  } catch (err) {
12940
- return { success: false, output: t("browser.error", { message: err.message }) };
13531
+ return {
13532
+ success: false,
13533
+ output: t("browser.error", { message: err.message })
13534
+ };
12941
13535
  }
12942
13536
  }
12943
13537
  async ensurePage() {
@@ -12951,7 +13545,10 @@ class BrowserSession {
12951
13545
  return;
12952
13546
  this.browser = await chromium.launch({ headless: this.config.headless });
12953
13547
  this.context = await this.browser.newContext({
12954
- viewport: { width: this.config.viewportWidth, height: this.config.viewportHeight }
13548
+ viewport: {
13549
+ width: this.config.viewportWidth,
13550
+ height: this.config.viewportHeight
13551
+ }
12955
13552
  });
12956
13553
  const savedCookies = await this.cookieStore.load();
12957
13554
  if (savedCookies.length > 0) {
@@ -12990,17 +13587,24 @@ class BrowserSession {
12990
13587
  async open(url) {
12991
13588
  if (!url)
12992
13589
  return { success: false, output: t("browser.url_required") };
12993
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
12994
- url = "https://" + url;
13590
+ if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("file://")) {
13591
+ const isLocalhost = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/i.test(url);
13592
+ url = isLocalhost ? "http://" + url : "https://" + url;
12995
13593
  }
12996
13594
  await this.launch();
12997
13595
  if (!this.page)
12998
13596
  return { success: false, output: t("browser.create_page_failed") };
12999
13597
  this.actionTracker.reset();
13000
13598
  try {
13001
- await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: this.config.navigationTimeout });
13599
+ await this.page.goto(url, {
13600
+ waitUntil: "domcontentloaded",
13601
+ timeout: this.config.navigationTimeout
13602
+ });
13002
13603
  } catch (err) {
13003
- return { success: false, output: t("browser.nav_failed", { message: err.message }) };
13604
+ return {
13605
+ success: false,
13606
+ output: t("browser.nav_failed", { message: err.message })
13607
+ };
13004
13608
  }
13005
13609
  const snapshot = await this.takeSnapshot();
13006
13610
  await this.saveCookies();
@@ -13024,7 +13628,10 @@ class BrowserSession {
13024
13628
  await this.saveCookies();
13025
13629
  return { success: true, output: snapshot };
13026
13630
  } catch (err2) {
13027
- return { success: false, output: t("browser.click_failed", { message: err2.message }) };
13631
+ return {
13632
+ success: false,
13633
+ output: t("browser.click_failed", { message: err2.message })
13634
+ };
13028
13635
  }
13029
13636
  }
13030
13637
  async type(target, text) {
@@ -13045,7 +13652,10 @@ class BrowserSession {
13045
13652
  const snapshot = await this.takeSnapshot();
13046
13653
  return { success: true, output: snapshot };
13047
13654
  } catch (err2) {
13048
- return { success: false, output: t("browser.type_failed", { message: err2.message }) };
13655
+ return {
13656
+ success: false,
13657
+ output: t("browser.type_failed", { message: err2.message })
13658
+ };
13049
13659
  }
13050
13660
  }
13051
13661
  async scroll(direction) {
@@ -13064,7 +13674,10 @@ class BrowserSession {
13064
13674
  const snapshot = await this.takeSnapshot();
13065
13675
  return { success: true, output: snapshot };
13066
13676
  } catch (err2) {
13067
- return { success: false, output: t("browser.scroll_failed", { message: err2.message }) };
13677
+ return {
13678
+ success: false,
13679
+ output: t("browser.scroll_failed", { message: err2.message })
13680
+ };
13068
13681
  }
13069
13682
  }
13070
13683
  async back() {
@@ -13073,7 +13686,10 @@ class BrowserSession {
13073
13686
  return err;
13074
13687
  if (!this.page)
13075
13688
  return { success: false, output: "No page" };
13076
- await this.page.goBack({ waitUntil: "domcontentloaded", timeout: this.config.navigationTimeout }).catch(() => {});
13689
+ await this.page.goBack({
13690
+ waitUntil: "domcontentloaded",
13691
+ timeout: this.config.navigationTimeout
13692
+ }).catch(() => {});
13077
13693
  await new Promise((r) => setTimeout(r, 300));
13078
13694
  const snapshot = await this.takeSnapshot();
13079
13695
  return { success: true, output: snapshot };
@@ -13084,7 +13700,10 @@ class BrowserSession {
13084
13700
  return err;
13085
13701
  if (!this.page)
13086
13702
  return { success: false, output: "No page" };
13087
- await this.page.goForward({ waitUntil: "domcontentloaded", timeout: this.config.navigationTimeout }).catch(() => {});
13703
+ await this.page.goForward({
13704
+ waitUntil: "domcontentloaded",
13705
+ timeout: this.config.navigationTimeout
13706
+ }).catch(() => {});
13088
13707
  await new Promise((r) => setTimeout(r, 300));
13089
13708
  const snapshot = await this.takeSnapshot();
13090
13709
  return { success: true, output: snapshot };
@@ -13153,10 +13772,10 @@ var init_types = __esm(() => {
13153
13772
  });
13154
13773
 
13155
13774
  // src/tools/browser.ts
13156
- import { join as join17 } from "path";
13775
+ import { join as join18 } from "path";
13157
13776
  function getSession(ctx) {
13158
13777
  if (!session) {
13159
- const cookieDir = join17(ctx.baseDir, ".mma", "browser");
13778
+ const cookieDir = join18(ctx.baseDir, ".mma", "browser");
13160
13779
  session = new BrowserSession({
13161
13780
  ...DEFAULT_BROWSER_CONFIG,
13162
13781
  headless: ctx.config.browser?.headless ?? true,
@@ -13285,9 +13904,9 @@ async function readClipboardImage() {
13285
13904
  async function readClipboardFallback() {
13286
13905
  const { platform: platform3 } = await import("os");
13287
13906
  const { execSync } = await import("child_process");
13288
- const { readFileSync: readFileSync13, unlinkSync: unlinkSync3 } = await import("fs");
13289
- const { join: join18 } = await import("path");
13290
- const tmpPath = join18(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
13907
+ const { readFileSync: readFileSync13, unlinkSync: unlinkSync4 } = await import("fs");
13908
+ const { join: join19 } = await import("path");
13909
+ const tmpPath = join19(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
13291
13910
  try {
13292
13911
  if (platform3() === "linux") {
13293
13912
  execSync(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
@@ -13295,11 +13914,11 @@ async function readClipboardFallback() {
13295
13914
  return null;
13296
13915
  }
13297
13916
  const buf = readFileSync13(tmpPath);
13298
- unlinkSync3(tmpPath);
13917
+ unlinkSync4(tmpPath);
13299
13918
  return buf.length > 0 ? buf : null;
13300
13919
  } catch {
13301
13920
  try {
13302
- unlinkSync3(tmpPath);
13921
+ unlinkSync4(tmpPath);
13303
13922
  } catch {}
13304
13923
  return null;
13305
13924
  }
@@ -13365,7 +13984,7 @@ var init_image_utils = __esm(() => {
13365
13984
  });
13366
13985
 
13367
13986
  // src/tools/attach-image.ts
13368
- import { existsSync as existsSync22 } from "fs";
13987
+ import { existsSync as existsSync23 } from "fs";
13369
13988
  import { resolve as resolve12 } from "path";
13370
13989
  var attachImageTool;
13371
13990
  var init_attach_image = __esm(() => {
@@ -13408,7 +14027,7 @@ var init_attach_image = __esm(() => {
13408
14027
  dataUrl = result.dataUrl;
13409
14028
  } else {
13410
14029
  const absPath = resolve12(ctx.baseDir, source);
13411
- if (!existsSync22(absPath)) {
14030
+ if (!existsSync23(absPath)) {
13412
14031
  return {
13413
14032
  success: false,
13414
14033
  output: t("image.not_found", { path: source })
@@ -13564,17 +14183,17 @@ class ModuleRegistry {
13564
14183
  }
13565
14184
 
13566
14185
  // src/modules/plugins/loader.ts
13567
- import { readdirSync as readdirSync6, existsSync as existsSync23, statSync as statSync4 } from "fs";
13568
- import { join as join18 } from "path";
14186
+ import { readdirSync as readdirSync7, existsSync as existsSync24, statSync as statSync5 } from "fs";
14187
+ import { join as join19 } from "path";
13569
14188
 
13570
14189
  class PluginLoader {
13571
14190
  loadFromDir(dirPath, pluginManager, logger) {
13572
- if (!existsSync23(dirPath))
14191
+ if (!existsSync24(dirPath))
13573
14192
  return;
13574
- const entries = readdirSync6(dirPath);
14193
+ const entries = readdirSync7(dirPath);
13575
14194
  for (const entry of entries) {
13576
- const fullPath = join18(dirPath, entry);
13577
- if (!statSync4(fullPath).isFile())
14195
+ const fullPath = join19(dirPath, entry);
14196
+ if (!statSync5(fullPath).isFile())
13578
14197
  continue;
13579
14198
  if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
13580
14199
  continue;
@@ -13597,9 +14216,16 @@ var init_loader = __esm(() => {
13597
14216
 
13598
14217
  // src/modules/plugins/builtin/lint-on-write.ts
13599
14218
  import { spawn as spawn4, execSync } from "child_process";
13600
- import { existsSync as existsSync24, readFileSync as readFileSync13 } from "fs";
13601
- import { resolve as resolve13, extname as extname4, join as join19 } from "path";
14219
+ import { existsSync as existsSync25, readFileSync as readFileSync13 } from "fs";
14220
+ import { resolve as resolve13, extname as extname4, join as join20 } from "path";
13602
14221
  import { platform as platform3 } from "os";
14222
+ function contentHash(content) {
14223
+ let h = 5381;
14224
+ for (let i = 0;i < content.length; i++) {
14225
+ h = (h << 5) + h + content.charCodeAt(i) | 0;
14226
+ }
14227
+ return String(h);
14228
+ }
13603
14229
  function getWinDecoder() {
13604
14230
  if (_winDecoder === undefined) {
13605
14231
  if (platform3() !== "win32") {
@@ -13637,7 +14263,7 @@ class LintOnWritePlugin {
13637
14263
  if (!path)
13638
14264
  return;
13639
14265
  const fullPath = resolve13(ctx.baseDir, path);
13640
- if (!existsSync24(fullPath))
14266
+ if (!existsSync25(fullPath))
13641
14267
  return;
13642
14268
  const ext = extname4(fullPath);
13643
14269
  const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
@@ -13652,20 +14278,30 @@ class LintOnWritePlugin {
13652
14278
  }
13653
14279
  async checkSyntax(filePath, ext, baseDir) {
13654
14280
  if (ext === ".ts" || ext === ".tsx") {
14281
+ let content = "";
13655
14282
  try {
13656
- await runAsync(`npx tsc --noEmit --skipLibCheck "${filePath}"`, baseDir, 1e4);
14283
+ content = readFileSync13(filePath, "utf-8");
14284
+ } catch {
14285
+ return null;
14286
+ }
14287
+ const hash = contentHash(content);
14288
+ const cached = syntaxCache.get(filePath);
14289
+ if (cached && cached.hash === hash) {
14290
+ return cached.error;
14291
+ }
14292
+ try {
14293
+ await runAsync(`bun build --no-bundle --target=bun "${filePath}"`, baseDir, 1e4);
14294
+ syntaxCache.set(filePath, { hash, error: null });
13657
14295
  return null;
13658
14296
  } catch (err) {
13659
14297
  if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
13660
14298
  return null;
13661
14299
  }
13662
14300
  const stderr = err.stderr?.toString() || err.stdout?.toString() || "";
13663
- if (stderr.includes("error TS")) {
13664
- const firstError = stderr.split(`
13665
- `).find((line) => line.includes("error TS")) || "TypeScript syntax error";
13666
- return firstError.trim();
13667
- }
13668
- return null;
14301
+ const firstError = stderr.split(`
14302
+ `).find((line) => line.trim()) || "TypeScript syntax error";
14303
+ syntaxCache.set(filePath, { hash, error: firstError.trim() });
14304
+ return firstError.trim();
13669
14305
  }
13670
14306
  }
13671
14307
  if (ext === ".js" || ext === ".jsx") {
@@ -13683,8 +14319,8 @@ class LintOnWritePlugin {
13683
14319
  }
13684
14320
  async runProjectLint(ctx, result) {
13685
14321
  try {
13686
- const packageJsonPath = join19(ctx.baseDir, "package.json");
13687
- if (!existsSync24(packageJsonPath)) {
14322
+ const packageJsonPath = join20(ctx.baseDir, "package.json");
14323
+ if (!existsSync25(packageJsonPath)) {
13688
14324
  return;
13689
14325
  }
13690
14326
  const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf-8"));
@@ -13703,8 +14339,8 @@ class LintOnWritePlugin {
13703
14339
  }
13704
14340
  }
13705
14341
  async runProjectTypeCheck(ctx, result) {
13706
- const tsconfigPath = join19(ctx.baseDir, "tsconfig.json");
13707
- if (!existsSync24(tsconfigPath)) {
14342
+ const tsconfigPath = join20(ctx.baseDir, "tsconfig.json");
14343
+ if (!existsSync25(tsconfigPath)) {
13708
14344
  return;
13709
14345
  }
13710
14346
  const now = Date.now();
@@ -13785,8 +14421,9 @@ function runAsync(command, cwd, timeoutMs) {
13785
14421
  });
13786
14422
  });
13787
14423
  }
13788
- var TYPE_CHECK_DEBOUNCE_MS = 2000, _winDecoder, plugin;
14424
+ var TYPE_CHECK_DEBOUNCE_MS = 2000, syntaxCache, _winDecoder, plugin;
13789
14425
  var init_lint_on_write = __esm(() => {
14426
+ syntaxCache = new Map;
13790
14427
  plugin = new LintOnWritePlugin;
13791
14428
  });
13792
14429
 
@@ -13847,6 +14484,25 @@ class PlanCreator {
13847
14484
  baseDir
13848
14485
  };
13849
14486
  }
14487
+ static replan(plan, newSteps, title) {
14488
+ const kept = plan.steps.filter((s) => s.status === "done" || s.status === "skipped");
14489
+ const baseTitle = plan.title.replace(/^\[\d+[^]]*\]\s*/, "");
14490
+ const newTitle = title || baseTitle;
14491
+ const totalSteps = kept.length + newSteps.length;
14492
+ const added = newSteps.map((desc, i) => ({
14493
+ id: kept.length + i + 1,
14494
+ description: desc,
14495
+ status: "pending"
14496
+ }));
14497
+ return {
14498
+ id: plan.id,
14499
+ title: `[${totalSteps}] ${newTitle}`,
14500
+ steps: [...kept, ...added],
14501
+ createdAt: plan.createdAt,
14502
+ baseDir: plan.baseDir,
14503
+ name: plan.name
14504
+ };
14505
+ }
13850
14506
  static toPromptBlock(plan, currentStepIndex) {
13851
14507
  const date = plan.createdAt.slice(0, 10);
13852
14508
  const lines = [
@@ -13919,15 +14575,89 @@ class PlanTracker {
13919
14575
  const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
13920
14576
  return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
13921
14577
  }
13922
- toPromptBlock() {
13923
- return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
14578
+ toPromptBlock() {
14579
+ return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
14580
+ }
14581
+ }
14582
+ var init_tracker = () => {};
14583
+
14584
+ // src/modules/execution/auditor.ts
14585
+ import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
14586
+ import { resolve as resolve14, join as join21 } from "path";
14587
+ function findTestFile(dir, depth = 0) {
14588
+ if (depth > 5)
14589
+ return null;
14590
+ let entries;
14591
+ try {
14592
+ entries = readdirSync8(dir, { withFileTypes: true });
14593
+ } catch {
14594
+ return null;
14595
+ }
14596
+ for (const e of entries) {
14597
+ const full = join21(dir, e.name);
14598
+ if (e.isDirectory()) {
14599
+ if (SKIP_DIRS.has(e.name))
14600
+ continue;
14601
+ const found = findTestFile(full, depth + 1);
14602
+ if (found)
14603
+ return found;
14604
+ } else if (TEST_EXT_RE.test(e.name)) {
14605
+ return full;
14606
+ }
14607
+ }
14608
+ return null;
14609
+ }
14610
+ function hasTestStep(plan) {
14611
+ return plan.steps.some((s) => TEST_STEP_RE.test(s.description));
14612
+ }
14613
+ function extractFailingNames(output, limit = 5) {
14614
+ const names = [];
14615
+ for (const m of output.matchAll(/\(fail\)\s*([^\n]+)/g)) {
14616
+ const name = m[1].trim();
14617
+ if (name && !names.includes(name))
14618
+ names.push(name);
14619
+ if (names.length >= limit)
14620
+ break;
14621
+ }
14622
+ return names;
14623
+ }
14624
+ async function runTests(baseDir) {
14625
+ const entry = processRegistry.start("bun test", baseDir);
14626
+ const exited = await processRegistry.waitForExit(entry.id, 90000);
14627
+ const output = entry.log.join(`
14628
+ `);
14629
+ processRegistry.remove(entry.id);
14630
+ if (!exited) {
14631
+ return {
14632
+ checked: true,
14633
+ passed: true,
14634
+ failed: 0,
14635
+ passedCount: 0,
14636
+ detail: "test run timed out after 90s — result unknown",
14637
+ command: "bun test"
14638
+ };
14639
+ }
14640
+ const run = detectTestResults(output);
14641
+ if (!run) {
14642
+ return {
14643
+ checked: true,
14644
+ passed: entry.exitCode === 0,
14645
+ failed: entry.exitCode === 0 ? 0 : -1,
14646
+ passedCount: 0,
14647
+ detail: output.slice(0, 200).trim(),
14648
+ command: "bun test"
14649
+ };
13924
14650
  }
14651
+ const names = extractFailingNames(output);
14652
+ return {
14653
+ checked: true,
14654
+ passed: run.failed === 0,
14655
+ failed: run.failed,
14656
+ passedCount: run.passed,
14657
+ detail: names.length ? names.join("; ") : run.summary || `${run.failed} failed / ${run.passed} passed`,
14658
+ command: "bun test"
14659
+ };
13925
14660
  }
13926
- var init_tracker = () => {};
13927
-
13928
- // src/modules/execution/auditor.ts
13929
- import { existsSync as existsSync25 } from "fs";
13930
- import { resolve as resolve14 } from "path";
13931
14661
 
13932
14662
  class Auditor {
13933
14663
  baseDir;
@@ -13937,29 +14667,58 @@ class Auditor {
13937
14667
  async audit(plan) {
13938
14668
  const allStepText = plan.steps.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
13939
14669
  const fileMatches = allStepText.match(/\b[\w./-]+\.[a-z]+/gi) || [];
13940
- const uniqueFiles = [...new Set(fileMatches)];
14670
+ const uniqueFiles = [
14671
+ ...new Set(fileMatches.filter((f) => !isJsMemberAccess(f)))
14672
+ ];
13941
14673
  const missingFiles = [];
13942
14674
  const existingFiles = [];
13943
14675
  for (const filePath of uniqueFiles) {
13944
14676
  const resolved = resolve14(this.baseDir, filePath);
13945
- if (existsSync25(resolved)) {
14677
+ if (existsSync26(resolved)) {
13946
14678
  existingFiles.push(filePath);
13947
14679
  } else {
13948
14680
  missingFiles.push(filePath);
13949
14681
  }
13950
14682
  }
14683
+ let testRun = null;
14684
+ if (hasTestStep(plan) && findTestFile(this.baseDir)) {
14685
+ try {
14686
+ testRun = await runTests(this.baseDir);
14687
+ } catch {
14688
+ testRun = null;
14689
+ }
14690
+ }
13951
14691
  const doneSteps = plan.steps.filter((s) => s.status === "done").length;
13952
14692
  const totalSteps = plan.steps.length;
13953
14693
  let massEditWarning = null;
13954
14694
  if (uniqueFiles.length > MASS_EDIT_THRESHOLD) {
13955
- massEditWarning = t("exec.mass_edit_warning", { count: String(uniqueFiles.length) });
14695
+ massEditWarning = t("exec.mass_edit_warning", {
14696
+ count: String(uniqueFiles.length)
14697
+ });
13956
14698
  }
13957
- const passed = missingFiles.length === 0;
14699
+ const testsFailing = testRun !== null && testRun.failed > 0;
14700
+ const passed = missingFiles.length === 0 && !testsFailing;
13958
14701
  let summary;
13959
- if (passed) {
13960
- summary = t("exec.audit_pass", { done: doneSteps, total: totalSteps, files: existingFiles.length });
14702
+ if (missingFiles.length > 0) {
14703
+ summary = t("exec.audit_fail", {
14704
+ done: doneSteps,
14705
+ total: totalSteps,
14706
+ files: missingFiles.length
14707
+ });
14708
+ } else if (testsFailing) {
14709
+ summary = t("exec.audit_fail_tests", {
14710
+ done: doneSteps,
14711
+ total: totalSteps,
14712
+ failed: String(testRun.failed),
14713
+ passed: String(testRun.passedCount),
14714
+ detail: testRun.detail
14715
+ });
13961
14716
  } else {
13962
- summary = t("exec.audit_fail", { done: doneSteps, total: totalSteps, files: missingFiles.length });
14717
+ summary = t("exec.audit_pass", {
14718
+ done: doneSteps,
14719
+ total: totalSteps,
14720
+ files: existingFiles.length
14721
+ });
13963
14722
  }
13964
14723
  return {
13965
14724
  passed,
@@ -13967,63 +14726,183 @@ class Auditor {
13967
14726
  createdFiles: existingFiles,
13968
14727
  modifiedFiles: [],
13969
14728
  summary,
13970
- massEditWarning
14729
+ massEditWarning,
14730
+ testRun
13971
14731
  };
13972
14732
  }
13973
14733
  }
13974
- var MASS_EDIT_THRESHOLD = 10;
14734
+ var MASS_EDIT_THRESHOLD = 10, SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE;
13975
14735
  var init_auditor = __esm(() => {
13976
14736
  init_i18n();
14737
+ init_js_identifiers();
14738
+ init_bash();
14739
+ init_processes();
14740
+ SKIP_DIRS = new Set([
14741
+ "node_modules",
14742
+ ".git",
14743
+ ".mma",
14744
+ "dist",
14745
+ "build",
14746
+ "coverage",
14747
+ ".next",
14748
+ ".nuxt",
14749
+ "vendor"
14750
+ ]);
14751
+ TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
14752
+ TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
13977
14753
  });
13978
14754
 
13979
- // src/modules/execution/plan-persister.ts
13980
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11, existsSync as existsSync26 } from "fs";
13981
- import { join as join20 } from "path";
13982
-
13983
- class PlanPersister {
13984
- filePath;
13985
- constructor(baseDir) {
13986
- const mmaDir = join20(baseDir, ".mma");
13987
- if (!existsSync26(mmaDir)) {
13988
- mkdirSync11(mmaDir, { recursive: true });
13989
- }
13990
- this.filePath = join20(mmaDir, "plan.json");
13991
- }
13992
- save(plan) {
13993
- const file = {
13994
- id: plan.id,
13995
- title: plan.title,
13996
- steps: plan.steps,
13997
- createdAt: plan.createdAt,
13998
- updatedAt: new Date().toISOString(),
13999
- baseDir: plan.baseDir
14000
- };
14001
- writeFileSync8(this.filePath, JSON.stringify(file, null, 2), "utf-8");
14002
- }
14003
- load() {
14004
- if (!existsSync26(this.filePath))
14755
+ // src/modules/execution/plan-store.ts
14756
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync12, existsSync as existsSync27, readdirSync as readdirSync9, rmSync } from "fs";
14757
+ import { join as join22 } from "path";
14758
+ function readPlanFile(path, fallbackBaseDir) {
14759
+ try {
14760
+ const raw = readFileSync14(path, "utf-8");
14761
+ if (!raw.trim())
14005
14762
  return null;
14006
- try {
14007
- const raw = readFileSync14(this.filePath, "utf-8");
14008
- const file = JSON.parse(raw);
14009
- return {
14010
- id: file.id || "plan_legacy",
14011
- title: file.title,
14012
- steps: file.steps,
14013
- createdAt: file.createdAt,
14014
- baseDir: file.baseDir || process.cwd()
14015
- };
14016
- } catch {
14763
+ const parsed = JSON.parse(raw);
14764
+ if (!parsed || !Array.isArray(parsed.steps))
14017
14765
  return null;
14018
- }
14766
+ return {
14767
+ id: parsed.id || "plan_legacy",
14768
+ title: parsed.title || "Legacy plan",
14769
+ steps: parsed.steps,
14770
+ createdAt: parsed.createdAt || new Date().toISOString(),
14771
+ baseDir: parsed.baseDir || fallbackBaseDir,
14772
+ name: parsed.name
14773
+ };
14774
+ } catch {
14775
+ return null;
14019
14776
  }
14020
- clear() {
14021
- if (existsSync26(this.filePath)) {
14022
- writeFileSync8(this.filePath, "", "utf-8");
14777
+ }
14778
+ function writePlanFile(path, plan) {
14779
+ writeFileSync8(path, JSON.stringify(plan, null, 2), "utf-8");
14780
+ }
14781
+ function listDir(dir, baseDir) {
14782
+ if (!existsSync27(dir))
14783
+ return [];
14784
+ const files = readdirSync9(dir).filter((f) => f.endsWith(".json"));
14785
+ return files.map((f) => readPlanFile(join22(dir, f), baseDir)).filter((p) => p !== null);
14786
+ }
14787
+ function toMeta(plan, status) {
14788
+ return {
14789
+ id: plan.id,
14790
+ name: plan.name,
14791
+ title: plan.title,
14792
+ status,
14793
+ createdAt: plan.createdAt,
14794
+ stepCount: plan.steps.length,
14795
+ doneCount: plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length
14796
+ };
14797
+ }
14798
+
14799
+ class PlanStore {
14800
+ baseDir;
14801
+ plansDir;
14802
+ draftsDir;
14803
+ archiveDir;
14804
+ legacyPath;
14805
+ constructor(baseDir) {
14806
+ const mmaDir = join22(baseDir, ".mma");
14807
+ if (!existsSync27(mmaDir))
14808
+ mkdirSync12(mmaDir, { recursive: true });
14809
+ this.baseDir = baseDir;
14810
+ this.plansDir = join22(mmaDir, "plans");
14811
+ this.draftsDir = join22(this.plansDir, "drafts");
14812
+ this.archiveDir = join22(this.plansDir, "archive");
14813
+ this.legacyPath = join22(mmaDir, LEGACY_FILE);
14814
+ for (const dir of [this.plansDir, this.draftsDir, this.archiveDir]) {
14815
+ if (!existsSync27(dir))
14816
+ mkdirSync12(dir, { recursive: true });
14817
+ }
14818
+ }
14819
+ activePath() {
14820
+ return join22(this.plansDir, "active.json");
14821
+ }
14822
+ saveActive(plan) {
14823
+ writePlanFile(this.activePath(), plan);
14824
+ }
14825
+ loadActive() {
14826
+ const activePath = this.activePath();
14827
+ if (existsSync27(activePath)) {
14828
+ const plan = readPlanFile(activePath, this.baseDir);
14829
+ if (plan)
14830
+ return plan;
14831
+ }
14832
+ if (existsSync27(this.legacyPath)) {
14833
+ const legacy = readPlanFile(this.legacyPath, this.baseDir);
14834
+ if (legacy) {
14835
+ this.saveActive(legacy);
14836
+ try {
14837
+ rmSync(this.legacyPath, { force: true });
14838
+ } catch {}
14839
+ return legacy;
14840
+ }
14023
14841
  }
14842
+ return null;
14843
+ }
14844
+ clearActive() {
14845
+ const p = this.activePath();
14846
+ if (existsSync27(p))
14847
+ rmSync(p, { force: true });
14848
+ }
14849
+ saveDraft(plan) {
14850
+ writePlanFile(join22(this.draftsDir, `${plan.id}.json`), plan);
14851
+ }
14852
+ loadDraft(id) {
14853
+ const p = join22(this.draftsDir, `${id}.json`);
14854
+ return existsSync27(p) ? readPlanFile(p, this.baseDir) : null;
14855
+ }
14856
+ removeDraft(id) {
14857
+ const p = join22(this.draftsDir, `${id}.json`);
14858
+ if (existsSync27(p))
14859
+ rmSync(p, { force: true });
14860
+ }
14861
+ listDrafts() {
14862
+ return listDir(this.draftsDir, this.baseDir);
14863
+ }
14864
+ archivePlan(plan) {
14865
+ writePlanFile(join22(this.archiveDir, `${plan.id}.json`), plan);
14866
+ this.removeDraft(plan.id);
14867
+ const active = this.loadActive();
14868
+ if (active && active.id === plan.id) {
14869
+ this.clearActive();
14870
+ }
14871
+ }
14872
+ listArchived() {
14873
+ return listDir(this.archiveDir, this.baseDir);
14874
+ }
14875
+ removeArchived(id) {
14876
+ const p = join22(this.archiveDir, `${id}.json`);
14877
+ if (existsSync27(p))
14878
+ rmSync(p, { force: true });
14879
+ }
14880
+ listAll() {
14881
+ const metas = [];
14882
+ const active = this.loadActive();
14883
+ if (active)
14884
+ metas.push(toMeta(active, "active"));
14885
+ for (const p of this.listDrafts())
14886
+ metas.push(toMeta(p, "draft"));
14887
+ for (const p of this.listArchived())
14888
+ metas.push(toMeta(p, "archived"));
14889
+ return metas;
14890
+ }
14891
+ find(id) {
14892
+ const active = this.loadActive();
14893
+ if (active && active.id === id)
14894
+ return { plan: active, status: "active" };
14895
+ const draft = this.loadDraft(id);
14896
+ if (draft)
14897
+ return { plan: draft, status: "draft" };
14898
+ const archived = this.listArchived().find((p) => p.id === id);
14899
+ if (archived)
14900
+ return { plan: archived, status: "archived" };
14901
+ return null;
14024
14902
  }
14025
14903
  }
14026
- var init_plan_persister = () => {};
14904
+ var LEGACY_FILE = "plan.json";
14905
+ var init_plan_store = () => {};
14027
14906
 
14028
14907
  // src/modules/execution/plan-coverage.ts
14029
14908
  function extractFilePaths(text) {
@@ -14089,7 +14968,7 @@ var init_plan_coverage = __esm(() => {
14089
14968
  });
14090
14969
 
14091
14970
  // src/modules/execution/module.ts
14092
- import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
14971
+ import { existsSync as existsSync28, readFileSync as readFileSync15 } from "fs";
14093
14972
  import { resolve as resolve15 } from "path";
14094
14973
 
14095
14974
  class ExecutionModule {
@@ -14098,26 +14977,29 @@ class ExecutionModule {
14098
14977
  verifier;
14099
14978
  stuckDetector;
14100
14979
  auditor;
14101
- persister;
14980
+ store;
14102
14981
  baseDir;
14103
14982
  lastRecoveryIteration = -STUCK_RECOVERY_COOLDOWN;
14104
14983
  consecutivePlanWarnings = 0;
14105
14984
  lastStepId = -1;
14985
+ depsGateHints = new Map;
14106
14986
  _auditSkipsRemaining = 0;
14987
+ stuckNotified = false;
14988
+ pendingMessages = [];
14107
14989
  constructor(baseDir, stuckThreshold = 6) {
14108
14990
  this.baseDir = baseDir;
14109
14991
  this.verifier = new StepVerifier(baseDir);
14110
14992
  this.stuckDetector = new StuckDetector(stuckThreshold);
14111
14993
  this.auditor = new Auditor(baseDir);
14112
- this.persister = new PlanPersister(baseDir);
14994
+ this.store = new PlanStore(baseDir);
14113
14995
  }
14114
14996
  setPlan(plan) {
14115
14997
  this.tracker = new PlanTracker(plan);
14116
- this.persister.save(plan);
14998
+ this.store.saveActive(plan);
14117
14999
  this._auditSkipsRemaining = 0;
14118
15000
  }
14119
15001
  restorePlan() {
14120
- const plan = this.persister.load();
15002
+ const plan = this.store.loadActive();
14121
15003
  if (!plan)
14122
15004
  return false;
14123
15005
  this.tracker = new PlanTracker(plan);
@@ -14128,6 +15010,9 @@ class ExecutionModule {
14128
15010
  getTracker() {
14129
15011
  return this.tracker;
14130
15012
  }
15013
+ getStore() {
15014
+ return this.store;
15015
+ }
14131
15016
  getStuckDetector() {
14132
15017
  return this.stuckDetector;
14133
15018
  }
@@ -14140,7 +15025,7 @@ class ExecutionModule {
14140
15025
  }
14141
15026
  const plan = this.tracker.getPlan();
14142
15027
  const audit = await this.auditor.audit(plan);
14143
- const pendingSteps = plan.steps.filter((s) => s.status !== "done" && s.status !== "skipped").map((s) => `${s.id}. ${s.description}`);
15028
+ const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
14144
15029
  const done = plan.steps.filter((s) => s.status === "done").length;
14145
15030
  const passed = audit.passed && pendingSteps.length === 0;
14146
15031
  return {
@@ -14166,7 +15051,16 @@ class ExecutionModule {
14166
15051
  return [
14167
15052
  {
14168
15053
  name: "plan",
14169
- description: `Create, update, show, or abort a multi-step plan. Use "create" at the start of complex tasks. Use "update" after completing each step to track progress. Use "show" to re-print the current plan checklist.
15054
+ description: `Create, update, show, abort, list, switch, or re-plan multi-step plans.
15055
+
15056
+ Actions:
15057
+ - create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.
15058
+ - update: Mark step status (done/failed/skipped), or rebuild plan with new steps.
15059
+ - show: Print current plan checklist.
15060
+ - abort: Archive current plan and clear active slot.
15061
+ - list: Show all plans (active, drafts, archived) with progress.
15062
+ - switch: Make a different plan active (by plan id).
15063
+ - re-plan: Iterative replanning: keep completed steps, replace remaining with new steps.
14170
15064
 
14171
15065
  Write CONCRETE steps with exact file paths and commands:
14172
15066
  - Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")
@@ -14180,24 +15074,102 @@ Write CONCRETE steps with exact file paths and commands:
14180
15074
  properties: {
14181
15075
  action: {
14182
15076
  type: "string",
14183
- enum: ["create", "update", "show", "abort"]
15077
+ enum: [
15078
+ "create",
15079
+ "update",
15080
+ "show",
15081
+ "abort",
15082
+ "list",
15083
+ "switch",
15084
+ "re-plan"
15085
+ ]
14184
15086
  },
14185
15087
  title: { type: "string" },
14186
15088
  steps: { type: "array", items: { type: "string" } },
14187
15089
  step: { type: "number" },
14188
15090
  status: { type: "string", enum: ["done", "failed", "skipped"] },
14189
- note: { type: "string" }
15091
+ note: { type: "string" },
15092
+ id: { type: "string", description: "Plan id (for switch action)" }
14190
15093
  },
14191
15094
  required: ["action"]
14192
15095
  },
14193
15096
  handler: async (_ctx, args) => {
14194
15097
  const action = String(args.action);
15098
+ if (action === "list") {
15099
+ const metas = this.store.listAll();
15100
+ if (metas.length === 0) {
15101
+ return { success: true, output: t("plan.list_empty") };
15102
+ }
15103
+ const lines = metas.map((m) => {
15104
+ const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
15105
+ const namePart = m.name ? ` (${m.name})` : "";
15106
+ return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
15107
+ });
15108
+ return {
15109
+ success: true,
15110
+ output: `${t("plan.list_header")}
15111
+ ${lines.join(`
15112
+ `)}`
15113
+ };
15114
+ }
15115
+ if (action === "switch") {
15116
+ const planId = String(args.id || "");
15117
+ if (!planId) {
15118
+ return { success: false, output: t("plan.switch_no_id") };
15119
+ }
15120
+ const found = this.store.find(planId);
15121
+ if (!found) {
15122
+ return {
15123
+ success: false,
15124
+ output: t("plan.not_found", { id: planId })
15125
+ };
15126
+ }
15127
+ this.preserveActive();
15128
+ if (found.status === "draft") {
15129
+ this.store.removeDraft(found.plan.id);
15130
+ } else if (found.status === "archived") {
15131
+ this.store.removeArchived(found.plan.id);
15132
+ }
15133
+ this.setPlan(found.plan);
15134
+ const display = PlanCreator.toPromptBlock(found.plan, 0);
15135
+ return {
15136
+ success: true,
15137
+ output: t("plan.switched", {
15138
+ id: found.plan.id,
15139
+ title: found.plan.title
15140
+ }),
15141
+ display
15142
+ };
15143
+ }
15144
+ if (action === "re-plan") {
15145
+ if (!this.tracker) {
15146
+ return { success: false, output: t("plan.no_active") };
15147
+ }
15148
+ const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
15149
+ if (newSteps.length === 0) {
15150
+ return { success: false, output: t("plan.replan_no_steps") };
15151
+ }
15152
+ const oldPlan = this.tracker.getPlan();
15153
+ const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
15154
+ const keptCount = replanned.steps.length - newSteps.length;
15155
+ this.setPlan(replanned);
15156
+ const display = PlanCreator.toPromptBlock(replanned, keptCount);
15157
+ return {
15158
+ success: true,
15159
+ output: t("plan.replanned", {
15160
+ kept: String(keptCount),
15161
+ steps: String(newSteps.length)
15162
+ }),
15163
+ display
15164
+ };
15165
+ }
14195
15166
  if (action === "create") {
14196
15167
  const title = String(args.title || "Task Plan");
14197
15168
  const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
14198
15169
  if (steps.length === 0) {
14199
15170
  return { success: false, output: t("plan.no_steps") };
14200
15171
  }
15172
+ this.preserveActive();
14201
15173
  const plan = PlanCreator.createPlan(title, steps, this.baseDir);
14202
15174
  this.setPlan(plan);
14203
15175
  const display = PlanCreator.toPromptBlock(plan, 0);
@@ -14242,7 +15214,7 @@ ${display}`,
14242
15214
  if (args.note)
14243
15215
  this.tracker.addNote(Number(args.step), String(args.note));
14244
15216
  this.tracker.syncCurrentStep();
14245
- this.persister.save(this.tracker.getPlan());
15217
+ this.store.saveActive(this.tracker.getPlan());
14246
15218
  const progress = this.tracker.getProgressString();
14247
15219
  const display = this.tracker.toPromptBlock();
14248
15220
  _ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
@@ -14270,8 +15242,11 @@ ${progress}`,
14270
15242
  };
14271
15243
  }
14272
15244
  if (action === "abort") {
15245
+ if (this.tracker) {
15246
+ this.store.archivePlan(this.tracker.getPlan());
15247
+ }
14273
15248
  this.tracker = null;
14274
- this.persister.clear();
15249
+ this.store.clearActive();
14275
15250
  return { success: true, output: t("plan.aborted") };
14276
15251
  }
14277
15252
  if (!this.tracker) {
@@ -14320,7 +15295,7 @@ ${progress}`,
14320
15295
  }
14321
15296
  this.tracker.updateStepStatus(currentStep.id, "done");
14322
15297
  this.tracker.syncCurrentStep();
14323
- this.persister.save(this.tracker.getPlan());
15298
+ this.store.saveActive(this.tracker.getPlan());
14324
15299
  const display = this.tracker.toPromptBlock();
14325
15300
  return {
14326
15301
  success: true,
@@ -14375,36 +15350,55 @@ Sub-tasks: ${note}`
14375
15350
  return {
14376
15351
  name: "execution",
14377
15352
  onBeforeThink: (ctx) => {
14378
- const step = this.tracker?.getCurrentStep();
14379
- if (this.tracker && step) {
14380
- if (step.id !== this.lastStepId) {
14381
- this.consecutivePlanWarnings = 0;
14382
- this.lastStepId = step.id;
15353
+ if (ctx.contextManager && this.pendingMessages.length > 0) {
15354
+ for (const m of this.pendingMessages.splice(0)) {
15355
+ ctx.contextManager.addMessage(m);
14383
15356
  }
14384
- this.stuckDetector.setCurrentStep(step.id, step.description);
14385
- this.stuckDetector.recordIteration(step.id);
14386
- } else {
15357
+ }
15358
+ if (this.tracker?.isComplete()) {
14387
15359
  this.stuckDetector.reset();
14388
15360
  this.consecutivePlanWarnings = 0;
14389
15361
  this.lastStepId = -1;
14390
- const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
14391
- if (iter === 3 && !this.tracker && ctx.contextManager) {
14392
- ctx.contextManager.addMessage({
14393
- role: "user",
14394
- content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`
14395
- });
14396
- }
14397
- if (iter >= 6 && !this.tracker && ctx.contextManager) {
14398
- ctx.contextManager.addMessage({
14399
- role: "user",
14400
- content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`
14401
- });
15362
+ this.stuckNotified = false;
15363
+ } else {
15364
+ const step = this.tracker?.getCurrentStep();
15365
+ if (this.tracker && step) {
15366
+ if (step.id !== this.lastStepId) {
15367
+ this.consecutivePlanWarnings = 0;
15368
+ this.lastStepId = step.id;
15369
+ this.stuckNotified = false;
15370
+ }
15371
+ this.stuckDetector.setCurrentStep(step.id, step.description);
15372
+ this.stuckDetector.recordIteration(step.id);
15373
+ } else {
15374
+ this.stuckDetector.reset();
15375
+ this.consecutivePlanWarnings = 0;
15376
+ this.lastStepId = -1;
15377
+ this.stuckNotified = false;
15378
+ const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
15379
+ if (iter === 3 && !this.tracker && ctx.contextManager) {
15380
+ ctx.contextManager.addMessage({
15381
+ role: "user",
15382
+ content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`
15383
+ });
15384
+ }
15385
+ if (iter >= 6 && !this.tracker && ctx.contextManager) {
15386
+ ctx.contextManager.addMessage({
15387
+ role: "user",
15388
+ content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`
15389
+ });
15390
+ }
14402
15391
  }
14403
15392
  }
14404
15393
  const stuckReason = this.stuckDetector.getStuckReason();
14405
15394
  if (stuckReason) {
14406
- ctx.logger?.warn(stuckReason);
14407
- ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
15395
+ const logIt = this.stuckDetector.isStuck() ? !this.stuckNotified : true;
15396
+ if (logIt) {
15397
+ ctx.logger?.warn(stuckReason);
15398
+ ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
15399
+ if (this.stuckDetector.isStuck())
15400
+ this.stuckNotified = true;
15401
+ }
14408
15402
  }
14409
15403
  if (this.stuckDetector.isStuck() || this.stuckDetector.hasRepetitiveToolCalls()) {
14410
15404
  const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
@@ -14447,29 +15441,32 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14447
15441
  ctx.onMeta(escalation);
14448
15442
  }
14449
15443
  if (this.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
14450
- const step2 = this.tracker?.getCurrentStep();
15444
+ const step = this.tracker?.getCurrentStep();
14451
15445
  ctx.contextManager.addMessage({
14452
15446
  role: "user",
14453
- content: `<system-summary>STOP. Step ${step2?.id ?? "?"} ("${step2?.description ?? ""}") took ${this.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step2?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step2?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`
15447
+ content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${this.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`
14454
15448
  });
14455
15449
  }
14456
15450
  }
14457
15451
  }
14458
15452
  },
14459
- onBeforeTool: (ctx, call) => {
15453
+ onBeforeTool: (_ctx, call) => {
14460
15454
  const warning = this.checkPlanAlignment(call);
14461
- if (warning && ctx.contextManager) {
14462
- ctx.contextManager.addMessage({
15455
+ if (warning) {
15456
+ this.pendingMessages.push({
14463
15457
  role: "user",
14464
15458
  content: `<system-summary>${warning}</system-summary>`
14465
15459
  });
14466
15460
  this.consecutivePlanWarnings++;
14467
15461
  if (this.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
14468
- ctx.contextManager.addMessage({
15462
+ this.pendingMessages.push({
14469
15463
  role: "user",
14470
15464
  content: `<system-summary>${t("exec.plan_blocked", { step: String(this.tracker?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
14471
15465
  });
14472
- return false;
15466
+ return t("exec.plan_blocked", {
15467
+ step: String(this.tracker?.getCurrentStep()?.id ?? "?"),
15468
+ max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
15469
+ });
14473
15470
  }
14474
15471
  } else {
14475
15472
  this.consecutivePlanWarnings = 0;
@@ -14484,16 +15481,23 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14484
15481
  }
14485
15482
  },
14486
15483
  onAfterTool: (ctx, call, result) => {
15484
+ if (call.name === "bash") {
15485
+ this.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
15486
+ }
15487
+ const toolText = String(result.output ?? "");
15488
+ if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
15489
+ this.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
15490
+ }
14487
15491
  if (!result.success) {
14488
15492
  this.stuckDetector.recordToolError(call.name, result.output);
14489
15493
  const actionableHints = this.stuckDetector.getActionableHints();
14490
15494
  const alternative = this.stuckDetector.getToolAlternative();
14491
- if ((actionableHints.length > 0 || alternative) && ctx.contextManager) {
15495
+ if (actionableHints.length > 0 || alternative) {
14492
15496
  const parts = [...actionableHints];
14493
15497
  if (alternative) {
14494
15498
  parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
14495
15499
  }
14496
- ctx.contextManager.addMessage({
15500
+ this.pendingMessages.push({
14497
15501
  role: "user",
14498
15502
  content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
14499
15503
  `) })}</system-summary>`
@@ -14501,10 +15505,21 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14501
15505
  }
14502
15506
  } else {
14503
15507
  this.stuckDetector.recordToolSuccess();
14504
- if (call.name === "bash" && result.success && ctx.contextManager) {
15508
+ if (call.name === "bash" && result.success) {
14505
15509
  const cmd = String(call.arguments?.command ?? "");
14506
- if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
14507
- ctx.contextManager.addMessage({
15510
+ const testRun = detectTestResults(String(result.output ?? ""));
15511
+ if (testRun && testRun.failed > 0) {
15512
+ this.pendingMessages.push({
15513
+ role: "user",
15514
+ content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`
15515
+ });
15516
+ } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
15517
+ this.pendingMessages.push({
15518
+ role: "user",
15519
+ content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`
15520
+ });
15521
+ } else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
15522
+ this.pendingMessages.push({
14508
15523
  role: "user",
14509
15524
  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>`
14510
15525
  });
@@ -14531,6 +15546,16 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14531
15546
  }
14532
15547
  };
14533
15548
  }
15549
+ preserveActive() {
15550
+ if (!this.tracker)
15551
+ return;
15552
+ const plan = this.tracker.getPlan();
15553
+ if (plan.steps.every((s) => s.status === "done" || s.status === "skipped")) {
15554
+ this.store.archivePlan(plan);
15555
+ } else {
15556
+ this.store.saveDraft(plan);
15557
+ }
15558
+ }
14534
15559
  checkPlanAlignment(call) {
14535
15560
  if (!this.tracker)
14536
15561
  return null;
@@ -14557,7 +15582,20 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14557
15582
  const callPaths = (argStr.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
14558
15583
  if (callPaths.length === 0)
14559
15584
  return null;
14560
- const offPath = callPaths.some((p) => !stepPaths.some((s) => p.includes(s) || s.includes(p)));
15585
+ const plan = this.tracker.getPlan();
15586
+ const finishedPaths = new Set;
15587
+ for (const s of plan.steps) {
15588
+ if (s.status === "done" || s.status === "skipped") {
15589
+ const ps = s.description.match(/\b[\w./\\-]+\.[a-z]+/gi)?.map((p) => p.toLowerCase()) ?? [];
15590
+ ps.forEach((p) => finishedPaths.add(p));
15591
+ }
15592
+ }
15593
+ const offPath = callPaths.some((p) => {
15594
+ if (finishedPaths.has(p) || [...finishedPaths].some((s) => s.includes(p))) {
15595
+ return false;
15596
+ }
15597
+ return !stepPaths.some((s) => p.includes(s) || s.includes(p));
15598
+ });
14561
15599
  if (!offPath)
14562
15600
  return null;
14563
15601
  return t("exec.plan_warning", {
@@ -14586,12 +15624,19 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14586
15624
  "poetry.lock",
14587
15625
  "requirements.txt"
14588
15626
  ];
14589
- const hasLockFile = lockFiles.some((f) => existsSync27(resolve15(this.baseDir, f)));
15627
+ const hasLockFile = lockFiles.some((f) => existsSync28(resolve15(this.baseDir, f)));
14590
15628
  if (!hasLockFile) {
14591
15629
  if (contextManager) {
15630
+ const hints = this.depsGateHints.get(step.id) || 0;
15631
+ this.depsGateHints.set(step.id, hints + 1);
15632
+ const force = hints >= 1;
15633
+ const content = force ? t("exec.step_gate_deps_force", { step: String(step.id) }) : t("exec.step_gate_deps", {
15634
+ step: String(step.id),
15635
+ description: step.description
15636
+ });
14592
15637
  contextManager.addMessage({
14593
15638
  role: "user",
14594
- content: `<system-summary>${t("exec.step_gate_deps", { step: String(step.id), description: step.description })}</system-summary>`
15639
+ content: `<system-summary>${content}</system-summary>`
14595
15640
  });
14596
15641
  }
14597
15642
  return;
@@ -14599,7 +15644,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14599
15644
  }
14600
15645
  if (stepPaths.length === 0)
14601
15646
  return;
14602
- const allExist = stepPaths.every((p) => existsSync27(resolve15(this.baseDir, p)));
15647
+ const allExist = stepPaths.every((p) => existsSync28(resolve15(this.baseDir, p)));
14603
15648
  if (!allExist)
14604
15649
  return;
14605
15650
  const emptyFiles = [];
@@ -14621,7 +15666,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
14621
15666
  this.tracker?.updateStepStatus(step.id, "done");
14622
15667
  const hadNext = this.tracker?.advance() ?? false;
14623
15668
  if (this.tracker) {
14624
- this.persister.save(this.tracker.getPlan());
15669
+ this.store.saveActive(this.tracker.getPlan());
14625
15670
  sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
14626
15671
  }
14627
15672
  if (contextManager) {
@@ -14655,13 +15700,20 @@ var init_module = __esm(() => {
14655
15700
  init_verifier();
14656
15701
  init_stuck_detector();
14657
15702
  init_auditor();
14658
- init_plan_persister();
15703
+ init_plan_store();
14659
15704
  init_plan_coverage();
15705
+ init_bash();
14660
15706
  });
14661
15707
 
14662
15708
  // src/modules/security/session-encryption.ts
14663
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync28, readdirSync as readdirSync7, unlinkSync as unlinkSync3 } from "fs";
14664
- import { join as join21 } from "path";
15709
+ import {
15710
+ readFileSync as readFileSync16,
15711
+ writeFileSync as writeFileSync9,
15712
+ existsSync as existsSync29,
15713
+ readdirSync as readdirSync10,
15714
+ unlinkSync as unlinkSync4
15715
+ } from "fs";
15716
+ import { join as join23 } from "path";
14665
15717
  import { homedir as homedir8 } from "os";
14666
15718
 
14667
15719
  class SessionFileEncryptor {
@@ -14670,7 +15722,7 @@ class SessionFileEncryptor {
14670
15722
  constructor(config) {
14671
15723
  this.config = { ...DEFAULT_SESSION_ENCRYPTION, ...config };
14672
15724
  this.encryptor = new ConfigEncryptor({
14673
- keyPath: config?.keyPath || join21(homedir8(), ".mma", ".session-encryption-key")
15725
+ keyPath: config?.keyPath || join23(homedir8(), ".mma", ".session-encryption-key")
14674
15726
  });
14675
15727
  }
14676
15728
  isEnabled() {
@@ -14694,10 +15746,15 @@ class SessionFileEncryptor {
14694
15746
  return this.encryptFileContent(JSON.stringify(obj));
14695
15747
  }
14696
15748
  decryptJSON(content) {
14697
- if (!this.config.enabled)
14698
- return JSON.parse(content);
14699
- const decrypted = this.decryptFileContent(content);
14700
- return JSON.parse(decrypted);
15749
+ let plain = content;
15750
+ if (this.config.enabled) {
15751
+ plain = this.decryptFileContent(content);
15752
+ }
15753
+ try {
15754
+ return JSON.parse(plain);
15755
+ } catch {
15756
+ throw new Error("Invalid JSON content in session file — file may be corrupted or encrypted with a different key");
15757
+ }
14701
15758
  }
14702
15759
  encryptJSONL(lines) {
14703
15760
  if (!this.config.enabled)
@@ -14730,25 +15787,34 @@ class SessionFileEncryptor {
14730
15787
  const lines = content.split(`
14731
15788
  `).filter((line) => line.trim());
14732
15789
  const decryptedLines = this.decryptJSONL(lines);
14733
- return decryptedLines.map((line) => JSON.parse(line));
15790
+ return decryptedLines.flatMap((line) => {
15791
+ try {
15792
+ return [JSON.parse(line)];
15793
+ } catch {
15794
+ return [];
15795
+ }
15796
+ });
14734
15797
  }
14735
15798
  appendToSessionJSONL(filePath, obj) {
14736
15799
  const encryptedLine = this.encryptFileContent(JSON.stringify(obj));
14737
15800
  writeFileSync9(filePath, encryptedLine + `
14738
- `, { flag: "a", encoding: "utf8" });
15801
+ `, {
15802
+ flag: "a",
15803
+ encoding: "utf8"
15804
+ });
14739
15805
  }
14740
15806
  encryptSessionDirectory(sessionDir) {
14741
15807
  if (!this.config.enabled)
14742
15808
  return;
14743
- const files = readdirSync7(sessionDir);
15809
+ const files = readdirSync10(sessionDir);
14744
15810
  for (const file of files) {
14745
- const filePath = join21(sessionDir, file);
14746
- if (existsSync28(filePath) && !file.endsWith(".enc")) {
15811
+ const filePath = join23(sessionDir, file);
15812
+ if (existsSync29(filePath) && !file.endsWith(".enc")) {
14747
15813
  try {
14748
15814
  const content = readFileSync16(filePath, "utf8");
14749
15815
  const encrypted = this.encryptFileContent(content);
14750
15816
  writeFileSync9(filePath + ".enc", encrypted, "utf8");
14751
- unlinkSync3(filePath);
15817
+ unlinkSync4(filePath);
14752
15818
  } catch {}
14753
15819
  }
14754
15820
  }
@@ -14756,16 +15822,16 @@ class SessionFileEncryptor {
14756
15822
  decryptSessionDirectory(sessionDir) {
14757
15823
  if (!this.config.enabled)
14758
15824
  return;
14759
- const files = readdirSync7(sessionDir);
15825
+ const files = readdirSync10(sessionDir);
14760
15826
  for (const file of files) {
14761
15827
  if (file.endsWith(".enc")) {
14762
- const encFilePath = join21(sessionDir, file);
15828
+ const encFilePath = join23(sessionDir, file);
14763
15829
  const decFilePath = encFilePath.slice(0, -4);
14764
15830
  try {
14765
15831
  const content = readFileSync16(encFilePath, "utf8");
14766
15832
  const decrypted = this.decryptFileContent(content);
14767
15833
  writeFileSync9(decFilePath, decrypted, "utf8");
14768
- unlinkSync3(encFilePath);
15834
+ unlinkSync4(encFilePath);
14769
15835
  } catch {}
14770
15836
  }
14771
15837
  }
@@ -14784,15 +15850,15 @@ var init_session_encryption = __esm(() => {
14784
15850
 
14785
15851
  // src/modules/session/store.ts
14786
15852
  import {
14787
- existsSync as existsSync29,
14788
- mkdirSync as mkdirSync12,
14789
- readdirSync as readdirSync8,
15853
+ existsSync as existsSync30,
15854
+ mkdirSync as mkdirSync13,
15855
+ readdirSync as readdirSync11,
14790
15856
  readFileSync as readFileSync17,
14791
- rmSync,
15857
+ rmSync as rmSync2,
14792
15858
  writeFileSync as writeFileSync10,
14793
- appendFileSync as appendFileSync5
15859
+ appendFileSync as appendFileSync6
14794
15860
  } from "fs";
14795
- import { join as join22 } from "path";
15861
+ import { join as join24 } from "path";
14796
15862
  import { gzipSync } from "zlib";
14797
15863
 
14798
15864
  class SessionStore {
@@ -14806,7 +15872,7 @@ class SessionStore {
14806
15872
  }
14807
15873
  }
14808
15874
  getSessionDir(id) {
14809
- return join22(this.baseDir, id);
15875
+ return join24(this.baseDir, id);
14810
15876
  }
14811
15877
  updateEncryption(config) {
14812
15878
  if (config?.enabled) {
@@ -14819,27 +15885,27 @@ class SessionStore {
14819
15885
  return this.encryptor?.isEnabled() ?? false;
14820
15886
  }
14821
15887
  init() {
14822
- mkdirSync12(this.baseDir, { recursive: true });
15888
+ mkdirSync13(this.baseDir, { recursive: true });
14823
15889
  }
14824
15890
  sessionDir(id) {
14825
- return join22(this.baseDir, id);
15891
+ return join24(this.baseDir, id);
14826
15892
  }
14827
15893
  metaPath(id) {
14828
- return join22(this.sessionDir(id), "meta.json");
15894
+ return join24(this.sessionDir(id), "meta.json");
14829
15895
  }
14830
15896
  historyPath(id) {
14831
- return join22(this.sessionDir(id), "history.jsonl");
15897
+ return join24(this.sessionDir(id), "history.jsonl");
14832
15898
  }
14833
15899
  sessionLogPath(id) {
14834
- return join22(this.sessionDir(id), "session.jsonl");
15900
+ return join24(this.sessionDir(id), "session.jsonl");
14835
15901
  }
14836
15902
  sessionExists(id) {
14837
- return existsSync29(this.metaPath(id));
15903
+ return existsSync30(this.metaPath(id));
14838
15904
  }
14839
15905
  saveMeta(id, meta) {
14840
15906
  this._metaCache.set(id, meta);
14841
15907
  const dir = this.sessionDir(id);
14842
- mkdirSync12(dir, { recursive: true });
15908
+ mkdirSync13(dir, { recursive: true });
14843
15909
  const content = JSON.stringify(meta, null, 2);
14844
15910
  if (this.encryptor) {
14845
15911
  writeFileSync10(this.metaPath(id), this.encryptor.encryptFileContent(content), "utf-8");
@@ -14852,7 +15918,7 @@ class SessionStore {
14852
15918
  if (cached)
14853
15919
  return cached;
14854
15920
  const path = this.metaPath(id);
14855
- if (!existsSync29(path))
15921
+ if (!existsSync30(path))
14856
15922
  return null;
14857
15923
  try {
14858
15924
  const raw = readFileSync17(path, "utf-8");
@@ -14866,13 +15932,13 @@ class SessionStore {
14866
15932
  }
14867
15933
  appendMessage(id, msg) {
14868
15934
  const dir = this.sessionDir(id);
14869
- mkdirSync12(dir, { recursive: true });
15935
+ mkdirSync13(dir, { recursive: true });
14870
15936
  const line = JSON.stringify(msg);
14871
15937
  if (this.encryptor?.isEnabled()) {
14872
- appendFileSync5(this.historyPath(id), this.encryptor.encryptFileContent(line) + `
15938
+ appendFileSync6(this.historyPath(id), this.encryptor.encryptFileContent(line) + `
14873
15939
  `, "utf-8");
14874
15940
  } else {
14875
- appendFileSync5(this.historyPath(id), line + `
15941
+ appendFileSync6(this.historyPath(id), line + `
14876
15942
  `, "utf-8");
14877
15943
  }
14878
15944
  const meta = this.loadMeta(id);
@@ -14884,7 +15950,7 @@ class SessionStore {
14884
15950
  }
14885
15951
  loadHistory(id) {
14886
15952
  const path = this.historyPath(id);
14887
- if (!existsSync29(path))
15953
+ if (!existsSync30(path))
14888
15954
  return [];
14889
15955
  try {
14890
15956
  const raw = readFileSync17(path, "utf-8");
@@ -14913,19 +15979,19 @@ class SessionStore {
14913
15979
  }
14914
15980
  appendSessionLog(id, entry) {
14915
15981
  const dir = this.sessionDir(id);
14916
- mkdirSync12(dir, { recursive: true });
15982
+ mkdirSync13(dir, { recursive: true });
14917
15983
  const line = JSON.stringify(entry);
14918
15984
  if (this.encryptor?.isEnabled()) {
14919
- appendFileSync5(this.sessionLogPath(id), this.encryptor.encryptFileContent(line) + `
15985
+ appendFileSync6(this.sessionLogPath(id), this.encryptor.encryptFileContent(line) + `
14920
15986
  `, "utf-8");
14921
15987
  } else {
14922
- appendFileSync5(this.sessionLogPath(id), line + `
15988
+ appendFileSync6(this.sessionLogPath(id), line + `
14923
15989
  `, "utf-8");
14924
15990
  }
14925
15991
  }
14926
15992
  loadSessionLog(id) {
14927
15993
  const path = this.sessionLogPath(id);
14928
- if (!existsSync29(path))
15994
+ if (!existsSync30(path))
14929
15995
  return [];
14930
15996
  try {
14931
15997
  const raw = readFileSync17(path, "utf-8");
@@ -14953,9 +16019,9 @@ class SessionStore {
14953
16019
  }
14954
16020
  }
14955
16021
  listSessions() {
14956
- if (!existsSync29(this.baseDir))
16022
+ if (!existsSync30(this.baseDir))
14957
16023
  return [];
14958
- const entries = readdirSync8(this.baseDir, { withFileTypes: true });
16024
+ const entries = readdirSync11(this.baseDir, { withFileTypes: true });
14959
16025
  const sessions = [];
14960
16026
  for (const entry of entries) {
14961
16027
  if (entry.isDirectory()) {
@@ -14970,8 +16036,8 @@ class SessionStore {
14970
16036
  deleteSession(id) {
14971
16037
  this._metaCache.delete(id);
14972
16038
  const dir = this.sessionDir(id);
14973
- if (existsSync29(dir)) {
14974
- rmSync(dir, { recursive: true, force: true });
16039
+ if (existsSync30(dir)) {
16040
+ rmSync2(dir, { recursive: true, force: true });
14975
16041
  }
14976
16042
  }
14977
16043
  rotateOldSessions() {
@@ -14982,12 +16048,12 @@ class SessionStore {
14982
16048
  const updatedAt = new Date(session2.updatedAt);
14983
16049
  if (updatedAt < thirtyDaysAgo) {
14984
16050
  const historyPath = this.historyPath(session2.id);
14985
- if (existsSync29(historyPath)) {
16051
+ if (existsSync30(historyPath)) {
14986
16052
  const content = readFileSync17(historyPath, "utf-8");
14987
16053
  const compressed = gzipSync(content);
14988
- const gzPath = join22(this.baseDir, `${session2.id}.jsonl.gz`);
16054
+ const gzPath = join24(this.baseDir, `${session2.id}.jsonl.gz`);
14989
16055
  writeFileSync10(gzPath, compressed);
14990
- rmSync(historyPath);
16056
+ rmSync2(historyPath);
14991
16057
  }
14992
16058
  }
14993
16059
  }
@@ -15195,8 +16261,8 @@ class ProfileCompressor {
15195
16261
  }
15196
16262
 
15197
16263
  // src/modules/user-profile/profile.ts
15198
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync11, existsSync as existsSync30, mkdirSync as mkdirSync13 } from "fs";
15199
- import { join as join23 } from "path";
16264
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync11, existsSync as existsSync31, mkdirSync as mkdirSync14 } from "fs";
16265
+ import { join as join25 } from "path";
15200
16266
  import { homedir as homedir9, hostname, platform as platform4, type } from "os";
15201
16267
  import { env } from "process";
15202
16268
 
@@ -15220,14 +16286,14 @@ class UserProfile {
15220
16286
  return this.info;
15221
16287
  }
15222
16288
  save() {
15223
- if (!existsSync30(this.profileDir)) {
15224
- mkdirSync13(this.profileDir, { recursive: true });
16289
+ if (!existsSync31(this.profileDir)) {
16290
+ mkdirSync14(this.profileDir, { recursive: true });
15225
16291
  }
15226
- writeFileSync11(join23(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
16292
+ writeFileSync11(join25(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
15227
16293
  }
15228
16294
  load() {
15229
- const path = join23(this.profileDir, "profile.json");
15230
- if (!existsSync30(path))
16295
+ const path = join25(this.profileDir, "profile.json");
16296
+ if (!existsSync31(path))
15231
16297
  return null;
15232
16298
  try {
15233
16299
  const data = JSON.parse(readFileSync18(path, "utf-8"));
@@ -15265,22 +16331,22 @@ class UserProfile {
15265
16331
  var init_profile = () => {};
15266
16332
 
15267
16333
  // src/modules/skills/loader.ts
15268
- import { readdirSync as readdirSync9, readFileSync as readFileSync19, existsSync as existsSync31, statSync as statSync5 } from "fs";
15269
- import { join as join24 } from "path";
16334
+ import { readdirSync as readdirSync12, readFileSync as readFileSync19, existsSync as existsSync32, statSync as statSync6 } from "fs";
16335
+ import { join as join26 } from "path";
15270
16336
 
15271
16337
  class SkillsLoader {
15272
16338
  loadFromDir(dirPath) {
15273
- if (!existsSync31(dirPath))
16339
+ if (!existsSync32(dirPath))
15274
16340
  return [];
15275
16341
  const skills = [];
15276
16342
  this.scanDir(dirPath, skills);
15277
16343
  return skills;
15278
16344
  }
15279
16345
  scanDir(dirPath, skills) {
15280
- const entries = readdirSync9(dirPath);
16346
+ const entries = readdirSync12(dirPath);
15281
16347
  for (const entry of entries) {
15282
- const fullPath = join24(dirPath, entry);
15283
- const stat = statSync5(fullPath);
16348
+ const fullPath = join26(dirPath, entry);
16349
+ const stat = statSync6(fullPath);
15284
16350
  if (stat.isDirectory()) {
15285
16351
  this.scanDir(fullPath, skills);
15286
16352
  continue;
@@ -15752,7 +16818,7 @@ var DEFAULT_TIMEOUT = 15000;
15752
16818
  var init_client2 = () => {};
15753
16819
 
15754
16820
  // src/modules/lsp/module.ts
15755
- import { existsSync as existsSync32 } from "fs";
16821
+ import { existsSync as existsSync33 } from "fs";
15756
16822
  import { resolve as resolve17 } from "path";
15757
16823
 
15758
16824
  class LspModule {
@@ -15778,7 +16844,7 @@ class LspModule {
15778
16844
  if (!filePath)
15779
16845
  return;
15780
16846
  const fullPath = resolve17(_ctx.baseDir, filePath);
15781
- if (!existsSync32(fullPath))
16847
+ if (!existsSync33(fullPath))
15782
16848
  return;
15783
16849
  const serverConfig = getServerForFile(fullPath, self.config);
15784
16850
  if (!serverConfig)
@@ -15836,8 +16902,8 @@ var init_lsp = __esm(() => {
15836
16902
  });
15837
16903
 
15838
16904
  // src/modules/indexer/walker.ts
15839
- import { readdirSync as readdirSync10, readFileSync as readFileSync20, statSync as statSync6, existsSync as existsSync33, watch } from "fs";
15840
- import { join as join25, relative, extname as extname5 } from "path";
16905
+ import { readdirSync as readdirSync13, readFileSync as readFileSync20, statSync as statSync7, existsSync as existsSync34, watch } from "fs";
16906
+ import { join as join27, relative, extname as extname5 } from "path";
15841
16907
 
15842
16908
  class Indexer {
15843
16909
  baseDir;
@@ -15864,20 +16930,20 @@ class Indexer {
15864
16930
  let totalSize = 0;
15865
16931
  let count = 0;
15866
16932
  const walkDir = (dir) => {
15867
- if (!existsSync33(dir))
16933
+ if (!existsSync34(dir))
15868
16934
  return;
15869
16935
  let entries;
15870
16936
  try {
15871
- entries = readdirSync10(dir);
16937
+ entries = readdirSync13(dir);
15872
16938
  } catch {
15873
16939
  return;
15874
16940
  }
15875
16941
  for (const entry of entries) {
15876
16942
  if (count >= this.MAX_FILES)
15877
16943
  return;
15878
- const fullPath = join25(dir, entry);
16944
+ const fullPath = join27(dir, entry);
15879
16945
  const relPath = relative(this.baseDir, fullPath);
15880
- const stat = statSync6(fullPath);
16946
+ const stat = statSync7(fullPath);
15881
16947
  if (stat.isDirectory()) {
15882
16948
  if (!IGNORE_DIRS.has(entry)) {
15883
16949
  walkDir(fullPath);
@@ -15938,19 +17004,19 @@ var init_walker = __esm(() => {
15938
17004
  });
15939
17005
 
15940
17006
  // src/modules/indexer/cache.ts
15941
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync12, existsSync as existsSync34, mkdirSync as mkdirSync14, rmSync as rmSync2 } from "fs";
15942
- import { join as join26 } from "path";
17007
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync12, existsSync as existsSync35, mkdirSync as mkdirSync15, rmSync as rmSync3 } from "fs";
17008
+ import { join as join28 } from "path";
15943
17009
 
15944
17010
  class IndexCache {
15945
17011
  cachePath;
15946
17012
  cache = null;
15947
17013
  constructor(cacheDir) {
15948
- this.cachePath = join26(cacheDir, "index-cache.json");
17014
+ this.cachePath = join28(cacheDir, "index-cache.json");
15949
17015
  }
15950
17016
  load() {
15951
17017
  if (this.cache)
15952
17018
  return this.cache;
15953
- if (!existsSync34(this.cachePath))
17019
+ if (!existsSync35(this.cachePath))
15954
17020
  return null;
15955
17021
  try {
15956
17022
  this.cache = JSON.parse(readFileSync21(this.cachePath, "utf-8"));
@@ -15961,16 +17027,16 @@ class IndexCache {
15961
17027
  }
15962
17028
  save(result) {
15963
17029
  this.cache = result;
15964
- const dir = join26(this.cachePath, "..");
15965
- if (!existsSync34(dir))
15966
- mkdirSync14(dir, { recursive: true });
17030
+ const dir = join28(this.cachePath, "..");
17031
+ if (!existsSync35(dir))
17032
+ mkdirSync15(dir, { recursive: true });
15967
17033
  writeFileSync12(this.cachePath, JSON.stringify(result), "utf-8");
15968
17034
  }
15969
17035
  invalidate() {
15970
17036
  this.cache = null;
15971
- if (existsSync34(this.cachePath)) {
17037
+ if (existsSync35(this.cachePath)) {
15972
17038
  try {
15973
- rmSync2(this.cachePath);
17039
+ rmSync3(this.cachePath);
15974
17040
  } catch {}
15975
17041
  }
15976
17042
  }
@@ -15978,7 +17044,7 @@ class IndexCache {
15978
17044
  var init_cache = () => {};
15979
17045
 
15980
17046
  // src/modules/indexer/module.ts
15981
- import { dirname as dirname8 } from "path";
17047
+ import { dirname as dirname7 } from "path";
15982
17048
 
15983
17049
  class IndexerModule {
15984
17050
  name = "indexer";
@@ -16095,7 +17161,7 @@ ${t("indexer.and_more", { count: result.files.length - 100 })}` : "";
16095
17161
  const counts = {};
16096
17162
  for (const f of result.files) {
16097
17163
  const normalized = f.path.replace(/\\/g, "/");
16098
- const dir = dirname8(normalized);
17164
+ const dir = dirname7(normalized);
16099
17165
  const key = dir === "." ? "(root)" : dir;
16100
17166
  counts[key] = (counts[key] || 0) + 1;
16101
17167
  }
@@ -16326,13 +17392,13 @@ var init_mcp = __esm(() => {
16326
17392
 
16327
17393
  // src/modules/memory/module.ts
16328
17394
  import { homedir as homedir10 } from "os";
16329
- import { join as join27 } from "path";
17395
+ import { join as join29 } from "path";
16330
17396
 
16331
17397
  class MemoryModule {
16332
17398
  name = "memory";
16333
17399
  store;
16334
17400
  constructor(memoryDir) {
16335
- const dir = memoryDir || join27(homedir10(), ".mma", "memory");
17401
+ const dir = memoryDir || join29(homedir10(), ".mma", "memory");
16336
17402
  this.store = new MemoryStore(dir);
16337
17403
  }
16338
17404
  getSystemPromptBlock() {
@@ -16376,46 +17442,38 @@ var init_module8 = __esm(() => {
16376
17442
  // src/core/bootstrap.ts
16377
17443
  var exports_bootstrap = {};
16378
17444
  __export(exports_bootstrap, {
17445
+ buildSystemInfo: () => buildSystemInfo,
16379
17446
  bootstrap: () => bootstrap
16380
17447
  });
16381
17448
  import { homedir as homedir11 } from "os";
16382
- import { join as join28, resolve as resolve18 } from "path";
16383
- import { existsSync as existsSync35, readFileSync as readFileSync22, writeFileSync as writeFileSync13 } from "fs";
17449
+ import { join as join30, resolve as resolve18 } from "path";
17450
+ import { existsSync as existsSync36, readFileSync as readFileSync22, writeFileSync as writeFileSync13 } from "fs";
16384
17451
  function buildSystemInfo(config, baseDir, profileCompressed) {
16385
17452
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
16386
17453
  const isWin = profileCompressed.toLowerCase().includes("win32");
16387
17454
  const lines = [
16388
- `You are MMA v2, an AI coding agent for small models (${config.model}).`,
16389
- `Date: ${now}`,
16390
- `Workspace: ${baseDir}`,
16391
- `${profileCompressed}`,
16392
- `Reply in the user's language. Use tools for: read/write/edit/delete files, search (glob/grep), shell commands (bash), web access (web_search, web_fetch), subagents, browser, MCP.`,
16393
- `Use tools when needed — explain briefly what you're doing if it's not obvious.`,
16394
- `If a tool call fails, analyze the error and correct the call — try up to 2 times with different approaches before asking the user for help.`,
16395
- ``,
16396
- `Design principles:`,
16397
- `- YAGNI: Do not add code, files, or abstractions not needed right now.`,
16398
- `- KISS: Prefer simple, straightforward solutions over clever or complex ones.`,
16399
- `- DRY: Do not duplicate code, logic, or configuration — reuse existing utilities and patterns.`
17455
+ `You are MMA v2, an AI coding agent for small models (${config.model}). Date: ${now}. Workspace: ${baseDir}. ${profileCompressed}.`,
17456
+ `Reply in the user's language. Use tools for file ops (read/write/edit/delete), search (glob/grep), shell (bash), web, subagents, browser, MCP. Explain briefly if not obvious. On tool failure: analyze, fix the call, retry up to 2x with different approaches, then ask the user.`,
17457
+ `Design: YAGNI (no unneeded code), KISS (simple over clever), DRY (reuse existing utilities).`
16400
17458
  ];
16401
17459
  if (isWin) {
16402
- lines.push(``, `Windows environment — the shell is PowerShell. Use these rules:`, `- For file listings, prefer the list_dir tool over "dir".`, `- For reading files, prefer the read_file tool over "type".`, `- For deleting files, prefer the delete_file tool over "del".`, `- For creating directories, prefer the create_dir tool over "mkdir".`, `- Do NOT use PowerShell cmdlets (Get-Content, Select-Object, Write-Output) use dedicated tools instead.`, `- Do not use "head", "tail", "grep", "cat" — they are Unix commands. Use the read_file and grep tools instead.`, `- Use forward slashes (/) or escaped backslashes (\\\\) in file paths.`, `- Your working directory is: ${baseDir}`);
17460
+ lines.push(`Windows (PowerShell): use list_dir/read_file/delete_file/create_dir tools instead of dir/type/del/mkdir. No PowerShell cmdlets (Get-Content, Select-Object, Write-Output), no head/tail/grep/cat. Use forward slashes in paths. CWD: ${baseDir}`);
16403
17461
  }
16404
- lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Prefer workdir over "cd dir && cmd" chaining.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`, `- Dev servers, watchers and other long-running processes: pass "background: true" to get a process id immediately. Without it, any command still running after a few seconds is automatically moved to the background — check output with process_log.`);
16405
- lines.push(``, `=== DEVELOPMENT RULES — follow these strictly ===`, ``, `1. DEPENDENCIES FIRST: Before writing any source code, ALWAYS install project dependencies (e.g., "npm install", "pip install -r requirements.txt", "cargo build", "go mod tidy"). Verify the package manager's lock file or dependency directory exists. Never write code that imports/uses packages that aren't installed yet.`, `2. TOOLKIT/FWK FIRST: If the task specifies a framework or UI library, initialize and configure it BEFORE writing application code. Run its project init command first, then add components/modules. Never write your own version of what the framework already provides.`, `3. ONE STEP AT A TIME: Follow the plan sequentially. Complete step N before starting step N+1. When a step is done: verify the deliverables exist and have real content (not empty), then call "plan update step=N status=done". Do not redo completed work.`, `4. VERIFY YOUR WORK: After creating/modifying files, verify they exist on disk. After installing dependencies, verify the package manager completed successfully. After any command, check its output for errors. Don't assume operations succeeded.`, `5. NO PREMATURE WORK: Do not create files for future steps. Do not add imports/references to packages or modules that haven't been installed yet. Do not reference files or components that don't exist yet. Build incrementally — one layer at a time.`, `6. WHEN STUCK: If a command fails 2+ times, STOP and try a different approach. Write files directly instead of using commands. Ask the user for help. Never repeat the same failing command more than twice.`, ``, `=== PLAN QUALITY RULES — your plan MUST follow these ===`, ``, `- Each step must describe CONCRETE deliverables: exact filenames with paths, exact packages to install, exact CLI commands to run. Avoid vague steps — be specific.`, `- A step like "Настройка проекта" or "Setup the project" is too vague — describe what exactly needs to be configured or set up.`, `- A step like "Создать src/components/Header.tsx с навигацией и логотипом, добавить в src/App.tsx импорт <Header />" is GOOD.`, `- Include file extensions (.tsx, .css, .json) and directory paths. Every step must mention at least one file or command.`, `- The plan must cover EVERYTHING needed: init → deps → framework setup → code → verification.`, `- Number of steps: 5-8 for a typical task. Too few means you're being vague. Too many means you're over-splitting.`);
17462
+ lines.push(`Runtime: Bun is available run TypeScript directly with "bun <file.ts>" and tests with "bun test" (no tsx/ts-node/npm install needed for that).`);
17463
+ lines.push(`Bash: use "workdir" param instead of "cd dir && cmd". One command per call. Long-running processes: pass "background: true" (id immediately), otherwise auto-backgrounded after a few seconds check with process_log.`, `DEVELOPMENT RULES (strict): 1) install deps BEFORE writing source (npm/pip/cargo, verify lockfile exists; never import uninstalled packages); 2) framework/toolkit init BEFORE app code; 3) follow plan sequentially, after each step verify deliverables exist with real content, then "plan update step=N status=done"; 4) verify work on disk + command output don't assume success; 5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, try a different approach, write files directly, ask the user.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact file paths with extensions, exact packages, exact commands). Vague steps ("Setup the project") forbidden. Cover init → deps → framework → code → verification. 5-8 steps.`);
16406
17464
  if (config.autoPlan) {
16407
- lines.push(``, `Plan rule (MANDATORY): For ANY task that requires creating files, installing packages, or multiple actions — you MUST create a plan using the "plan" tool BEFORE starting work. Each step must describe a concrete deliverable (specific files to create, packages to install, commands to run). Do not combine unrelated work into one step.`);
17465
+ lines.push(`Plan rule (MANDATORY): any task creating files, installing packages, or requiring multiple actions MUST create a plan with the "plan" tool BEFORE starting. Each step = a concrete deliverable.`);
16408
17466
  }
16409
17467
  const hasMCP = config.mcpServers && Object.values(config.mcpServers).some((s) => s.enabled !== false);
16410
17468
  if (hasMCP) {
16411
- lines.push(``, `MCP servers are available. Use the named MCP tools (prefixed with mcp__) to query external services.`);
17469
+ lines.push(`MCP servers are available use the named MCP tools (prefixed mcp__) to query external services.`);
16412
17470
  }
16413
17471
  return lines.join(`
16414
17472
  `);
16415
17473
  }
16416
17474
  async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16417
- const dir = configDir || join28(homedir11(), ".mma");
16418
- const projectConfigPath = projectDir ? join28(projectDir, ".mmrc") : join28(process.cwd(), ".mmrc");
17475
+ const dir = configDir || join30(homedir11(), ".mma");
17476
+ const projectConfigPath = projectDir ? join30(projectDir, ".mmrc") : join30(process.cwd(), ".mmrc");
16419
17477
  const config = loadConfig({ configDir: dir, projectConfigPath });
16420
17478
  setLocale(config.locale);
16421
17479
  try {
@@ -16425,7 +17483,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16425
17483
  }
16426
17484
  } catch {}
16427
17485
  const logger = new Logger(config.logLevel);
16428
- logger.setLogDir(join28(dir, "logs"));
17486
+ logger.setLogDir(join30(dir, "logs"));
16429
17487
  logger.debug("MMA bootstrap", {
16430
17488
  version: config.version,
16431
17489
  model: config.model
@@ -16447,7 +17505,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16447
17505
  logger.info(`Model ${config.model} loaded in ${loadResult.loadTime}s`);
16448
17506
  }
16449
17507
  }
16450
- const profile = new UserProfile(join28(dir));
17508
+ const profile = new UserProfile(join30(dir));
16451
17509
  profile.load() || profile.collect();
16452
17510
  profile.save();
16453
17511
  const llmProvider = new OpenAICompatProvider({
@@ -16459,7 +17517,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16459
17517
  rateLimits: config.security?.rateLimits
16460
17518
  });
16461
17519
  const baseDir = projectDir ? resolve18(projectDir) : process.cwd();
16462
- const projectMapCacheDir = join28(baseDir, ".mma");
17520
+ const projectMapCacheDir = join30(baseDir, ".mma");
16463
17521
  const indexerModule = new IndexerModule({
16464
17522
  baseDir,
16465
17523
  cacheDir: projectMapCacheDir
@@ -16470,26 +17528,27 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16470
17528
  logger.warn(`Project indexing failed: ${err.message}`);
16471
17529
  }
16472
17530
  const skillsLoader = new SkillsLoader;
16473
- const builtinDir = join28(import.meta.dirname, "skills", "builtin");
16474
- const globalDir = join28(homedir11(), ".agents", "skills");
16475
- const projectSkillsDir = join28(baseDir, ".mma", "skills");
17531
+ const builtinDir = join30(import.meta.dirname, "skills", "builtin");
17532
+ const globalDir = join30(homedir11(), ".agents", "skills");
17533
+ const projectSkillsDir = join30(baseDir, ".mma", "skills");
16476
17534
  const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
16477
17535
  const skillsBudget = Math.floor(config.contextWindow * config.skills.budget);
16478
17536
  const skillsModule = new SkillsModule(availableSkills, skillsBudget);
16479
17537
  const toolRegistry = new ToolRegistry;
16480
17538
  registerAllTools(toolRegistry, skillsModule);
16481
17539
  const pluginManager = new PluginManager;
17540
+ const systemInfoContent = buildSystemInfo(config, baseDir, profile.compress());
16482
17541
  const systemInfoPrompt = {
16483
- content: buildSystemInfo(config, baseDir, profile.compress()),
17542
+ content: systemInfoContent,
16484
17543
  priority: "critical",
16485
17544
  essential: true,
16486
- estimatedTokens: 250
17545
+ estimatedTokens: Math.ceil(systemInfoContent.length / 4)
16487
17546
  };
16488
- const agentsMdGlobal = join28(dir, "AGENTS.md");
16489
- if (!existsSync35(agentsMdGlobal)) {
17547
+ const agentsMdGlobal = join30(dir, "AGENTS.md");
17548
+ if (!existsSync36(agentsMdGlobal)) {
16490
17549
  writeFileSync13(agentsMdGlobal, "", "utf-8");
16491
17550
  }
16492
- const sessionDir = join28(dir, "sessions");
17551
+ const sessionDir = join30(dir, "sessions");
16493
17552
  const sessionStore = new SessionStore(sessionDir);
16494
17553
  sessionStore.init();
16495
17554
  const sessionManager = new SessionManager(sessionStore, {
@@ -16556,7 +17615,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16556
17615
  const mcpModule = new MCPModule(config);
16557
17616
  await mcpModule.initialize();
16558
17617
  moduleRegistry.register(mcpModule);
16559
- const memoryModule = new MemoryModule(join28(dir, "memory"));
17618
+ const memoryModule = new MemoryModule(join30(dir, "memory"));
16560
17619
  moduleRegistry.register(memoryModule);
16561
17620
  if (config.browser.enabled) {
16562
17621
  const browserModule = new BrowserModule;
@@ -16606,8 +17665,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16606
17665
  pluginManager.register(plugin);
16607
17666
  pluginManager.register(plugin2);
16608
17667
  const pluginLoader = new PluginLoader;
16609
- const globalPluginsDir = join28(homedir11(), ".mma", "plugins");
16610
- const projectPluginsDir = join28(baseDir, ".mma", "plugins");
17668
+ const globalPluginsDir = join30(homedir11(), ".mma", "plugins");
17669
+ const projectPluginsDir = join30(baseDir, ".mma", "plugins");
16611
17670
  pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
16612
17671
  pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
16613
17672
  contextManager.onCompact = (summary) => {
@@ -16625,12 +17684,12 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
16625
17684
  const skipAgentsMd = noAgentsMd === true;
16626
17685
  if (!skipAgentsMd) {
16627
17686
  const agentsMdCandidates = [
16628
- join28(baseDir, "AGENTS.md"),
16629
- join28(baseDir, ".mma", "AGENTS.md"),
16630
- join28(dir, "AGENTS.md")
17687
+ join30(baseDir, "AGENTS.md"),
17688
+ join30(baseDir, ".mma", "AGENTS.md"),
17689
+ join30(dir, "AGENTS.md")
16631
17690
  ];
16632
17691
  for (const p of agentsMdCandidates) {
16633
- if (existsSync35(p)) {
17692
+ if (existsSync36(p)) {
16634
17693
  const content = readFileSync22(p, "utf-8").trim();
16635
17694
  if (content) {
16636
17695
  agentsMdBlocks.push({
@@ -16719,7 +17778,7 @@ function ansiRegex({ onlyFirst = false } = {}) {
16719
17778
  return new RegExp(pattern, onlyFirst ? undefined : "g");
16720
17779
  }
16721
17780
 
16722
- // node_modules/string-width/node_modules/strip-ansi/index.js
17781
+ // node_modules/strip-ansi/index.js
16723
17782
  function stripAnsi(string) {
16724
17783
  if (typeof string !== "string") {
16725
17784
  throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
@@ -16818,65 +17877,135 @@ var init_get_east_asian_width = __esm(() => {
16818
17877
  init_lookup();
16819
17878
  });
16820
17879
 
16821
- // node_modules/emoji-regex/index.js
16822
- var require_emoji_regex = __commonJS((exports, module) => {
16823
- module.exports = () => {
16824
- return /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E-\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED8\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDD1D\uDEEF]\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE]|[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE]|\uDEEF\u200D\uD83D\uDC69\uD83C[\uDFFB-\uDFFE])))?))?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3C-\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE8A\uDE8E-\uDEC2\uDEC6\uDEC8\uDECD-\uDEDC\uDEDF-\uDEEA\uDEEF]|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC30\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3\uDE70]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF]|\uDEEF\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g;
16825
- };
16826
- });
16827
-
16828
17880
  // node_modules/string-width/index.js
16829
- function stringWidth(string, options = {}) {
16830
- if (typeof string !== "string" || string.length === 0) {
17881
+ function isDoubleWidthNonRgiEmojiSequence(segment) {
17882
+ if (segment.length > 50) {
17883
+ return false;
17884
+ }
17885
+ if (unqualifiedKeycapRegex.test(segment)) {
17886
+ return true;
17887
+ }
17888
+ if (segment.includes("‍")) {
17889
+ const pictographics = segment.match(extendedPictographicRegex);
17890
+ return pictographics !== null && pictographics.length >= 2;
17891
+ }
17892
+ return false;
17893
+ }
17894
+ function baseVisible(segment) {
17895
+ return segment.replace(leadingNonPrintingRegex, "");
17896
+ }
17897
+ function isZeroWidthCluster(segment) {
17898
+ return zeroWidthClusterRegex.test(segment);
17899
+ }
17900
+ function isHangulLeadingJamo(codePoint) {
17901
+ return codePoint >= 4352 && codePoint <= 4447 || codePoint >= 43360 && codePoint <= 43388;
17902
+ }
17903
+ function isHangulVowelJamo(codePoint) {
17904
+ return codePoint >= 4448 && codePoint <= 4519 || codePoint >= 55216 && codePoint <= 55238;
17905
+ }
17906
+ function isHangulTrailingJamo(codePoint) {
17907
+ return codePoint >= 4520 && codePoint <= 4607 || codePoint >= 55243 && codePoint <= 55291;
17908
+ }
17909
+ function isHangulJamo(codePoint) {
17910
+ return isHangulLeadingJamo(codePoint) || isHangulVowelJamo(codePoint) || isHangulTrailingJamo(codePoint);
17911
+ }
17912
+ function hangulClusterWidth(visibleSegment, eastAsianWidthOptions) {
17913
+ const codePoints = [];
17914
+ for (const character of visibleSegment) {
17915
+ if (zeroWidthClusterRegex.test(character)) {
17916
+ continue;
17917
+ }
17918
+ codePoints.push(character.codePointAt(0));
17919
+ }
17920
+ if (codePoints.length === 0) {
17921
+ return;
17922
+ }
17923
+ let width = 0;
17924
+ for (let index = 0;index < codePoints.length; index++) {
17925
+ const codePoint = codePoints[index];
17926
+ if (!isHangulJamo(codePoint)) {
17927
+ if (width === 0) {
17928
+ return;
17929
+ }
17930
+ for (let remaining = index;remaining < codePoints.length; remaining++) {
17931
+ width += eastAsianWidth(codePoints[remaining], eastAsianWidthOptions);
17932
+ }
17933
+ return width;
17934
+ }
17935
+ if (isHangulLeadingJamo(codePoint) && isHangulVowelJamo(codePoints[index + 1])) {
17936
+ width += 2;
17937
+ index += isHangulTrailingJamo(codePoints[index + 2]) ? 2 : 1;
17938
+ continue;
17939
+ }
17940
+ width += eastAsianWidth(codePoint, eastAsianWidthOptions);
17941
+ }
17942
+ return width;
17943
+ }
17944
+ function trailingWidth(visibleSegment, eastAsianWidthOptions) {
17945
+ let extra = 0;
17946
+ let first = true;
17947
+ for (const character of visibleSegment) {
17948
+ if (first) {
17949
+ first = false;
17950
+ continue;
17951
+ }
17952
+ if (spacingMarkRegex.test(character) || character >= "＀" && character <= "￯") {
17953
+ extra += eastAsianWidth(character.codePointAt(0), eastAsianWidthOptions);
17954
+ }
17955
+ }
17956
+ return extra;
17957
+ }
17958
+ function stringWidth(input, options = {}) {
17959
+ if (typeof input !== "string" || input.length === 0) {
16831
17960
  return 0;
16832
17961
  }
16833
17962
  const {
16834
17963
  ambiguousIsNarrow = true,
16835
17964
  countAnsiEscapeCodes = false
16836
17965
  } = options;
16837
- if (!countAnsiEscapeCodes) {
17966
+ let string = input;
17967
+ if (!countAnsiEscapeCodes && (string.includes("\x1B") || string.includes("›"))) {
16838
17968
  string = stripAnsi(string);
16839
17969
  }
16840
17970
  if (string.length === 0) {
16841
17971
  return 0;
16842
17972
  }
17973
+ if (/^[\u0020-\u007E]*$/.test(string)) {
17974
+ return string.length;
17975
+ }
16843
17976
  let width = 0;
16844
17977
  const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
16845
- for (const { segment: character } of segmenter.segment(string)) {
16846
- const codePoint = character.codePointAt(0);
16847
- if (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) {
16848
- continue;
16849
- }
16850
- if (codePoint >= 8203 && codePoint <= 8207 || codePoint === 65279) {
16851
- continue;
16852
- }
16853
- if (codePoint >= 768 && codePoint <= 879 || codePoint >= 6832 && codePoint <= 6911 || codePoint >= 7616 && codePoint <= 7679 || codePoint >= 8400 && codePoint <= 8447 || codePoint >= 65056 && codePoint <= 65071) {
16854
- continue;
16855
- }
16856
- if (codePoint >= 55296 && codePoint <= 57343) {
16857
- continue;
16858
- }
16859
- if (codePoint >= 65024 && codePoint <= 65039) {
17978
+ for (const { segment } of segmenter.segment(string)) {
17979
+ if (isZeroWidthCluster(segment)) {
16860
17980
  continue;
16861
17981
  }
16862
- if (defaultIgnorableCodePointRegex.test(character)) {
17982
+ if (rgiEmojiRegex.test(segment) || isDoubleWidthNonRgiEmojiSequence(segment)) {
17983
+ width += 2;
16863
17984
  continue;
16864
17985
  }
16865
- if (import_emoji_regex.default().test(character)) {
16866
- width += 2;
17986
+ const visibleSegment = baseVisible(segment);
17987
+ const hangulWidth = hangulClusterWidth(visibleSegment, eastAsianWidthOptions);
17988
+ if (hangulWidth !== undefined) {
17989
+ width += hangulWidth;
16867
17990
  continue;
16868
17991
  }
17992
+ const codePoint = visibleSegment.codePointAt(0);
16869
17993
  width += eastAsianWidth(codePoint, eastAsianWidthOptions);
17994
+ width += trailingWidth(visibleSegment, eastAsianWidthOptions);
16870
17995
  }
16871
17996
  return width;
16872
17997
  }
16873
- var import_emoji_regex, segmenter, defaultIgnorableCodePointRegex;
17998
+ var segmenter, zeroWidthClusterRegex, leadingNonPrintingRegex, spacingMarkRegex, rgiEmojiRegex, unqualifiedKeycapRegex, extendedPictographicRegex;
16874
17999
  var init_string_width = __esm(() => {
16875
18000
  init_strip_ansi();
16876
18001
  init_get_east_asian_width();
16877
- import_emoji_regex = __toESM(require_emoji_regex(), 1);
16878
18002
  segmenter = new Intl.Segmenter;
16879
- defaultIgnorableCodePointRegex = /^\p{Default_Ignorable_Code_Point}$/u;
18003
+ zeroWidthClusterRegex = /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Format}|\p{Nonspacing_Mark}|\p{Enclosing_Mark}|\p{Surrogate})+$/v;
18004
+ leadingNonPrintingRegex = /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Nonspacing_Mark}\p{Enclosing_Mark}\p{Surrogate}]+/v;
18005
+ spacingMarkRegex = /\p{Spacing_Mark}/v;
18006
+ rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
18007
+ unqualifiedKeycapRegex = /^[\d#*]\u20E3$/;
18008
+ extendedPictographicRegex = /\p{Extended_Pictographic}/gu;
16880
18009
  });
16881
18010
 
16882
18011
  // src/ui/table.ts
@@ -17410,12 +18539,12 @@ __export(exports_manifest, {
17410
18539
  getCertMark: () => getCertMark,
17411
18540
  MANIFEST_PATH: () => MANIFEST_PATH
17412
18541
  });
17413
- import { existsSync as existsSync36, readFileSync as readFileSync23, mkdirSync as mkdirSync15, writeFileSync as writeFileSync14 } from "fs";
18542
+ import { existsSync as existsSync37, readFileSync as readFileSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
17414
18543
  import { homedir as homedir13 } from "os";
17415
- import { join as join30 } from "path";
18544
+ import { join as join32 } from "path";
17416
18545
  function readManifest(path = MANIFEST_PATH) {
17417
18546
  try {
17418
- if (existsSync36(path)) {
18547
+ if (existsSync37(path)) {
17419
18548
  const raw = JSON.parse(readFileSync23(path, "utf-8"));
17420
18549
  return { version: 1, certifications: raw.certifications ?? [] };
17421
18550
  }
@@ -17423,7 +18552,7 @@ function readManifest(path = MANIFEST_PATH) {
17423
18552
  return { version: 1, certifications: [] };
17424
18553
  }
17425
18554
  function saveManifest(m, path = MANIFEST_PATH) {
17426
- mkdirSync15(join30(homedir13(), ".mma"), { recursive: true });
18555
+ mkdirSync16(join32(homedir13(), ".mma"), { recursive: true });
17427
18556
  writeFileSync14(path, JSON.stringify(m, null, 2), "utf-8");
17428
18557
  }
17429
18558
  function upsertCertification(entry, path = MANIFEST_PATH) {
@@ -17458,7 +18587,7 @@ function getCertMark(model, providerUrl, currentVersion, path = MANIFEST_PATH) {
17458
18587
  }
17459
18588
  var MANIFEST_PATH;
17460
18589
  var init_manifest = __esm(() => {
17461
- MANIFEST_PATH = join30(homedir13(), ".mma", "certifications.json");
18590
+ MANIFEST_PATH = join32(homedir13(), ".mma", "certifications.json");
17462
18591
  });
17463
18592
 
17464
18593
  // node_modules/yaml/dist/nodes/identity.js
@@ -24581,8 +25710,8 @@ var init_scenarios = __esm(() => {
24581
25710
  });
24582
25711
 
24583
25712
  // src/modules/certification/loader.ts
24584
- import { existsSync as existsSync37, readdirSync as readdirSync11, readFileSync as readFileSync24 } from "fs";
24585
- import { join as join31 } from "path";
25713
+ import { existsSync as existsSync38, readdirSync as readdirSync14, readFileSync as readFileSync24 } from "fs";
25714
+ import { join as join33 } from "path";
24586
25715
  function validateScenario(s) {
24587
25716
  const errors2 = [];
24588
25717
  const isSkip = s.mode === "skip";
@@ -24631,12 +25760,12 @@ function loadScenarios(userDir) {
24631
25760
  else
24632
25761
  scenarios.push(s);
24633
25762
  }
24634
- if (userDir && existsSync37(userDir)) {
24635
- for (const file of readdirSync11(userDir)) {
25763
+ if (userDir && existsSync38(userDir)) {
25764
+ for (const file of readdirSync14(userDir)) {
24636
25765
  if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
24637
25766
  continue;
24638
25767
  try {
24639
- const raw = readFileSync24(join31(userDir, file), "utf-8");
25768
+ const raw = readFileSync24(join33(userDir, file), "utf-8");
24640
25769
  const data = $parse(raw);
24641
25770
  const parsed = normalizeScenario(data, file);
24642
25771
  const errs = validateScenario(parsed);
@@ -24689,8 +25818,8 @@ var init_loader3 = __esm(() => {
24689
25818
  });
24690
25819
 
24691
25820
  // src/modules/certification/fact-checker.ts
24692
- import { existsSync as existsSync38, readFileSync as readFileSync25, statSync as statSync7 } from "fs";
24693
- import { join as join32 } from "path";
25821
+ import { existsSync as existsSync39, readFileSync as readFileSync25, statSync as statSync8 } from "fs";
25822
+ import { join as join34 } from "path";
24694
25823
  function checkSandbox(sandboxDir, checks, exitCode, output) {
24695
25824
  const failures = [];
24696
25825
  for (const check of checks) {
@@ -24707,13 +25836,13 @@ function runCheck(sandboxDir, check, exitCode, output) {
24707
25836
  case "outputContains":
24708
25837
  return output.includes(check.text);
24709
25838
  case "fileExists":
24710
- return isFile(join32(sandboxDir, check.path));
25839
+ return isFile(join34(sandboxDir, check.path));
24711
25840
  case "fileNotExists":
24712
- return !existsSync38(join32(sandboxDir, check.path));
25841
+ return !existsSync39(join34(sandboxDir, check.path));
24713
25842
  case "dirExists":
24714
- return isDir(join32(sandboxDir, check.path));
25843
+ return isDir(join34(sandboxDir, check.path));
24715
25844
  case "fileContent": {
24716
- const abs = join32(sandboxDir, check.path);
25845
+ const abs = join34(sandboxDir, check.path);
24717
25846
  if (!isFile(abs))
24718
25847
  return false;
24719
25848
  const content = readFileSync25(abs, "utf-8");
@@ -24724,7 +25853,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
24724
25853
  return false;
24725
25854
  }
24726
25855
  case "fileRegex": {
24727
- const abs = join32(sandboxDir, check.path);
25856
+ const abs = join34(sandboxDir, check.path);
24728
25857
  if (!isFile(abs))
24729
25858
  return false;
24730
25859
  return new RegExp(check.pattern).test(readFileSync25(abs, "utf-8"));
@@ -24735,14 +25864,14 @@ function runCheck(sandboxDir, check, exitCode, output) {
24735
25864
  }
24736
25865
  function isFile(p) {
24737
25866
  try {
24738
- return existsSync38(p) && statSync7(p).isFile();
25867
+ return existsSync39(p) && statSync8(p).isFile();
24739
25868
  } catch {
24740
25869
  return false;
24741
25870
  }
24742
25871
  }
24743
25872
  function isDir(p) {
24744
25873
  try {
24745
- return existsSync38(p) && statSync7(p).isDirectory();
25874
+ return existsSync39(p) && statSync8(p).isDirectory();
24746
25875
  } catch {
24747
25876
  return false;
24748
25877
  }
@@ -24773,9 +25902,9 @@ var init_fact_checker = () => {};
24773
25902
 
24774
25903
  // src/modules/certification/runner.ts
24775
25904
  import { spawn as spawn6 } from "child_process";
24776
- import { existsSync as existsSync39, mkdirSync as mkdirSync16, rmSync as rmSync3, cpSync as cpSync2 } from "fs";
25905
+ import { existsSync as existsSync40, mkdirSync as mkdirSync17, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
24777
25906
  import { platform as platform5 } from "os";
24778
- import { join as join33, resolve as resolve19, dirname as dirname9 } from "path";
25907
+ import { join as join35, resolve as resolve19, dirname as dirname8 } from "path";
24779
25908
  async function runScenario(scenario, opts) {
24780
25909
  if (scenario.mode === "skip") {
24781
25910
  return {
@@ -24787,14 +25916,14 @@ async function runScenario(scenario, opts) {
24787
25916
  };
24788
25917
  }
24789
25918
  const reps = scenario.reps ?? opts.defaultReps;
24790
- const threshold = scenario.passThreshold ?? opts.defaultThreshold;
25919
+ const threshold = Math.min(scenario.passThreshold ?? opts.defaultThreshold, reps);
24791
25920
  const runner = opts.runner ?? defaultRunner;
24792
25921
  const timeoutMs = opts.timeoutMs ?? 120000;
24793
25922
  const entryPoint = resolveMmaEntry(opts.mmaRoot);
24794
25923
  let passed = 0;
24795
25924
  let firstError;
24796
25925
  for (let i = 1;i <= reps; i++) {
24797
- const sandbox = join33(opts.sandboxBase, `run-${scenario.id}-${i}`);
25926
+ const sandbox = join35(opts.sandboxBase, `run-${scenario.id}-${i}`);
24798
25927
  let failures = [];
24799
25928
  let exitCode = -1;
24800
25929
  let output = "";
@@ -24852,23 +25981,23 @@ ${res.stderr}`;
24852
25981
  };
24853
25982
  }
24854
25983
  function prepareSandbox(sandbox, scenario, mmaRoot) {
24855
- rmSync3(sandbox, { recursive: true, force: true });
24856
- mkdirSync16(sandbox, { recursive: true });
25984
+ rmSync4(sandbox, { recursive: true, force: true });
25985
+ mkdirSync17(sandbox, { recursive: true });
24857
25986
  for (const f of scenario.fixtures ?? []) {
24858
- const src = join33(mmaRoot, f.source);
24859
- if (!existsSync39(src)) {
25987
+ const src = join35(mmaRoot, f.source);
25988
+ if (!existsSync40(src)) {
24860
25989
  throw new Error(`fixture missing: ${f.source}`);
24861
25990
  }
24862
- const dest = join33(sandbox, f.dest);
24863
- mkdirSync16(dirname9(dest), { recursive: true });
25991
+ const dest = join35(sandbox, f.dest);
25992
+ mkdirSync17(dirname8(dest), { recursive: true });
24864
25993
  cpSync2(src, dest);
24865
25994
  }
24866
25995
  }
24867
25996
  function resolveMmaEntry(mmaRoot) {
24868
- const dev = join33(mmaRoot, "src", "cli", "main.ts");
24869
- if (existsSync39(dev))
25997
+ const dev = join35(mmaRoot, "src", "cli", "main.ts");
25998
+ if (existsSync40(dev))
24870
25999
  return dev;
24871
- return join33(mmaRoot, "dist", "main.js");
26000
+ return join35(mmaRoot, "dist", "main.js");
24872
26001
  }
24873
26002
  function findMmaRoot(fromDir) {
24874
26003
  const candidates = [
@@ -24876,7 +26005,7 @@ function findMmaRoot(fromDir) {
24876
26005
  resolve19(fromDir, "..")
24877
26006
  ];
24878
26007
  for (const c of candidates) {
24879
- if (existsSync39(join33(c, "package.json")))
26008
+ if (existsSync40(join35(c, "package.json")))
24880
26009
  return c;
24881
26010
  }
24882
26011
  return process.cwd();
@@ -24942,20 +26071,20 @@ __export(exports_cli, {
24942
26071
  certStatus: () => certStatus,
24943
26072
  certList: () => certList
24944
26073
  });
24945
- import { rmSync as rmSync4 } from "fs";
26074
+ import { rmSync as rmSync5 } from "fs";
24946
26075
  import { homedir as homedir14 } from "os";
24947
- import { join as join34, dirname as dirname10 } from "path";
26076
+ import { join as join36, dirname as dirname9 } from "path";
24948
26077
  import { fileURLToPath } from "url";
24949
- import { existsSync as existsSync40, readFileSync as readFileSync26 } from "fs";
26078
+ import { existsSync as existsSync41, readFileSync as readFileSync26 } from "fs";
24950
26079
  function readVersion() {
24951
- const candidates = [
24952
- join34(MMA_ROOT, "package.json")
24953
- ];
26080
+ const candidates = [join36(MMA_ROOT, "package.json")];
24954
26081
  for (const p of candidates) {
24955
- if (existsSync40(p)) {
24956
- const raw = JSON.parse(readFileSync26(p, "utf-8"));
24957
- if (raw.version)
24958
- return raw.version;
26082
+ if (existsSync41(p)) {
26083
+ try {
26084
+ const raw = JSON.parse(readFileSync26(p, "utf-8"));
26085
+ if (raw.version)
26086
+ return raw.version;
26087
+ } catch {}
24959
26088
  }
24960
26089
  }
24961
26090
  return "0.0.0";
@@ -24988,7 +26117,7 @@ async function certify(opts) {
24988
26117
  return;
24989
26118
  }
24990
26119
  console.log(t("cli.cert_started", { model: opts.name, provider: providerUrl }));
24991
- const sandboxBase = join34(process.cwd(), ".mma", "certification");
26120
+ const sandboxBase = join36(process.cwd(), ".mma", "certification");
24992
26121
  const results = [];
24993
26122
  const total = selected.length;
24994
26123
  let idx = 0;
@@ -24996,7 +26125,13 @@ async function certify(opts) {
24996
26125
  idx++;
24997
26126
  if (scenario.mode === "skip") {
24998
26127
  console.log(pc2.dim(`[${idx}/${total}] ${scenario.id} ... skipped`));
24999
- results.push({ id: scenario.id, title: scenario.title, status: "skipped", passed: 0, of: 0 });
26128
+ results.push({
26129
+ id: scenario.id,
26130
+ title: scenario.title,
26131
+ status: "skipped",
26132
+ passed: 0,
26133
+ of: 0
26134
+ });
25000
26135
  continue;
25001
26136
  }
25002
26137
  const res = await runScenario(scenario, {
@@ -25019,7 +26154,7 @@ async function certify(opts) {
25019
26154
  }
25020
26155
  if (opts.clean) {
25021
26156
  try {
25022
- rmSync4(sandboxBase, { recursive: true, force: true });
26157
+ rmSync5(sandboxBase, { recursive: true, force: true });
25023
26158
  } catch {}
25024
26159
  }
25025
26160
  const suite = summarize(results);
@@ -25094,9 +26229,9 @@ var init_cli = __esm(() => {
25094
26229
  init_loader3();
25095
26230
  init_runner2();
25096
26231
  init_manifest();
25097
- HERE = dirname10(fileURLToPath(import.meta.url));
26232
+ HERE = dirname9(fileURLToPath(import.meta.url));
25098
26233
  MMA_ROOT = findMmaRoot(HERE);
25099
- USER_SCENARIO_DIR = join34(homedir14(), ".mma", "certification", "scenarios");
26234
+ USER_SCENARIO_DIR = join36(homedir14(), ".mma", "certification", "scenarios");
25100
26235
  });
25101
26236
 
25102
26237
  // src/cli/repl-commands.ts
@@ -25105,21 +26240,23 @@ __export(exports_repl_commands, {
25105
26240
  registerAllCommands: () => registerAllCommands,
25106
26241
  COMMAND_GROUPS: () => COMMAND_GROUPS
25107
26242
  });
25108
- import { join as join36, dirname as dirname12 } from "path";
26243
+ import { join as join38, dirname as dirname11 } from "path";
25109
26244
  import { homedir as homedir16 } from "os";
25110
- import { existsSync as existsSync42, readFileSync as readFileSync28 } from "fs";
26245
+ import { existsSync as existsSync43, readFileSync as readFileSync28 } from "fs";
25111
26246
  import { fileURLToPath as fileURLToPath3 } from "url";
25112
26247
  function readVersion3() {
25113
- const here = dirname12(fileURLToPath3(import.meta.url));
26248
+ const here = dirname11(fileURLToPath3(import.meta.url));
25114
26249
  const candidates = [
25115
- join36(here, "..", "..", "package.json"),
25116
- join36(here, "..", "package.json")
26250
+ join38(here, "..", "..", "package.json"),
26251
+ join38(here, "..", "package.json")
25117
26252
  ];
25118
26253
  for (const p of candidates) {
25119
- if (existsSync42(p)) {
25120
- const raw = JSON.parse(readFileSync28(p, "utf8"));
25121
- if (raw.version)
25122
- return raw.version;
26254
+ if (existsSync43(p)) {
26255
+ try {
26256
+ const raw = JSON.parse(readFileSync28(p, "utf8"));
26257
+ if (raw.version)
26258
+ return raw.version;
26259
+ } catch {}
25123
26260
  }
25124
26261
  }
25125
26262
  return "0.0.0";
@@ -25180,7 +26317,7 @@ function registerMmaCommands(ctx) {
25180
26317
  }
25181
26318
  try {
25182
26319
  const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
25183
- const { existsSync: existsSync43 } = await import("fs");
26320
+ const { existsSync: existsSync44 } = await import("fs");
25184
26321
  const { resolve: resolve20 } = await import("path");
25185
26322
  let dataUrl;
25186
26323
  let label;
@@ -25200,7 +26337,7 @@ function registerMmaCommands(ctx) {
25200
26337
  label = source;
25201
26338
  } else {
25202
26339
  const absPath = resolve20(process.cwd(), source);
25203
- if (!existsSync43(absPath)) {
26340
+ if (!existsSync44(absPath)) {
25204
26341
  console.log(pc2.red(t("image.not_found", { path: source })));
25205
26342
  return;
25206
26343
  }
@@ -25279,7 +26416,7 @@ function registerMmaCommands(ctx) {
25279
26416
  console.log(pc2.yellow(t("repl.wizard_running")));
25280
26417
  await ctx.withExclusiveInput(async () => {
25281
26418
  const answers = await runSetup(ctx.rl);
25282
- const configPath = join36(homedir16(), ".mma", "config.json");
26419
+ const configPath = join38(homedir16(), ".mma", "config.json");
25283
26420
  ctx.config.provider.type = answers.provider;
25284
26421
  ctx.config.provider.baseUrl = answers.apiBase;
25285
26422
  ctx.config.provider.apiKey = answers.apiKey;
@@ -25333,7 +26470,7 @@ Excluded blocks: ${info.excluded.length}`));
25333
26470
  return;
25334
26471
  }
25335
26472
  ctx.config.provider.type = name;
25336
- const configPath = join36(homedir16(), ".mma", "config.json");
26473
+ const configPath = join38(homedir16(), ".mma", "config.json");
25337
26474
  saveConfig(ctx.config, configPath);
25338
26475
  await ctx.agent.reconfigure(ctx.config);
25339
26476
  console.log(pc2.green(t("repl.provider_set", { name })));
@@ -25389,7 +26526,7 @@ Excluded blocks: ${info.excluded.length}`));
25389
26526
  return;
25390
26527
  }
25391
26528
  ctx.config.model = name;
25392
- const configPath = join36(homedir16(), ".mma", "config.json");
26529
+ const configPath = join38(homedir16(), ".mma", "config.json");
25393
26530
  saveConfig(ctx.config, configPath);
25394
26531
  await ctx.agent.reconfigure(ctx.config);
25395
26532
  console.log(pc2.green(t("repl.model_set", { name })));
@@ -25414,7 +26551,7 @@ Excluded blocks: ${info.excluded.length}`));
25414
26551
  return;
25415
26552
  }
25416
26553
  ctx.config.contextWindow = size;
25417
- const configPath = join36(homedir16(), ".mma", "config.json");
26554
+ const configPath = join38(homedir16(), ".mma", "config.json");
25418
26555
  saveConfig(ctx.config, configPath);
25419
26556
  await ctx.agent.reconfigure(ctx.config);
25420
26557
  console.log(pc2.green(t("cli.context_set", { size })));
@@ -25433,10 +26570,10 @@ Excluded blocks: ${info.excluded.length}`));
25433
26570
  ctx.agent.shutdown();
25434
26571
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
25435
26572
  const { homedir: homedir17 } = await import("os");
25436
- const { join: join37 } = await import("path");
26573
+ const { join: join39 } = await import("path");
25437
26574
  const configDir = ctx.configDir;
25438
26575
  const baseDir = ctx.baseDir;
25439
- const projectConfigPath = join37(baseDir, ".mmrc");
26576
+ const projectConfigPath = join39(baseDir, ".mmrc");
25440
26577
  const freshConfig = loadConfig2({ configDir, projectConfigPath });
25441
26578
  Object.assign(ctx.config, freshConfig);
25442
26579
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
@@ -25736,14 +26873,14 @@ init_bootstrap();
25736
26873
  init_config2();
25737
26874
  init_setup();
25738
26875
  init_i18n();
25739
- import { join as join35, dirname as dirname11 } from "path";
26876
+ import { join as join37, dirname as dirname10 } from "path";
25740
26877
  import { homedir as homedir15 } from "os";
25741
- import { existsSync as existsSync41, readFileSync as readFileSync27 } from "fs";
26878
+ import { existsSync as existsSync42, readFileSync as readFileSync27 } from "fs";
25742
26879
 
25743
26880
  // src/cli/security-commands.ts
25744
26881
  init_bootstrap();
25745
26882
  init_config2();
25746
- import { join as join29 } from "path";
26883
+ import { join as join31 } from "path";
25747
26884
  import { homedir as homedir12 } from "os";
25748
26885
 
25749
26886
  // src/modules/security/security-policies.ts
@@ -26273,7 +27410,7 @@ function createSecurityCommand(program2) {
26273
27410
  }
26274
27411
  });
26275
27412
  securityCmd.command("set-policy").argument("<preset>", t("cli.security.preset")).description(t("cli.security.set_policy")).action(async (preset) => {
26276
- const configPath = join29(homedir12(), ".mma", "config.json");
27413
+ const configPath = join31(homedir12(), ".mma", "config.json");
26277
27414
  const { config: appConfig } = await bootstrap();
26278
27415
  const validPresets = ["strict", "balanced", "permissive"];
26279
27416
  if (!validPresets.includes(preset)) {
@@ -26288,7 +27425,7 @@ function createSecurityCommand(program2) {
26288
27425
  console.log(t("cli.security.policy_description", { description: policy.description }));
26289
27426
  });
26290
27427
  securityCmd.command("enable-encryption").description(t("cli.security.enable_encryption")).action(async () => {
26291
- const configPath = join29(homedir12(), ".mma", "config.json");
27428
+ const configPath = join31(homedir12(), ".mma", "config.json");
26292
27429
  const { config: appConfig } = await bootstrap();
26293
27430
  appConfig.security = appConfig.security || {};
26294
27431
  appConfig.security.sessionEncryption = {
@@ -26300,7 +27437,7 @@ function createSecurityCommand(program2) {
26300
27437
  console.log(t("cli.security.encryption_enabled"));
26301
27438
  });
26302
27439
  securityCmd.command("disable-encryption").description(t("cli.security.disable_encryption")).action(async () => {
26303
- const configPath = join29(homedir12(), ".mma", "config.json");
27440
+ const configPath = join31(homedir12(), ".mma", "config.json");
26304
27441
  const { config: appConfig } = await bootstrap();
26305
27442
  appConfig.security = appConfig.security || {};
26306
27443
  appConfig.security.sessionEncryption = {
@@ -26312,7 +27449,7 @@ function createSecurityCommand(program2) {
26312
27449
  console.log(t("cli.security.encryption_disabled"));
26313
27450
  });
26314
27451
  securityCmd.command("enable-audit").description(t("cli.security.enable_audit")).action(async () => {
26315
- const configPath = join29(homedir12(), ".mma", "config.json");
27452
+ const configPath = join31(homedir12(), ".mma", "config.json");
26316
27453
  const { config: appConfig } = await bootstrap();
26317
27454
  appConfig.security = appConfig.security || {};
26318
27455
  appConfig.security.auditNotifier = {
@@ -26326,7 +27463,7 @@ function createSecurityCommand(program2) {
26326
27463
  console.log(t("cli.security.audit_enabled"));
26327
27464
  });
26328
27465
  securityCmd.command("disable-audit").description(t("cli.security.disable_audit")).action(async () => {
26329
- const configPath = join29(homedir12(), ".mma", "config.json");
27466
+ const configPath = join31(homedir12(), ".mma", "config.json");
26330
27467
  const { config: appConfig } = await bootstrap();
26331
27468
  appConfig.security = appConfig.security || {};
26332
27469
  appConfig.security.auditNotifier = {
@@ -26360,16 +27497,18 @@ function createSecurityCommand(program2) {
26360
27497
  // src/cli/commands.ts
26361
27498
  import { fileURLToPath as fileURLToPath2 } from "url";
26362
27499
  function readVersion2() {
26363
- const here = dirname11(fileURLToPath2(import.meta.url));
27500
+ const here = dirname10(fileURLToPath2(import.meta.url));
26364
27501
  const candidates = [
26365
- join35(here, "..", "..", "package.json"),
26366
- join35(here, "..", "package.json")
27502
+ join37(here, "..", "..", "package.json"),
27503
+ join37(here, "..", "package.json")
26367
27504
  ];
26368
27505
  for (const p of candidates) {
26369
- if (existsSync41(p)) {
26370
- const raw = JSON.parse(readFileSync27(p, "utf8"));
26371
- if (raw.version)
26372
- return raw.version;
27506
+ if (existsSync42(p)) {
27507
+ try {
27508
+ const raw = JSON.parse(readFileSync27(p, "utf8"));
27509
+ if (raw.version)
27510
+ return raw.version;
27511
+ } catch {}
26373
27512
  }
26374
27513
  }
26375
27514
  return "0.0.0";
@@ -26379,7 +27518,7 @@ function createProgram() {
26379
27518
  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"));
26380
27519
  program2.command("init").description(t("cli.init")).action(async () => {
26381
27520
  const answers = await runSetup();
26382
- const configPath = join35(homedir15(), ".mma", "config.json");
27521
+ const configPath = join37(homedir15(), ".mma", "config.json");
26383
27522
  const { config } = await bootstrap();
26384
27523
  config.provider.type = answers.provider;
26385
27524
  config.provider.baseUrl = answers.apiBase;
@@ -26424,7 +27563,7 @@ function createProgram() {
26424
27563
  });
26425
27564
  const configCmd = program2.command("config").description(t("cli.manage_config"));
26426
27565
  configCmd.command("set").argument("<key>", t("cli.config_key")).argument("<value>", "Config value").description(t("cli.set_value")).action(async (key, value) => {
26427
- const configPath = join35(homedir15(), ".mma", "config.json");
27566
+ const configPath = join37(homedir15(), ".mma", "config.json");
26428
27567
  const { config } = await bootstrap();
26429
27568
  const keys = key.split(".");
26430
27569
  let obj = config;
@@ -26487,7 +27626,7 @@ function createProgram() {
26487
27626
  console.log(t("cli.model_hint"));
26488
27627
  });
26489
27628
  model.command("use").argument("<name>", "Model name").description(t("cli.set_model")).action(async (name) => {
26490
- const configPath = join35(homedir15(), ".mma", "config.json");
27629
+ const configPath = join37(homedir15(), ".mma", "config.json");
26491
27630
  const { config } = await bootstrap();
26492
27631
  config.model = name;
26493
27632
  saveConfig(config, configPath);
@@ -26523,7 +27662,7 @@ function createProgram() {
26523
27662
  await uncertify2(name, config);
26524
27663
  });
26525
27664
  program2.command("context").description(t("cli.manage_context")).argument("<size>", "Context window size in tokens").action(async (size) => {
26526
- const configPath = join35(homedir15(), ".mma", "config.json");
27665
+ const configPath = join37(homedir15(), ".mma", "config.json");
26527
27666
  const { config } = await bootstrap();
26528
27667
  const contextWindow = parseInt(size, 10);
26529
27668
  if (isNaN(contextWindow) || contextWindow < 1024) {
@@ -26541,7 +27680,7 @@ function createProgram() {
26541
27680
  console.log(t("cli.base_url"), config.provider.baseUrl);
26542
27681
  });
26543
27682
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
26544
- const configPath = join35(homedir15(), ".mma", "config.json");
27683
+ const configPath = join37(homedir15(), ".mma", "config.json");
26545
27684
  const { config } = await bootstrap();
26546
27685
  config.provider.type = name;
26547
27686
  saveConfig(config, configPath);
@@ -26593,8 +27732,8 @@ init_bootstrap();
26593
27732
  // src/cli/repl.ts
26594
27733
  init_colors();
26595
27734
  import * as readline2 from "readline";
26596
- import { existsSync as existsSync43, readFileSync as readFileSync29, writeFileSync as writeFileSync15 } from "fs";
26597
- import { join as join37, dirname as dirname13 } from "path";
27735
+ import { existsSync as existsSync44, readFileSync as readFileSync29, writeFileSync as writeFileSync15 } from "fs";
27736
+ import { join as join39, dirname as dirname12 } from "path";
26598
27737
  import { homedir as homedir17 } from "os";
26599
27738
  import { fileURLToPath as fileURLToPath4 } from "url";
26600
27739
 
@@ -27106,16 +28245,18 @@ init_box();
27106
28245
  init_i18n();
27107
28246
  init_repl_commands();
27108
28247
  function readVersion4() {
27109
- const here = dirname13(fileURLToPath4(import.meta.url));
28248
+ const here = dirname12(fileURLToPath4(import.meta.url));
27110
28249
  const candidates = [
27111
- join37(here, "..", "..", "package.json"),
27112
- join37(here, "..", "package.json")
28250
+ join39(here, "..", "..", "package.json"),
28251
+ join39(here, "..", "package.json")
27113
28252
  ];
27114
28253
  for (const p of candidates) {
27115
- if (existsSync43(p)) {
27116
- const raw = JSON.parse(readFileSync29(p, "utf8"));
27117
- if (raw.version)
27118
- return raw.version;
28254
+ if (existsSync44(p)) {
28255
+ try {
28256
+ const raw = JSON.parse(readFileSync29(p, "utf8"));
28257
+ if (raw.version)
28258
+ return raw.version;
28259
+ } catch {}
27119
28260
  }
27120
28261
  }
27121
28262
  return "0.0.0";
@@ -27158,16 +28299,18 @@ class Repl {
27158
28299
  sessionManager;
27159
28300
  skillsModule;
27160
28301
  pluginManager;
27161
- constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd) {
28302
+ logger;
28303
+ constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger) {
27162
28304
  this.agent = agent;
27163
28305
  this.config = config;
27164
28306
  this.sessionManager = sessionManager;
27165
28307
  this.skillsModule = skillsModule;
27166
28308
  this.pluginManager = pluginManager;
27167
- this.configDir = configDir || join37(homedir17(), ".mma");
28309
+ this.logger = logger;
28310
+ this.configDir = configDir || join39(homedir17(), ".mma");
27168
28311
  this.baseDir = baseDir || process.cwd();
27169
28312
  this.noAgentsMd = noAgentsMd === true;
27170
- this.historyPath = join37(homedir17(), ".mma", "repl-history");
28313
+ this.historyPath = join39(homedir17(), ".mma", "repl-history");
27171
28314
  this.loadHistory();
27172
28315
  this.rl = readline2.createInterface({
27173
28316
  input: process.stdin,
@@ -27188,7 +28331,7 @@ class Repl {
27188
28331
  this.setupListeners();
27189
28332
  }
27190
28333
  loadHistory() {
27191
- if (existsSync43(this.historyPath)) {
28334
+ if (existsSync44(this.historyPath)) {
27192
28335
  try {
27193
28336
  const raw = readFileSync29(this.historyPath, "utf-8");
27194
28337
  this.history = raw.split(`
@@ -27365,6 +28508,7 @@ ${t("image.clipboard_empty")}`));
27365
28508
  }
27366
28509
  this.pendingClipboardImage = null;
27367
28510
  }
28511
+ this.logger?.logREPL("user", input);
27368
28512
  process.stdout.write(`
27369
28513
  ` + pc2.green(t("repl.agent")));
27370
28514
  const renderer = new Renderer({
@@ -27387,6 +28531,7 @@ ${t("image.clipboard_empty")}`));
27387
28531
  renderer.flush();
27388
28532
  process.stdout.write(`
27389
28533
  `);
28534
+ this.logger?.logREPL(result.success ? "assistant" : "system", result.text?.slice(0, 400) || result.error || "");
27390
28535
  if (!result.success) {
27391
28536
  console.error(pc2.red(`${t("error.prefix")}${result.error}`));
27392
28537
  }
@@ -27519,11 +28664,11 @@ ${t("image.clipboard_empty")}`));
27519
28664
  row(t("repl.agents_label"), pc2.red(t("repl.disabled")));
27520
28665
  } else {
27521
28666
  const agentsMdCandidates = [
27522
- join37(this.baseDir, "AGENTS.md"),
27523
- join37(this.baseDir, ".mma", "AGENTS.md"),
27524
- join37(this.configDir, "AGENTS.md")
28667
+ join39(this.baseDir, "AGENTS.md"),
28668
+ join39(this.baseDir, ".mma", "AGENTS.md"),
28669
+ join39(this.configDir, "AGENTS.md")
27525
28670
  ];
27526
- const foundAgents = agentsMdCandidates.filter((p) => existsSync43(p));
28671
+ const foundAgents = agentsMdCandidates.filter((p) => existsSync44(p));
27527
28672
  if (foundAgents.length > 0) {
27528
28673
  for (const p of foundAgents) {
27529
28674
  row(t("repl.agents_label"), pc2.dim(p));
@@ -27534,7 +28679,7 @@ ${t("image.clipboard_empty")}`));
27534
28679
  }
27535
28680
  const meta = this.sessionManager?.getActiveMeta();
27536
28681
  if (meta) {
27537
- const sessionPath = join37(this.configDir, "sessions", meta.id);
28682
+ const sessionPath = join39(this.configDir, "sessions", meta.id);
27538
28683
  row(t("repl.session_label"), `${pc2.cyan(meta.name)} ${pc2.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc2.dim(sessionPath)}`);
27539
28684
  }
27540
28685
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
@@ -27560,8 +28705,8 @@ init_setup();
27560
28705
  init_config2();
27561
28706
  init_i18n();
27562
28707
  init_colors();
27563
- import { existsSync as existsSync44 } from "fs";
27564
- import { join as join38 } from "path";
28708
+ import { existsSync as existsSync45 } from "fs";
28709
+ import { join as join40 } from "path";
27565
28710
  import { homedir as homedir18 } from "os";
27566
28711
  async function main() {
27567
28712
  const program2 = createProgram();
@@ -27628,15 +28773,15 @@ async function main() {
27628
28773
  }
27629
28774
  agent.shutdown();
27630
28775
  } else {
27631
- const configPath = join38(homedir18(), ".mma", "config.json");
27632
- if (!existsSync44(configPath)) {
28776
+ const configPath = join40(homedir18(), ".mma", "config.json");
28777
+ if (!existsSync45(configPath)) {
27633
28778
  console.log(pc2.yellow(`
27634
28779
  ` + t("cli.first_run") + `
27635
28780
  `));
27636
28781
  const answers = await runSetup();
27637
28782
  const config2 = loadConfig({
27638
- configDir: join38(homedir18(), ".mma"),
27639
- projectConfigPath: projectDir ? join38(projectDir, ".mmrc") : join38(process.cwd(), ".mmrc")
28783
+ configDir: join40(homedir18(), ".mma"),
28784
+ projectConfigPath: projectDir ? join40(projectDir, ".mmrc") : join40(process.cwd(), ".mmrc")
27640
28785
  });
27641
28786
  config2.provider.type = answers.provider;
27642
28787
  config2.provider.baseUrl = answers.apiBase;
@@ -27685,9 +28830,10 @@ async function main() {
27685
28830
  skillsModule,
27686
28831
  pluginManager,
27687
28832
  configDir,
27688
- baseDir
28833
+ baseDir,
28834
+ logger
27689
28835
  } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
27690
- const repl = new Repl(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd);
28836
+ const repl = new Repl(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger);
27691
28837
  repl.start();
27692
28838
  }
27693
28839
  }