micro-models-agent 0.41.2 → 0.42.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 +299 -55
  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}"',
@@ -2561,6 +2573,7 @@ Command: {command}`,
2561
2573
  "lsp.check_notfound": "Path not found: {path}",
2562
2574
  "lsp.check_unsupported": "No LSP server configured for: {path}",
2563
2575
  "lsp.check_clean": "No errors or warnings detected ({count} file(s) checked).",
2576
+ "lsp.checked_files": "Checked {count} file(s):",
2564
2577
  "lsp.startup_header": "[Existing project errors (checked at session start) — fix these before continuing]:",
2565
2578
  "cli.description": "Micro Models Agent — AI coding agent for small models",
2566
2579
  "cli.init": "Run interactive setup wizard",
@@ -2860,6 +2873,11 @@ Use this knowledge to answer the user's question.`,
2860
2873
  {hints}`,
2861
2874
  "exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
2862
2875
  "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.',
2876
+ "exec.error_search_results": `The error "{sig}" has occurred {count} times. Web search found:
2877
+ {results}
2878
+ Apply a matching solution from these results. If none is relevant — do NOT repeat the same approach; change strategy or honestly report being stuck.`,
2879
+ "exec.error_search_failed": 'Web search returned nothing for "{query}".',
2880
+ "exec.error_search_no_query": "Error output is not meaningful — skipping the web search.",
2863
2881
  "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
2882
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2865
2883
  "hall.short_response": "Response too short or empty",
@@ -2909,6 +2927,7 @@ Use this knowledge to answer the user's question.`,
2909
2927
  "ui.success_prefix": "✓ ",
2910
2928
  "ui.warning_prefix": "⚠ ",
2911
2929
  "ui.thinking": "Thinking…",
2930
+ "ui.tool_running": "{tool}…",
2912
2931
  "ui.step_context": "step {id}: {desc}",
2913
2932
  "indexer.map_header": "Project map",
2914
2933
  "indexer.top_directories": "Top directories",
@@ -3111,6 +3130,7 @@ var init_ru = __esm(() => {
3111
3130
  "tool.no_results": 'Нет результатов по "{query}"',
3112
3131
  "tool.search_results": `Результаты поиска "{query}":
3113
3132
  {results}`,
3133
+ "tool.web_search_disabled": "Веб-поиск отключён (webSearch.enabled: false). Используй только файловые и системные инструменты.",
3114
3134
  "tool.history_results": `Результаты истории "{query}":
3115
3135
  {results}`,
3116
3136
  "tool.no_history": 'Нет записей истории по "{query}"',
@@ -3196,6 +3216,7 @@ var init_ru = __esm(() => {
3196
3216
  "lsp.check_notfound": "Путь не найден: {path}",
3197
3217
  "lsp.check_unsupported": "Для файла не настроен LSP-сервер: {path}",
3198
3218
  "lsp.check_clean": "Ошибок и предупреждений не обнаружено (проверено файлов: {count}).",
3219
+ "lsp.checked_files": "Проверено файлов: {count}:",
3199
3220
  "lsp.startup_header": "[Существующие ошибки проекта (проверено при старте сессии) — исправьте их перед продолжением]:",
3200
3221
  "cli.description": "Micro Models Agent — ИИ-агент для кодинга на малых моделях",
3201
3222
  "cli.init": "Запустить мастер настройки",
@@ -3495,6 +3516,11 @@ var init_ru = __esm(() => {
3495
3516
  {hints}`,
3496
3517
  "exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
3497
3518
  "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 для этого.',
3519
+ "exec.error_search_results": `Ошибка "{sig}" повторилась {count} раз. Поиск в интернете нашёл:
3520
+ {results}
3521
+ Примени подходящее решение из результатов. Если ни один результат не релевантен — НЕ повторяй тот же подход, смени стратегию или честно сообщи о застревании.`,
3522
+ "exec.error_search_failed": 'Поиск в интернете для "{query}" ничего не дал.',
3523
+ "exec.error_search_no_query": "Текст ошибки незначимый — поиск в интернете пропущен.",
3498
3524
  "exec.npm_exec_hint": '"could not determine executable to run" — у пакета/скрипта нет "bin". Используй "npm run <script>" (скрипт должен быть в package.json) или "bunx <pkg>" для пакета с объявленным bin.',
3499
3525
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3500
3526
  "hall.short_response": "Слишком короткий или пустой ответ",
@@ -3544,6 +3570,7 @@ var init_ru = __esm(() => {
3544
3570
  "ui.success_prefix": "✓ ",
3545
3571
  "ui.warning_prefix": "⚠ ",
3546
3572
  "ui.thinking": "Думаю…",
3573
+ "ui.tool_running": "{tool}…",
3547
3574
  "ui.step_context": "шаг {id}: {desc}",
3548
3575
  "indexer.map_header": "Карта проекта",
3549
3576
  "indexer.top_directories": "Основные директории",
@@ -9464,6 +9491,41 @@ function filterToolsByTags(tools, toolTags) {
9464
9491
  }
9465
9492
 
9466
9493
  // src/modules/execution/stuck-detector.ts
9494
+ function normalizeErrorSignature(toolName, output) {
9495
+ if (!output)
9496
+ return null;
9497
+ const cleaned = output.replace(ANSI_RE, "").replace(ERROR_LOC_RE, " <path>").split(`
9498
+ `).map((l) => l.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
9499
+ if (!cleaned)
9500
+ return null;
9501
+ return `${toolName}::${cleaned.slice(0, SIGNATURE_MAX_CHARS)}`;
9502
+ }
9503
+ function isSearchableError(output) {
9504
+ if (!output || !output.trim())
9505
+ return false;
9506
+ const text = output.trim();
9507
+ return SEARCHABLE_PATTERNS.some((p) => p.test(text));
9508
+ }
9509
+ function buildSearchQuery(stepDescription, lastBashCommand, lastErrorOutput, toolName, maxQueryChars) {
9510
+ const errorText = (lastErrorOutput || "").replace(ANSI_RE, "").replace(ERROR_LOC_RE, " <path>").split(`
9511
+ `).map((l) => l.trim()).filter(Boolean).slice(0, 3).join(" ").replace(/\s+/g, " ").trim();
9512
+ const context = `${stepDescription} ${lastBashCommand}`.toLowerCase();
9513
+ const runtime = RUNTIME_WORDS.find((w) => context.includes(w)) || "";
9514
+ const framework = FRAMEWORK_WORDS.find((w) => context.includes(w)) || "";
9515
+ let query;
9516
+ if (errorText) {
9517
+ query = runtime && !errorText.toLowerCase().includes(runtime) ? `${runtime} ${errorText}` : errorText;
9518
+ if (framework && !query.toLowerCase().includes(framework))
9519
+ query = `${query} ${framework}`;
9520
+ query = `${query} how to fix`;
9521
+ } else if (runtime || framework) {
9522
+ query = `${[runtime, framework, "error", "how to fix"].filter(Boolean).join(" ")}`;
9523
+ } else {
9524
+ query = `${toolName} ${stepDescription} how to fix`;
9525
+ }
9526
+ return query.slice(0, maxQueryChars);
9527
+ }
9528
+
9467
9529
  class StuckDetector {
9468
9530
  threshold;
9469
9531
  errorThreshold;
@@ -9485,9 +9547,13 @@ class StuckDetector {
9485
9547
  lastBashOutput = "";
9486
9548
  emptyBashRunCount = 0;
9487
9549
  readOnlyStreak = 0;
9488
- constructor(threshold = 6, errorThreshold = 3) {
9550
+ errorSignatureCounts = new Map;
9551
+ searchedErrorSignatures = new Set;
9552
+ webSearchThreshold;
9553
+ constructor(threshold = 6, errorThreshold = 3, webSearchThreshold = 5) {
9489
9554
  this.threshold = threshold;
9490
9555
  this.errorThreshold = errorThreshold;
9556
+ this.webSearchThreshold = webSearchThreshold;
9491
9557
  }
9492
9558
  recordIteration(stepId) {
9493
9559
  if (stepId === this.currentStepId) {
@@ -9517,6 +9583,10 @@ class StuckDetector {
9517
9583
  this.lastFailedTool = toolName;
9518
9584
  if (output)
9519
9585
  this.lastErrorOutput = output;
9586
+ const sig = normalizeErrorSignature(toolName, output);
9587
+ if (sig) {
9588
+ this.errorSignatureCounts.set(sig, (this.errorSignatureCounts.get(sig) || 0) + 1);
9589
+ }
9520
9590
  }
9521
9591
  getLastErrorOutput() {
9522
9592
  return this.lastErrorOutput;
@@ -9540,6 +9610,26 @@ class StuckDetector {
9540
9610
  getLastFailedTool() {
9541
9611
  return this.lastFailedTool;
9542
9612
  }
9613
+ getRepeatedErrorSignature() {
9614
+ for (const [sig, count] of this.errorSignatureCounts) {
9615
+ if (count >= this.webSearchThreshold && !this.searchedErrorSignatures.has(sig)) {
9616
+ return sig;
9617
+ }
9618
+ }
9619
+ return null;
9620
+ }
9621
+ getErrorSignatureCount(sig) {
9622
+ return this.errorSignatureCounts.get(sig) || 0;
9623
+ }
9624
+ markErrorSearched(sig) {
9625
+ this.searchedErrorSignatures.add(sig);
9626
+ }
9627
+ unmarkErrorSearched(sig) {
9628
+ this.searchedErrorSignatures.delete(sig);
9629
+ }
9630
+ hasErrorSearched(sig) {
9631
+ return this.searchedErrorSignatures.has(sig);
9632
+ }
9543
9633
  getIterationsOnCurrentStep() {
9544
9634
  return this.iterationsOnCurrentStep;
9545
9635
  }
@@ -9812,6 +9902,8 @@ class StuckDetector {
9812
9902
  this.lastBashOutput = "";
9813
9903
  this.emptyBashRunCount = 0;
9814
9904
  this.readOnlyStreak = 0;
9905
+ this.errorSignatureCounts.clear();
9906
+ this.searchedErrorSignatures.clear();
9815
9907
  }
9816
9908
  resetStepProgress() {
9817
9909
  this.currentStepId = null;
@@ -9825,6 +9917,8 @@ class StuckDetector {
9825
9917
  this.lastBashCommand = "";
9826
9918
  this.lastBashOutput = "";
9827
9919
  this.emptyBashRunCount = 0;
9920
+ this.errorSignatureCounts.clear();
9921
+ this.searchedErrorSignatures.clear();
9828
9922
  }
9829
9923
  resetStepState(stepId) {
9830
9924
  this.currentStepId = stepId;
@@ -9839,9 +9933,11 @@ class StuckDetector {
9839
9933
  this.lastBashOutput = "";
9840
9934
  this.emptyBashRunCount = 0;
9841
9935
  this.readOnlyStreak = 0;
9936
+ this.errorSignatureCounts.clear();
9937
+ this.searchedErrorSignatures.clear();
9842
9938
  }
9843
9939
  }
9844
- var READ_ONLY_TOOLS, READ_ONLY_LOOP_THRESHOLD = 10;
9940
+ 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
9941
  var init_stuck_detector = __esm(() => {
9846
9942
  init_i18n();
9847
9943
  READ_ONLY_TOOLS = new Set([
@@ -9865,6 +9961,55 @@ var init_stuck_detector = __esm(() => {
9865
9961
  "verify",
9866
9962
  "load_skill"
9867
9963
  ]);
9964
+ ANSI_RE = /\x1b\[[0-9;]*m/g;
9965
+ ERROR_LOC_RE = /(?:[A-Za-z]:[\\/])?(?:[\w.@+-]+[\\/])+[\w.@+-]+\.[a-zA-Z0-9]{1,6}(?::\d+(?::\d+)?)?/g;
9966
+ SEARCHABLE_PATTERNS = [
9967
+ /error TS\d+/i,
9968
+ /ERR_[A-Z_]+/,
9969
+ /ENOENT|EACCES|EPERM|ECONNREFUSED|ETIMEDOUT|ECONNRESET|ENOTFOUND|EADDRINUSE/i,
9970
+ /Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i,
9971
+ /TypeError|ReferenceError|SyntaxError|RangeError/i,
9972
+ /is not a function|is not defined|is not a constructor/i,
9973
+ /Cannot read properties of/i,
9974
+ /failed to (compile|build|resolve|parse)/i,
9975
+ /fatal error|panic/i
9976
+ ];
9977
+ RUNTIME_WORDS = [
9978
+ "bun",
9979
+ "node",
9980
+ "deno",
9981
+ "npm",
9982
+ "yarn",
9983
+ "pnpm",
9984
+ "pip",
9985
+ "pip3",
9986
+ "python",
9987
+ "python3",
9988
+ "tsc",
9989
+ "tsx",
9990
+ "npx",
9991
+ "go",
9992
+ "cargo",
9993
+ "gradle",
9994
+ "maven",
9995
+ "make"
9996
+ ];
9997
+ FRAMEWORK_WORDS = [
9998
+ "react",
9999
+ "vue",
10000
+ "angular",
10001
+ "svelte",
10002
+ "vite",
10003
+ "next",
10004
+ "nest",
10005
+ "express",
10006
+ "fastify",
10007
+ "flask",
10008
+ "django",
10009
+ "spring",
10010
+ "webpack",
10011
+ "tailwind"
10012
+ ];
9868
10013
  });
9869
10014
 
9870
10015
  // src/modules/artifacts/store.ts
@@ -13425,6 +13570,43 @@ var init_network_validator = __esm(() => {
13425
13570
  });
13426
13571
 
13427
13572
  // src/tools/web-search.ts
13573
+ async function performWebSearch(query, numResults, networkConfig, requestTimeoutMs) {
13574
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13575
+ const validation = isUrlAllowed(url, networkConfig);
13576
+ if (!validation.allowed) {
13577
+ return {
13578
+ success: false,
13579
+ blocked: true,
13580
+ results: [],
13581
+ error: validation.reason || "URL blocked by security policy"
13582
+ };
13583
+ }
13584
+ try {
13585
+ const response = await fetch(url, {
13586
+ signal: AbortSignal.timeout(requestTimeoutMs ?? networkConfig?.requestTimeout ?? 1e4)
13587
+ });
13588
+ const html = await response.text();
13589
+ const results = [];
13590
+ const snippetRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
13591
+ let match;
13592
+ while ((match = snippetRegex.exec(html)) !== null && results.length < numResults) {
13593
+ results.push({
13594
+ url: match[1].trim(),
13595
+ title: match[2].replace(/<[^>]+>/g, "").trim(),
13596
+ snippet: match[3].replace(/<[^>]+>/g, "").trim()
13597
+ });
13598
+ }
13599
+ return { success: true, results };
13600
+ } catch (err) {
13601
+ return { success: false, results: [], error: err.message };
13602
+ }
13603
+ }
13604
+ function formatResults(results) {
13605
+ return results.map((r, i) => `${i + 1}. ${r.title}
13606
+ URL: ${r.url}
13607
+ ${r.snippet}`).join(`
13608
+ `);
13609
+ }
13428
13610
  var webSearchTool;
13429
13611
  var init_web_search = __esm(() => {
13430
13612
  init_i18n();
@@ -13447,59 +13629,42 @@ var init_web_search = __esm(() => {
13447
13629
  required: ["query"]
13448
13630
  },
13449
13631
  handler: async (ctx, args) => {
13632
+ if (ctx.config?.webSearch?.enabled === false) {
13633
+ return { success: false, output: t("tool.web_search_disabled") };
13634
+ }
13450
13635
  const query = String(args.query || "");
13451
13636
  const numResults = Number(args.numResults) || 5;
13452
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13453
13637
  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));
13638
+ const outcome = await performWebSearch(query, numResults, securityConfig);
13639
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13640
+ if (outcome.blocked) {
13641
+ logSecurityBlock(ctx.sessionId, "network_request", outcome.error || "URL blocked by security policy", sanitizeUrl(url));
13457
13642
  return {
13458
13643
  success: false,
13459
- output: `[SECURITY BLOCKED] Search URL is not allowed: ${validation.reason}`
13644
+ output: t("error.search_failed", { message: String(outcome.error) })
13460
13645
  };
13461
13646
  }
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}`);
13647
+ logNetworkRequest(ctx.sessionId, sanitizeUrl(url), outcome.success, `Results: ${outcome.results.length}`);
13648
+ if (!outcome.success) {
13498
13649
  return {
13499
13650
  success: false,
13500
- output: t("error.search_failed", { message: err.message })
13651
+ output: t("error.search_failed", { message: String(outcome.error) })
13501
13652
  };
13502
13653
  }
13654
+ if (outcome.results.length === 0) {
13655
+ return { success: true, output: t("tool.no_results", { query }) };
13656
+ }
13657
+ return {
13658
+ success: true,
13659
+ output: t("tool.search_results", {
13660
+ query,
13661
+ results: formatResults(outcome.results)
13662
+ }),
13663
+ display: t("tool.web_search_result", {
13664
+ query,
13665
+ count: String(outcome.results.length)
13666
+ })
13667
+ };
13503
13668
  }
13504
13669
  };
13505
13670
  });
@@ -17785,7 +17950,8 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17785
17950
  deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
17786
17951
  }
17787
17952
  const toolText = String(result.output ?? "");
17788
- if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
17953
+ const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
17954
+ if (hasTypeError) {
17789
17955
  deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
17790
17956
  }
17791
17957
  if (!result.success) {
@@ -17803,7 +17969,9 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17803
17969
  }
17804
17970
  }
17805
17971
  }
17806
- deps.stuckDetector.recordToolError(call.name, result.output);
17972
+ if (!hasTypeError) {
17973
+ deps.stuckDetector.recordToolError(call.name, result.output);
17974
+ }
17807
17975
  const actionableHints = deps.stuckDetector.getActionableHints();
17808
17976
  const alternative = deps.stuckDetector.getToolAlternative();
17809
17977
  if (actionableHints.length > 0 || alternative) {
@@ -17857,6 +18025,7 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17857
18025
  }
17858
18026
  deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
17859
18027
  }
18028
+ deps.maybeSearchError(ctx, call);
17860
18029
  }
17861
18030
  };
17862
18031
  }
@@ -17882,6 +18051,12 @@ class ExecutionModule {
17882
18051
  _auditSkipsRemaining = 0;
17883
18052
  pendingMessages = [];
17884
18053
  forbiddenBashFailures = new Map;
18054
+ searchRunner;
18055
+ webSearchThreshold;
18056
+ searchesThisSession = 0;
18057
+ retryWebSearchSignatures = new Set;
18058
+ lastErrorSearchAt = 0;
18059
+ errorSearchCooldownMs;
17885
18060
  state = {
17886
18061
  lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
17887
18062
  consecutivePlanWarnings: 0,
@@ -17900,12 +18075,15 @@ class ExecutionModule {
17900
18075
  }
17901
18076
  };
17902
18077
  })();
17903
- constructor(baseDir, stuckThreshold = 6) {
18078
+ constructor(baseDir, stuckThreshold = 6, webSearchThreshold = 5, searchRunner = performWebSearch, errorSearchCooldownMs = ERROR_SEARCH_MIN_INTERVAL_MS) {
17904
18079
  this.baseDir = baseDir;
17905
18080
  this.verifier = new StepVerifier(baseDir);
17906
- this.stuckDetector = new StuckDetector(stuckThreshold);
18081
+ this.stuckDetector = new StuckDetector(stuckThreshold, 3, webSearchThreshold);
17907
18082
  this.auditor = new Auditor(baseDir);
17908
18083
  this.store = new PlanStore(baseDir);
18084
+ this.webSearchThreshold = webSearchThreshold;
18085
+ this.searchRunner = searchRunner;
18086
+ this.errorSearchCooldownMs = errorSearchCooldownMs;
17909
18087
  }
17910
18088
  setPlan(plan) {
17911
18089
  this.tracker = new PlanTracker(plan);
@@ -17936,6 +18114,58 @@ class ExecutionModule {
17936
18114
  getStuckDetector() {
17937
18115
  return this.stuckDetector;
17938
18116
  }
18117
+ maybeSearchError(ctx, call) {
18118
+ if (ctx.config?.webSearch?.enabled === false)
18119
+ return;
18120
+ const cfg = ctx.config?.errorWebSearch;
18121
+ if (!cfg?.enabled)
18122
+ return;
18123
+ const sig = this.stuckDetector.getRepeatedErrorSignature();
18124
+ if (!sig)
18125
+ return;
18126
+ if (this.stuckDetector.hasErrorSearched(sig) && !this.retryWebSearchSignatures.has(sig))
18127
+ return;
18128
+ if (this.searchesThisSession >= (cfg.maxSearchesPerSession ?? 3))
18129
+ return;
18130
+ const lastError = this.stuckDetector.getLastErrorOutput();
18131
+ if (!isSearchableError(lastError)) {
18132
+ ctx.logger?.debug(t("exec.error_search_no_query"));
18133
+ return;
18134
+ }
18135
+ const now = Date.now();
18136
+ if (now - this.lastErrorSearchAt < this.errorSearchCooldownMs)
18137
+ return;
18138
+ const stepDesc = this.tracker?.getCurrentStep()?.description ?? "";
18139
+ const query = buildSearchQuery(stepDesc, this.stuckDetector.getLastBashCommand(), lastError, call.name, cfg.maxQueryChars ?? 200);
18140
+ const count = this.stuckDetector.getErrorSignatureCount(sig);
18141
+ this.stuckDetector.markErrorSearched(sig);
18142
+ this.searchesThisSession++;
18143
+ this.lastErrorSearchAt = now;
18144
+ const networkConfig = getSessionSecurityConfig(ctx.config, ctx.sessionContext).network;
18145
+ this.searchRunner(query, cfg.maxResults ?? 5, networkConfig, cfg.requestTimeoutMs ?? 1e4).then((res) => {
18146
+ if (!res.success || res.results.length === 0) {
18147
+ if (!this.retryWebSearchSignatures.has(sig)) {
18148
+ this.retryWebSearchSignatures.add(sig);
18149
+ this.stuckDetector.unmarkErrorSearched(sig);
18150
+ }
18151
+ ctx.logger?.warn(t("exec.error_search_failed", { query }));
18152
+ ctx.sessionLog?.plan("web-search", `empty/failed: ${query}`);
18153
+ return;
18154
+ }
18155
+ const resultsText = res.results.map((r, i) => `${i + 1}. ${r.title} — ${r.url}
18156
+ ${r.snippet}`).join(`
18157
+ `);
18158
+ this.pendingMessages.push({
18159
+ role: "user",
18160
+ content: `<system-summary>${t("exec.error_search_results", {
18161
+ sig,
18162
+ count: String(count),
18163
+ query,
18164
+ results: resultsText
18165
+ })}</system-summary>`
18166
+ });
18167
+ }).catch((e) => ctx.logger?.warn(`error web search: ${e.message}`));
18168
+ }
17939
18169
  async runFinalAudit() {
17940
18170
  if (!this.tracker)
17941
18171
  return null;
@@ -18001,7 +18231,8 @@ class ExecutionModule {
18001
18231
  forbiddenBashFailures: this.forbiddenBashFailures,
18002
18232
  state: this.state,
18003
18233
  checkPlanAlignment: (call) => this.checkPlanAlignment(call),
18004
- advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl)
18234
+ advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl),
18235
+ maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call)
18005
18236
  });
18006
18237
  }
18007
18238
  preserveActive() {
@@ -18198,6 +18429,7 @@ class ExecutionModule {
18198
18429
  return getMessageText(firstUser.content).trim();
18199
18430
  }
18200
18431
  }
18432
+ var ERROR_SEARCH_MIN_INTERVAL_MS = 30000;
18201
18433
  var init_module = __esm(() => {
18202
18434
  init_i18n();
18203
18435
  init_tracker();
@@ -18206,6 +18438,8 @@ var init_module = __esm(() => {
18206
18438
  init_auditor();
18207
18439
  init_plan_store();
18208
18440
  init_js_identifiers();
18441
+ init_web_search();
18442
+ init_session_isolation();
18209
18443
  init_plan_tool();
18210
18444
  init_execution_plugin();
18211
18445
  });
@@ -19402,7 +19636,7 @@ var init_check_tool = __esm(() => {
19402
19636
 
19403
19637
  // src/modules/lsp/module.ts
19404
19638
  import { existsSync as existsSync38 } from "fs";
19405
- import { resolve as resolve22 } from "path";
19639
+ import { relative as relative3, resolve as resolve22 } from "path";
19406
19640
 
19407
19641
  class LspModule {
19408
19642
  name = "lsp";
@@ -19537,6 +19771,7 @@ ${items}`;
19537
19771
  const errors = [];
19538
19772
  const warnings = [];
19539
19773
  const notes = [];
19774
+ const checkedFiles = [];
19540
19775
  let checked = 0;
19541
19776
  for (const file of files) {
19542
19777
  const serverConfig = getServerForFile(file, this.config);
@@ -19550,6 +19785,7 @@ ${items}`;
19550
19785
  const diags = await this.client.checkFile(file, ctx.baseDir, serverConfig, projectRoot);
19551
19786
  this.failuresByServer.set(key, 0);
19552
19787
  checked++;
19788
+ checkedFiles.push(relative3(ctx.baseDir, file).replace(/\\/g, "/"));
19553
19789
  for (const d of diags) {
19554
19790
  if (d.severity === 1)
19555
19791
  errors.push({ file, diag: d });
@@ -19572,6 +19808,10 @@ ${items}`;
19572
19808
  }
19573
19809
  }
19574
19810
  const lines = [];
19811
+ if (checked > 0) {
19812
+ lines.push(t("lsp.checked_files", { count: String(checked) }));
19813
+ lines.push(...checkedFiles.map((f) => ` ${f}`));
19814
+ }
19575
19815
  if (errors.length > 0) {
19576
19816
  lines.push(`[LSP errors] (${errors.length}):`);
19577
19817
  lines.push(formatCheckDiagnostics(errors, ctx.baseDir));
@@ -19749,7 +19989,7 @@ var init_startup_check = __esm(() => {
19749
19989
 
19750
19990
  // src/modules/indexer/walker.ts
19751
19991
  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";
19992
+ import { join as join32, relative as relative4, extname as extname5 } from "path";
19753
19993
 
19754
19994
  class Indexer {
19755
19995
  baseDir;
@@ -19788,7 +20028,7 @@ class Indexer {
19788
20028
  if (count >= this.MAX_FILES)
19789
20029
  return;
19790
20030
  const fullPath = join32(dir, entry);
19791
- const relPath = relative3(this.baseDir, fullPath);
20031
+ const relPath = relative4(this.baseDir, fullPath);
19792
20032
  const stat2 = statSync7(fullPath);
19793
20033
  if (stat2.isDirectory()) {
19794
20034
  if (!IGNORE_DIRS.has(entry)) {
@@ -20735,7 +20975,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20735
20975
  toolCtx.toolExecutor = toolExecutor;
20736
20976
  const hallucinationDetector = new HallucinationDetector(baseDir, llmProvider);
20737
20977
  const moduleRegistry = new ModuleRegistry;
20738
- const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
20978
+ const execModule = new ExecutionModule(baseDir, config.stuckThreshold, config.errorWebSearch?.threshold ?? 5);
20739
20979
  const activeMeta = sessionManager.getActiveMeta();
20740
20980
  if (activeMeta && activeMeta.messageCount > 0) {
20741
20981
  execModule.restorePlan();
@@ -30940,9 +31180,9 @@ import * as readline2 from "readline";
30940
31180
 
30941
31181
  // src/ui/line-math.ts
30942
31182
  init_string_width();
30943
- var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
31183
+ var ANSI_RE2 = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
30944
31184
  function stripAnsi2(s) {
30945
- return s.replace(ANSI_RE, "");
31185
+ return s.replace(ANSI_RE2, "");
30946
31186
  }
30947
31187
  function charLen(s) {
30948
31188
  return Array.from(s).length;
@@ -31996,6 +32236,7 @@ init_box();
31996
32236
  init_table();
31997
32237
  init_i18n();
31998
32238
  var GUTTER = " ";
32239
+ var BUSY_TOOLS = new Set(["lsp_check"]);
31999
32240
  function toolMarker(tool) {
32000
32241
  switch (tool) {
32001
32242
  case "write_file":
@@ -32115,6 +32356,9 @@ ${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}$
32115
32356
  this.out.write(`
32116
32357
  ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}
32117
32358
  `);
32359
+ if (BUSY_TOOLS.has(tool)) {
32360
+ this.spinner.start(t("ui.tool_running", { tool: friendlyTool(tool) }));
32361
+ }
32118
32362
  return;
32119
32363
  }
32120
32364
  this.spinner.start(`${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}`);