micro-models-agent 0.41.2 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +19 -9
  3. package/dist/cli/completer.js +37 -36
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +23 -48
  6. package/dist/cli/repl-commands.js +12 -40
  7. package/dist/cli/repl.js +87 -217
  8. package/dist/cli/security-commands.js +7 -5
  9. package/dist/cli/setup.js +26 -8
  10. package/dist/config/config.js +5 -52
  11. package/dist/config/defaults.js +5 -29
  12. package/dist/config/experts.js +1 -1
  13. package/dist/config/index.js +3 -3
  14. package/dist/config/security.js +10 -3
  15. package/dist/core/agent-moe.js +10 -2
  16. package/dist/core/agent.js +82 -273
  17. package/dist/core/bootstrap.js +13 -80
  18. package/dist/core/index.js +2 -2
  19. package/dist/core/prompt-builder.js +2 -23
  20. package/dist/core/session-logger.js +4 -46
  21. package/dist/i18n/en.json +2 -75
  22. package/dist/i18n/ru.json +1 -74
  23. package/dist/index.js +1 -1
  24. package/dist/llm/image-utils.js +5 -4
  25. package/dist/llm/index.js +4 -4
  26. package/dist/llm/model-loader.js +6 -6
  27. package/dist/llm/openai-compat.js +34 -40
  28. package/dist/llm/orchestrator.js +29 -33
  29. package/dist/llm/response.js +9 -9
  30. package/dist/logger/app-logger.js +1 -1
  31. package/dist/logger/index.js +1 -1
  32. package/dist/main.js +648 -183
  33. package/dist/migration/backup.js +13 -13
  34. package/dist/migration/detect.js +11 -11
  35. package/dist/migration/index.js +2 -2
  36. package/dist/modules/browser/actions.js +4 -34
  37. package/dist/modules/browser/bridge-server.mjs +202 -202
  38. package/dist/modules/browser/cookie-store.js +6 -6
  39. package/dist/modules/browser/index.js +5 -7
  40. package/dist/modules/browser/module.js +7 -8
  41. package/dist/modules/browser/session.js +84 -87
  42. package/dist/modules/browser/snapshot.js +58 -92
  43. package/dist/modules/browser/types.js +1 -4
  44. package/dist/modules/certification/cli.js +4 -2
  45. package/dist/modules/certification/fact-checker.js +3 -1
  46. package/dist/modules/certification/loader.js +9 -3
  47. package/dist/modules/certification/runner.js +4 -1
  48. package/dist/modules/context/index.js +1 -1
  49. package/dist/modules/context/manager.js +86 -160
  50. package/dist/modules/execution/auditor.js +25 -177
  51. package/dist/modules/execution/module.js +544 -201
  52. package/dist/modules/execution/moe-executor.js +0 -25
  53. package/dist/modules/execution/plan-store.js +3 -1
  54. package/dist/modules/execution/plan-validator.js +10 -10
  55. package/dist/modules/execution/planner.js +1 -6
  56. package/dist/modules/execution/stuck-detector.js +10 -173
  57. package/dist/modules/execution/verifier.js +42 -86
  58. package/dist/modules/hallucination/confidence.js +1 -8
  59. package/dist/modules/hallucination/detector.js +5 -2
  60. package/dist/modules/hallucination/factual.js +64 -3
  61. package/dist/modules/hallucination/index.js +1 -1
  62. package/dist/modules/hallucination/js-identifiers.js +0 -190
  63. package/dist/modules/hallucination/llm-judge.js +3 -1
  64. package/dist/modules/indexer/cache.js +7 -9
  65. package/dist/modules/indexer/index.js +3 -3
  66. package/dist/modules/indexer/module.js +42 -95
  67. package/dist/modules/indexer/walker.js +17 -17
  68. package/dist/modules/lsp/client.js +31 -74
  69. package/dist/modules/lsp/config.js +33 -87
  70. package/dist/modules/lsp/index.js +3 -3
  71. package/dist/modules/lsp/module.js +21 -185
  72. package/dist/modules/mcp/module.js +6 -2
  73. package/dist/modules/memory/index.js +1 -1
  74. package/dist/modules/memory/module.js +23 -71
  75. package/dist/modules/memory/search.js +9 -11
  76. package/dist/modules/memory/store.js +13 -13
  77. package/dist/modules/pipelines/engine.js +10 -10
  78. package/dist/modules/pipelines/index.js +3 -3
  79. package/dist/modules/pipelines/parser.js +14 -17
  80. package/dist/modules/pipelines/template.js +1 -1
  81. package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
  82. package/dist/modules/plugins/builtin/notify.js +2 -3
  83. package/dist/modules/plugins/index.js +1 -1
  84. package/dist/modules/plugins/loader.js +17 -59
  85. package/dist/modules/plugins/manager.js +17 -73
  86. package/dist/modules/processes/index.js +1 -1
  87. package/dist/modules/processes/registry.js +46 -135
  88. package/dist/modules/registry.js +2 -4
  89. package/dist/modules/security/audit-notifier.js +39 -39
  90. package/dist/modules/security/command-validator.js +8 -2
  91. package/dist/modules/security/data-sanitizer.js +9 -1
  92. package/dist/modules/security/encryption.js +56 -58
  93. package/dist/modules/security/network-validator.js +9 -1
  94. package/dist/modules/security/path-validator.js +3 -1
  95. package/dist/modules/security/security-policies.js +19 -3
  96. package/dist/modules/security/session-encryption.js +1 -1
  97. package/dist/modules/security/session-isolation.js +8 -8
  98. package/dist/modules/session/index.js +3 -3
  99. package/dist/modules/session/module.js +5 -5
  100. package/dist/modules/session/store.js +9 -3
  101. package/dist/modules/skills/module.js +2 -1
  102. package/dist/modules/updater/checker.js +6 -70
  103. package/dist/modules/updater/index.js +1 -2
  104. package/dist/modules/user-profile/compressor.js +2 -2
  105. package/dist/modules/user-profile/index.js +1 -1
  106. package/dist/modules/user-profile/profile.js +9 -9
  107. package/dist/tools/attach-image.js +1 -1
  108. package/dist/tools/bash.js +19 -178
  109. package/dist/tools/browser.js +29 -46
  110. package/dist/tools/executor.js +5 -4
  111. package/dist/tools/file-info.js +12 -13
  112. package/dist/tools/filter-tools.js +2 -9
  113. package/dist/tools/glob-tool.js +11 -11
  114. package/dist/tools/grep-tool.js +3 -1
  115. package/dist/tools/index.js +2 -13
  116. package/dist/tools/list-dir.js +17 -18
  117. package/dist/tools/load-skill.js +3 -1
  118. package/dist/tools/path-utils.js +4 -4
  119. package/dist/tools/pipeline-run.js +25 -25
  120. package/dist/tools/process-kill.js +11 -11
  121. package/dist/tools/process-list.js +22 -20
  122. package/dist/tools/process-log.js +18 -22
  123. package/dist/tools/question.js +3 -1
  124. package/dist/tools/read-file.js +2 -10
  125. package/dist/tools/recall.js +37 -44
  126. package/dist/tools/registry.js +4 -15
  127. package/dist/tools/remember.js +29 -29
  128. package/dist/tools/scope-check.js +9 -9
  129. package/dist/tools/subagent.js +9 -54
  130. package/dist/tools/user-input.js +1 -1
  131. package/dist/tools/web-browse.js +3 -3
  132. package/dist/tools/web-fetch.js +3 -3
  133. package/dist/tools/web-search.js +3 -3
  134. package/dist/tools/write-file.js +3 -1
  135. package/dist/ui/box.js +5 -1
  136. package/dist/ui/index.js +6 -6
  137. package/dist/ui/md-formatter.js +33 -33
  138. package/dist/ui/output.js +5 -5
  139. package/dist/ui/renderer.js +10 -15
  140. package/dist/ui/table.js +1 -1
  141. package/package.json +48 -48
  142. package/dist/cli/plugin-commands.js +0 -36
  143. package/dist/cli/run-result.js +0 -22
  144. package/dist/core/version.js +0 -24
  145. package/dist/modules/artifacts/store.js +0 -61
  146. package/dist/modules/browser/bridge-client.js +0 -199
  147. package/dist/modules/browser/bridge-path.js +0 -10
  148. package/dist/modules/browser/driver.js +0 -136
  149. package/dist/modules/context/chunk-query.js +0 -100
  150. package/dist/modules/context/fact-extractor.js +0 -162
  151. package/dist/modules/context/history.js +0 -15
  152. package/dist/modules/execution/audit-runners.js +0 -152
  153. package/dist/modules/execution/execution-plugin.js +0 -272
  154. package/dist/modules/execution/plan-tool.js +0 -508
  155. package/dist/modules/execution/windows-commands.js +0 -41
  156. package/dist/modules/indexer/project-profile.js +0 -183
  157. package/dist/modules/lsp/check-tool.js +0 -58
  158. package/dist/modules/lsp/command.js +0 -60
  159. package/dist/modules/lsp/probe.js +0 -76
  160. package/dist/modules/lsp/project-root.js +0 -32
  161. package/dist/modules/lsp/startup-check.js +0 -141
  162. package/dist/modules/processes/detect.js +0 -34
  163. package/dist/modules/skills/matcher.js +0 -27
  164. package/dist/modules/updater/module.js +0 -116
  165. package/dist/tools/chunk-query.js +0 -99
  166. package/dist/tools/download-file.js +0 -116
  167. package/dist/tools/enable-tools.js +0 -58
  168. package/dist/tools/hidden-tools-block.js +0 -37
  169. package/dist/ui/line-editor.js +0 -703
  170. package/dist/ui/line-math.js +0 -69
  171. package/dist/ui/plan-view.js +0 -103
package/dist/main.js CHANGED
@@ -2287,6 +2287,17 @@ var init_defaults = __esm(() => {
2287
2287
  viewportHeight: 720,
2288
2288
  navigationTimeout: 15000
2289
2289
  },
2290
+ webSearch: {
2291
+ enabled: true
2292
+ },
2293
+ errorWebSearch: {
2294
+ enabled: true,
2295
+ threshold: 5,
2296
+ maxResults: 5,
2297
+ maxQueryChars: 200,
2298
+ maxSearchesPerSession: 3,
2299
+ requestTimeoutMs: 1e4
2300
+ },
2290
2301
  ui: {
2291
2302
  spinner: true,
2292
2303
  toolStyle: "inline",
@@ -2476,6 +2487,7 @@ Summary:
2476
2487
  "tool.no_results": 'No results found for "{query}"',
2477
2488
  "tool.search_results": `Search results for "{query}":
2478
2489
  {results}`,
2490
+ "tool.web_search_disabled": "Web search is disabled (webSearch.enabled: false). Use file/system tools only.",
2479
2491
  "tool.history_results": `History results for "{query}":
2480
2492
  {results}`,
2481
2493
  "tool.no_history": 'No history entries matching "{query}"',
@@ -2540,6 +2552,15 @@ Command: {command}`,
2540
2552
  "plan.active_in_progress": 'Cannot create a new plan: active plan {id} already has progress ({done}/{total} done, current: step {current}). Resume it — use "plan show" to view, then continue working. To replace it, call "plan abort" first, then "plan create" again.',
2541
2553
  "plan.id_ignored": 'note: plan ids are auto-generated; use "plan switch" to activate an existing plan by id.',
2542
2554
  "plan.existing_fresh": "note: the previous active plan had no progress and was preserved as a draft.",
2555
+ "plan.already_active": "Plan {id} is already active — nothing to switch.",
2556
+ "plan.already_archived": "Plan {id} is already archived — nothing to abort.",
2557
+ "plan.aborted_id": "Plan {id} aborted and archived.",
2558
+ "plan.delete_no_id": "Provide a plan id to delete (plan delete id=plan_xxx).",
2559
+ "plan.deleted": "Plan {id} deleted permanently.",
2560
+ "plan.purged": "Deleted {count} plan(s). Plan storage is empty.",
2561
+ "plan.no_deliverables": "note: step {step} names no files — its completion cannot be auto-verified; run an explicit check (e.g. build/tests) before your final answer.",
2562
+ "plan.list_legend": "[*] active · [ ] draft · [-] archived",
2563
+ "exec.plan_nudge": "You made {count} file-changing tool call(s) without a plan. For tasks that create or modify files, or span multiple steps, create a plan first (plan create) with concrete steps (exact filenames, commands, deliverables), then continue.",
2543
2564
  "todo.added": "Added {count} todo(s): {items}",
2544
2565
  "todo.marked_done": "Marked {count} item(s) as done",
2545
2566
  "todo.no_active": "No active todos",
@@ -2561,6 +2582,7 @@ Command: {command}`,
2561
2582
  "lsp.check_notfound": "Path not found: {path}",
2562
2583
  "lsp.check_unsupported": "No LSP server configured for: {path}",
2563
2584
  "lsp.check_clean": "No errors or warnings detected ({count} file(s) checked).",
2585
+ "lsp.checked_files": "Checked {count} file(s):",
2564
2586
  "lsp.startup_header": "[Existing project errors (checked at session start) — fix these before continuing]:",
2565
2587
  "cli.description": "Micro Models Agent — AI coding agent for small models",
2566
2588
  "cli.init": "Run interactive setup wizard",
@@ -2860,6 +2882,11 @@ Use this knowledge to answer the user's question.`,
2860
2882
  {hints}`,
2861
2883
  "exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
2862
2884
  "exec.forbidden_cmd": 'STOP using "{cmd}" via the bash tool — it is not a native Windows cmd.exe command and has failed repeatedly this session. Use the dedicated tool instead: grep → the grep tool, ls/dir → list_dir, find → glob, rm → delete_file, sed → edit_file, touch → write_file, which → `where`, cp/mv → move_file, diff → read_file. Do NOT call bash for this purpose again.',
2885
+ "exec.error_search_results": `The error "{sig}" has occurred {count} times. Web search found:
2886
+ {results}
2887
+ Apply a matching solution from these results. If none is relevant — do NOT repeat the same approach; change strategy or honestly report being stuck.`,
2888
+ "exec.error_search_failed": 'Web search returned nothing for "{query}".',
2889
+ "exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
2863
2890
  "exec.npm_exec_hint": '"could not determine executable to run" — no "bin" for that package/script. Use "npm run <script>" (script must exist in package.json) or "bunx <pkg>" for a package that declares a bin.',
2864
2891
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2865
2892
  "hall.short_response": "Response too short or empty",
@@ -2909,6 +2936,7 @@ Use this knowledge to answer the user's question.`,
2909
2936
  "ui.success_prefix": "✓ ",
2910
2937
  "ui.warning_prefix": "⚠ ",
2911
2938
  "ui.thinking": "Thinking…",
2939
+ "ui.tool_running": "{tool}…",
2912
2940
  "ui.step_context": "step {id}: {desc}",
2913
2941
  "indexer.map_header": "Project map",
2914
2942
  "indexer.top_directories": "Top directories",
@@ -3111,6 +3139,7 @@ var init_ru = __esm(() => {
3111
3139
  "tool.no_results": 'Нет результатов по "{query}"',
3112
3140
  "tool.search_results": `Результаты поиска "{query}":
3113
3141
  {results}`,
3142
+ "tool.web_search_disabled": "Веб-поиск отключён (webSearch.enabled: false). Используй только файловые и системные инструменты.",
3114
3143
  "tool.history_results": `Результаты истории "{query}":
3115
3144
  {results}`,
3116
3145
  "tool.no_history": 'Нет записей истории по "{query}"',
@@ -3175,6 +3204,15 @@ var init_ru = __esm(() => {
3175
3204
  "plan.active_in_progress": 'Нельзя создать новый план: активный план {id} уже имеет прогресс ({done}/{total} выполнено, текущий: шаг {current}). Продолжай его — вызови "plan show", чтобы увидеть, и продолжай работу. Чтобы заменить план, сначала вызови "plan abort", затем "plan create".',
3176
3205
  "plan.id_ignored": 'примечание: id плана генерируется автоматически; используй "plan switch", чтобы активировать существующий план по id.',
3177
3206
  "plan.existing_fresh": "примечание: предыдущий активный план не имел прогресса и сохранён как черновик.",
3207
+ "plan.already_active": "План {id} уже активен — переключаться некуда.",
3208
+ "plan.already_archived": "План {id} уже в архиве — отменять нечего.",
3209
+ "plan.aborted_id": "План {id} отменён и архивирован.",
3210
+ "plan.delete_no_id": "Укажите id плана для удаления (plan delete id=plan_xxx).",
3211
+ "plan.deleted": "План {id} удалён окончательно.",
3212
+ "plan.purged": "Удалено планов: {count}. Хранилище планов пусто.",
3213
+ "plan.no_deliverables": "примечание: шаг {step} не называет файлов — его выполнение нельзя проверить автоматически; перед финальным ответом явно проверь результат (например, запусти сборку/тесты).",
3214
+ "plan.list_legend": "[*] активный · [ ] черновик · [-] архив",
3215
+ "exec.plan_nudge": "Ты сделал(а) {count} вызов(ов), изменяющих файлы, без плана. Для задач, создающих/изменяющих файлы или состоящих из нескольких шагов, сначала создай план (plan create) с конкретными шагами (точные имена файлов, команды, результаты), а потом продолжай.",
3178
3216
  "todo.added": "Добавлено {count} задач: {items}",
3179
3217
  "todo.marked_done": "Отмечено выполненными: {count}",
3180
3218
  "todo.no_active": "Нет активных задач",
@@ -3196,6 +3234,7 @@ var init_ru = __esm(() => {
3196
3234
  "lsp.check_notfound": "Путь не найден: {path}",
3197
3235
  "lsp.check_unsupported": "Для файла не настроен LSP-сервер: {path}",
3198
3236
  "lsp.check_clean": "Ошибок и предупреждений не обнаружено (проверено файлов: {count}).",
3237
+ "lsp.checked_files": "Проверено файлов: {count}:",
3199
3238
  "lsp.startup_header": "[Существующие ошибки проекта (проверено при старте сессии) — исправьте их перед продолжением]:",
3200
3239
  "cli.description": "Micro Models Agent — ИИ-агент для кодинга на малых моделях",
3201
3240
  "cli.init": "Запустить мастер настройки",
@@ -3495,6 +3534,11 @@ var init_ru = __esm(() => {
3495
3534
  {hints}`,
3496
3535
  "exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
3497
3536
  "exec.forbidden_cmd": 'ПРЕКРАТИ использовать "{cmd}" через bash — это не команда Windows cmd.exe, и она уже неоднократно падала в этой сессии. Используй предназначенный тул: grep → тул grep, ls/dir → list_dir, find → glob, rm → delete_file, sed → edit_file, touch → write_file, which → `where`, cp/mv → move_file, diff → read_file. Больше не вызывай bash для этого.',
3537
+ "exec.error_search_results": `Ошибка "{sig}" повторилась {count} раз. Поиск в интернете нашёл:
3538
+ {results}
3539
+ Примени подходящее решение из результатов. Если ни один результат не релевантен — НЕ повторяй тот же подход, смени стратегию или честно сообщи о застревании.`,
3540
+ "exec.error_search_failed": 'Поиск в интернете для "{query}" ничего не дал.',
3541
+ "exec.error_search_no_query": "Текст ошибки незначимый — поиск в интернете пропущен.",
3498
3542
  "exec.npm_exec_hint": '"could not determine executable to run" — у пакета/скрипта нет "bin". Используй "npm run <script>" (скрипт должен быть в package.json) или "bunx <pkg>" для пакета с объявленным bin.',
3499
3543
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3500
3544
  "hall.short_response": "Слишком короткий или пустой ответ",
@@ -3544,6 +3588,7 @@ var init_ru = __esm(() => {
3544
3588
  "ui.success_prefix": "✓ ",
3545
3589
  "ui.warning_prefix": "⚠ ",
3546
3590
  "ui.thinking": "Думаю…",
3591
+ "ui.tool_running": "{tool}…",
3547
3592
  "ui.step_context": "шаг {id}: {desc}",
3548
3593
  "indexer.map_header": "Карта проекта",
3549
3594
  "indexer.top_directories": "Основные директории",
@@ -9464,6 +9509,41 @@ function filterToolsByTags(tools, toolTags) {
9464
9509
  }
9465
9510
 
9466
9511
  // src/modules/execution/stuck-detector.ts
9512
+ function normalizeErrorSignature(toolName, output) {
9513
+ if (!output)
9514
+ return null;
9515
+ const cleaned = output.replace(ANSI_RE, "").replace(ERROR_LOC_RE, " <path>").split(`
9516
+ `).map((l) => l.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
9517
+ if (!cleaned)
9518
+ return null;
9519
+ return `${toolName}::${cleaned.slice(0, SIGNATURE_MAX_CHARS)}`;
9520
+ }
9521
+ function isSearchableError(output) {
9522
+ if (!output || !output.trim())
9523
+ return false;
9524
+ const text = output.trim();
9525
+ return SEARCHABLE_PATTERNS.some((p) => p.test(text));
9526
+ }
9527
+ function buildSearchQuery(stepDescription, lastBashCommand, lastErrorOutput, toolName, maxQueryChars) {
9528
+ const errorText = (lastErrorOutput || "").replace(ANSI_RE, "").replace(ERROR_LOC_RE, " <path>").split(`
9529
+ `).map((l) => l.trim()).filter(Boolean).slice(0, 3).join(" ").replace(/\s+/g, " ").trim();
9530
+ const context = `${stepDescription} ${lastBashCommand}`.toLowerCase();
9531
+ const runtime = RUNTIME_WORDS.find((w) => context.includes(w)) || "";
9532
+ const framework = FRAMEWORK_WORDS.find((w) => context.includes(w)) || "";
9533
+ let query;
9534
+ if (errorText) {
9535
+ query = runtime && !errorText.toLowerCase().includes(runtime) ? `${runtime} ${errorText}` : errorText;
9536
+ if (framework && !query.toLowerCase().includes(framework))
9537
+ query = `${query} ${framework}`;
9538
+ query = `${query} how to fix`;
9539
+ } else if (runtime || framework) {
9540
+ query = `${[runtime, framework, "error", "how to fix"].filter(Boolean).join(" ")}`;
9541
+ } else {
9542
+ query = `${toolName} ${stepDescription} how to fix`;
9543
+ }
9544
+ return query.slice(0, maxQueryChars);
9545
+ }
9546
+
9467
9547
  class StuckDetector {
9468
9548
  threshold;
9469
9549
  errorThreshold;
@@ -9485,9 +9565,13 @@ class StuckDetector {
9485
9565
  lastBashOutput = "";
9486
9566
  emptyBashRunCount = 0;
9487
9567
  readOnlyStreak = 0;
9488
- constructor(threshold = 6, errorThreshold = 3) {
9568
+ errorSignatureCounts = new Map;
9569
+ searchedErrorSignatures = new Set;
9570
+ webSearchThreshold;
9571
+ constructor(threshold = 6, errorThreshold = 3, webSearchThreshold = 5) {
9489
9572
  this.threshold = threshold;
9490
9573
  this.errorThreshold = errorThreshold;
9574
+ this.webSearchThreshold = webSearchThreshold;
9491
9575
  }
9492
9576
  recordIteration(stepId) {
9493
9577
  if (stepId === this.currentStepId) {
@@ -9517,6 +9601,10 @@ class StuckDetector {
9517
9601
  this.lastFailedTool = toolName;
9518
9602
  if (output)
9519
9603
  this.lastErrorOutput = output;
9604
+ const sig = normalizeErrorSignature(toolName, output);
9605
+ if (sig) {
9606
+ this.errorSignatureCounts.set(sig, (this.errorSignatureCounts.get(sig) || 0) + 1);
9607
+ }
9520
9608
  }
9521
9609
  getLastErrorOutput() {
9522
9610
  return this.lastErrorOutput;
@@ -9540,6 +9628,26 @@ class StuckDetector {
9540
9628
  getLastFailedTool() {
9541
9629
  return this.lastFailedTool;
9542
9630
  }
9631
+ getRepeatedErrorSignature() {
9632
+ for (const [sig, count] of this.errorSignatureCounts) {
9633
+ if (count >= this.webSearchThreshold && !this.searchedErrorSignatures.has(sig)) {
9634
+ return sig;
9635
+ }
9636
+ }
9637
+ return null;
9638
+ }
9639
+ getErrorSignatureCount(sig) {
9640
+ return this.errorSignatureCounts.get(sig) || 0;
9641
+ }
9642
+ markErrorSearched(sig) {
9643
+ this.searchedErrorSignatures.add(sig);
9644
+ }
9645
+ unmarkErrorSearched(sig) {
9646
+ this.searchedErrorSignatures.delete(sig);
9647
+ }
9648
+ hasErrorSearched(sig) {
9649
+ return this.searchedErrorSignatures.has(sig);
9650
+ }
9543
9651
  getIterationsOnCurrentStep() {
9544
9652
  return this.iterationsOnCurrentStep;
9545
9653
  }
@@ -9812,6 +9920,8 @@ class StuckDetector {
9812
9920
  this.lastBashOutput = "";
9813
9921
  this.emptyBashRunCount = 0;
9814
9922
  this.readOnlyStreak = 0;
9923
+ this.errorSignatureCounts.clear();
9924
+ this.searchedErrorSignatures.clear();
9815
9925
  }
9816
9926
  resetStepProgress() {
9817
9927
  this.currentStepId = null;
@@ -9825,6 +9935,8 @@ class StuckDetector {
9825
9935
  this.lastBashCommand = "";
9826
9936
  this.lastBashOutput = "";
9827
9937
  this.emptyBashRunCount = 0;
9938
+ this.errorSignatureCounts.clear();
9939
+ this.searchedErrorSignatures.clear();
9828
9940
  }
9829
9941
  resetStepState(stepId) {
9830
9942
  this.currentStepId = stepId;
@@ -9839,9 +9951,11 @@ class StuckDetector {
9839
9951
  this.lastBashOutput = "";
9840
9952
  this.emptyBashRunCount = 0;
9841
9953
  this.readOnlyStreak = 0;
9954
+ this.errorSignatureCounts.clear();
9955
+ this.searchedErrorSignatures.clear();
9842
9956
  }
9843
9957
  }
9844
- var READ_ONLY_TOOLS, READ_ONLY_LOOP_THRESHOLD = 10;
9958
+ var READ_ONLY_TOOLS, READ_ONLY_LOOP_THRESHOLD = 10, ANSI_RE, ERROR_LOC_RE, SIGNATURE_MAX_CHARS = 120, SEARCHABLE_PATTERNS, RUNTIME_WORDS, FRAMEWORK_WORDS;
9845
9959
  var init_stuck_detector = __esm(() => {
9846
9960
  init_i18n();
9847
9961
  READ_ONLY_TOOLS = new Set([
@@ -9865,6 +9979,55 @@ var init_stuck_detector = __esm(() => {
9865
9979
  "verify",
9866
9980
  "load_skill"
9867
9981
  ]);
9982
+ ANSI_RE = /\x1b\[[0-9;]*m/g;
9983
+ ERROR_LOC_RE = /(?:[A-Za-z]:[\\/])?(?:[\w.@+-]+[\\/])+[\w.@+-]+\.[a-zA-Z0-9]{1,6}(?::\d+(?::\d+)?)?/g;
9984
+ SEARCHABLE_PATTERNS = [
9985
+ /error TS\d+/i,
9986
+ /ERR_[A-Z_]+/,
9987
+ /ENOENT|EACCES|EPERM|ECONNREFUSED|ETIMEDOUT|ECONNRESET|ENOTFOUND|EADDRINUSE/i,
9988
+ /Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i,
9989
+ /TypeError|ReferenceError|SyntaxError|RangeError/i,
9990
+ /is not a function|is not defined|is not a constructor/i,
9991
+ /Cannot read properties of/i,
9992
+ /failed to (compile|build|resolve|parse)/i,
9993
+ /fatal error|panic/i
9994
+ ];
9995
+ RUNTIME_WORDS = [
9996
+ "bun",
9997
+ "node",
9998
+ "deno",
9999
+ "npm",
10000
+ "yarn",
10001
+ "pnpm",
10002
+ "pip",
10003
+ "pip3",
10004
+ "python",
10005
+ "python3",
10006
+ "tsc",
10007
+ "tsx",
10008
+ "npx",
10009
+ "go",
10010
+ "cargo",
10011
+ "gradle",
10012
+ "maven",
10013
+ "make"
10014
+ ];
10015
+ FRAMEWORK_WORDS = [
10016
+ "react",
10017
+ "vue",
10018
+ "angular",
10019
+ "svelte",
10020
+ "vite",
10021
+ "next",
10022
+ "nest",
10023
+ "express",
10024
+ "fastify",
10025
+ "flask",
10026
+ "django",
10027
+ "spring",
10028
+ "webpack",
10029
+ "tailwind"
10030
+ ];
9868
10031
  });
9869
10032
 
9870
10033
  // src/modules/artifacts/store.ts
@@ -10453,8 +10616,46 @@ var init_js_identifiers = __esm(() => {
10453
10616
  });
10454
10617
 
10455
10618
  // src/modules/execution/audit-runners.ts
10456
- import { existsSync as existsSync20, readdirSync as readdirSync5 } from "fs";
10619
+ import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
10457
10620
  import { dirname as dirname7, join as join11, resolve as resolve11 } from "path";
10621
+ function resolveTestCommand(dir) {
10622
+ const pkgPath = join11(dir, "package.json");
10623
+ if (existsSync20(pkgPath)) {
10624
+ try {
10625
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
10626
+ const script = pkg?.scripts?.test;
10627
+ if (typeof script === "string" && script.trim())
10628
+ return script.trim();
10629
+ } catch {}
10630
+ }
10631
+ for (const f of [
10632
+ "vitest.config.ts",
10633
+ "vitest.config.js",
10634
+ "vitest.config.mjs",
10635
+ "jest.config.js",
10636
+ "jest.config.ts",
10637
+ "jest.config.mjs",
10638
+ "jest.config.cjs",
10639
+ "bunfig.toml"
10640
+ ]) {
10641
+ if (existsSync20(join11(dir, f))) {
10642
+ if (f.startsWith("vitest"))
10643
+ return "bunx vitest run";
10644
+ if (f.startsWith("jest"))
10645
+ return "npx --no-install jest";
10646
+ if (f === "bunfig.toml")
10647
+ return "bun test";
10648
+ }
10649
+ }
10650
+ if (existsSync20(join11(dir, "pyproject.toml")) || existsSync20(join11(dir, "pytest.ini")) || existsSync20(join11(dir, "conftest.py"))) {
10651
+ return "python -m pytest -q";
10652
+ }
10653
+ if (existsSync20(join11(dir, "go.mod")))
10654
+ return "go test ./...";
10655
+ if (existsSync20(join11(dir, "Cargo.toml")))
10656
+ return "cargo test";
10657
+ return "bun test";
10658
+ }
10458
10659
  function findTestFile(dir, depth = 0) {
10459
10660
  if (depth > 5)
10460
10661
  return null;
@@ -10472,7 +10673,7 @@ function findTestFile(dir, depth = 0) {
10472
10673
  const found = findTestFile(full, depth + 1);
10473
10674
  if (found)
10474
10675
  return found;
10475
- } else if (TEST_EXT_RE.test(e.name)) {
10676
+ } else if (TEST_EXT_RE.test(e.name) || PY_TEST_RE.test(e.name)) {
10476
10677
  return full;
10477
10678
  }
10478
10679
  }
@@ -10493,7 +10694,8 @@ function extractFailingNames(output, limit = 5) {
10493
10694
  return names;
10494
10695
  }
10495
10696
  async function runTests(baseDir) {
10496
- const entry = processRegistry.start("bun test", baseDir);
10697
+ const command = resolveTestCommand(baseDir);
10698
+ const entry = processRegistry.start(command, baseDir);
10497
10699
  const exited = await processRegistry.waitForExit(entry.id, 90000);
10498
10700
  const output = entry.log.join(`
10499
10701
  `);
@@ -10504,8 +10706,8 @@ async function runTests(baseDir) {
10504
10706
  passed: true,
10505
10707
  failed: 0,
10506
10708
  passedCount: 0,
10507
- detail: "test run timed out after 90s — result unknown",
10508
- command: "bun test"
10709
+ detail: `test run timed out after 90s — result unknown`,
10710
+ command
10509
10711
  };
10510
10712
  }
10511
10713
  const run = detectTestResults(output);
@@ -10516,7 +10718,7 @@ async function runTests(baseDir) {
10516
10718
  failed: entry.exitCode === 0 ? 0 : 1,
10517
10719
  passedCount: 0,
10518
10720
  detail: output.slice(0, 200).trim(),
10519
- command: "bun test"
10721
+ command
10520
10722
  };
10521
10723
  }
10522
10724
  const names = extractFailingNames(output);
@@ -10526,7 +10728,7 @@ async function runTests(baseDir) {
10526
10728
  failed: run.failed,
10527
10729
  passedCount: run.passed,
10528
10730
  detail: names.length ? names.join("; ") : run.summary || `${run.failed} failed / ${run.passed} passed`,
10529
- command: "bun test"
10731
+ command
10530
10732
  };
10531
10733
  }
10532
10734
  function parseTypecheckErrors(output) {
@@ -10563,7 +10765,7 @@ async function runTypecheck(baseDir) {
10563
10765
  return null;
10564
10766
  return parseTypecheckErrors(output);
10565
10767
  }
10566
- var SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE;
10768
+ var SKIP_DIRS, TEST_EXT_RE, PY_TEST_RE, TEST_STEP_RE;
10567
10769
  var init_audit_runners = __esm(() => {
10568
10770
  init_bash();
10569
10771
  init_processes();
@@ -10579,6 +10781,7 @@ var init_audit_runners = __esm(() => {
10579
10781
  "vendor"
10580
10782
  ]);
10581
10783
  TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
10784
+ PY_TEST_RE = /^test_.*\.py$|^.*_test\.py$/i;
10582
10785
  TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
10583
10786
  });
10584
10787
 
@@ -13263,7 +13466,7 @@ ${joined}` }
13263
13466
  var DEFAULT_CHUNK_SYSTEM_PROMPT = 'Answer the query using ONLY the provided text. Be concise. If the text does not contain the answer, say "NO_EVIDENCE".', DEFAULT_SYNTHESIS_SYSTEM_PROMPT = "You are given a query and per-chunk answers over a large text. Produce the final answer to the query, combining evidence from the chunks. If no chunk had evidence, say so.";
13264
13467
 
13265
13468
  // src/tools/chunk-query.ts
13266
- import { readFileSync as readFileSync10 } from "node:fs";
13469
+ import { readFileSync as readFileSync11 } from "node:fs";
13267
13470
  import { resolve as resolve16 } from "node:path";
13268
13471
  var chunkQueryTool;
13269
13472
  var init_chunk_query = __esm(() => {
@@ -13332,7 +13535,7 @@ var init_chunk_query = __esm(() => {
13332
13535
  return { success: false, output: `[SCOPE] ${check.reason || "Path not allowed"}` };
13333
13536
  }
13334
13537
  try {
13335
- content = readFileSync10(resolve16(ctx.baseDir, inputPath), "utf8");
13538
+ content = readFileSync11(resolve16(ctx.baseDir, inputPath), "utf8");
13336
13539
  } catch (e) {
13337
13540
  return { success: false, output: `Cannot read ${inputPath}: ${e.message}` };
13338
13541
  }
@@ -13425,6 +13628,43 @@ var init_network_validator = __esm(() => {
13425
13628
  });
13426
13629
 
13427
13630
  // src/tools/web-search.ts
13631
+ async function performWebSearch(query, numResults, networkConfig, requestTimeoutMs) {
13632
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13633
+ const validation = isUrlAllowed(url, networkConfig);
13634
+ if (!validation.allowed) {
13635
+ return {
13636
+ success: false,
13637
+ blocked: true,
13638
+ results: [],
13639
+ error: validation.reason || "URL blocked by security policy"
13640
+ };
13641
+ }
13642
+ try {
13643
+ const response = await fetch(url, {
13644
+ signal: AbortSignal.timeout(requestTimeoutMs ?? networkConfig?.requestTimeout ?? 1e4)
13645
+ });
13646
+ const html = await response.text();
13647
+ const results = [];
13648
+ const snippetRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
13649
+ let match;
13650
+ while ((match = snippetRegex.exec(html)) !== null && results.length < numResults) {
13651
+ results.push({
13652
+ url: match[1].trim(),
13653
+ title: match[2].replace(/<[^>]+>/g, "").trim(),
13654
+ snippet: match[3].replace(/<[^>]+>/g, "").trim()
13655
+ });
13656
+ }
13657
+ return { success: true, results };
13658
+ } catch (err) {
13659
+ return { success: false, results: [], error: err.message };
13660
+ }
13661
+ }
13662
+ function formatResults(results) {
13663
+ return results.map((r, i) => `${i + 1}. ${r.title}
13664
+ URL: ${r.url}
13665
+ ${r.snippet}`).join(`
13666
+ `);
13667
+ }
13428
13668
  var webSearchTool;
13429
13669
  var init_web_search = __esm(() => {
13430
13670
  init_i18n();
@@ -13447,59 +13687,42 @@ var init_web_search = __esm(() => {
13447
13687
  required: ["query"]
13448
13688
  },
13449
13689
  handler: async (ctx, args) => {
13690
+ if (ctx.config?.webSearch?.enabled === false) {
13691
+ return { success: false, output: t("tool.web_search_disabled") };
13692
+ }
13450
13693
  const query = String(args.query || "");
13451
13694
  const numResults = Number(args.numResults) || 5;
13452
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13453
13695
  const securityConfig = ctx.sessionContext ? getSessionSecurityConfig(ctx.config, ctx.sessionContext).network : ctx.config.security?.network;
13454
- const validation = isUrlAllowed(url, securityConfig);
13455
- if (!validation.allowed) {
13456
- logSecurityBlock(ctx.sessionId, "network_request", validation.reason || "URL blocked by security policy", sanitizeUrl(url));
13696
+ const outcome = await performWebSearch(query, numResults, securityConfig);
13697
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13698
+ if (outcome.blocked) {
13699
+ logSecurityBlock(ctx.sessionId, "network_request", outcome.error || "URL blocked by security policy", sanitizeUrl(url));
13457
13700
  return {
13458
13701
  success: false,
13459
- output: `[SECURITY BLOCKED] Search URL is not allowed: ${validation.reason}`
13702
+ output: t("error.search_failed", { message: String(outcome.error) })
13460
13703
  };
13461
13704
  }
13462
- try {
13463
- const response = await fetch(url, {
13464
- signal: AbortSignal.timeout(securityConfig?.requestTimeout || 1e4)
13465
- });
13466
- const html = await response.text();
13467
- const results = [];
13468
- const snippetRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
13469
- let match;
13470
- let count = 0;
13471
- while ((match = snippetRegex.exec(html)) !== null && count < numResults) {
13472
- const href = match[1].trim();
13473
- const title = match[2].replace(/<[^>]+>/g, "").trim();
13474
- const snippet = match[3].replace(/<[^>]+>/g, "").trim();
13475
- results.push(`${count + 1}. ${title}
13476
- URL: ${href}
13477
- ${snippet}`);
13478
- count++;
13479
- }
13480
- logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Results: ${results.length}`);
13481
- if (results.length === 0) {
13482
- return { success: true, output: t("tool.no_results", { query }) };
13483
- }
13484
- return {
13485
- success: true,
13486
- output: t("tool.search_results", {
13487
- query,
13488
- results: results.join(`
13489
- `)
13490
- }),
13491
- display: t("tool.web_search_result", {
13492
- query,
13493
- count: String(results.length)
13494
- })
13495
- };
13496
- } catch (err) {
13497
- logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${err.message}`);
13705
+ logNetworkRequest(ctx.sessionId, sanitizeUrl(url), outcome.success, `Results: ${outcome.results.length}`);
13706
+ if (!outcome.success) {
13498
13707
  return {
13499
13708
  success: false,
13500
- output: t("error.search_failed", { message: err.message })
13709
+ output: t("error.search_failed", { message: String(outcome.error) })
13501
13710
  };
13502
13711
  }
13712
+ if (outcome.results.length === 0) {
13713
+ return { success: true, output: t("tool.no_results", { query }) };
13714
+ }
13715
+ return {
13716
+ success: true,
13717
+ output: t("tool.search_results", {
13718
+ query,
13719
+ results: formatResults(outcome.results)
13720
+ }),
13721
+ display: t("tool.web_search_result", {
13722
+ query,
13723
+ count: String(outcome.results.length)
13724
+ })
13725
+ };
13503
13726
  }
13504
13727
  };
13505
13728
  });
@@ -14684,7 +14907,7 @@ var init_search_history = __esm(() => {
14684
14907
  });
14685
14908
 
14686
14909
  // src/modules/memory/search.ts
14687
- import { readFileSync as readFileSync12, existsSync as existsSync26 } from "fs";
14910
+ import { readFileSync as readFileSync13, existsSync as existsSync26 } from "fs";
14688
14911
  import { join as join17 } from "path";
14689
14912
 
14690
14913
  class MemorySearch {
@@ -14699,7 +14922,7 @@ class MemorySearch {
14699
14922
  const path = join17(this.memoryDir, `${name}.md`);
14700
14923
  if (!existsSync26(path))
14701
14924
  continue;
14702
- const content = readFileSync12(path, "utf-8");
14925
+ const content = readFileSync13(path, "utf-8");
14703
14926
  const lines = content.split(`
14704
14927
  `);
14705
14928
  for (const line of lines) {
@@ -14711,7 +14934,7 @@ class MemorySearch {
14711
14934
  const prefsPath = join17(this.memoryDir, "preferences.json");
14712
14935
  if (existsSync26(prefsPath)) {
14713
14936
  try {
14714
- const prefs = JSON.parse(readFileSync12(prefsPath, "utf-8"));
14937
+ const prefs = JSON.parse(readFileSync13(prefsPath, "utf-8"));
14715
14938
  for (const [key, value] of Object.entries(prefs)) {
14716
14939
  const searchStr = `${key}=${value}`;
14717
14940
  if (searchStr.toLowerCase().includes(lowerQuery)) {
@@ -14729,7 +14952,7 @@ var init_search = __esm(() => {
14729
14952
  });
14730
14953
 
14731
14954
  // src/modules/memory/store.ts
14732
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync27, mkdirSync as mkdirSync13 } from "fs";
14955
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync27, mkdirSync as mkdirSync13 } from "fs";
14733
14956
  import { join as join18 } from "path";
14734
14957
 
14735
14958
  class MemoryStore {
@@ -14755,7 +14978,7 @@ class MemoryStore {
14755
14978
  const path = join18(this.memoryDir, `${name}.md`);
14756
14979
  if (!existsSync27(path))
14757
14980
  return "";
14758
- return readFileSync13(path, "utf-8");
14981
+ return readFileSync14(path, "utf-8");
14759
14982
  }
14760
14983
  append(name, entry) {
14761
14984
  const path = join18(this.memoryDir, `${name}.md`);
@@ -14776,7 +14999,7 @@ class MemoryStore {
14776
14999
  if (!existsSync27(path))
14777
15000
  return {};
14778
15001
  try {
14779
- return JSON.parse(readFileSync13(path, "utf-8"));
15002
+ return JSON.parse(readFileSync14(path, "utf-8"));
14780
15003
  } catch {
14781
15004
  return {};
14782
15005
  }
@@ -16065,7 +16288,7 @@ __export(exports_image_utils, {
16065
16288
  detectMime: () => detectMime,
16066
16289
  bufferToDataUrl: () => bufferToDataUrl
16067
16290
  });
16068
- import { readFileSync as readFileSync14 } from "fs";
16291
+ import { readFileSync as readFileSync15 } from "fs";
16069
16292
  import { extname as extname3 } from "path";
16070
16293
  function detectMime(filePath) {
16071
16294
  const ext = extname3(filePath).toLowerCase();
@@ -16086,7 +16309,7 @@ async function readClipboardImage() {
16086
16309
  async function readClipboardFallback() {
16087
16310
  const { platform: platform5 } = await import("os");
16088
16311
  const { execSync } = await import("child_process");
16089
- const { readFileSync: readFileSync15, unlinkSync: unlinkSync4 } = await import("fs");
16312
+ const { readFileSync: readFileSync16, unlinkSync: unlinkSync4 } = await import("fs");
16090
16313
  const { join: join24 } = await import("path");
16091
16314
  const tmpPath = join24(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
16092
16315
  try {
@@ -16097,7 +16320,7 @@ async function readClipboardFallback() {
16097
16320
  } else {
16098
16321
  return null;
16099
16322
  }
16100
- const buf = readFileSync15(tmpPath);
16323
+ const buf = readFileSync16(tmpPath);
16101
16324
  unlinkSync4(tmpPath);
16102
16325
  return buf.length > 0 ? buf : null;
16103
16326
  } catch {
@@ -16108,7 +16331,7 @@ async function readClipboardFallback() {
16108
16331
  }
16109
16332
  }
16110
16333
  async function loadFileAsDataUrl(filePath) {
16111
- const buf = readFileSync14(filePath);
16334
+ const buf = readFileSync15(filePath);
16112
16335
  if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
16113
16336
  try {
16114
16337
  const img = new Bun.Image(buf);
@@ -16535,7 +16758,7 @@ var init_loader = __esm(() => {
16535
16758
 
16536
16759
  // src/modules/plugins/builtin/lint-on-write.ts
16537
16760
  import { spawn as spawn5, execSync } from "child_process";
16538
- import { existsSync as existsSync31, readFileSync as readFileSync15 } from "fs";
16761
+ import { existsSync as existsSync31, readFileSync as readFileSync16 } from "fs";
16539
16762
  import { resolve as resolve18, extname as extname4, join as join25 } from "path";
16540
16763
  import { platform as platform5 } from "os";
16541
16764
  function contentHash(content) {
@@ -16600,7 +16823,7 @@ class LintOnWritePlugin {
16600
16823
  if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
16601
16824
  let content = "";
16602
16825
  try {
16603
- content = readFileSync15(filePath, "utf-8");
16826
+ content = readFileSync16(filePath, "utf-8");
16604
16827
  } catch {
16605
16828
  return null;
16606
16829
  }
@@ -16643,7 +16866,7 @@ class LintOnWritePlugin {
16643
16866
  if (!existsSync31(packageJsonPath)) {
16644
16867
  return;
16645
16868
  }
16646
- const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
16869
+ const packageJson = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
16647
16870
  const lintScript = packageJson.scripts?.lint;
16648
16871
  if (!lintScript) {
16649
16872
  return;
@@ -16779,25 +17002,6 @@ function generatePlanId() {
16779
17002
  }
16780
17003
 
16781
17004
  class PlanCreator {
16782
- static isMultiStep(task) {
16783
- const fileCount = (task.match(/\b[\w./-]+\.[a-z]+\b/gi) || []).length;
16784
- if (fileCount > 1)
16785
- return true;
16786
- const actionWords = [
16787
- "implement",
16788
- "create",
16789
- "add",
16790
- "build",
16791
- "setup",
16792
- "configure",
16793
- "write",
16794
- "make",
16795
- "develop"
16796
- ];
16797
- const words = task.split(/\s+/);
16798
- const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
16799
- return hasActionWord && words.length > 8;
16800
- }
16801
17005
  static createPlan(title, stepDescriptions, baseDir, kinds) {
16802
17006
  const stepCount = stepDescriptions.length;
16803
17007
  return {
@@ -16818,16 +17022,17 @@ class PlanCreator {
16818
17022
  const baseTitle = plan.title.replace(/^\[\d+[^]]*\]\s*/, "");
16819
17023
  const newTitle = title || baseTitle;
16820
17024
  const totalSteps = kept.length + newSteps.length;
16821
- const added = newSteps.map((desc, i) => ({
16822
- id: kept.length + i + 1,
17025
+ const added = newSteps.map((desc) => ({
17026
+ id: -1,
16823
17027
  description: desc,
16824
17028
  status: "pending",
16825
17029
  kind: "create"
16826
17030
  }));
17031
+ const steps = [...kept, ...added].map((s, i) => ({ ...s, id: i + 1 }));
16827
17032
  return {
16828
17033
  id: plan.id,
16829
17034
  title: `[${totalSteps}] ${newTitle}`,
16830
- steps: [...kept, ...added],
17035
+ steps,
16831
17036
  createdAt: plan.createdAt,
16832
17037
  baseDir: plan.baseDir,
16833
17038
  name: plan.name
@@ -16835,10 +17040,13 @@ class PlanCreator {
16835
17040
  }
16836
17041
  static toPromptBlock(plan, currentStepIndex) {
16837
17042
  const date = plan.createdAt.slice(0, 10);
17043
+ const doneCount = plan.steps.filter((s) => s.status === "done").length;
17044
+ const terminal = plan.steps.every((s) => s.status === "done" || s.status === "skipped");
17045
+ const progress = terminal ? `${doneCount}/${plan.steps.length} done, complete` : `${doneCount}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`;
16838
17046
  const lines = [
16839
17047
  `[${plan.id}] ${plan.title}`,
16840
17048
  `Dir: ${plan.baseDir}`,
16841
- `Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
17049
+ `Created: ${date} | Progress: ${progress}`,
16842
17050
  ``
16843
17051
  ];
16844
17052
  for (const step of plan.steps) {
@@ -16915,11 +17123,11 @@ class PlanTracker {
16915
17123
  var init_tracker = () => {};
16916
17124
 
16917
17125
  // src/modules/execution/plan-store.ts
16918
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync32, readdirSync as readdirSync10, rmSync } from "fs";
17126
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync32, readdirSync as readdirSync10, rmSync } from "fs";
16919
17127
  import { join as join26 } from "path";
16920
17128
  function readPlanFile(path, fallbackBaseDir) {
16921
17129
  try {
16922
- const raw = readFileSync16(path, "utf-8");
17130
+ const raw = readFileSync17(path, "utf-8");
16923
17131
  if (!raw.trim())
16924
17132
  return null;
16925
17133
  const parsed = JSON.parse(raw);
@@ -17062,6 +17270,41 @@ class PlanStore {
17062
17270
  return { plan: archived, status: "archived" };
17063
17271
  return null;
17064
17272
  }
17273
+ deletePlan(id) {
17274
+ const active = this.loadActive();
17275
+ if (active && active.id === id) {
17276
+ this.clearActive();
17277
+ return "active";
17278
+ }
17279
+ const draftPath = join26(this.draftsDir, `${id}.json`);
17280
+ if (existsSync32(draftPath)) {
17281
+ rmSync(draftPath, { force: true });
17282
+ return "draft";
17283
+ }
17284
+ const archivedPath = join26(this.archiveDir, `${id}.json`);
17285
+ if (existsSync32(archivedPath)) {
17286
+ rmSync(archivedPath, { force: true });
17287
+ return "archived";
17288
+ }
17289
+ return null;
17290
+ }
17291
+ purgeAll() {
17292
+ let n = 0;
17293
+ const active = this.loadActive();
17294
+ if (active) {
17295
+ this.clearActive();
17296
+ n++;
17297
+ }
17298
+ for (const p of this.listDrafts()) {
17299
+ this.removeDraft(p.id);
17300
+ n++;
17301
+ }
17302
+ for (const p of this.listArchived()) {
17303
+ this.removeArchived(p.id);
17304
+ n++;
17305
+ }
17306
+ return n;
17307
+ }
17065
17308
  }
17066
17309
  var LEGACY_FILE = "plan.json";
17067
17310
  var init_plan_store = () => {};
@@ -17135,16 +17378,18 @@ function createPlanToolDefinitions(deps) {
17135
17378
  {
17136
17379
  name: "plan",
17137
17380
  alwaysOn: true,
17138
- description: `Create, update, show, abort, list, switch, or re-plan multi-step plans.
17381
+ description: `Create, update, show, abort, list, switch, delete, purge, or re-plan multi-step plans.
17139
17382
 
17140
17383
  Actions:
17141
17384
  - create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.
17142
17385
  - update: Mark step status (done/failed/skipped), or rebuild plan with new steps.
17143
17386
  - show: Print current plan checklist.
17144
- - abort: Archive current plan and clear active slot.
17387
+ - abort: Archive the current plan (or the plan given by id) and clear the active slot.
17145
17388
  - list: Show all plans (active, drafts, archived) with progress.
17146
17389
  - switch: Make a different plan active (by plan id).
17147
17390
  - re-plan: Iterative replanning: keep completed steps, replace remaining with new steps.
17391
+ - delete: Permanently delete a plan by id (plan delete id=plan_xxx).
17392
+ - purge: Delete ALL plans (active, drafts, archived).
17148
17393
 
17149
17394
  Write CONCRETE steps with exact file paths and commands:
17150
17395
  - Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")
@@ -17159,7 +17404,17 @@ Write CONCRETE steps with exact file paths and commands:
17159
17404
  properties: {
17160
17405
  action: {
17161
17406
  type: "string",
17162
- enum: ["create", "update", "show", "abort", "list", "switch", "re-plan"]
17407
+ enum: [
17408
+ "create",
17409
+ "update",
17410
+ "show",
17411
+ "abort",
17412
+ "list",
17413
+ "switch",
17414
+ "re-plan",
17415
+ "delete",
17416
+ "purge"
17417
+ ]
17163
17418
  },
17164
17419
  title: { type: "string" },
17165
17420
  steps: { type: "array", items: { type: "string" } },
@@ -17187,6 +17442,8 @@ Write CONCRETE steps with exact file paths and commands:
17187
17442
  const namePart = m.name ? ` (${m.name})` : "";
17188
17443
  return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
17189
17444
  });
17445
+ lines.push("");
17446
+ lines.push(t("plan.list_legend"));
17190
17447
  return {
17191
17448
  success: true,
17192
17449
  output: `${t("plan.list_header")}
@@ -17206,6 +17463,16 @@ ${lines.join(`
17206
17463
  output: t("plan.not_found", { id: planId })
17207
17464
  };
17208
17465
  }
17466
+ if (found.status === "active") {
17467
+ return {
17468
+ success: true,
17469
+ output: t("plan.already_active", {
17470
+ id: found.plan.id,
17471
+ title: found.plan.title
17472
+ }),
17473
+ display: deps.trackerRef.current?.toPromptBlock()
17474
+ };
17475
+ }
17209
17476
  deps.preserveActive();
17210
17477
  if (found.status === "draft") {
17211
17478
  deps.store.removeDraft(found.plan.id);
@@ -17213,7 +17480,7 @@ ${lines.join(`
17213
17480
  deps.store.removeArchived(found.plan.id);
17214
17481
  }
17215
17482
  deps.setPlan(found.plan);
17216
- const display = PlanCreator.toPromptBlock(found.plan, 0);
17483
+ const display = deps.trackerRef.current?.toPromptBlock();
17217
17484
  return {
17218
17485
  success: true,
17219
17486
  output: t("plan.switched", {
@@ -17236,7 +17503,7 @@ ${lines.join(`
17236
17503
  const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
17237
17504
  const keptCount = replanned.steps.length - newSteps.length;
17238
17505
  deps.setPlan(replanned);
17239
- const display = PlanCreator.toPromptBlock(replanned, keptCount);
17506
+ const display = deps.trackerRef.current?.toPromptBlock();
17240
17507
  return {
17241
17508
  success: true,
17242
17509
  output: t("plan.replanned", {
@@ -17349,6 +17616,7 @@ ${display}`,
17349
17616
  if (tracker.isComplete()) {
17350
17617
  const plan = tracker.getPlan();
17351
17618
  deps.store.archivePlan(plan);
17619
+ deps.recordCompleted(plan);
17352
17620
  deps.trackerRef.current = null;
17353
17621
  const done = plan.steps.filter((s) => s.status === "done").length;
17354
17622
  return {
@@ -17415,9 +17683,13 @@ ${progress2}`,
17415
17683
  tracker.addNote(Number(args.step), String(args.note));
17416
17684
  tracker.syncCurrentStep();
17417
17685
  deps.store.saveActive(tracker.getPlan());
17686
+ const vacuousGate = status === "done" && !deps.hasStepDeliverables(target);
17687
+ const vacuousNote = vacuousGate ? `
17688
+ ${t("plan.no_deliverables", { step: String(stepId) })}` : "";
17418
17689
  if (tracker.isComplete()) {
17419
17690
  const plan = tracker.getPlan();
17420
17691
  deps.store.archivePlan(plan);
17692
+ deps.recordCompleted(plan);
17421
17693
  deps.trackerRef.current = null;
17422
17694
  const done = plan.steps.filter((s) => s.status === "done").length;
17423
17695
  return {
@@ -17427,7 +17699,7 @@ ${t("plan.completed_archived", {
17427
17699
  id: plan.id,
17428
17700
  done: String(done),
17429
17701
  total: String(plan.steps.length)
17430
- })}`
17702
+ })}${vacuousNote}`
17431
17703
  };
17432
17704
  }
17433
17705
  const progress = tracker.getProgressString();
@@ -17436,7 +17708,7 @@ ${t("plan.completed_archived", {
17436
17708
  return {
17437
17709
  success: true,
17438
17710
  output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
17439
- ${progress}`,
17711
+ ${progress}${vacuousNote}`,
17440
17712
  display
17441
17713
  };
17442
17714
  }
@@ -17450,7 +17722,7 @@ ${progress}`,
17450
17722
  }
17451
17723
  const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
17452
17724
  deps.setPlan(plan);
17453
- const display = PlanCreator.toPromptBlock(plan, 0);
17725
+ const display = deps.trackerRef.current?.toPromptBlock();
17454
17726
  const output = t("plan.updated", {
17455
17727
  title,
17456
17728
  steps: String(steps.length)
@@ -17462,14 +17734,71 @@ ${progress}`,
17462
17734
  };
17463
17735
  }
17464
17736
  if (action === "abort") {
17737
+ const planId = args.id ? String(args.id) : "";
17738
+ if (planId) {
17739
+ const found = deps.store.find(planId);
17740
+ if (!found) {
17741
+ return {
17742
+ success: false,
17743
+ output: t("plan.not_found", { id: planId })
17744
+ };
17745
+ }
17746
+ if (found.status === "archived") {
17747
+ return {
17748
+ success: false,
17749
+ output: t("plan.already_archived", { id: planId })
17750
+ };
17751
+ }
17752
+ deps.store.archivePlan(found.plan);
17753
+ if (found.status === "active") {
17754
+ deps.trackerRef.current = null;
17755
+ deps.clearCompleted();
17756
+ }
17757
+ return {
17758
+ success: true,
17759
+ output: t("plan.aborted_id", { id: found.plan.id })
17760
+ };
17761
+ }
17465
17762
  const tracker = deps.trackerRef.current;
17466
- if (tracker) {
17467
- deps.store.archivePlan(tracker.getPlan());
17763
+ if (!tracker) {
17764
+ return { success: false, output: t("plan.no_active") };
17468
17765
  }
17766
+ deps.store.archivePlan(tracker.getPlan());
17469
17767
  deps.trackerRef.current = null;
17470
17768
  deps.store.clearActive();
17769
+ deps.clearCompleted();
17471
17770
  return { success: true, output: t("plan.aborted") };
17472
17771
  }
17772
+ if (action === "delete") {
17773
+ const planId = String(args.id || "");
17774
+ if (!planId) {
17775
+ return { success: false, output: t("plan.delete_no_id") };
17776
+ }
17777
+ const deleted = deps.store.deletePlan(planId);
17778
+ if (!deleted) {
17779
+ return {
17780
+ success: false,
17781
+ output: t("plan.not_found", { id: planId })
17782
+ };
17783
+ }
17784
+ if (deleted === "active") {
17785
+ deps.trackerRef.current = null;
17786
+ deps.clearCompleted();
17787
+ }
17788
+ return {
17789
+ success: true,
17790
+ output: t("plan.deleted", { id: planId })
17791
+ };
17792
+ }
17793
+ if (action === "purge") {
17794
+ const count = deps.store.purgeAll();
17795
+ deps.trackerRef.current = null;
17796
+ deps.clearCompleted();
17797
+ return {
17798
+ success: true,
17799
+ output: t("plan.purged", { count: String(count) })
17800
+ };
17801
+ }
17473
17802
  if (!deps.trackerRef.current) {
17474
17803
  return { success: false, output: t("plan.no_active") };
17475
17804
  }
@@ -17660,6 +17989,8 @@ function createExecutionPlugin(deps) {
17660
17989
  deps.state.consecutivePlanWarnings = 0;
17661
17990
  deps.state.lastStepId = -1;
17662
17991
  deps.state.stuckNotified = false;
17992
+ deps.state.mutationsWithoutPlan = 0;
17993
+ deps.state.planNudgeSent = false;
17663
17994
  } else {
17664
17995
  const step = deps.trackerRef.current?.getCurrentStep();
17665
17996
  if (deps.trackerRef.current && step) {
@@ -17670,22 +18001,21 @@ function createExecutionPlugin(deps) {
17670
18001
  }
17671
18002
  deps.stuckDetector.setCurrentStep(step.id, step.description);
17672
18003
  deps.stuckDetector.recordIteration(step.id);
18004
+ deps.state.mutationsWithoutPlan = 0;
18005
+ deps.state.planNudgeSent = false;
17673
18006
  } else {
17674
18007
  deps.stuckDetector.reset();
17675
18008
  deps.state.consecutivePlanWarnings = 0;
17676
18009
  deps.state.lastStepId = -1;
17677
18010
  deps.state.stuckNotified = false;
17678
- const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
17679
- if (iter === 3 && !deps.trackerRef.current && ctx.contextManager) {
18011
+ const mutations = deps.state.mutationsWithoutPlan;
18012
+ if (mutations >= PLAN_NUDGE_THRESHOLD && !deps.state.planNudgeSent && ctx.contextManager) {
18013
+ deps.state.planNudgeSent = true;
17680
18014
  ctx.contextManager.addMessage({
17681
18015
  role: "user",
17682
- 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>`
17683
- });
17684
- }
17685
- if (iter >= 6 && !deps.trackerRef.current && ctx.contextManager) {
17686
- ctx.contextManager.addMessage({
17687
- role: "user",
17688
- 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>`
18016
+ content: `<system-summary>${t("exec.plan_nudge", {
18017
+ count: String(mutations)
18018
+ })}</system-summary>`
17689
18019
  });
17690
18020
  }
17691
18021
  }
@@ -17778,6 +18108,9 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17778
18108
  const args = ctx?.args;
17779
18109
  if (toolName && args) {
17780
18110
  deps.stuckDetector.recordToolCall(toolName, args);
18111
+ if (!deps.trackerRef.current && (toolName === "write_file" || toolName === "edit_file" || toolName === "bash" || toolName === "download_file")) {
18112
+ deps.state.mutationsWithoutPlan++;
18113
+ }
17781
18114
  }
17782
18115
  },
17783
18116
  onAfterTool: (ctx, call, result) => {
@@ -17785,7 +18118,8 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17785
18118
  deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
17786
18119
  }
17787
18120
  const toolText = String(result.output ?? "");
17788
- if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
18121
+ const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
18122
+ if (hasTypeError) {
17789
18123
  deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
17790
18124
  }
17791
18125
  if (!result.success) {
@@ -17803,7 +18137,9 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17803
18137
  }
17804
18138
  }
17805
18139
  }
17806
- deps.stuckDetector.recordToolError(call.name, result.output);
18140
+ if (!hasTypeError) {
18141
+ deps.stuckDetector.recordToolError(call.name, result.output);
18142
+ }
17807
18143
  const actionableHints = deps.stuckDetector.getActionableHints();
17808
18144
  const alternative = deps.stuckDetector.getToolAlternative();
17809
18145
  if (actionableHints.length > 0 || alternative) {
@@ -17857,10 +18193,11 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17857
18193
  }
17858
18194
  deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
17859
18195
  }
18196
+ deps.maybeSearchError(ctx, call);
17860
18197
  }
17861
18198
  };
17862
18199
  }
17863
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10;
18200
+ var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, PLAN_NUDGE_THRESHOLD = 2;
17864
18201
  var init_execution_plugin = __esm(() => {
17865
18202
  init_i18n();
17866
18203
  init_bash();
@@ -17868,7 +18205,7 @@ var init_execution_plugin = __esm(() => {
17868
18205
  });
17869
18206
 
17870
18207
  // src/modules/execution/module.ts
17871
- import { existsSync as existsSync33, readFileSync as readFileSync17 } from "fs";
18208
+ import { existsSync as existsSync33, readFileSync as readFileSync18 } from "fs";
17872
18209
  import { resolve as resolve19 } from "path";
17873
18210
 
17874
18211
  class ExecutionModule {
@@ -17880,14 +18217,23 @@ class ExecutionModule {
17880
18217
  store;
17881
18218
  baseDir;
17882
18219
  _auditSkipsRemaining = 0;
18220
+ completedPlan = null;
17883
18221
  pendingMessages = [];
17884
18222
  forbiddenBashFailures = new Map;
18223
+ searchRunner;
18224
+ webSearchThreshold;
18225
+ searchesThisSession = 0;
18226
+ retryWebSearchSignatures = new Set;
18227
+ lastErrorSearchAt = 0;
18228
+ errorSearchCooldownMs;
17885
18229
  state = {
17886
18230
  lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
17887
18231
  consecutivePlanWarnings: 0,
17888
18232
  lastStepId: -1,
17889
18233
  stuckNotified: false,
17890
- depsGateHints: new Map
18234
+ depsGateHints: new Map,
18235
+ mutationsWithoutPlan: 0,
18236
+ planNudgeSent: false
17891
18237
  };
17892
18238
  trackerRef = (() => {
17893
18239
  const self = this;
@@ -17900,18 +18246,29 @@ class ExecutionModule {
17900
18246
  }
17901
18247
  };
17902
18248
  })();
17903
- constructor(baseDir, stuckThreshold = 6) {
18249
+ constructor(baseDir, stuckThreshold = 6, webSearchThreshold = 5, searchRunner = performWebSearch, errorSearchCooldownMs = ERROR_SEARCH_MIN_INTERVAL_MS) {
17904
18250
  this.baseDir = baseDir;
17905
18251
  this.verifier = new StepVerifier(baseDir);
17906
- this.stuckDetector = new StuckDetector(stuckThreshold);
18252
+ this.stuckDetector = new StuckDetector(stuckThreshold, 3, webSearchThreshold);
17907
18253
  this.auditor = new Auditor(baseDir);
17908
18254
  this.store = new PlanStore(baseDir);
18255
+ this.webSearchThreshold = webSearchThreshold;
18256
+ this.searchRunner = searchRunner;
18257
+ this.errorSearchCooldownMs = errorSearchCooldownMs;
17909
18258
  }
17910
18259
  setPlan(plan) {
17911
18260
  this.tracker = new PlanTracker(plan);
18261
+ this.tracker.syncCurrentStep();
17912
18262
  this.store.saveActive(plan);
18263
+ this.completedPlan = null;
17913
18264
  this._auditSkipsRemaining = 0;
17914
18265
  }
18266
+ recordCompleted(plan) {
18267
+ this.completedPlan = plan;
18268
+ }
18269
+ clearCompleted() {
18270
+ this.completedPlan = null;
18271
+ }
17915
18272
  restorePlan() {
17916
18273
  const plan = this.store.loadActive();
17917
18274
  if (!plan)
@@ -17936,9 +18293,75 @@ class ExecutionModule {
17936
18293
  getStuckDetector() {
17937
18294
  return this.stuckDetector;
17938
18295
  }
18296
+ maybeSearchError(ctx, call) {
18297
+ if (ctx.config?.webSearch?.enabled === false)
18298
+ return;
18299
+ const cfg = ctx.config?.errorWebSearch;
18300
+ if (!cfg?.enabled)
18301
+ return;
18302
+ const sig = this.stuckDetector.getRepeatedErrorSignature();
18303
+ if (!sig)
18304
+ return;
18305
+ if (this.stuckDetector.hasErrorSearched(sig) && !this.retryWebSearchSignatures.has(sig))
18306
+ return;
18307
+ if (this.searchesThisSession >= (cfg.maxSearchesPerSession ?? 3))
18308
+ return;
18309
+ const lastError = this.stuckDetector.getLastErrorOutput();
18310
+ if (!isSearchableError(lastError)) {
18311
+ ctx.logger?.debug(t("exec.error_search_no_query"));
18312
+ return;
18313
+ }
18314
+ const now = Date.now();
18315
+ if (now - this.lastErrorSearchAt < this.errorSearchCooldownMs)
18316
+ return;
18317
+ const stepDesc = this.tracker?.getCurrentStep()?.description ?? "";
18318
+ const query = buildSearchQuery(stepDesc, this.stuckDetector.getLastBashCommand(), lastError, call.name, cfg.maxQueryChars ?? 200);
18319
+ const count = this.stuckDetector.getErrorSignatureCount(sig);
18320
+ this.stuckDetector.markErrorSearched(sig);
18321
+ this.searchesThisSession++;
18322
+ this.lastErrorSearchAt = now;
18323
+ const networkConfig = getSessionSecurityConfig(ctx.config, ctx.sessionContext).network;
18324
+ this.searchRunner(query, cfg.maxResults ?? 5, networkConfig, cfg.requestTimeoutMs ?? 1e4).then((res) => {
18325
+ if (!res.success || res.results.length === 0) {
18326
+ if (!this.retryWebSearchSignatures.has(sig)) {
18327
+ this.retryWebSearchSignatures.add(sig);
18328
+ this.stuckDetector.unmarkErrorSearched(sig);
18329
+ }
18330
+ ctx.logger?.warn(t("exec.error_search_failed", { query }));
18331
+ ctx.sessionLog?.plan("web-search", `empty/failed: ${query}`);
18332
+ return;
18333
+ }
18334
+ const resultsText = res.results.map((r, i) => `${i + 1}. ${r.title} — ${r.url}
18335
+ ${r.snippet}`).join(`
18336
+ `);
18337
+ this.pendingMessages.push({
18338
+ role: "user",
18339
+ content: `<system-summary>${t("exec.error_search_results", {
18340
+ sig,
18341
+ count: String(count),
18342
+ query,
18343
+ results: resultsText
18344
+ })}</system-summary>`
18345
+ });
18346
+ }).catch((e) => ctx.logger?.warn(`error web search: ${e.message}`));
18347
+ }
17939
18348
  async runFinalAudit() {
17940
- if (!this.tracker)
17941
- return null;
18349
+ if (!this.tracker) {
18350
+ if (!this.completedPlan)
18351
+ return null;
18352
+ const plan2 = this.completedPlan;
18353
+ const audit2 = await this.auditor.audit(plan2);
18354
+ const pendingSteps2 = plan2.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
18355
+ const done2 = plan2.steps.filter((s) => s.status === "done").length;
18356
+ return {
18357
+ passed: audit2.passed && pendingSteps2.length === 0,
18358
+ done: done2,
18359
+ total: plan2.steps.length,
18360
+ pendingSteps: pendingSteps2,
18361
+ missingFiles: audit2.missingFiles,
18362
+ summary: audit2.summary
18363
+ };
18364
+ }
17942
18365
  if (this._auditSkipsRemaining > 0) {
17943
18366
  this._auditSkipsRemaining--;
17944
18367
  return null;
@@ -17986,6 +18409,9 @@ class ExecutionModule {
17986
18409
  trackerRef: this.trackerRef,
17987
18410
  preserveActive: () => this.preserveActive(),
17988
18411
  setPlan: (p) => this.setPlan(p),
18412
+ recordCompleted: (p) => this.recordCompleted(p),
18413
+ clearCompleted: () => this.clearCompleted(),
18414
+ hasStepDeliverables: (s) => this.hasStepDeliverables(s),
17989
18415
  missingStepDeliverables: (s) => this.missingStepDeliverables(s),
17990
18416
  stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
17991
18417
  parseKinds: (a, st) => this.parseKinds(a, st),
@@ -18001,7 +18427,8 @@ class ExecutionModule {
18001
18427
  forbiddenBashFailures: this.forbiddenBashFailures,
18002
18428
  state: this.state,
18003
18429
  checkPlanAlignment: (call) => this.checkPlanAlignment(call),
18004
- advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl)
18430
+ advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl),
18431
+ maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call)
18005
18432
  });
18006
18433
  }
18007
18434
  preserveActive() {
@@ -18036,7 +18463,7 @@ class ExecutionModule {
18036
18463
  const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map((p) => p.toLowerCase());
18037
18464
  if (stepPaths.length === 0)
18038
18465
  return null;
18039
- const argStr = JSON.stringify(call.arguments);
18466
+ const argStr = this.pathArgStrings(call.arguments).join(" ");
18040
18467
  const callPaths = extractFileLikeTokens(stripUrls(argStr)).map((p) => p.toLowerCase());
18041
18468
  if (callPaths.length === 0)
18042
18469
  return null;
@@ -18061,13 +18488,31 @@ class ExecutionModule {
18061
18488
  tool: call.name
18062
18489
  });
18063
18490
  }
18491
+ pathArgStrings(args) {
18492
+ if (!args)
18493
+ return [];
18494
+ const out = [];
18495
+ for (const [key, value] of Object.entries(args)) {
18496
+ if (!/path|file|dir|src|dst|source|target|input|output|destination|glob|workdir|cwd|command|pattern|query/i.test(key)) {
18497
+ continue;
18498
+ }
18499
+ if (typeof value === "string")
18500
+ out.push(value);
18501
+ else if (Array.isArray(value)) {
18502
+ for (const v of value)
18503
+ if (typeof v === "string")
18504
+ out.push(v);
18505
+ }
18506
+ }
18507
+ return out;
18508
+ }
18064
18509
  advancePlanIfStepComplete(contextManager, sessionLog) {
18065
18510
  const step = this.tracker?.getCurrentStep();
18066
18511
  if (!step)
18067
18512
  return;
18068
18513
  const stepText = step.description.toLowerCase();
18069
18514
  const stepPaths = extractFileLikeTokens(stripUrls(step.description)) || [];
18070
- const isDepsStep = stepText.includes("install") || stepText.includes("зависим") || stepText.includes("init") || stepText.includes("инициализац");
18515
+ const isDepsStep = /(?:npm|bun|yarn|pnpm|pip|pip3|pipenv|poetry|composer|deno|go|gem|cargo)\s+(?:install|add|init|i|get)\b|bun\s+(?:add|install)|npm\s+(?:i|install|add)|(?:install|установ\w*)\s+(?:dependencies|зависимост\w*|пакет\w*|packages?)/i.test(stepText);
18071
18516
  if (isDepsStep) {
18072
18517
  const lockFiles = [
18073
18518
  "package-lock.json",
@@ -18101,18 +18546,21 @@ class ExecutionModule {
18101
18546
  }
18102
18547
  if (stepPaths.length === 0)
18103
18548
  return;
18104
- const allExist = stepPaths.every((p) => existsSync33(resolve19(this.baseDir, p)));
18105
- const allGone = stepPaths.every((p) => !existsSync33(resolve19(this.baseDir, p)));
18549
+ const resolved = stepPaths.map((p) => findExistingFile(this.baseDir, p));
18550
+ const allExist = resolved.every((r) => r !== null);
18551
+ const allGone = resolved.every((r) => r === null);
18106
18552
  const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
18107
18553
  if (!satisfied)
18108
18554
  return;
18109
18555
  if (step.kind !== "delete") {
18110
18556
  const emptyFiles = [];
18111
- for (const p of stepPaths) {
18557
+ for (const r of resolved) {
18558
+ if (!r)
18559
+ continue;
18112
18560
  try {
18113
- const content = readFileSync17(resolve19(this.baseDir, p), "utf-8");
18561
+ const content = readFileSync18(r, "utf-8");
18114
18562
  if (content.trim().length < 10) {
18115
- emptyFiles.push(p);
18563
+ emptyFiles.push(r);
18116
18564
  }
18117
18565
  } catch {}
18118
18566
  }
@@ -18145,6 +18593,7 @@ class ExecutionModule {
18145
18593
  if (this.tracker?.isComplete()) {
18146
18594
  const completed = this.tracker.getPlan();
18147
18595
  this.store.archivePlan(completed);
18596
+ this.recordCompleted(completed);
18148
18597
  this.tracker = null;
18149
18598
  sessionLog?.plan("auto-archive", `Plan ${completed.id} complete — archived`);
18150
18599
  }
@@ -18161,6 +18610,9 @@ class ExecutionModule {
18161
18610
  return [];
18162
18611
  return tokens.filter((p) => findExistingFile(this.baseDir, p));
18163
18612
  }
18613
+ hasStepDeliverables(step) {
18614
+ return extractFileLikeTokens(stripUrls(step.description)).length > 0;
18615
+ }
18164
18616
  parseKinds(args, steps) {
18165
18617
  if (args.kinds === undefined)
18166
18618
  return { kinds: null, error: null };
@@ -18198,6 +18650,7 @@ class ExecutionModule {
18198
18650
  return getMessageText(firstUser.content).trim();
18199
18651
  }
18200
18652
  }
18653
+ var ERROR_SEARCH_MIN_INTERVAL_MS = 30000;
18201
18654
  var init_module = __esm(() => {
18202
18655
  init_i18n();
18203
18656
  init_tracker();
@@ -18206,12 +18659,14 @@ var init_module = __esm(() => {
18206
18659
  init_auditor();
18207
18660
  init_plan_store();
18208
18661
  init_js_identifiers();
18662
+ init_web_search();
18663
+ init_session_isolation();
18209
18664
  init_plan_tool();
18210
18665
  init_execution_plugin();
18211
18666
  });
18212
18667
 
18213
18668
  // src/modules/security/session-encryption.ts
18214
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync11, existsSync as existsSync34, readdirSync as readdirSync11, unlinkSync as unlinkSync4 } from "fs";
18669
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync11, existsSync as existsSync34, readdirSync as readdirSync11, unlinkSync as unlinkSync4 } from "fs";
18215
18670
  import { join as join27 } from "path";
18216
18671
  import { homedir as homedir8 } from "os";
18217
18672
 
@@ -18266,7 +18721,7 @@ class SessionFileEncryptor {
18266
18721
  return lines.map((line) => this.decryptFileContent(line));
18267
18722
  }
18268
18723
  readSessionFile(filePath) {
18269
- const content = readFileSync18(filePath, "utf8");
18724
+ const content = readFileSync19(filePath, "utf8");
18270
18725
  return this.decryptFileContent(content);
18271
18726
  }
18272
18727
  writeSessionFile(filePath, content) {
@@ -18274,7 +18729,7 @@ class SessionFileEncryptor {
18274
18729
  writeFileSync11(filePath, encrypted, "utf8");
18275
18730
  }
18276
18731
  readSessionJSON(filePath) {
18277
- const content = readFileSync18(filePath, "utf8");
18732
+ const content = readFileSync19(filePath, "utf8");
18278
18733
  return this.decryptJSON(content);
18279
18734
  }
18280
18735
  writeSessionJSON(filePath, obj) {
@@ -18282,7 +18737,7 @@ class SessionFileEncryptor {
18282
18737
  writeFileSync11(filePath, content, "utf8");
18283
18738
  }
18284
18739
  readSessionJSONL(filePath) {
18285
- const content = readFileSync18(filePath, "utf8");
18740
+ const content = readFileSync19(filePath, "utf8");
18286
18741
  const lines = content.split(`
18287
18742
  `).filter((line) => line.trim());
18288
18743
  const decryptedLines = this.decryptJSONL(lines);
@@ -18310,7 +18765,7 @@ class SessionFileEncryptor {
18310
18765
  const filePath = join27(sessionDir, file);
18311
18766
  if (existsSync34(filePath) && !file.endsWith(".enc")) {
18312
18767
  try {
18313
- const content = readFileSync18(filePath, "utf8");
18768
+ const content = readFileSync19(filePath, "utf8");
18314
18769
  const encrypted = this.encryptFileContent(content);
18315
18770
  writeFileSync11(filePath + ".enc", encrypted, "utf8");
18316
18771
  unlinkSync4(filePath);
@@ -18327,7 +18782,7 @@ class SessionFileEncryptor {
18327
18782
  const encFilePath = join27(sessionDir, file);
18328
18783
  const decFilePath = encFilePath.slice(0, -4);
18329
18784
  try {
18330
- const content = readFileSync18(encFilePath, "utf8");
18785
+ const content = readFileSync19(encFilePath, "utf8");
18331
18786
  const decrypted = this.decryptFileContent(content);
18332
18787
  writeFileSync11(decFilePath, decrypted, "utf8");
18333
18788
  unlinkSync4(encFilePath);
@@ -18352,7 +18807,7 @@ import {
18352
18807
  existsSync as existsSync35,
18353
18808
  mkdirSync as mkdirSync15,
18354
18809
  readdirSync as readdirSync12,
18355
- readFileSync as readFileSync19,
18810
+ readFileSync as readFileSync20,
18356
18811
  rmSync as rmSync2,
18357
18812
  writeFileSync as writeFileSync12,
18358
18813
  appendFileSync as appendFileSync6
@@ -18420,7 +18875,7 @@ class SessionStore {
18420
18875
  if (!existsSync35(path))
18421
18876
  return null;
18422
18877
  try {
18423
- const raw = readFileSync19(path, "utf-8");
18878
+ const raw = readFileSync20(path, "utf-8");
18424
18879
  const content = this.encryptor ? this.encryptor.decryptFileContent(raw) : raw;
18425
18880
  const meta = JSON.parse(content);
18426
18881
  this._metaCache.set(id, meta);
@@ -18452,7 +18907,7 @@ class SessionStore {
18452
18907
  if (!existsSync35(path))
18453
18908
  return [];
18454
18909
  try {
18455
- const raw = readFileSync19(path, "utf-8");
18910
+ const raw = readFileSync20(path, "utf-8");
18456
18911
  const lines = raw.split(`
18457
18912
  `).filter(Boolean);
18458
18913
  const parseLine = (line) => {
@@ -18493,7 +18948,7 @@ class SessionStore {
18493
18948
  if (!existsSync35(path))
18494
18949
  return [];
18495
18950
  try {
18496
- const raw = readFileSync19(path, "utf-8");
18951
+ const raw = readFileSync20(path, "utf-8");
18497
18952
  const lines = raw.split(`
18498
18953
  `).filter(Boolean);
18499
18954
  const parseLine = (line) => {
@@ -18548,7 +19003,7 @@ class SessionStore {
18548
19003
  if (updatedAt < thirtyDaysAgo) {
18549
19004
  const historyPath = this.historyPath(session2.id);
18550
19005
  if (existsSync35(historyPath)) {
18551
- const content = readFileSync19(historyPath, "utf-8");
19006
+ const content = readFileSync20(historyPath, "utf-8");
18552
19007
  const compressed = gzipSync(content);
18553
19008
  const gzPath = join28(this.baseDir, `${session2.id}.jsonl.gz`);
18554
19009
  writeFileSync12(gzPath, compressed);
@@ -18760,7 +19215,7 @@ class ProfileCompressor {
18760
19215
  }
18761
19216
 
18762
19217
  // src/modules/user-profile/profile.ts
18763
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync36, mkdirSync as mkdirSync16 } from "fs";
19218
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync13, existsSync as existsSync36, mkdirSync as mkdirSync16 } from "fs";
18764
19219
  import { join as join29 } from "path";
18765
19220
  import { homedir as homedir9, hostname, platform as platform7, type } from "os";
18766
19221
  import { env } from "process";
@@ -18795,7 +19250,7 @@ class UserProfile {
18795
19250
  if (!existsSync36(path))
18796
19251
  return null;
18797
19252
  try {
18798
- const data = JSON.parse(readFileSync20(path, "utf-8"));
19253
+ const data = JSON.parse(readFileSync21(path, "utf-8"));
18799
19254
  this.info = {
18800
19255
  platform: data.platform,
18801
19256
  os: data.os,
@@ -18830,7 +19285,7 @@ class UserProfile {
18830
19285
  var init_profile = () => {};
18831
19286
 
18832
19287
  // src/modules/skills/loader.ts
18833
- import { readdirSync as readdirSync13, readFileSync as readFileSync21, existsSync as existsSync37, statSync as statSync6 } from "fs";
19288
+ import { readdirSync as readdirSync13, readFileSync as readFileSync22, existsSync as existsSync37, statSync as statSync6 } from "fs";
18834
19289
  import { join as join30 } from "path";
18835
19290
 
18836
19291
  class SkillsLoader {
@@ -18852,7 +19307,7 @@ class SkillsLoader {
18852
19307
  }
18853
19308
  if (!entry.endsWith(".md") && !entry.endsWith(".skill.md"))
18854
19309
  continue;
18855
- const content = readFileSync21(fullPath, "utf-8");
19310
+ const content = readFileSync22(fullPath, "utf-8");
18856
19311
  const parsed = this.parseSkillFile(content, fullPath);
18857
19312
  if (parsed)
18858
19313
  skills.push(parsed);
@@ -19402,7 +19857,7 @@ var init_check_tool = __esm(() => {
19402
19857
 
19403
19858
  // src/modules/lsp/module.ts
19404
19859
  import { existsSync as existsSync38 } from "fs";
19405
- import { resolve as resolve22 } from "path";
19860
+ import { relative as relative3, resolve as resolve22 } from "path";
19406
19861
 
19407
19862
  class LspModule {
19408
19863
  name = "lsp";
@@ -19537,6 +19992,7 @@ ${items}`;
19537
19992
  const errors = [];
19538
19993
  const warnings = [];
19539
19994
  const notes = [];
19995
+ const checkedFiles = [];
19540
19996
  let checked = 0;
19541
19997
  for (const file of files) {
19542
19998
  const serverConfig = getServerForFile(file, this.config);
@@ -19550,6 +20006,7 @@ ${items}`;
19550
20006
  const diags = await this.client.checkFile(file, ctx.baseDir, serverConfig, projectRoot);
19551
20007
  this.failuresByServer.set(key, 0);
19552
20008
  checked++;
20009
+ checkedFiles.push(relative3(ctx.baseDir, file).replace(/\\/g, "/"));
19553
20010
  for (const d of diags) {
19554
20011
  if (d.severity === 1)
19555
20012
  errors.push({ file, diag: d });
@@ -19572,6 +20029,10 @@ ${items}`;
19572
20029
  }
19573
20030
  }
19574
20031
  const lines = [];
20032
+ if (checked > 0) {
20033
+ lines.push(t("lsp.checked_files", { count: String(checked) }));
20034
+ lines.push(...checkedFiles.map((f) => ` ${f}`));
20035
+ }
19575
20036
  if (errors.length > 0) {
19576
20037
  lines.push(`[LSP errors] (${errors.length}):`);
19577
20038
  lines.push(formatCheckDiagnostics(errors, ctx.baseDir));
@@ -19748,8 +20209,8 @@ var init_startup_check = __esm(() => {
19748
20209
  });
19749
20210
 
19750
20211
  // src/modules/indexer/walker.ts
19751
- import { readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync40, watch } from "fs";
19752
- import { join as join32, relative as relative3, extname as extname5 } from "path";
20212
+ import { readdirSync as readdirSync14, readFileSync as readFileSync23, statSync as statSync7, existsSync as existsSync40, watch } from "fs";
20213
+ import { join as join32, relative as relative4, extname as extname5 } from "path";
19753
20214
 
19754
20215
  class Indexer {
19755
20216
  baseDir;
@@ -19788,7 +20249,7 @@ class Indexer {
19788
20249
  if (count >= this.MAX_FILES)
19789
20250
  return;
19790
20251
  const fullPath = join32(dir, entry);
19791
- const relPath = relative3(this.baseDir, fullPath);
20252
+ const relPath = relative4(this.baseDir, fullPath);
19792
20253
  const stat2 = statSync7(fullPath);
19793
20254
  if (stat2.isDirectory()) {
19794
20255
  if (!IGNORE_DIRS.has(entry)) {
@@ -19798,7 +20259,7 @@ class Indexer {
19798
20259
  const ext = extname5(entry).toLowerCase();
19799
20260
  const language = LANGUAGES[ext];
19800
20261
  if (language) {
19801
- const content = readFileSync22(fullPath, "utf-8");
20262
+ const content = readFileSync23(fullPath, "utf-8");
19802
20263
  const exports = this.extractExports(content, language);
19803
20264
  files.push({ path: relPath, language, exports, size: stat2.size });
19804
20265
  totalSize += stat2.size;
@@ -19850,7 +20311,7 @@ var init_walker = __esm(() => {
19850
20311
  });
19851
20312
 
19852
20313
  // src/modules/indexer/cache.ts
19853
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync41, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
20314
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync14, existsSync as existsSync41, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
19854
20315
  import { join as join33 } from "path";
19855
20316
 
19856
20317
  class IndexCache {
@@ -19865,7 +20326,7 @@ class IndexCache {
19865
20326
  if (!existsSync41(this.cachePath))
19866
20327
  return null;
19867
20328
  try {
19868
- this.cache = JSON.parse(readFileSync23(this.cachePath, "utf-8"));
20329
+ this.cache = JSON.parse(readFileSync24(this.cachePath, "utf-8"));
19869
20330
  return this.cache;
19870
20331
  } catch {
19871
20332
  return null;
@@ -19890,7 +20351,7 @@ class IndexCache {
19890
20351
  var init_cache = () => {};
19891
20352
 
19892
20353
  // src/modules/indexer/project-profile.ts
19893
- import { readFileSync as readFileSync24, existsSync as existsSync42 } from "fs";
20354
+ import { readFileSync as readFileSync25, existsSync as existsSync42 } from "fs";
19894
20355
  import { join as join34 } from "path";
19895
20356
  function detectManifest(baseDir) {
19896
20357
  for (const manifest of MANIFEST_ORDER) {
@@ -19911,7 +20372,7 @@ function cleanDependency(entry) {
19911
20372
  }
19912
20373
  function readPackageJson(baseDir) {
19913
20374
  try {
19914
- const raw = JSON.parse(readFileSync24(join34(baseDir, "package.json"), "utf-8"));
20375
+ const raw = JSON.parse(readFileSync25(join34(baseDir, "package.json"), "utf-8"));
19915
20376
  if (!raw || typeof raw !== "object")
19916
20377
  return null;
19917
20378
  const profile = {
@@ -19935,7 +20396,7 @@ function readPackageJson(baseDir) {
19935
20396
  }
19936
20397
  function readPyproject(baseDir) {
19937
20398
  try {
19938
- const content = readFileSync24(join34(baseDir, "pyproject.toml"), "utf-8");
20399
+ const content = readFileSync25(join34(baseDir, "pyproject.toml"), "utf-8");
19939
20400
  const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
19940
20401
  const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
19941
20402
  if (nameMatch)
@@ -19951,7 +20412,7 @@ function readPyproject(baseDir) {
19951
20412
  }
19952
20413
  function readCargo(baseDir) {
19953
20414
  try {
19954
- const content = readFileSync24(join34(baseDir, "Cargo.toml"), "utf-8");
20415
+ const content = readFileSync25(join34(baseDir, "Cargo.toml"), "utf-8");
19955
20416
  const profile = { runtime: "rust", deps: [], devDeps: [], scripts: {} };
19956
20417
  const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
19957
20418
  if (nameMatch)
@@ -19975,7 +20436,7 @@ function readCargo(baseDir) {
19975
20436
  }
19976
20437
  function readGoMod(baseDir) {
19977
20438
  try {
19978
- const content = readFileSync24(join34(baseDir, "go.mod"), "utf-8");
20439
+ const content = readFileSync25(join34(baseDir, "go.mod"), "utf-8");
19979
20440
  const profile = { runtime: "go", deps: [], devDeps: [], scripts: {} };
19980
20441
  const moduleMatch = content.match(/^module\s+(\S+)/m);
19981
20442
  if (moduleMatch)
@@ -19993,7 +20454,7 @@ function readGoMod(baseDir) {
19993
20454
  }
19994
20455
  function readRequirements(baseDir) {
19995
20456
  try {
19996
- const content = readFileSync24(join34(baseDir, "requirements.txt"), "utf-8");
20457
+ const content = readFileSync25(join34(baseDir, "requirements.txt"), "utf-8");
19997
20458
  const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
19998
20459
  for (const line of content.split(`
19999
20460
  `)) {
@@ -20546,7 +21007,7 @@ var init_module8 = __esm(() => {
20546
21007
  });
20547
21008
 
20548
21009
  // src/core/version.ts
20549
- import { existsSync as existsSync43, readFileSync as readFileSync25 } from "fs";
21010
+ import { existsSync as existsSync43, readFileSync as readFileSync26 } from "fs";
20550
21011
  import { join as join36, dirname as dirname13 } from "path";
20551
21012
  import { fileURLToPath as fileURLToPath2 } from "url";
20552
21013
  function readMmaVersion() {
@@ -20555,7 +21016,7 @@ function readMmaVersion() {
20555
21016
  for (const p of candidates) {
20556
21017
  if (existsSync43(p)) {
20557
21018
  try {
20558
- const raw = JSON.parse(readFileSync25(p, "utf8"));
21019
+ const raw = JSON.parse(readFileSync26(p, "utf8"));
20559
21020
  if (raw.version)
20560
21021
  return raw.version;
20561
21022
  } catch {}
@@ -20573,7 +21034,7 @@ __export(exports_bootstrap, {
20573
21034
  });
20574
21035
  import { homedir as homedir11 } from "os";
20575
21036
  import { join as join37, resolve as resolve23 } from "path";
20576
- import { existsSync as existsSync44, readFileSync as readFileSync26, writeFileSync as writeFileSync15 } from "fs";
21037
+ import { existsSync as existsSync44, readFileSync as readFileSync27, writeFileSync as writeFileSync15 } from "fs";
20577
21038
  function buildSystemInfo(config, baseDir, profileCompressed) {
20578
21039
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
20579
21040
  const isWin = profileCompressed.toLowerCase().includes("win32");
@@ -20735,7 +21196,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20735
21196
  toolCtx.toolExecutor = toolExecutor;
20736
21197
  const hallucinationDetector = new HallucinationDetector(baseDir, llmProvider);
20737
21198
  const moduleRegistry = new ModuleRegistry;
20738
- const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
21199
+ const execModule = new ExecutionModule(baseDir, config.stuckThreshold, config.errorWebSearch?.threshold ?? 5);
20739
21200
  const activeMeta = sessionManager.getActiveMeta();
20740
21201
  if (activeMeta && activeMeta.messageCount > 0) {
20741
21202
  execModule.restorePlan();
@@ -20833,7 +21294,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20833
21294
  ];
20834
21295
  for (const p of agentsMdCandidates) {
20835
21296
  if (existsSync44(p)) {
20836
- const content = readFileSync26(p, "utf-8").trim();
21297
+ const content = readFileSync27(p, "utf-8").trim();
20837
21298
  if (content) {
20838
21299
  agentsMdBlocks.push({
20839
21300
  content,
@@ -21697,13 +22158,13 @@ __export(exports_manifest, {
21697
22158
  getCertMark: () => getCertMark,
21698
22159
  MANIFEST_PATH: () => MANIFEST_PATH
21699
22160
  });
21700
- import { existsSync as existsSync45, readFileSync as readFileSync27, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
22161
+ import { existsSync as existsSync45, readFileSync as readFileSync28, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
21701
22162
  import { homedir as homedir13 } from "os";
21702
22163
  import { join as join39 } from "path";
21703
22164
  function readManifest(path = MANIFEST_PATH) {
21704
22165
  try {
21705
22166
  if (existsSync45(path)) {
21706
- const raw = JSON.parse(readFileSync27(path, "utf-8"));
22167
+ const raw = JSON.parse(readFileSync28(path, "utf-8"));
21707
22168
  return { version: 1, certifications: raw.certifications ?? [] };
21708
22169
  }
21709
22170
  } catch {}
@@ -28868,7 +29329,7 @@ var init_scenarios = __esm(() => {
28868
29329
  });
28869
29330
 
28870
29331
  // src/modules/certification/loader.ts
28871
- import { existsSync as existsSync46, readdirSync as readdirSync15, readFileSync as readFileSync28 } from "fs";
29332
+ import { existsSync as existsSync46, readdirSync as readdirSync15, readFileSync as readFileSync29 } from "fs";
28872
29333
  import { join as join40 } from "path";
28873
29334
  function validateScenario(s) {
28874
29335
  const errors2 = [];
@@ -28923,7 +29384,7 @@ function loadScenarios(userDir) {
28923
29384
  if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
28924
29385
  continue;
28925
29386
  try {
28926
- const raw = readFileSync28(join40(userDir, file), "utf-8");
29387
+ const raw = readFileSync29(join40(userDir, file), "utf-8");
28927
29388
  const data = $parse(raw);
28928
29389
  const parsed = normalizeScenario(data, file);
28929
29390
  const errs = validateScenario(parsed);
@@ -28976,7 +29437,7 @@ var init_loader3 = __esm(() => {
28976
29437
  });
28977
29438
 
28978
29439
  // src/modules/certification/fact-checker.ts
28979
- import { existsSync as existsSync47, readFileSync as readFileSync29, statSync as statSync8 } from "fs";
29440
+ import { existsSync as existsSync47, readFileSync as readFileSync30, statSync as statSync8 } from "fs";
28980
29441
  import { join as join41 } from "path";
28981
29442
  function checkSandbox(sandboxDir, checks, exitCode, output) {
28982
29443
  const failures = [];
@@ -29003,7 +29464,7 @@ function runCheck2(sandboxDir, check, exitCode, output) {
29003
29464
  const abs = join41(sandboxDir, check.path);
29004
29465
  if (!isFile(abs))
29005
29466
  return false;
29006
- const content = readFileSync29(abs, "utf-8");
29467
+ const content = readFileSync30(abs, "utf-8");
29007
29468
  if (check.contains !== undefined)
29008
29469
  return content.includes(check.contains);
29009
29470
  if (check.equals !== undefined)
@@ -29014,7 +29475,7 @@ function runCheck2(sandboxDir, check, exitCode, output) {
29014
29475
  const abs = join41(sandboxDir, check.path);
29015
29476
  if (!isFile(abs))
29016
29477
  return false;
29017
- return new RegExp(check.pattern).test(readFileSync29(abs, "utf-8"));
29478
+ return new RegExp(check.pattern).test(readFileSync30(abs, "utf-8"));
29018
29479
  }
29019
29480
  default:
29020
29481
  return false;
@@ -29230,13 +29691,13 @@ import { rmSync as rmSync5 } from "fs";
29230
29691
  import { homedir as homedir14 } from "os";
29231
29692
  import { join as join43, dirname as dirname15 } from "path";
29232
29693
  import { fileURLToPath as fileURLToPath3 } from "url";
29233
- import { existsSync as existsSync49, readFileSync as readFileSync30 } from "fs";
29694
+ import { existsSync as existsSync49, readFileSync as readFileSync31 } from "fs";
29234
29695
  function readVersion() {
29235
29696
  const candidates = [join43(MMA_ROOT, "package.json")];
29236
29697
  for (const p of candidates) {
29237
29698
  if (existsSync49(p)) {
29238
29699
  try {
29239
- const raw = JSON.parse(readFileSync30(p, "utf-8"));
29700
+ const raw = JSON.parse(readFileSync31(p, "utf-8"));
29240
29701
  if (raw.version)
29241
29702
  return raw.version;
29242
29703
  } catch {}
@@ -29397,7 +29858,7 @@ __export(exports_repl_commands, {
29397
29858
  });
29398
29859
  import { join as join45, dirname as dirname17 } from "path";
29399
29860
  import { homedir as homedir16 } from "os";
29400
- import { existsSync as existsSync51, readFileSync as readFileSync32 } from "fs";
29861
+ import { existsSync as existsSync51, readFileSync as readFileSync33 } from "fs";
29401
29862
  import { fileURLToPath as fileURLToPath5 } from "url";
29402
29863
  function readVersion3() {
29403
29864
  const here = dirname17(fileURLToPath5(import.meta.url));
@@ -29405,7 +29866,7 @@ function readVersion3() {
29405
29866
  for (const p of candidates) {
29406
29867
  if (existsSync51(p)) {
29407
29868
  try {
29408
- const raw = JSON.parse(readFileSync32(p, "utf8"));
29869
+ const raw = JSON.parse(readFileSync33(p, "utf8"));
29409
29870
  if (raw.version)
29410
29871
  return raw.version;
29411
29872
  } catch {}
@@ -30063,7 +30524,7 @@ init_setup();
30063
30524
  init_i18n();
30064
30525
  import { join as join44, dirname as dirname16 } from "path";
30065
30526
  import { homedir as homedir15 } from "os";
30066
- import { existsSync as existsSync50, readFileSync as readFileSync31 } from "fs";
30527
+ import { existsSync as existsSync50, readFileSync as readFileSync32 } from "fs";
30067
30528
 
30068
30529
  // src/cli/security-commands.ts
30069
30530
  init_bootstrap();
@@ -30705,7 +31166,7 @@ function readVersion2() {
30705
31166
  for (const p of candidates) {
30706
31167
  if (existsSync50(p)) {
30707
31168
  try {
30708
- const raw = JSON.parse(readFileSync31(p, "utf8"));
31169
+ const raw = JSON.parse(readFileSync32(p, "utf8"));
30709
31170
  if (raw.version)
30710
31171
  return raw.version;
30711
31172
  } catch {}
@@ -30940,9 +31401,9 @@ import * as readline2 from "readline";
30940
31401
 
30941
31402
  // src/ui/line-math.ts
30942
31403
  init_string_width();
30943
- var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
31404
+ var ANSI_RE2 = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
30944
31405
  function stripAnsi2(s) {
30945
- return s.replace(ANSI_RE, "");
31406
+ return s.replace(ANSI_RE2, "");
30946
31407
  }
30947
31408
  function charLen(s) {
30948
31409
  return Array.from(s).length;
@@ -31688,7 +32149,7 @@ class LineEditor {
31688
32149
  }
31689
32150
 
31690
32151
  // src/cli/repl.ts
31691
- import { existsSync as existsSync53, readFileSync as readFileSync34, writeFileSync as writeFileSync17 } from "fs";
32152
+ import { existsSync as existsSync53, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "fs";
31692
32153
  import { join as join47, dirname as dirname18 } from "path";
31693
32154
  import { homedir as homedir17 } from "os";
31694
32155
  import { fileURLToPath as fileURLToPath6 } from "url";
@@ -31996,6 +32457,7 @@ init_box();
31996
32457
  init_table();
31997
32458
  init_i18n();
31998
32459
  var GUTTER = " ";
32460
+ var BUSY_TOOLS = new Set(["lsp_check"]);
31999
32461
  function toolMarker(tool) {
32000
32462
  switch (tool) {
32001
32463
  case "write_file":
@@ -32115,6 +32577,9 @@ ${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}$
32115
32577
  this.out.write(`
32116
32578
  ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}
32117
32579
  `);
32580
+ if (BUSY_TOOLS.has(tool)) {
32581
+ this.spinner.start(t("ui.tool_running", { tool: friendlyTool(tool) }));
32582
+ }
32118
32583
  return;
32119
32584
  }
32120
32585
  this.spinner.start(`${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}`);
@@ -32273,14 +32738,14 @@ init_config();
32273
32738
  init_colors();
32274
32739
  init_js_identifiers();
32275
32740
  init_i18n();
32276
- import { existsSync as existsSync52, readFileSync as readFileSync33 } from "fs";
32741
+ import { existsSync as existsSync52, readFileSync as readFileSync34 } from "fs";
32277
32742
  import { join as join46 } from "path";
32278
32743
  function readActivePlan(baseDir) {
32279
32744
  const p = join46(baseDir, ".mma", "plans", "active.json");
32280
32745
  if (!existsSync52(p))
32281
32746
  return null;
32282
32747
  try {
32283
- const raw = readFileSync33(p, "utf-8");
32748
+ const raw = readFileSync34(p, "utf-8");
32284
32749
  if (!raw.trim())
32285
32750
  return null;
32286
32751
  const parsed = JSON.parse(raw);
@@ -32379,7 +32844,7 @@ function readVersion4() {
32379
32844
  for (const p of candidates) {
32380
32845
  if (existsSync53(p)) {
32381
32846
  try {
32382
- const raw = JSON.parse(readFileSync34(p, "utf8"));
32847
+ const raw = JSON.parse(readFileSync35(p, "utf8"));
32383
32848
  if (raw.version)
32384
32849
  return raw.version;
32385
32850
  } catch {}
@@ -32475,7 +32940,7 @@ class Repl {
32475
32940
  loadHistory() {
32476
32941
  if (existsSync53(this.historyPath)) {
32477
32942
  try {
32478
- const raw = readFileSync34(this.historyPath, "utf-8");
32943
+ const raw = readFileSync35(this.historyPath, "utf-8");
32479
32944
  this.history = raw.split(`
32480
32945
  `).filter(Boolean).slice(-this.maxHistory);
32481
32946
  } catch {
@@ -32948,7 +33413,7 @@ init_setup();
32948
33413
  init_config2();
32949
33414
  init_i18n();
32950
33415
  init_colors();
32951
- import { existsSync as existsSync54, readFileSync as readFileSync35 } from "fs";
33416
+ import { existsSync as existsSync54, readFileSync as readFileSync36 } from "fs";
32952
33417
  import { join as join48, dirname as dirname19 } from "path";
32953
33418
  import { homedir as homedir18 } from "os";
32954
33419
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -33057,7 +33522,7 @@ function readVersion5() {
33057
33522
  for (const p of candidates) {
33058
33523
  if (existsSync54(p)) {
33059
33524
  try {
33060
- const raw = JSON.parse(readFileSync35(p, "utf8"));
33525
+ const raw = JSON.parse(readFileSync36(p, "utf8"));
33061
33526
  if (raw.version)
33062
33527
  return raw.version;
33063
33528
  } catch {}