micro-models-agent 0.41.0 → 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 (2) hide show
  1. package/dist/main.js +1362 -961
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2092,15 +2092,31 @@ var init_config = __esm(() => {
2092
2092
  servers: {
2093
2093
  typescript: {
2094
2094
  command: "npx",
2095
- args: ["typescript-language-server", "--stdio"],
2096
- timeout: 20000,
2095
+ args: [
2096
+ "--yes",
2097
+ "--package",
2098
+ "typescript-language-server",
2099
+ "--package",
2100
+ "typescript@5",
2101
+ "typescript-language-server",
2102
+ "--stdio"
2103
+ ],
2104
+ timeout: 30000,
2097
2105
  autoInstall: true,
2098
2106
  workspaceMarkers: ["package.json", "tsconfig.json"]
2099
2107
  },
2100
2108
  javascript: {
2101
2109
  command: "npx",
2102
- args: ["typescript-language-server", "--stdio"],
2103
- timeout: 15000,
2110
+ args: [
2111
+ "--yes",
2112
+ "--package",
2113
+ "typescript-language-server",
2114
+ "--package",
2115
+ "typescript@5",
2116
+ "typescript-language-server",
2117
+ "--stdio"
2118
+ ],
2119
+ timeout: 30000,
2104
2120
  autoInstall: true,
2105
2121
  workspaceMarkers: ["package.json", "tsconfig.json"]
2106
2122
  },
@@ -2142,6 +2158,8 @@ var init_config = __esm(() => {
2142
2158
  "--yes",
2143
2159
  "--package",
2144
2160
  "vscode-langservers-extracted",
2161
+ "--package",
2162
+ "typescript@5",
2145
2163
  "vscode-html-language-server",
2146
2164
  "--stdio"
2147
2165
  ],
@@ -2155,6 +2173,8 @@ var init_config = __esm(() => {
2155
2173
  "--yes",
2156
2174
  "--package",
2157
2175
  "vscode-langservers-extracted",
2176
+ "--package",
2177
+ "typescript@5",
2158
2178
  "vscode-css-language-server",
2159
2179
  "--stdio"
2160
2180
  ],
@@ -2267,6 +2287,17 @@ var init_defaults = __esm(() => {
2267
2287
  viewportHeight: 720,
2268
2288
  navigationTimeout: 15000
2269
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
+ },
2270
2301
  ui: {
2271
2302
  spinner: true,
2272
2303
  toolStyle: "inline",
@@ -2456,6 +2487,7 @@ Summary:
2456
2487
  "tool.no_results": 'No results found for "{query}"',
2457
2488
  "tool.search_results": `Search results for "{query}":
2458
2489
  {results}`,
2490
+ "tool.web_search_disabled": "Web search is disabled (webSearch.enabled: false). Use file/system tools only.",
2459
2491
  "tool.history_results": `History results for "{query}":
2460
2492
  {results}`,
2461
2493
  "tool.no_history": 'No history entries matching "{query}"',
@@ -2494,6 +2526,7 @@ Command: {command}`,
2494
2526
  "plan.title_steps": 'Plan "{title}" created with {count} steps',
2495
2527
  "plan.step_marked": "Step {step} marked as {status}",
2496
2528
  "plan.aborted": "Plan aborted",
2529
+ "plan.completed_archived": "Plan {id} is complete ({done}/{total} steps) — archived. You may create a new plan for new work (plan create), or reply with your final answer.",
2497
2530
  "plan.unknown_action": "Unknown plan action: {action}",
2498
2531
  "plan.updated": "Plan updated: {title} ({steps} steps)",
2499
2532
  "plan.acknowledged": "Plan {action}: acknowledged",
@@ -2533,12 +2566,14 @@ Command: {command}`,
2533
2566
  "verify.script_passed": "Script '{script}' passed",
2534
2567
  "verify.script_failed": "Script '{script}' failed: {message}",
2535
2568
  "verify.syntax_error": "Syntax error in: {path}",
2569
+ "verify.no_files": "Step {step} has no named files to verify — run a real check instead (bash: bun run build / bun test / lsp_check) and confirm the result.",
2536
2570
  "lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
2537
2571
  "lsp.check_disabled": "LSP checks are disabled in the config.",
2538
2572
  "lsp.check_no_path": "Provide a path (file or directory) to check.",
2539
2573
  "lsp.check_notfound": "Path not found: {path}",
2540
2574
  "lsp.check_unsupported": "No LSP server configured for: {path}",
2541
2575
  "lsp.check_clean": "No errors or warnings detected ({count} file(s) checked).",
2576
+ "lsp.checked_files": "Checked {count} file(s):",
2542
2577
  "lsp.startup_header": "[Existing project errors (checked at session start) — fix these before continuing]:",
2543
2578
  "cli.description": "Micro Models Agent — AI coding agent for small models",
2544
2579
  "cli.init": "Run interactive setup wizard",
@@ -2838,6 +2873,11 @@ Use this knowledge to answer the user's question.`,
2838
2873
  {hints}`,
2839
2874
  "exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
2840
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.",
2841
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.',
2842
2882
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2843
2883
  "hall.short_response": "Response too short or empty",
@@ -2887,6 +2927,7 @@ Use this knowledge to answer the user's question.`,
2887
2927
  "ui.success_prefix": "✓ ",
2888
2928
  "ui.warning_prefix": "⚠ ",
2889
2929
  "ui.thinking": "Thinking…",
2930
+ "ui.tool_running": "{tool}…",
2890
2931
  "ui.step_context": "step {id}: {desc}",
2891
2932
  "indexer.map_header": "Project map",
2892
2933
  "indexer.top_directories": "Top directories",
@@ -3089,6 +3130,7 @@ var init_ru = __esm(() => {
3089
3130
  "tool.no_results": 'Нет результатов по "{query}"',
3090
3131
  "tool.search_results": `Результаты поиска "{query}":
3091
3132
  {results}`,
3133
+ "tool.web_search_disabled": "Веб-поиск отключён (webSearch.enabled: false). Используй только файловые и системные инструменты.",
3092
3134
  "tool.history_results": `Результаты истории "{query}":
3093
3135
  {results}`,
3094
3136
  "tool.no_history": 'Нет записей истории по "{query}"',
@@ -3127,6 +3169,7 @@ var init_ru = __esm(() => {
3127
3169
  "plan.title_steps": 'План "{title}" создан с {count} шагами',
3128
3170
  "plan.step_marked": "Шаг {step} отмечен как {status}",
3129
3171
  "plan.aborted": "План отменён",
3172
+ "plan.completed_archived": "План {id} завершён ({done}/{total} шагов) — архивирован. Для новой задачи создай новый план (plan create) или дай финальный ответ.",
3130
3173
  "plan.unknown_action": "Неизвестное действие плана: {action}",
3131
3174
  "plan.updated": "План обновлён: {title} ({steps} шагов)",
3132
3175
  "plan.acknowledged": "План {action}: принято",
@@ -3166,12 +3209,14 @@ var init_ru = __esm(() => {
3166
3209
  "verify.script_passed": "Скрипт '{script}' пройден",
3167
3210
  "verify.script_failed": "Скрипт '{script}' не пройден: {message}",
3168
3211
  "verify.syntax_error": "Синтаксическая ошибка в: {path}",
3212
+ "verify.no_files": "Шаг {step} не содержит именованных файлов для проверки — выполни реальную проверку (bun run build / bun test / lsp_check) и подтверди результат.",
3169
3213
  "lsp.unavailable": "статические проверки недоступны (сервер не найден). Проверяйте через собственный build/test проекта.",
3170
3214
  "lsp.check_disabled": "LSP-проверки отключены в конфигурации.",
3171
3215
  "lsp.check_no_path": "Укажите путь (файл или каталог) для проверки.",
3172
3216
  "lsp.check_notfound": "Путь не найден: {path}",
3173
3217
  "lsp.check_unsupported": "Для файла не настроен LSP-сервер: {path}",
3174
3218
  "lsp.check_clean": "Ошибок и предупреждений не обнаружено (проверено файлов: {count}).",
3219
+ "lsp.checked_files": "Проверено файлов: {count}:",
3175
3220
  "lsp.startup_header": "[Существующие ошибки проекта (проверено при старте сессии) — исправьте их перед продолжением]:",
3176
3221
  "cli.description": "Micro Models Agent — ИИ-агент для кодинга на малых моделях",
3177
3222
  "cli.init": "Запустить мастер настройки",
@@ -3471,6 +3516,11 @@ var init_ru = __esm(() => {
3471
3516
  {hints}`,
3472
3517
  "exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
3473
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": "Текст ошибки незначимый — поиск в интернете пропущен.",
3474
3524
  "exec.npm_exec_hint": '"could not determine executable to run" — у пакета/скрипта нет "bin". Используй "npm run <script>" (скрипт должен быть в package.json) или "bunx <pkg>" для пакета с объявленным bin.',
3475
3525
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3476
3526
  "hall.short_response": "Слишком короткий или пустой ответ",
@@ -3520,6 +3570,7 @@ var init_ru = __esm(() => {
3520
3570
  "ui.success_prefix": "✓ ",
3521
3571
  "ui.warning_prefix": "⚠ ",
3522
3572
  "ui.thinking": "Думаю…",
3573
+ "ui.tool_running": "{tool}…",
3523
3574
  "ui.step_context": "шаг {id}: {desc}",
3524
3575
  "indexer.map_header": "Карта проекта",
3525
3576
  "indexer.top_directories": "Основные директории",
@@ -3979,6 +4030,23 @@ function regexReviver(_key, value) {
3979
4030
  }
3980
4031
  return value;
3981
4032
  }
4033
+ function normalizeLspServerArgs(config) {
4034
+ const servers = config.lsp?.servers;
4035
+ if (!servers)
4036
+ return;
4037
+ for (const [lang, legacy] of Object.entries(LEGACY_LSP_SERVER_ARGS)) {
4038
+ const server = servers[lang];
4039
+ const def = DEFAULT_LSP_CONFIG.servers[lang];
4040
+ if (!server || server.command !== "npx" || !def)
4041
+ continue;
4042
+ const storedArgs = Array.isArray(server.args) ? server.args : null;
4043
+ const isLegacy = storedArgs === null || JSON.stringify(storedArgs) === JSON.stringify(legacy.args);
4044
+ if (isLegacy) {
4045
+ server.args = def.args;
4046
+ server.timeout = def.timeout;
4047
+ }
4048
+ }
4049
+ }
3982
4050
  function loadJSON(path) {
3983
4051
  try {
3984
4052
  if (existsSync4(path)) {
@@ -4051,6 +4119,7 @@ function loadConfig(options) {
4051
4119
  if (config.security?.contentScan?.dangerousPatterns) {
4052
4120
  config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
4053
4121
  }
4122
+ normalizeLspServerArgs(config);
4054
4123
  config = applyEnvVars(config);
4055
4124
  try {
4056
4125
  const encryptor = new ConfigEncryptor;
@@ -4091,13 +4160,22 @@ function saveConfig(config, configPath) {
4091
4160
  writeFileSync3(configPath, JSON.stringify(config, regexReplacer, 2), "utf-8");
4092
4161
  }
4093
4162
  }
4163
+ var LEGACY_LSP_SERVER_ARGS;
4094
4164
  var init_config2 = __esm(() => {
4095
4165
  init_defaults();
4096
4166
  init_security();
4167
+ init_config();
4097
4168
  init_i18n();
4098
4169
  init_detect();
4099
4170
  init_backup();
4100
4171
  init_encryption();
4172
+ LEGACY_LSP_SERVER_ARGS = {
4173
+ typescript: { args: ["typescript-language-server", "--stdio"], timeout: 30000 },
4174
+ javascript: { args: ["typescript-language-server", "--stdio"], timeout: 30000 },
4175
+ json: { args: ["vscode-json-languageserver", "--stdio"], timeout: 30000 },
4176
+ html: { args: ["vscode-html-languageserver", "--stdio"], timeout: 60000 },
4177
+ css: { args: ["vscode-css-languageserver", "--stdio"], timeout: 60000 }
4178
+ };
4101
4179
  });
4102
4180
 
4103
4181
  // src/modules/security/data-sanitizer.ts
@@ -9413,6 +9491,41 @@ function filterToolsByTags(tools, toolTags) {
9413
9491
  }
9414
9492
 
9415
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
+
9416
9529
  class StuckDetector {
9417
9530
  threshold;
9418
9531
  errorThreshold;
@@ -9434,9 +9547,13 @@ class StuckDetector {
9434
9547
  lastBashOutput = "";
9435
9548
  emptyBashRunCount = 0;
9436
9549
  readOnlyStreak = 0;
9437
- constructor(threshold = 6, errorThreshold = 3) {
9550
+ errorSignatureCounts = new Map;
9551
+ searchedErrorSignatures = new Set;
9552
+ webSearchThreshold;
9553
+ constructor(threshold = 6, errorThreshold = 3, webSearchThreshold = 5) {
9438
9554
  this.threshold = threshold;
9439
9555
  this.errorThreshold = errorThreshold;
9556
+ this.webSearchThreshold = webSearchThreshold;
9440
9557
  }
9441
9558
  recordIteration(stepId) {
9442
9559
  if (stepId === this.currentStepId) {
@@ -9466,6 +9583,10 @@ class StuckDetector {
9466
9583
  this.lastFailedTool = toolName;
9467
9584
  if (output)
9468
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
+ }
9469
9590
  }
9470
9591
  getLastErrorOutput() {
9471
9592
  return this.lastErrorOutput;
@@ -9489,6 +9610,26 @@ class StuckDetector {
9489
9610
  getLastFailedTool() {
9490
9611
  return this.lastFailedTool;
9491
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
+ }
9492
9633
  getIterationsOnCurrentStep() {
9493
9634
  return this.iterationsOnCurrentStep;
9494
9635
  }
@@ -9761,6 +9902,8 @@ class StuckDetector {
9761
9902
  this.lastBashOutput = "";
9762
9903
  this.emptyBashRunCount = 0;
9763
9904
  this.readOnlyStreak = 0;
9905
+ this.errorSignatureCounts.clear();
9906
+ this.searchedErrorSignatures.clear();
9764
9907
  }
9765
9908
  resetStepProgress() {
9766
9909
  this.currentStepId = null;
@@ -9774,6 +9917,8 @@ class StuckDetector {
9774
9917
  this.lastBashCommand = "";
9775
9918
  this.lastBashOutput = "";
9776
9919
  this.emptyBashRunCount = 0;
9920
+ this.errorSignatureCounts.clear();
9921
+ this.searchedErrorSignatures.clear();
9777
9922
  }
9778
9923
  resetStepState(stepId) {
9779
9924
  this.currentStepId = stepId;
@@ -9788,9 +9933,11 @@ class StuckDetector {
9788
9933
  this.lastBashOutput = "";
9789
9934
  this.emptyBashRunCount = 0;
9790
9935
  this.readOnlyStreak = 0;
9936
+ this.errorSignatureCounts.clear();
9937
+ this.searchedErrorSignatures.clear();
9791
9938
  }
9792
9939
  }
9793
- 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;
9794
9941
  var init_stuck_detector = __esm(() => {
9795
9942
  init_i18n();
9796
9943
  READ_ONLY_TOOLS = new Set([
@@ -9814,6 +9961,55 @@ var init_stuck_detector = __esm(() => {
9814
9961
  "verify",
9815
9962
  "load_skill"
9816
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
+ ];
9817
10013
  });
9818
10014
 
9819
10015
  // src/modules/artifacts/store.ts
@@ -10192,215 +10388,720 @@ function findProjectRoot(filePath, baseDir, markers) {
10192
10388
  }
10193
10389
  var init_project_root = () => {};
10194
10390
 
10195
- // src/modules/execution/verifier.ts
10196
- import { existsSync as existsSync20 } from "fs";
10197
- import { resolve as resolve11, extname as extname2, join as join11 } from "path";
10198
- import { spawn as spawn2 } from "child_process";
10199
-
10200
- class StepVerifier {
10201
- baseDir;
10202
- constructor(baseDir) {
10203
- this.baseDir = baseDir;
10204
- }
10205
- async checkFileExists(path) {
10206
- const resolved = resolve11(this.baseDir, path);
10207
- const exists = existsSync20(resolved);
10208
- return {
10209
- passed: exists,
10210
- message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path })
10211
- };
10212
- }
10213
- async runScript(scriptName) {
10214
- try {
10215
- await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60000);
10216
- return { passed: true, message: t("verify.script_passed", { script: scriptName }) };
10217
- } catch (e) {
10218
- return {
10219
- passed: false,
10220
- message: t("verify.script_failed", { script: scriptName, message: e.message })
10221
- };
10222
- }
10223
- }
10224
- async runTypeCheck() {
10225
- const tsconfigPath = resolve11(this.baseDir, "tsconfig.json");
10226
- if (!existsSync20(tsconfigPath)) {
10227
- return { passed: true, message: "No tsconfig.json found — skipping type check" };
10228
- }
10229
- try {
10230
- await this.runAsync("npx tsc --noEmit", this.baseDir, 60000);
10231
- return { passed: true, message: "TypeScript type check passed" };
10232
- } catch (e) {
10233
- const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
10234
- return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
10235
- }
10236
- }
10237
- async runTypeCheckForFile(filePath) {
10238
- const projectRoot = findProjectRoot(filePath, this.baseDir, ["tsconfig.json", "package.json"]);
10239
- const tsconfigPath = join11(projectRoot, "tsconfig.json");
10240
- if (!existsSync20(tsconfigPath)) {
10241
- return { passed: true, message: "No tsconfig.json found — skipping type check" };
10242
- }
10243
- try {
10244
- await this.runAsync("npx tsc --noEmit --skipLibCheck", projectRoot, 60000);
10245
- return { passed: true, message: "TypeScript type check passed" };
10246
- } catch (e) {
10247
- const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
10248
- return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
10249
- }
10250
- }
10251
- async runTests() {
10252
- const pkgPath = resolve11(this.baseDir, "package.json");
10253
- if (!existsSync20(pkgPath)) {
10254
- return { passed: true, message: "No package.json found — skipping tests" };
10255
- }
10256
- try {
10257
- const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
10258
- if (!pkg.scripts?.test) {
10259
- return { passed: true, message: "No test script defined — skipping tests" };
10260
- }
10261
- } catch {}
10262
- return this.runScript("test");
10263
- }
10264
- async verifyArtifactFiles(files) {
10265
- return Promise.all(files.map((f) => this.checkFileExists(f)));
10266
- }
10267
- async verifyMoEManifest(plan, config, allToolTags) {
10268
- const errors = [];
10269
- const warnings = [];
10270
- const details = [];
10271
- const expertErrors = validateExpertConfig(config, allToolTags);
10272
- for (const err of expertErrors) {
10273
- errors.push(err);
10274
- details.push({ passed: false, message: err });
10275
- }
10276
- const tscResult = await this.runTypeCheck();
10277
- details.push(tscResult);
10278
- if (!tscResult.passed) {
10279
- errors.push(tscResult.message);
10280
- }
10281
- for (const sub of plan.subtasks) {
10282
- const fileChecks = await this.verifyArtifactFiles(sub.allowed_files || []);
10283
- for (const check of fileChecks) {
10284
- details.push(check);
10285
- if (!check.passed) {
10286
- errors.push(check.message);
10287
- }
10288
- }
10289
- }
10290
- return {
10291
- success: errors.length === 0,
10292
- errors,
10293
- warnings,
10294
- details
10295
- };
10296
- }
10297
- async verifyStep(stepDescription) {
10298
- const fileMatches = stepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || [];
10299
- const results = [];
10300
- let syntaxValid = true;
10301
- for (const filePath of fileMatches) {
10302
- const result = await this.checkFileExists(filePath);
10303
- results.push(result);
10304
- if (result.passed) {
10305
- const fullPath = resolve11(this.baseDir, filePath);
10306
- if (!await this.validateSyntax(fullPath)) {
10307
- syntaxValid = false;
10308
- results.push({ passed: false, message: t("verify.syntax_error", { path: filePath }) });
10309
- }
10310
- }
10311
- }
10312
- return {
10313
- passed: results.every((r) => r.passed),
10314
- syntaxValid,
10315
- failed: results.filter((r) => !r.passed)
10316
- };
10317
- }
10318
- async validateSyntax(filePath) {
10319
- const ext = extname2(filePath);
10320
- if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
10321
- try {
10322
- await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 1e4);
10323
- return true;
10324
- } catch (err) {
10325
- if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
10326
- return true;
10327
- }
10328
- const stderr = err.stderr?.toString() || "";
10329
- if (stderr.includes("error TS") && !stderr.includes("Cannot find module")) {
10330
- return false;
10331
- }
10332
- return true;
10333
- }
10334
- }
10335
- if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
10336
- try {
10337
- await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
10338
- return true;
10339
- } catch {
10340
- return false;
10341
- }
10342
- }
10343
- return true;
10344
- }
10345
- runAsync(command, cwd, timeoutMs) {
10346
- return new Promise((resolve12, reject) => {
10347
- const child = spawn2(command, {
10348
- cwd,
10349
- shell: true,
10350
- windowsHide: true,
10351
- stdio: ["ignore", "pipe", "pipe"]
10352
- });
10353
- let stdout = "";
10354
- let stderr = "";
10355
- child.stdout?.on("data", (d) => {
10356
- stdout += d.toString();
10357
- });
10358
- child.stderr?.on("data", (d) => {
10359
- stderr += d.toString();
10360
- });
10361
- const timer = setTimeout(() => {
10362
- child.kill();
10363
- reject(new Error(`Command timed out after ${timeoutMs}ms`));
10364
- }, timeoutMs);
10365
- child.on("error", (err) => {
10366
- clearTimeout(timer);
10367
- reject(err);
10368
- });
10369
- child.on("close", (code) => {
10370
- clearTimeout(timer);
10371
- if (code === 0) {
10372
- resolve12({ stdout, stderr });
10373
- } else {
10374
- const err = new Error(`Command failed with exit code ${code}`);
10375
- err.stdout = stdout;
10376
- err.stderr = stderr;
10377
- err.status = code;
10378
- reject(err);
10379
- }
10380
- });
10381
- });
10382
- }
10391
+ // src/modules/hallucination/js-identifiers.ts
10392
+ function isJsMemberAccess(candidate) {
10393
+ const lower = candidate.toLowerCase();
10394
+ const m = lower.match(/^([\w-]+)\./);
10395
+ return m !== null && JS_GLOBALS.has(m[1]);
10383
10396
  }
10384
- var init_verifier = __esm(() => {
10385
- init_i18n();
10386
- init_project_root();
10387
- });
10388
-
10389
- // src/core/agent-moe.ts
10390
- async function runWithMoE(deps, input, fallback, opts = {}) {
10391
- const { config, llmProvider, logger, toolExecutor, baseDir } = deps;
10392
- const { onMeta, onPhase } = opts;
10393
- const orchestrator = new OrchestratorClient({
10394
- model: config.orchestrator.model,
10395
- provider: config.orchestrator.provider
10396
- }, llmProvider);
10397
- if (!orchestrator.isEnabled()) {
10398
- logger.debug("MoE enabled but no orchestrator model configured — falling back to single-agent");
10399
- return fallback();
10397
+ function isFilePathLike(s) {
10398
+ const lower = s.toLowerCase();
10399
+ if (TECH_NAMES.has(lower))
10400
+ return false;
10401
+ if (isJsMemberAccess(s))
10402
+ return false;
10403
+ const ext = s.split(".").pop()?.toLowerCase() || "";
10404
+ if (!s.includes("/") && !s.includes("\\")) {
10405
+ if (!/^\w+\.[a-z]{1,6}$/i.test(s))
10406
+ return false;
10407
+ return FILE_EXTENSIONS.has(ext);
10400
10408
  }
10401
- onMeta?.(`\uD83E\uDD16 Planning with MoE mode...
10402
- `);
10403
- onPhase?.("thinking");
10409
+ const last = s.split(/[\\/]/).pop() ?? "";
10410
+ const lastDot = last.lastIndexOf(".");
10411
+ if (lastDot <= 0)
10412
+ return false;
10413
+ return true;
10414
+ }
10415
+ function extractFileLikeTokens(text) {
10416
+ const matches = text.match(FILE_TOKEN_RE) || [];
10417
+ return [...new Set(matches.filter(isFilePathLike))];
10418
+ }
10419
+ function stripUrls(text) {
10420
+ return text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, " ");
10421
+ }
10422
+ var JS_GLOBALS, FILE_EXTENSIONS, TECH_NAMES, FILE_TOKEN_RE;
10423
+ var init_js_identifiers = __esm(() => {
10424
+ JS_GLOBALS = new Set([
10425
+ "process",
10426
+ "console",
10427
+ "math",
10428
+ "json",
10429
+ "global",
10430
+ "globalthis",
10431
+ "window",
10432
+ "document",
10433
+ "buffer",
10434
+ "module",
10435
+ "require",
10436
+ "exports",
10437
+ "url",
10438
+ "promise",
10439
+ "array",
10440
+ "object",
10441
+ "string",
10442
+ "number",
10443
+ "boolean",
10444
+ "symbol",
10445
+ "date",
10446
+ "regexp",
10447
+ "error",
10448
+ "map",
10449
+ "set",
10450
+ "weakmap",
10451
+ "weakset",
10452
+ "proxy",
10453
+ "reflect",
10454
+ "intl",
10455
+ "crypto",
10456
+ "performance",
10457
+ "fetch",
10458
+ "navigator",
10459
+ "location",
10460
+ "settimeout",
10461
+ "setinterval",
10462
+ "cleartimeout",
10463
+ "clearinterval",
10464
+ "queuemicrotask",
10465
+ "structuredclone",
10466
+ "textencoder",
10467
+ "textdecoder",
10468
+ "atomics",
10469
+ "sharedarraybuffer",
10470
+ "dataview",
10471
+ "arraybuffer",
10472
+ "bigint",
10473
+ "infinity",
10474
+ "nan",
10475
+ "undefined",
10476
+ "bun",
10477
+ "deno",
10478
+ "node"
10479
+ ]);
10480
+ FILE_EXTENSIONS = new Set([
10481
+ "ts",
10482
+ "tsx",
10483
+ "js",
10484
+ "jsx",
10485
+ "mjs",
10486
+ "cjs",
10487
+ "py",
10488
+ "pyc",
10489
+ "rb",
10490
+ "go",
10491
+ "rs",
10492
+ "java",
10493
+ "jar",
10494
+ "kt",
10495
+ "kts",
10496
+ "c",
10497
+ "h",
10498
+ "cc",
10499
+ "cpp",
10500
+ "cxx",
10501
+ "hpp",
10502
+ "hh",
10503
+ "cs",
10504
+ "php",
10505
+ "swift",
10506
+ "m",
10507
+ "mm",
10508
+ "lua",
10509
+ "pl",
10510
+ "sh",
10511
+ "bash",
10512
+ "zsh",
10513
+ "fish",
10514
+ "bat",
10515
+ "cmd",
10516
+ "ps1",
10517
+ "vue",
10518
+ "svelte",
10519
+ "dart",
10520
+ "json",
10521
+ "jsonc",
10522
+ "json5",
10523
+ "yaml",
10524
+ "yml",
10525
+ "toml",
10526
+ "ini",
10527
+ "cfg",
10528
+ "conf",
10529
+ "env",
10530
+ "xml",
10531
+ "html",
10532
+ "htm",
10533
+ "md",
10534
+ "markdown",
10535
+ "rst",
10536
+ "txt",
10537
+ "log",
10538
+ "csv",
10539
+ "tsv",
10540
+ "lock",
10541
+ "sql",
10542
+ "sqlite",
10543
+ "db",
10544
+ "wasm",
10545
+ "map",
10546
+ "css",
10547
+ "scss",
10548
+ "sass",
10549
+ "less",
10550
+ "svg",
10551
+ "png",
10552
+ "jpg",
10553
+ "jpeg",
10554
+ "gif",
10555
+ "webp",
10556
+ "bmp",
10557
+ "ico",
10558
+ "avif",
10559
+ "tiff",
10560
+ "pdf",
10561
+ "doc",
10562
+ "docx",
10563
+ "xls",
10564
+ "xlsx",
10565
+ "ppt",
10566
+ "pptx",
10567
+ "odt",
10568
+ "ods",
10569
+ "zip",
10570
+ "gz",
10571
+ "tar",
10572
+ "tgz",
10573
+ "bz2",
10574
+ "xz",
10575
+ "7z",
10576
+ "rar",
10577
+ "bin",
10578
+ "exe",
10579
+ "dll",
10580
+ "so",
10581
+ "o",
10582
+ "a",
10583
+ "dylib"
10584
+ ]);
10585
+ TECH_NAMES = new Set([
10586
+ "node.js",
10587
+ "react.js",
10588
+ "vue.js",
10589
+ "next.js",
10590
+ "angular.js",
10591
+ "express.js",
10592
+ "deno.js",
10593
+ "qwik.js",
10594
+ "svelte.js",
10595
+ "jquery.js"
10596
+ ]);
10597
+ FILE_TOKEN_RE = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{1,6}\b/gi;
10598
+ });
10599
+
10600
+ // src/modules/execution/audit-runners.ts
10601
+ import { existsSync as existsSync20, readdirSync as readdirSync5 } from "fs";
10602
+ import { dirname as dirname7, join as join11, resolve as resolve11 } from "path";
10603
+ function findTestFile(dir, depth = 0) {
10604
+ if (depth > 5)
10605
+ return null;
10606
+ let entries;
10607
+ try {
10608
+ entries = readdirSync5(dir, { withFileTypes: true });
10609
+ } catch {
10610
+ return null;
10611
+ }
10612
+ for (const e of entries) {
10613
+ const full = join11(dir, e.name);
10614
+ if (e.isDirectory()) {
10615
+ if (SKIP_DIRS.has(e.name))
10616
+ continue;
10617
+ const found = findTestFile(full, depth + 1);
10618
+ if (found)
10619
+ return found;
10620
+ } else if (TEST_EXT_RE.test(e.name)) {
10621
+ return full;
10622
+ }
10623
+ }
10624
+ return null;
10625
+ }
10626
+ function hasTestStep(plan) {
10627
+ return plan.steps.some((s) => TEST_STEP_RE.test(s.description));
10628
+ }
10629
+ function extractFailingNames(output, limit = 5) {
10630
+ const names = [];
10631
+ for (const m of output.matchAll(/\(fail\)\s*([^\n]+)/g)) {
10632
+ const name = m[1].trim();
10633
+ if (name && !names.includes(name))
10634
+ names.push(name);
10635
+ if (names.length >= limit)
10636
+ break;
10637
+ }
10638
+ return names;
10639
+ }
10640
+ async function runTests(baseDir) {
10641
+ const entry = processRegistry.start("bun test", baseDir);
10642
+ const exited = await processRegistry.waitForExit(entry.id, 90000);
10643
+ const output = entry.log.join(`
10644
+ `);
10645
+ processRegistry.remove(entry.id);
10646
+ if (!exited) {
10647
+ return {
10648
+ checked: true,
10649
+ passed: true,
10650
+ failed: 0,
10651
+ passedCount: 0,
10652
+ detail: "test run timed out after 90s — result unknown",
10653
+ command: "bun test"
10654
+ };
10655
+ }
10656
+ const run = detectTestResults(output);
10657
+ if (!run) {
10658
+ return {
10659
+ checked: true,
10660
+ passed: entry.exitCode === 0,
10661
+ failed: entry.exitCode === 0 ? 0 : 1,
10662
+ passedCount: 0,
10663
+ detail: output.slice(0, 200).trim(),
10664
+ command: "bun test"
10665
+ };
10666
+ }
10667
+ const names = extractFailingNames(output);
10668
+ return {
10669
+ checked: true,
10670
+ passed: run.failed === 0,
10671
+ failed: run.failed,
10672
+ passedCount: run.passed,
10673
+ detail: names.length ? names.join("; ") : run.summary || `${run.failed} failed / ${run.passed} passed`,
10674
+ command: "bun test"
10675
+ };
10676
+ }
10677
+ function parseTypecheckErrors(output) {
10678
+ const line = output.split(`
10679
+ `).find((l) => /error TS\d+/.test(l));
10680
+ return line ? line.trim().slice(0, 300) : null;
10681
+ }
10682
+ function findTypecheckRoot(baseDir, existingFiles = []) {
10683
+ const candidates = [baseDir, ...existingFiles];
10684
+ let best = null;
10685
+ for (const start of candidates) {
10686
+ let dir = resolve11(start);
10687
+ for (let depth = 0;depth <= 10; depth++) {
10688
+ if (existsSync20(join11(dir, "tsconfig.json"))) {
10689
+ if (!best || depth < best.depth)
10690
+ best = { depth, root: dir };
10691
+ break;
10692
+ }
10693
+ const parent = dirname7(dir);
10694
+ if (parent === dir)
10695
+ break;
10696
+ dir = parent;
10697
+ }
10698
+ }
10699
+ return best?.root ?? null;
10700
+ }
10701
+ async function runTypecheck(baseDir) {
10702
+ const entry = processRegistry.start("npx --no-install tsc --noEmit --skipLibCheck", baseDir);
10703
+ const exited = await processRegistry.waitForExit(entry.id, 90000);
10704
+ const output = entry.log.join(`
10705
+ `);
10706
+ processRegistry.remove(entry.id);
10707
+ if (!exited)
10708
+ return null;
10709
+ return parseTypecheckErrors(output);
10710
+ }
10711
+ var SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE;
10712
+ var init_audit_runners = __esm(() => {
10713
+ init_bash();
10714
+ init_processes();
10715
+ SKIP_DIRS = new Set([
10716
+ "node_modules",
10717
+ ".git",
10718
+ ".mma",
10719
+ "dist",
10720
+ "build",
10721
+ "coverage",
10722
+ ".next",
10723
+ ".nuxt",
10724
+ "vendor"
10725
+ ]);
10726
+ TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
10727
+ TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
10728
+ });
10729
+
10730
+ // src/modules/execution/auditor.ts
10731
+ import { existsSync as existsSync21, readdirSync as readdirSync6 } from "fs";
10732
+ import { resolve as resolve12, join as join12, basename as basename2 } from "path";
10733
+ function findExistingFile(baseDir, filePath) {
10734
+ const direct = resolve12(baseDir, filePath);
10735
+ if (existsSync21(direct))
10736
+ return direct;
10737
+ const name = basename2(filePath).toLowerCase();
10738
+ const suffix = filePath.replace(/\\/g, "/").toLowerCase();
10739
+ let found = null;
10740
+ const walk = (dir, depth) => {
10741
+ if (found || depth > RESOLVE_MAX_DEPTH)
10742
+ return;
10743
+ let entries;
10744
+ try {
10745
+ entries = readdirSync6(dir, { withFileTypes: true });
10746
+ } catch {
10747
+ return;
10748
+ }
10749
+ for (const e of entries) {
10750
+ if (found)
10751
+ return;
10752
+ const full = join12(dir, e.name);
10753
+ if (e.isDirectory()) {
10754
+ if (SKIP_DIRS.has(e.name))
10755
+ continue;
10756
+ walk(full, depth + 1);
10757
+ } else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
10758
+ found = full;
10759
+ return;
10760
+ }
10761
+ }
10762
+ };
10763
+ walk(baseDir, 0);
10764
+ return found;
10765
+ }
10766
+
10767
+ class Auditor {
10768
+ baseDir;
10769
+ constructor(baseDir) {
10770
+ this.baseDir = baseDir;
10771
+ }
10772
+ async audit(plan) {
10773
+ const doneCreate = plan.steps.filter((s) => s.status === "done" && s.kind !== "delete");
10774
+ const doneDelete = plan.steps.filter((s) => s.status === "done" && s.kind === "delete");
10775
+ const createText = doneCreate.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
10776
+ const deleteText = doneDelete.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
10777
+ const createFiles = extractFileLikeTokens(stripUrls(createText));
10778
+ const deleteFiles = extractFileLikeTokens(stripUrls(deleteText));
10779
+ const missingFiles = [];
10780
+ const existingFiles = [];
10781
+ const leftoverFiles = [];
10782
+ for (const filePath of createFiles) {
10783
+ const resolved = findExistingFile(this.baseDir, filePath);
10784
+ if (resolved) {
10785
+ existingFiles.push(resolved);
10786
+ } else {
10787
+ missingFiles.push(filePath);
10788
+ }
10789
+ }
10790
+ for (const filePath of deleteFiles) {
10791
+ if (findExistingFile(this.baseDir, filePath)) {
10792
+ leftoverFiles.push(filePath);
10793
+ }
10794
+ }
10795
+ let testRun = null;
10796
+ if (hasTestStep(plan) && findTestFile(this.baseDir)) {
10797
+ try {
10798
+ testRun = await runTests(this.baseDir);
10799
+ } catch {
10800
+ testRun = null;
10801
+ }
10802
+ }
10803
+ let typecheckError = null;
10804
+ if (missingFiles.length === 0 && leftoverFiles.length === 0) {
10805
+ const root = findTypecheckRoot(this.baseDir, existingFiles);
10806
+ if (root) {
10807
+ try {
10808
+ typecheckError = await runTypecheck(root);
10809
+ } catch {
10810
+ typecheckError = null;
10811
+ }
10812
+ }
10813
+ }
10814
+ const doneSteps = plan.steps.filter((s) => s.status === "done").length;
10815
+ const terminalSteps = plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
10816
+ const totalSteps = plan.steps.length;
10817
+ const auditedFiles = [...new Set([...createFiles, ...deleteFiles])];
10818
+ let massEditWarning = null;
10819
+ if (auditedFiles.length > MASS_EDIT_THRESHOLD) {
10820
+ massEditWarning = t("exec.mass_edit_warning", {
10821
+ count: String(auditedFiles.length)
10822
+ });
10823
+ }
10824
+ const testsFailing = testRun !== null && !testRun.passed;
10825
+ const typecheckFailing = typecheckError !== null;
10826
+ const passed = missingFiles.length === 0 && leftoverFiles.length === 0 && !testsFailing && !typecheckFailing;
10827
+ const stepsPending = terminalSteps < totalSteps;
10828
+ let summary;
10829
+ if (missingFiles.length > 0) {
10830
+ summary = t("exec.audit_fail", {
10831
+ done: doneSteps,
10832
+ total: totalSteps,
10833
+ files: missingFiles.length
10834
+ });
10835
+ } else if (leftoverFiles.length > 0) {
10836
+ summary = t("exec.audit_leftovers", {
10837
+ done: doneSteps,
10838
+ total: totalSteps,
10839
+ files: leftoverFiles.length
10840
+ });
10841
+ } else if (testsFailing) {
10842
+ summary = t("exec.audit_fail_tests", {
10843
+ done: doneSteps,
10844
+ total: totalSteps,
10845
+ failed: String(testRun.failed),
10846
+ passed: String(testRun.passedCount),
10847
+ detail: testRun.detail
10848
+ });
10849
+ } else if (typecheckFailing) {
10850
+ summary = t("exec.audit_fail_typecheck", {
10851
+ done: doneSteps,
10852
+ total: totalSteps,
10853
+ missing: String(missingFiles.length),
10854
+ typeError: typecheckError
10855
+ });
10856
+ } else if (stepsPending) {
10857
+ summary = t("exec.audit_pending", {
10858
+ done: doneSteps,
10859
+ total: totalSteps
10860
+ });
10861
+ } else {
10862
+ summary = t("exec.audit_pass", {
10863
+ done: doneSteps,
10864
+ total: totalSteps,
10865
+ files: existingFiles.length
10866
+ });
10867
+ }
10868
+ return {
10869
+ passed,
10870
+ missingFiles,
10871
+ leftoverFiles,
10872
+ createdFiles: existingFiles,
10873
+ modifiedFiles: [],
10874
+ summary,
10875
+ massEditWarning,
10876
+ testRun,
10877
+ typecheckError
10878
+ };
10879
+ }
10880
+ }
10881
+ var MASS_EDIT_THRESHOLD = 10, RESOLVE_MAX_DEPTH = 8;
10882
+ var init_auditor = __esm(() => {
10883
+ init_i18n();
10884
+ init_js_identifiers();
10885
+ init_audit_runners();
10886
+ init_audit_runners();
10887
+ });
10888
+
10889
+ // src/modules/execution/verifier.ts
10890
+ import { existsSync as existsSync22 } from "fs";
10891
+ import { resolve as resolve13, extname as extname2, join as join13 } from "path";
10892
+ import { spawn as spawn2 } from "child_process";
10893
+
10894
+ class StepVerifier {
10895
+ baseDir;
10896
+ constructor(baseDir) {
10897
+ this.baseDir = baseDir;
10898
+ }
10899
+ async checkFileExists(path) {
10900
+ const resolved = resolve13(this.baseDir, path);
10901
+ const exists = existsSync22(resolved);
10902
+ return {
10903
+ passed: exists,
10904
+ message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path })
10905
+ };
10906
+ }
10907
+ async runScript(scriptName) {
10908
+ try {
10909
+ await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60000);
10910
+ return { passed: true, message: t("verify.script_passed", { script: scriptName }) };
10911
+ } catch (e) {
10912
+ return {
10913
+ passed: false,
10914
+ message: t("verify.script_failed", { script: scriptName, message: e.message })
10915
+ };
10916
+ }
10917
+ }
10918
+ async runTypeCheck() {
10919
+ const tsconfigPath = resolve13(this.baseDir, "tsconfig.json");
10920
+ if (!existsSync22(tsconfigPath)) {
10921
+ return { passed: true, message: "No tsconfig.json found — skipping type check" };
10922
+ }
10923
+ try {
10924
+ await this.runAsync("npx tsc --noEmit", this.baseDir, 60000);
10925
+ return { passed: true, message: "TypeScript type check passed" };
10926
+ } catch (e) {
10927
+ const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
10928
+ return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
10929
+ }
10930
+ }
10931
+ async runTypeCheckForFile(filePath) {
10932
+ const projectRoot = findProjectRoot(filePath, this.baseDir, ["tsconfig.json", "package.json"]);
10933
+ const tsconfigPath = join13(projectRoot, "tsconfig.json");
10934
+ if (!existsSync22(tsconfigPath)) {
10935
+ return { passed: true, message: "No tsconfig.json found — skipping type check" };
10936
+ }
10937
+ try {
10938
+ await this.runAsync("npx tsc --noEmit --skipLibCheck", projectRoot, 60000);
10939
+ return { passed: true, message: "TypeScript type check passed" };
10940
+ } catch (e) {
10941
+ const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
10942
+ return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
10943
+ }
10944
+ }
10945
+ async runTests() {
10946
+ const pkgPath = resolve13(this.baseDir, "package.json");
10947
+ if (!existsSync22(pkgPath)) {
10948
+ return { passed: true, message: "No package.json found — skipping tests" };
10949
+ }
10950
+ try {
10951
+ const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
10952
+ if (!pkg.scripts?.test) {
10953
+ return { passed: true, message: "No test script defined — skipping tests" };
10954
+ }
10955
+ } catch {}
10956
+ return this.runScript("test");
10957
+ }
10958
+ async verifyArtifactFiles(files) {
10959
+ return Promise.all(files.map((f) => this.checkFileExists(f)));
10960
+ }
10961
+ async verifyMoEManifest(plan, config, allToolTags) {
10962
+ const errors = [];
10963
+ const warnings = [];
10964
+ const details = [];
10965
+ const expertErrors = validateExpertConfig(config, allToolTags);
10966
+ for (const err of expertErrors) {
10967
+ errors.push(err);
10968
+ details.push({ passed: false, message: err });
10969
+ }
10970
+ const tscResult = await this.runTypeCheck();
10971
+ details.push(tscResult);
10972
+ if (!tscResult.passed) {
10973
+ errors.push(tscResult.message);
10974
+ }
10975
+ for (const sub of plan.subtasks) {
10976
+ const fileChecks = await this.verifyArtifactFiles(sub.allowed_files || []);
10977
+ for (const check of fileChecks) {
10978
+ details.push(check);
10979
+ if (!check.passed) {
10980
+ errors.push(check.message);
10981
+ }
10982
+ }
10983
+ }
10984
+ return {
10985
+ success: errors.length === 0,
10986
+ errors,
10987
+ warnings,
10988
+ details
10989
+ };
10990
+ }
10991
+ async verifyStep(stepDescription) {
10992
+ const fileMatches = extractFileLikeTokens(stripUrls(stepDescription));
10993
+ if (fileMatches.length === 0) {
10994
+ return { passed: false, syntaxValid: true, failed: [], noFiles: true };
10995
+ }
10996
+ const results = [];
10997
+ let syntaxValid = true;
10998
+ for (const filePath of fileMatches) {
10999
+ const resolved = findExistingFile(this.baseDir, filePath);
11000
+ if (!resolved) {
11001
+ results.push({ passed: false, message: t("verify.file_not_found", { path: filePath }) });
11002
+ continue;
11003
+ }
11004
+ results.push({ passed: true, message: t("verify.file_exists", { path: filePath }) });
11005
+ if (!await this.validateSyntax(resolved)) {
11006
+ syntaxValid = false;
11007
+ results.push({ passed: false, message: t("verify.syntax_error", { path: filePath }) });
11008
+ }
11009
+ }
11010
+ return {
11011
+ passed: results.every((r) => r.passed),
11012
+ syntaxValid,
11013
+ failed: results.filter((r) => !r.passed),
11014
+ noFiles: false
11015
+ };
11016
+ }
11017
+ async validateSyntax(filePath) {
11018
+ const ext = extname2(filePath);
11019
+ if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
11020
+ try {
11021
+ await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 1e4);
11022
+ return true;
11023
+ } catch (err) {
11024
+ if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
11025
+ return true;
11026
+ }
11027
+ const stderr = err.stderr?.toString() || "";
11028
+ if (stderr.includes("error TS") && !stderr.includes("Cannot find module")) {
11029
+ return false;
11030
+ }
11031
+ return true;
11032
+ }
11033
+ }
11034
+ if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
11035
+ try {
11036
+ await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
11037
+ return true;
11038
+ } catch {
11039
+ return false;
11040
+ }
11041
+ }
11042
+ return true;
11043
+ }
11044
+ runAsync(command, cwd, timeoutMs) {
11045
+ return new Promise((resolve14, reject) => {
11046
+ const child = spawn2(command, {
11047
+ cwd,
11048
+ shell: true,
11049
+ windowsHide: true,
11050
+ stdio: ["ignore", "pipe", "pipe"]
11051
+ });
11052
+ let stdout = "";
11053
+ let stderr = "";
11054
+ child.stdout?.on("data", (d) => {
11055
+ stdout += d.toString();
11056
+ });
11057
+ child.stderr?.on("data", (d) => {
11058
+ stderr += d.toString();
11059
+ });
11060
+ const timer = setTimeout(() => {
11061
+ child.kill();
11062
+ reject(new Error(`Command timed out after ${timeoutMs}ms`));
11063
+ }, timeoutMs);
11064
+ child.on("error", (err) => {
11065
+ clearTimeout(timer);
11066
+ reject(err);
11067
+ });
11068
+ child.on("close", (code) => {
11069
+ clearTimeout(timer);
11070
+ if (code === 0) {
11071
+ resolve14({ stdout, stderr });
11072
+ } else {
11073
+ const err = new Error(`Command failed with exit code ${code}`);
11074
+ err.stdout = stdout;
11075
+ err.stderr = stderr;
11076
+ err.status = code;
11077
+ reject(err);
11078
+ }
11079
+ });
11080
+ });
11081
+ }
11082
+ }
11083
+ var init_verifier = __esm(() => {
11084
+ init_i18n();
11085
+ init_project_root();
11086
+ init_js_identifiers();
11087
+ init_auditor();
11088
+ });
11089
+
11090
+ // src/core/agent-moe.ts
11091
+ async function runWithMoE(deps, input, fallback, opts = {}) {
11092
+ const { config, llmProvider, logger, toolExecutor, baseDir } = deps;
11093
+ const { onMeta, onPhase } = opts;
11094
+ const orchestrator = new OrchestratorClient({
11095
+ model: config.orchestrator.model,
11096
+ provider: config.orchestrator.provider
11097
+ }, llmProvider);
11098
+ if (!orchestrator.isEnabled()) {
11099
+ logger.debug("MoE enabled but no orchestrator model configured — falling back to single-agent");
11100
+ return fallback();
11101
+ }
11102
+ onMeta?.(`\uD83E\uDD16 Planning with MoE mode...
11103
+ `);
11104
+ onPhase?.("thinking");
10404
11105
  const planResult = await orchestrator.plan(input);
10405
11106
  onPhase?.("done");
10406
11107
  if ("error" in planResult) {
@@ -10640,6 +11341,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10640
11341
  logger.debug(`Session started: ${sessionManager.getActive()}`);
10641
11342
  }
10642
11343
  if (!this.systemPromptAdded && !contextManager.getActiveHistory().some((m) => m.role === "system")) {
11344
+ const lazy = this.deps.lazyPromptBlocks ? await this.deps.lazyPromptBlocks() : [];
11345
+ if (lazy.length > 0) {
11346
+ this.deps.promptBlocks.push(...lazy);
11347
+ }
10643
11348
  const { prompt: systemPrompt, excluded } = this.buildSystemPrompt();
10644
11349
  contextManager.addMessage({ role: "system", content: systemPrompt });
10645
11350
  this.systemPromptAdded = true;
@@ -11840,218 +12545,9 @@ var init_confidence = __esm(() => {
11840
12545
  init_i18n();
11841
12546
  });
11842
12547
 
11843
- // src/modules/hallucination/js-identifiers.ts
11844
- function isJsMemberAccess(candidate) {
11845
- const lower = candidate.toLowerCase();
11846
- const m = lower.match(/^([\w-]+)\./);
11847
- return m !== null && JS_GLOBALS.has(m[1]);
11848
- }
11849
- function isFilePathLike(s) {
11850
- const lower = s.toLowerCase();
11851
- if (TECH_NAMES.has(lower))
11852
- return false;
11853
- if (isJsMemberAccess(s))
11854
- return false;
11855
- const ext = s.split(".").pop()?.toLowerCase() || "";
11856
- if (!s.includes("/") && !s.includes("\\")) {
11857
- if (!/^\w+\.[a-z]{1,6}$/i.test(s))
11858
- return false;
11859
- return FILE_EXTENSIONS.has(ext);
11860
- }
11861
- const last = s.split(/[\\/]/).pop() ?? "";
11862
- const lastDot = last.lastIndexOf(".");
11863
- if (lastDot <= 0)
11864
- return false;
11865
- return true;
11866
- }
11867
- function extractFileLikeTokens(text) {
11868
- const matches = text.match(FILE_TOKEN_RE) || [];
11869
- return [...new Set(matches.filter(isFilePathLike))];
11870
- }
11871
- function stripUrls(text) {
11872
- return text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, " ");
11873
- }
11874
- var JS_GLOBALS, FILE_EXTENSIONS, TECH_NAMES, FILE_TOKEN_RE;
11875
- var init_js_identifiers = __esm(() => {
11876
- JS_GLOBALS = new Set([
11877
- "process",
11878
- "console",
11879
- "math",
11880
- "json",
11881
- "global",
11882
- "globalthis",
11883
- "window",
11884
- "document",
11885
- "buffer",
11886
- "module",
11887
- "require",
11888
- "exports",
11889
- "url",
11890
- "promise",
11891
- "array",
11892
- "object",
11893
- "string",
11894
- "number",
11895
- "boolean",
11896
- "symbol",
11897
- "date",
11898
- "regexp",
11899
- "error",
11900
- "map",
11901
- "set",
11902
- "weakmap",
11903
- "weakset",
11904
- "proxy",
11905
- "reflect",
11906
- "intl",
11907
- "crypto",
11908
- "performance",
11909
- "fetch",
11910
- "navigator",
11911
- "location",
11912
- "settimeout",
11913
- "setinterval",
11914
- "cleartimeout",
11915
- "clearinterval",
11916
- "queuemicrotask",
11917
- "structuredclone",
11918
- "textencoder",
11919
- "textdecoder",
11920
- "atomics",
11921
- "sharedarraybuffer",
11922
- "dataview",
11923
- "arraybuffer",
11924
- "bigint",
11925
- "infinity",
11926
- "nan",
11927
- "undefined",
11928
- "bun",
11929
- "deno",
11930
- "node"
11931
- ]);
11932
- FILE_EXTENSIONS = new Set([
11933
- "ts",
11934
- "tsx",
11935
- "js",
11936
- "jsx",
11937
- "mjs",
11938
- "cjs",
11939
- "py",
11940
- "pyc",
11941
- "rb",
11942
- "go",
11943
- "rs",
11944
- "java",
11945
- "jar",
11946
- "kt",
11947
- "kts",
11948
- "c",
11949
- "h",
11950
- "cc",
11951
- "cpp",
11952
- "cxx",
11953
- "hpp",
11954
- "hh",
11955
- "cs",
11956
- "php",
11957
- "swift",
11958
- "m",
11959
- "mm",
11960
- "lua",
11961
- "pl",
11962
- "sh",
11963
- "bash",
11964
- "zsh",
11965
- "fish",
11966
- "bat",
11967
- "cmd",
11968
- "ps1",
11969
- "vue",
11970
- "svelte",
11971
- "dart",
11972
- "json",
11973
- "jsonc",
11974
- "json5",
11975
- "yaml",
11976
- "yml",
11977
- "toml",
11978
- "ini",
11979
- "cfg",
11980
- "conf",
11981
- "env",
11982
- "xml",
11983
- "html",
11984
- "htm",
11985
- "md",
11986
- "markdown",
11987
- "rst",
11988
- "txt",
11989
- "log",
11990
- "csv",
11991
- "tsv",
11992
- "lock",
11993
- "sql",
11994
- "sqlite",
11995
- "db",
11996
- "wasm",
11997
- "map",
11998
- "css",
11999
- "scss",
12000
- "sass",
12001
- "less",
12002
- "svg",
12003
- "png",
12004
- "jpg",
12005
- "jpeg",
12006
- "gif",
12007
- "webp",
12008
- "bmp",
12009
- "ico",
12010
- "avif",
12011
- "tiff",
12012
- "pdf",
12013
- "doc",
12014
- "docx",
12015
- "xls",
12016
- "xlsx",
12017
- "ppt",
12018
- "pptx",
12019
- "odt",
12020
- "ods",
12021
- "zip",
12022
- "gz",
12023
- "tar",
12024
- "tgz",
12025
- "bz2",
12026
- "xz",
12027
- "7z",
12028
- "rar",
12029
- "bin",
12030
- "exe",
12031
- "dll",
12032
- "so",
12033
- "o",
12034
- "a",
12035
- "dylib"
12036
- ]);
12037
- TECH_NAMES = new Set([
12038
- "node.js",
12039
- "react.js",
12040
- "vue.js",
12041
- "next.js",
12042
- "angular.js",
12043
- "express.js",
12044
- "deno.js",
12045
- "qwik.js",
12046
- "svelte.js",
12047
- "jquery.js"
12048
- ]);
12049
- FILE_TOKEN_RE = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{1,6}\b/gi;
12050
- });
12051
-
12052
12548
  // src/modules/hallucination/factual.ts
12053
- import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
12054
- import { resolve as resolve12, isAbsolute as isAbsolute2, join as join12 } from "path";
12549
+ import { existsSync as existsSync23, readdirSync as readdirSync7 } from "fs";
12550
+ import { resolve as resolve14, isAbsolute as isAbsolute2, join as join14 } from "path";
12055
12551
 
12056
12552
  class FactualCheck {
12057
12553
  baseDir;
@@ -12086,14 +12582,14 @@ class FactualCheck {
12086
12582
  }
12087
12583
  pathExists(fp) {
12088
12584
  if (isAbsolute2(fp))
12089
- return existsSync21(fp);
12585
+ return existsSync23(fp);
12090
12586
  if (!fp.includes("/") && !fp.includes("\\")) {
12091
12587
  return this.bareNameExists(fp);
12092
12588
  }
12093
- return existsSync21(resolve12(this.baseDir, fp));
12589
+ return existsSync23(resolve14(this.baseDir, fp));
12094
12590
  }
12095
12591
  bareNameExists(name) {
12096
- if (existsSync21(resolve12(this.baseDir, name)))
12592
+ if (existsSync23(resolve14(this.baseDir, name)))
12097
12593
  return true;
12098
12594
  if (this.indexHas(name))
12099
12595
  return true;
@@ -12126,14 +12622,14 @@ class FactualCheck {
12126
12622
  return count;
12127
12623
  let entries;
12128
12624
  try {
12129
- entries = readdirSync5(dir, { withFileTypes: true });
12625
+ entries = readdirSync7(dir, { withFileTypes: true });
12130
12626
  } catch {
12131
12627
  return count;
12132
12628
  }
12133
12629
  for (const entry of entries) {
12134
12630
  if (count >= FactualCheck.MAX_INDEXED_FILES)
12135
12631
  break;
12136
- const full = join12(dir, entry.name);
12632
+ const full = join14(dir, entry.name);
12137
12633
  if (entry.isDirectory()) {
12138
12634
  if (!IGNORED_DIRS.has(entry.name)) {
12139
12635
  count = this.scanDir(full, index, count);
@@ -12304,8 +12800,8 @@ var init_detector = __esm(() => {
12304
12800
  });
12305
12801
 
12306
12802
  // src/modules/lsp/command.ts
12307
- import { delimiter, join as join13 } from "path";
12308
- import { existsSync as existsSync22 } from "fs";
12803
+ import { delimiter, join as join15 } from "path";
12804
+ import { existsSync as existsSync24 } from "fs";
12309
12805
  import { platform as platform3 } from "os";
12310
12806
  function resolveSpawnCommand(command, platformName = platform3(), pathEnv = process.env.PATH ?? "") {
12311
12807
  if (platformName !== "win32")
@@ -12316,8 +12812,8 @@ function resolveSpawnCommand(command, platformName = platform3(), pathEnv = proc
12316
12812
  const dirs = pathEnv.split(delimiter).filter(Boolean);
12317
12813
  for (const dir of dirs) {
12318
12814
  for (const ext of WIN_EXTS) {
12319
- const candidate = join13(dir, `${command}${ext}`);
12320
- if (existsSync22(candidate))
12815
+ const candidate = join15(dir, `${command}${ext}`);
12816
+ if (existsSync24(candidate))
12321
12817
  return `${command}${ext}`;
12322
12818
  }
12323
12819
  }
@@ -12418,8 +12914,8 @@ class Updater {
12418
12914
  }
12419
12915
  var defaultRunner = async (command, args, options) => {
12420
12916
  const { execFile } = await import("child_process");
12421
- return new Promise((resolve13) => {
12422
- execFile(command, args, options, (err) => resolve13({ error: err?.message }));
12917
+ return new Promise((resolve15) => {
12918
+ execFile(command, args, options, (err) => resolve15({ error: err?.message }));
12423
12919
  });
12424
12920
  };
12425
12921
  var init_checker = __esm(() => {
@@ -12789,17 +13285,17 @@ Iterations: ${result.iterationCount}`
12789
13285
  });
12790
13286
 
12791
13287
  // src/tools/scope-check.ts
12792
- import { resolve as resolve13, normalize as normalize4 } from "path";
13288
+ import { resolve as resolve15, normalize as normalize4 } from "path";
12793
13289
  function isPathInScope2(baseDir, targetPath, scope) {
12794
- const baseResolved = resolve13(baseDir);
12795
- const targetResolved = resolve13(baseDir, normalize4(targetPath));
13290
+ const baseResolved = resolve15(baseDir);
13291
+ const targetResolved = resolve15(baseDir, normalize4(targetPath));
12796
13292
  if (!targetResolved.startsWith(baseResolved)) {
12797
13293
  return { allowed: false, reason: "Path is outside the base working directory" };
12798
13294
  }
12799
13295
  if (!scope)
12800
13296
  return { allowed: true };
12801
- const isRead = scope.read_only_files.some((f) => targetResolved.startsWith(resolve13(baseDir, f)));
12802
- const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve13(baseDir, f)));
13297
+ const isRead = scope.read_only_files.some((f) => targetResolved.startsWith(resolve15(baseDir, f)));
13298
+ const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve15(baseDir, f)));
12803
13299
  if (isWrite)
12804
13300
  return { allowed: true };
12805
13301
  if (isRead)
@@ -12913,7 +13409,7 @@ var DEFAULT_CHUNK_SYSTEM_PROMPT = 'Answer the query using ONLY the provided text
12913
13409
 
12914
13410
  // src/tools/chunk-query.ts
12915
13411
  import { readFileSync as readFileSync10 } from "node:fs";
12916
- import { resolve as resolve14 } from "node:path";
13412
+ import { resolve as resolve16 } from "node:path";
12917
13413
  var chunkQueryTool;
12918
13414
  var init_chunk_query = __esm(() => {
12919
13415
  init_i18n();
@@ -12981,7 +13477,7 @@ var init_chunk_query = __esm(() => {
12981
13477
  return { success: false, output: `[SCOPE] ${check.reason || "Path not allowed"}` };
12982
13478
  }
12983
13479
  try {
12984
- content = readFileSync10(resolve14(ctx.baseDir, inputPath), "utf8");
13480
+ content = readFileSync10(resolve16(ctx.baseDir, inputPath), "utf8");
12985
13481
  } catch (e) {
12986
13482
  return { success: false, output: `Cannot read ${inputPath}: ${e.message}` };
12987
13483
  }
@@ -13074,6 +13570,43 @@ var init_network_validator = __esm(() => {
13074
13570
  });
13075
13571
 
13076
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
+ }
13077
13610
  var webSearchTool;
13078
13611
  var init_web_search = __esm(() => {
13079
13612
  init_i18n();
@@ -13096,59 +13629,42 @@ var init_web_search = __esm(() => {
13096
13629
  required: ["query"]
13097
13630
  },
13098
13631
  handler: async (ctx, args) => {
13632
+ if (ctx.config?.webSearch?.enabled === false) {
13633
+ return { success: false, output: t("tool.web_search_disabled") };
13634
+ }
13099
13635
  const query = String(args.query || "");
13100
13636
  const numResults = Number(args.numResults) || 5;
13101
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
13102
13637
  const securityConfig = ctx.sessionContext ? getSessionSecurityConfig(ctx.config, ctx.sessionContext).network : ctx.config.security?.network;
13103
- const validation = isUrlAllowed(url, securityConfig);
13104
- if (!validation.allowed) {
13105
- logSecurityBlock(ctx.sessionId, "network_request", validation.reason || "URL blocked by security policy", sanitizeUrl(url));
13106
- return {
13107
- success: false,
13108
- output: `[SECURITY BLOCKED] Search URL is not allowed: ${validation.reason}`
13109
- };
13110
- }
13111
- try {
13112
- const response = await fetch(url, {
13113
- signal: AbortSignal.timeout(securityConfig?.requestTimeout || 1e4)
13114
- });
13115
- const html = await response.text();
13116
- const results = [];
13117
- const snippetRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
13118
- let match;
13119
- let count = 0;
13120
- while ((match = snippetRegex.exec(html)) !== null && count < numResults) {
13121
- const href = match[1].trim();
13122
- const title = match[2].replace(/<[^>]+>/g, "").trim();
13123
- const snippet = match[3].replace(/<[^>]+>/g, "").trim();
13124
- results.push(`${count + 1}. ${title}
13125
- URL: ${href}
13126
- ${snippet}`);
13127
- count++;
13128
- }
13129
- logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Results: ${results.length}`);
13130
- if (results.length === 0) {
13131
- return { success: true, output: t("tool.no_results", { query }) };
13132
- }
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));
13133
13642
  return {
13134
- success: true,
13135
- output: t("tool.search_results", {
13136
- query,
13137
- results: results.join(`
13138
- `)
13139
- }),
13140
- display: t("tool.web_search_result", {
13141
- query,
13142
- count: String(results.length)
13143
- })
13643
+ success: false,
13644
+ output: t("error.search_failed", { message: String(outcome.error) })
13144
13645
  };
13145
- } catch (err) {
13146
- logNetworkRequest(ctx.sessionId, sanitizeUrl(url), false, `Error: ${err.message}`);
13646
+ }
13647
+ logNetworkRequest(ctx.sessionId, sanitizeUrl(url), outcome.success, `Results: ${outcome.results.length}`);
13648
+ if (!outcome.success) {
13147
13649
  return {
13148
13650
  success: false,
13149
- output: t("error.search_failed", { message: err.message })
13651
+ output: t("error.search_failed", { message: String(outcome.error) })
13150
13652
  };
13151
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
+ };
13152
13668
  }
13153
13669
  };
13154
13670
  });
@@ -13323,7 +13839,7 @@ var init_web_browse = __esm(() => {
13323
13839
 
13324
13840
  // src/tools/download-file.ts
13325
13841
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync12 } from "fs";
13326
- import { dirname as dirname8 } from "path";
13842
+ import { dirname as dirname9 } from "path";
13327
13843
  var MAX_DOWNLOAD_BYTES, downloadFileTool;
13328
13844
  var init_download_file = __esm(() => {
13329
13845
  init_i18n();
@@ -13410,7 +13926,7 @@ var init_download_file = __esm(() => {
13410
13926
  output: t("tool.download_too_large", { max: String(maxBytes) })
13411
13927
  };
13412
13928
  }
13413
- mkdirSync12(dirname8(resolved), { recursive: true });
13929
+ mkdirSync12(dirname9(resolved), { recursive: true });
13414
13930
  writeFileSync8(resolved, buffer);
13415
13931
  const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() || "unknown";
13416
13932
  logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Status: ${response.status}`);
@@ -13867,7 +14383,7 @@ class MCPClient {
13867
14383
  }
13868
14384
  }
13869
14385
  connectStdio() {
13870
- return new Promise((resolve15, reject) => {
14386
+ return new Promise((resolve17, reject) => {
13871
14387
  const child = spawn3(this.config.command, this.config.args || [], {
13872
14388
  env: { ...process.env, ...this.config.env },
13873
14389
  stdio: ["pipe", "pipe", "pipe"]
@@ -13891,7 +14407,7 @@ class MCPClient {
13891
14407
  this._connected = true;
13892
14408
  this.process = child;
13893
14409
  _resolved = true;
13894
- resolve15();
14410
+ resolve17();
13895
14411
  });
13896
14412
  });
13897
14413
  }
@@ -13960,7 +14476,7 @@ class MCPClient {
13960
14476
  if (!this.process) {
13961
14477
  throw new Error("Not connected to MCP server");
13962
14478
  }
13963
- return new Promise((resolve15, reject) => {
14479
+ return new Promise((resolve17, reject) => {
13964
14480
  const request = {
13965
14481
  jsonrpc: "2.0",
13966
14482
  id: Date.now(),
@@ -13984,7 +14500,7 @@ class MCPClient {
13984
14500
  if (response.error) {
13985
14501
  reject(new Error(response.error.message));
13986
14502
  } else {
13987
- resolve15(response.result?.tools || []);
14503
+ resolve17(response.result?.tools || []);
13988
14504
  }
13989
14505
  return;
13990
14506
  }
@@ -14066,8 +14582,8 @@ class MCPClient {
14066
14582
  arguments: args
14067
14583
  }
14068
14584
  };
14069
- return new Promise((resolve15, reject) => {
14070
- this.pendingResolve = resolve15;
14585
+ return new Promise((resolve17, reject) => {
14586
+ this.pendingResolve = resolve17;
14071
14587
  this.pendingReject = reject;
14072
14588
  const timeout = this.config.timeout || 30000;
14073
14589
  const timer = setTimeout(() => {
@@ -14094,7 +14610,7 @@ class MCPClient {
14094
14610
  if (result.error) {
14095
14611
  reject(new Error(result.error.message));
14096
14612
  } else {
14097
- resolve15(result.result || result);
14613
+ resolve17(result.result || result);
14098
14614
  }
14099
14615
  }).catch((err) => {
14100
14616
  clearTimeout(timer);
@@ -14108,7 +14624,7 @@ class MCPClient {
14108
14624
  if (!this.process) {
14109
14625
  throw new Error("Not connected to MCP server");
14110
14626
  }
14111
- return new Promise((resolve15, reject) => {
14627
+ return new Promise((resolve17, reject) => {
14112
14628
  const request = {
14113
14629
  jsonrpc: "2.0",
14114
14630
  id: Date.now(),
@@ -14136,7 +14652,7 @@ class MCPClient {
14136
14652
  if (response.error) {
14137
14653
  reject(new Error(response.error.message));
14138
14654
  } else {
14139
- resolve15(response.result);
14655
+ resolve17(response.result);
14140
14656
  }
14141
14657
  return;
14142
14658
  }
@@ -14247,7 +14763,7 @@ ${JSON.stringify(result, null, 2)}`
14247
14763
 
14248
14764
  // src/tools/search-history.ts
14249
14765
  import * as fs from "fs";
14250
- import { join as join14 } from "path";
14766
+ import { join as join16 } from "path";
14251
14767
  import { homedir as homedir5 } from "os";
14252
14768
  function searchFile(filePath, query, maxResults, results) {
14253
14769
  if (!fs.existsSync(filePath))
@@ -14291,7 +14807,7 @@ var init_search_history = __esm(() => {
14291
14807
  const query = String(args.query || "").toLowerCase();
14292
14808
  const maxResults = Number(args.maxResults) || 5;
14293
14809
  const sessionId = args.sessionId ? String(args.sessionId) : null;
14294
- const sessionDir = join14(homedir5(), ".mma", "sessions");
14810
+ const sessionDir = join16(homedir5(), ".mma", "sessions");
14295
14811
  const results = [];
14296
14812
  try {
14297
14813
  if (!fs.existsSync(sessionDir)) {
@@ -14306,7 +14822,7 @@ var init_search_history = __esm(() => {
14306
14822
  continue;
14307
14823
  if (sessionId && entry.name !== sessionId)
14308
14824
  continue;
14309
- const historyFile = join14(sessionDir, entry.name, "history.jsonl");
14825
+ const historyFile = join16(sessionDir, entry.name, "history.jsonl");
14310
14826
  searchFile(historyFile, query, maxResults, results);
14311
14827
  if (results.length >= maxResults)
14312
14828
  break;
@@ -14333,8 +14849,8 @@ var init_search_history = __esm(() => {
14333
14849
  });
14334
14850
 
14335
14851
  // src/modules/memory/search.ts
14336
- import { readFileSync as readFileSync12, existsSync as existsSync24 } from "fs";
14337
- import { join as join15 } from "path";
14852
+ import { readFileSync as readFileSync12, existsSync as existsSync26 } from "fs";
14853
+ import { join as join17 } from "path";
14338
14854
 
14339
14855
  class MemorySearch {
14340
14856
  memoryDir;
@@ -14345,8 +14861,8 @@ class MemorySearch {
14345
14861
  const results = [];
14346
14862
  const lowerQuery = query.toLowerCase();
14347
14863
  for (const name of MEMORY_FILES) {
14348
- const path = join15(this.memoryDir, `${name}.md`);
14349
- if (!existsSync24(path))
14864
+ const path = join17(this.memoryDir, `${name}.md`);
14865
+ if (!existsSync26(path))
14350
14866
  continue;
14351
14867
  const content = readFileSync12(path, "utf-8");
14352
14868
  const lines = content.split(`
@@ -14357,8 +14873,8 @@ class MemorySearch {
14357
14873
  }
14358
14874
  }
14359
14875
  }
14360
- const prefsPath = join15(this.memoryDir, "preferences.json");
14361
- if (existsSync24(prefsPath)) {
14876
+ const prefsPath = join17(this.memoryDir, "preferences.json");
14877
+ if (existsSync26(prefsPath)) {
14362
14878
  try {
14363
14879
  const prefs = JSON.parse(readFileSync12(prefsPath, "utf-8"));
14364
14880
  for (const [key, value] of Object.entries(prefs)) {
@@ -14378,8 +14894,8 @@ var init_search = __esm(() => {
14378
14894
  });
14379
14895
 
14380
14896
  // src/modules/memory/store.ts
14381
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync25, mkdirSync as mkdirSync13 } from "fs";
14382
- import { join as join16 } from "path";
14897
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync27, mkdirSync as mkdirSync13 } from "fs";
14898
+ import { join as join18 } from "path";
14383
14899
 
14384
14900
  class MemoryStore {
14385
14901
  memoryDir;
@@ -14387,8 +14903,8 @@ class MemoryStore {
14387
14903
  this.memoryDir = memoryDir;
14388
14904
  this.ensureDir();
14389
14905
  for (const name of MEMORY_FILES2) {
14390
- const path = join16(this.memoryDir, `${name}.md`);
14391
- if (!existsSync25(path)) {
14906
+ const path = join18(this.memoryDir, `${name}.md`);
14907
+ if (!existsSync27(path)) {
14392
14908
  writeFileSync9(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
14393
14909
 
14394
14910
  `, "utf-8");
@@ -14396,18 +14912,18 @@ class MemoryStore {
14396
14912
  }
14397
14913
  }
14398
14914
  ensureDir() {
14399
- if (!existsSync25(this.memoryDir)) {
14915
+ if (!existsSync27(this.memoryDir)) {
14400
14916
  mkdirSync13(this.memoryDir, { recursive: true });
14401
14917
  }
14402
14918
  }
14403
14919
  read(name) {
14404
- const path = join16(this.memoryDir, `${name}.md`);
14405
- if (!existsSync25(path))
14920
+ const path = join18(this.memoryDir, `${name}.md`);
14921
+ if (!existsSync27(path))
14406
14922
  return "";
14407
14923
  return readFileSync13(path, "utf-8");
14408
14924
  }
14409
14925
  append(name, entry) {
14410
- const path = join16(this.memoryDir, `${name}.md`);
14926
+ const path = join18(this.memoryDir, `${name}.md`);
14411
14927
  const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
14412
14928
  const formatted = `- **${timestamp}** — ${entry}
14413
14929
  `;
@@ -14418,11 +14934,11 @@ class MemoryStore {
14418
14934
  return searchModule.query(query);
14419
14935
  }
14420
14936
  prefsPath() {
14421
- return join16(this.memoryDir, "preferences.json");
14937
+ return join18(this.memoryDir, "preferences.json");
14422
14938
  }
14423
14939
  getPreferences() {
14424
14940
  const path = this.prefsPath();
14425
- if (!existsSync25(path))
14941
+ if (!existsSync27(path))
14426
14942
  return {};
14427
14943
  try {
14428
14944
  return JSON.parse(readFileSync13(path, "utf-8"));
@@ -14458,7 +14974,7 @@ var init_store2 = __esm(() => {
14458
14974
 
14459
14975
  // src/tools/remember.ts
14460
14976
  import { homedir as homedir6 } from "os";
14461
- import { join as join17 } from "path";
14977
+ import { join as join19 } from "path";
14462
14978
  var CATEGORIES, rememberTool;
14463
14979
  var init_remember = __esm(() => {
14464
14980
  init_i18n();
@@ -14496,7 +15012,7 @@ var init_remember = __esm(() => {
14496
15012
  if (!CATEGORIES.includes(category)) {
14497
15013
  return { success: false, output: t("tool.invalid_params") };
14498
15014
  }
14499
- const memoryDir = join17(homedir6(), ".mma", "memory");
15015
+ const memoryDir = join19(homedir6(), ".mma", "memory");
14500
15016
  const store = new MemoryStore(memoryDir);
14501
15017
  try {
14502
15018
  if (category === "preferences") {
@@ -14529,7 +15045,7 @@ var init_remember = __esm(() => {
14529
15045
 
14530
15046
  // src/tools/recall.ts
14531
15047
  import { homedir as homedir7 } from "os";
14532
- import { join as join18 } from "path";
15048
+ import { join as join20 } from "path";
14533
15049
  function formatAll(store) {
14534
15050
  const parts = [];
14535
15051
  const prefs = store.getPreferences();
@@ -14612,7 +15128,7 @@ var init_recall = __esm(() => {
14612
15128
  handler: async (_ctx, args) => {
14613
15129
  const query = args.query ? String(args.query) : "";
14614
15130
  const category = args.category ? String(args.category) : "";
14615
- const memoryDir = join18(homedir7(), ".mma", "memory");
15131
+ const memoryDir = join20(homedir7(), ".mma", "memory");
14616
15132
  const store = new MemoryStore(memoryDir);
14617
15133
  try {
14618
15134
  if (!query && !category) {
@@ -14650,9 +15166,9 @@ var init_recall = __esm(() => {
14650
15166
  });
14651
15167
 
14652
15168
  // src/modules/browser/bridge-path.ts
14653
- import { existsSync as existsSync26 } from "fs";
15169
+ import { existsSync as existsSync28 } from "fs";
14654
15170
  function pickExistingPath(candidates, fallback = candidates[0]) {
14655
- return candidates.find((p) => existsSync26(p)) ?? fallback;
15171
+ return candidates.find((p) => existsSync28(p)) ?? fallback;
14656
15172
  }
14657
15173
  var init_bridge_path = () => {};
14658
15174
 
@@ -14663,13 +15179,13 @@ __export(exports_bridge_client, {
14663
15179
  });
14664
15180
  import { spawn as spawn4 } from "child_process";
14665
15181
  import { createInterface } from "readline";
14666
- import { dirname as dirname9, join as join19 } from "path";
15182
+ import { dirname as dirname10, join as join21 } from "path";
14667
15183
  import { fileURLToPath } from "url";
14668
15184
  function bridgeScriptPath() {
14669
- const dir = dirname9(fileURLToPath(import.meta.url));
15185
+ const dir = dirname10(fileURLToPath(import.meta.url));
14670
15186
  const candidates = [
14671
- join19(dir, "bridge-server.mjs"),
14672
- join19(dir, "modules", "browser", "bridge-server.mjs")
15187
+ join21(dir, "bridge-server.mjs"),
15188
+ join21(dir, "modules", "browser", "bridge-server.mjs")
14673
15189
  ];
14674
15190
  return pickExistingPath(candidates);
14675
15191
  }
@@ -14695,14 +15211,14 @@ class BridgeDriver {
14695
15211
  return this.send(cmd, params, true);
14696
15212
  }
14697
15213
  const id = this.nextId++;
14698
- const response = await new Promise((resolve15, reject) => {
15214
+ const response = await new Promise((resolve17, reject) => {
14699
15215
  const timer = setTimeout(() => {
14700
15216
  this.pending.delete(id);
14701
15217
  reject(new Error(`Bridge timeout waiting for "${cmd}"`));
14702
15218
  }, BRIDGE_REQUEST_TIMEOUT_MS);
14703
15219
  this.pending.set(id, (resp) => {
14704
15220
  clearTimeout(timer);
14705
- resolve15(resp);
15221
+ resolve17(resp);
14706
15222
  });
14707
15223
  this.proc.stdin.write(JSON.stringify({ id, cmd, params }) + `
14708
15224
  `);
@@ -15223,15 +15739,15 @@ function buildTextExtractionScript() {
15223
15739
 
15224
15740
  // src/modules/browser/cookie-store.ts
15225
15741
  import { readFile, writeFile, mkdir } from "fs/promises";
15226
- import { join as join20 } from "path";
15742
+ import { join as join22 } from "path";
15227
15743
 
15228
15744
  class CookieStore {
15229
15745
  filePath;
15230
15746
  constructor(cookieDir) {
15231
- this.filePath = join20(cookieDir, "cookies.json");
15747
+ this.filePath = join22(cookieDir, "cookies.json");
15232
15748
  }
15233
15749
  async save(cookies) {
15234
- await mkdir(join20(this.filePath, ".."), { recursive: true });
15750
+ await mkdir(join22(this.filePath, ".."), { recursive: true });
15235
15751
  await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
15236
15752
  }
15237
15753
  async load() {
@@ -15586,10 +16102,10 @@ var init_session = __esm(() => {
15586
16102
  });
15587
16103
 
15588
16104
  // src/tools/browser.ts
15589
- import { join as join21 } from "path";
16105
+ import { join as join23 } from "path";
15590
16106
  function getSession(ctx) {
15591
16107
  if (!session) {
15592
- const cookieDir = join21(ctx.baseDir, ".mma", "browser");
16108
+ const cookieDir = join23(ctx.baseDir, ".mma", "browser");
15593
16109
  session = new BrowserSession({
15594
16110
  ...DEFAULT_BROWSER_CONFIG,
15595
16111
  headless: ctx.config.browser?.headless ?? true,
@@ -15736,8 +16252,8 @@ async function readClipboardFallback() {
15736
16252
  const { platform: platform5 } = await import("os");
15737
16253
  const { execSync } = await import("child_process");
15738
16254
  const { readFileSync: readFileSync15, unlinkSync: unlinkSync4 } = await import("fs");
15739
- const { join: join22 } = await import("path");
15740
- const tmpPath = join22(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
16255
+ const { join: join24 } = await import("path");
16256
+ const tmpPath = join24(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
15741
16257
  try {
15742
16258
  if (platform5() === "linux") {
15743
16259
  execSync(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, {
@@ -15817,8 +16333,8 @@ var init_image_utils = __esm(() => {
15817
16333
  });
15818
16334
 
15819
16335
  // src/tools/attach-image.ts
15820
- import { existsSync as existsSync27 } from "fs";
15821
- import { resolve as resolve15 } from "path";
16336
+ import { existsSync as existsSync29 } from "fs";
16337
+ import { resolve as resolve17 } from "path";
15822
16338
  var attachImageTool;
15823
16339
  var init_attach_image = __esm(() => {
15824
16340
  init_i18n();
@@ -15859,8 +16375,8 @@ var init_attach_image = __esm(() => {
15859
16375
  const result = await loadUrlAsDataUrl(source);
15860
16376
  dataUrl = result.dataUrl;
15861
16377
  } else {
15862
- const absPath = resolve15(ctx.baseDir, source);
15863
- if (!existsSync27(absPath)) {
16378
+ const absPath = resolve17(ctx.baseDir, source);
16379
+ if (!existsSync29(absPath)) {
15864
16380
  return {
15865
16381
  success: false,
15866
16382
  output: t("image.not_found", { path: source })
@@ -16116,16 +16632,16 @@ class ModuleRegistry {
16116
16632
  }
16117
16633
 
16118
16634
  // src/modules/plugins/loader.ts
16119
- import { readdirSync as readdirSync7, existsSync as existsSync28, statSync as statSync5 } from "fs";
16120
- import { join as join22, basename as basename2 } from "path";
16635
+ import { readdirSync as readdirSync9, existsSync as existsSync30, statSync as statSync5 } from "fs";
16636
+ import { join as join24, basename as basename3 } from "path";
16121
16637
 
16122
16638
  class PluginLoader {
16123
16639
  loadFromDir(dirPath, pluginManager, logger, options) {
16124
- if (!existsSync28(dirPath))
16640
+ if (!existsSync30(dirPath))
16125
16641
  return;
16126
- const entries = readdirSync7(dirPath).sort();
16642
+ const entries = readdirSync9(dirPath).sort();
16127
16643
  for (const entry of entries) {
16128
- const fullPath = join22(dirPath, entry);
16644
+ const fullPath = join24(dirPath, entry);
16129
16645
  const stat = statSync5(fullPath);
16130
16646
  if (stat.isFile()) {
16131
16647
  if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
@@ -16134,15 +16650,15 @@ class PluginLoader {
16134
16650
  } else if (stat.isDirectory()) {
16135
16651
  const entryFile = this.findEntryFile(fullPath);
16136
16652
  if (entryFile) {
16137
- this.tryLoad(entryFile, `${entry}/${basename2(entryFile)}`, pluginManager, logger, options);
16653
+ this.tryLoad(entryFile, `${entry}/${basename3(entryFile)}`, pluginManager, logger, options);
16138
16654
  }
16139
16655
  }
16140
16656
  }
16141
16657
  }
16142
16658
  findEntryFile(dir) {
16143
16659
  for (const name of FOLDER_ENTRY_NAMES) {
16144
- const candidate = join22(dir, name);
16145
- if (existsSync28(candidate))
16660
+ const candidate = join24(dir, name);
16661
+ if (existsSync30(candidate))
16146
16662
  return candidate;
16147
16663
  }
16148
16664
  return null;
@@ -16184,8 +16700,8 @@ var init_loader = __esm(() => {
16184
16700
 
16185
16701
  // src/modules/plugins/builtin/lint-on-write.ts
16186
16702
  import { spawn as spawn5, execSync } from "child_process";
16187
- import { existsSync as existsSync29, readFileSync as readFileSync15 } from "fs";
16188
- import { resolve as resolve16, extname as extname4, join as join23 } from "path";
16703
+ import { existsSync as existsSync31, readFileSync as readFileSync15 } from "fs";
16704
+ import { resolve as resolve18, extname as extname4, join as join25 } from "path";
16189
16705
  import { platform as platform5 } from "os";
16190
16706
  function contentHash(content) {
16191
16707
  let h = 5381;
@@ -16231,8 +16747,8 @@ class LintOnWritePlugin {
16231
16747
  const path = String(call.arguments.path || "");
16232
16748
  if (!path)
16233
16749
  return;
16234
- const fullPath = resolve16(ctx.baseDir, path);
16235
- if (!existsSync29(fullPath))
16750
+ const fullPath = resolve18(ctx.baseDir, path);
16751
+ if (!existsSync31(fullPath))
16236
16752
  return;
16237
16753
  const ext = extname4(fullPath);
16238
16754
  const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
@@ -16288,8 +16804,8 @@ class LintOnWritePlugin {
16288
16804
  }
16289
16805
  async runProjectLint(ctx, result) {
16290
16806
  try {
16291
- const packageJsonPath = join23(ctx.baseDir, "package.json");
16292
- if (!existsSync29(packageJsonPath)) {
16807
+ const packageJsonPath = join25(ctx.baseDir, "package.json");
16808
+ if (!existsSync31(packageJsonPath)) {
16293
16809
  return;
16294
16810
  }
16295
16811
  const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
@@ -16301,16 +16817,21 @@ class LintOnWritePlugin {
16301
16817
  await runAsync(lintScript, ctx.baseDir, 30000);
16302
16818
  ctx.logger.debug("Lint passed");
16303
16819
  } catch (err) {
16820
+ const stderr = (err.stderr?.toString?.() || "").trim();
16821
+ const stdout = (err.stdout?.toString?.() || "").trim();
16822
+ const detail = (stderr || stdout || err.message).split(`
16823
+ `).filter(Boolean).slice(-5).join(`
16824
+ `);
16304
16825
  ctx.logger.warn(`Lint failed: ${err.message}`);
16305
16826
  result.output += `
16306
16827
 
16307
- [Project lint failed]: ${err.message}`;
16828
+ [Project lint failed]: ${detail}`;
16308
16829
  }
16309
16830
  }
16310
16831
  async runProjectTypeCheck(filePath, baseDir, result) {
16311
16832
  const projectRoot = findProjectRoot(filePath, baseDir, ["tsconfig.json", "package.json"]);
16312
- const tsconfigPath = join23(projectRoot, "tsconfig.json");
16313
- if (!existsSync29(tsconfigPath)) {
16833
+ const tsconfigPath = join25(projectRoot, "tsconfig.json");
16834
+ if (!existsSync31(tsconfigPath)) {
16314
16835
  return;
16315
16836
  }
16316
16837
  const now = Date.now();
@@ -16351,7 +16872,7 @@ class LintOnWritePlugin {
16351
16872
  }
16352
16873
  }
16353
16874
  function runAsync(command, cwd, timeoutMs) {
16354
- return new Promise((resolve17, reject) => {
16875
+ return new Promise((resolve19, reject) => {
16355
16876
  const child = spawn5(command, {
16356
16877
  cwd,
16357
16878
  shell: true,
@@ -16380,7 +16901,7 @@ function runAsync(command, cwd, timeoutMs) {
16380
16901
  stdout += decoder.decode();
16381
16902
  stderr += decoder.decode();
16382
16903
  if (code === 0) {
16383
- resolve17({ stdout, stderr });
16904
+ resolve19({ stdout, stderr });
16384
16905
  } else {
16385
16906
  const err = new Error(`Command failed with exit code ${code}`);
16386
16907
  err.stdout = stdout;
@@ -16473,379 +16994,90 @@ class PlanCreator {
16473
16994
  title: `[${totalSteps}] ${newTitle}`,
16474
16995
  steps: [...kept, ...added],
16475
16996
  createdAt: plan.createdAt,
16476
- baseDir: plan.baseDir,
16477
- name: plan.name
16478
- };
16479
- }
16480
- static toPromptBlock(plan, currentStepIndex) {
16481
- const date = plan.createdAt.slice(0, 10);
16482
- const lines = [
16483
- `[${plan.id}] ${plan.title}`,
16484
- `Dir: ${plan.baseDir}`,
16485
- `Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
16486
- ``
16487
- ];
16488
- for (const step of plan.steps) {
16489
- const icon = step.status === "done" ? "[x]" : step.status === "in_progress" ? "[*]" : step.status === "failed" ? "[!]" : step.status === "skipped" ? "[-]" : "[ ]";
16490
- const note = step.note ? ` — ${step.note}` : "";
16491
- lines.push(`${icon} ${step.id}. ${step.description}${note}`);
16492
- for (const sub of step.subtasks ?? []) {
16493
- lines.push(` ${sub.done ? "[x]" : "[ ]"} ${sub.text}`);
16494
- }
16495
- }
16496
- return lines.join(`
16497
- `);
16498
- }
16499
- }
16500
-
16501
- // src/modules/execution/tracker.ts
16502
- class PlanTracker {
16503
- plan;
16504
- currentStepIndex = 0;
16505
- constructor(plan) {
16506
- this.plan = plan;
16507
- }
16508
- getPlan() {
16509
- return this.plan;
16510
- }
16511
- getCurrentStepIndex() {
16512
- return this.currentStepIndex;
16513
- }
16514
- getCurrentStep() {
16515
- return this.plan.steps[this.currentStepIndex];
16516
- }
16517
- getStep(id) {
16518
- return this.plan.steps.find((s) => s.id === id);
16519
- }
16520
- updateStepStatus(id, status) {
16521
- const step = this.getStep(id);
16522
- if (step)
16523
- step.status = status;
16524
- }
16525
- addNote(id, note) {
16526
- const step = this.getStep(id);
16527
- if (step)
16528
- step.note = note;
16529
- }
16530
- advance() {
16531
- if (this.currentStepIndex < this.plan.steps.length - 1) {
16532
- this.currentStepIndex++;
16533
- this.plan.steps[this.currentStepIndex].status = "in_progress";
16534
- return true;
16535
- }
16536
- return false;
16537
- }
16538
- syncCurrentStep() {
16539
- while (this.currentStepIndex < this.plan.steps.length - 1 && (this.plan.steps[this.currentStepIndex].status === "done" || this.plan.steps[this.currentStepIndex].status === "skipped")) {
16540
- this.currentStepIndex++;
16541
- }
16542
- }
16543
- isComplete() {
16544
- return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
16545
- }
16546
- getProgressString() {
16547
- const done = this.plan.steps.filter((s) => s.status === "done").length;
16548
- const total = this.plan.steps.length;
16549
- const pct = total > 0 ? Math.round(done / total * 100) : 0;
16550
- const barWidth = 10;
16551
- const filled = Math.round(done / total * barWidth);
16552
- const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
16553
- return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
16554
- }
16555
- toPromptBlock() {
16556
- return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
16557
- }
16558
- }
16559
- var init_tracker = () => {};
16560
-
16561
- // src/modules/execution/audit-runners.ts
16562
- import { existsSync as existsSync30, readdirSync as readdirSync8 } from "fs";
16563
- import { dirname as dirname10, join as join24, resolve as resolve17 } from "path";
16564
- function findTestFile(dir, depth = 0) {
16565
- if (depth > 5)
16566
- return null;
16567
- let entries;
16568
- try {
16569
- entries = readdirSync8(dir, { withFileTypes: true });
16570
- } catch {
16571
- return null;
16572
- }
16573
- for (const e of entries) {
16574
- const full = join24(dir, e.name);
16575
- if (e.isDirectory()) {
16576
- if (SKIP_DIRS.has(e.name))
16577
- continue;
16578
- const found = findTestFile(full, depth + 1);
16579
- if (found)
16580
- return found;
16581
- } else if (TEST_EXT_RE.test(e.name)) {
16582
- return full;
16583
- }
16584
- }
16585
- return null;
16586
- }
16587
- function hasTestStep(plan) {
16588
- return plan.steps.some((s) => TEST_STEP_RE.test(s.description));
16589
- }
16590
- function extractFailingNames(output, limit = 5) {
16591
- const names = [];
16592
- for (const m of output.matchAll(/\(fail\)\s*([^\n]+)/g)) {
16593
- const name = m[1].trim();
16594
- if (name && !names.includes(name))
16595
- names.push(name);
16596
- if (names.length >= limit)
16597
- break;
16598
- }
16599
- return names;
16600
- }
16601
- async function runTests(baseDir) {
16602
- const entry = processRegistry.start("bun test", baseDir);
16603
- const exited = await processRegistry.waitForExit(entry.id, 90000);
16604
- const output = entry.log.join(`
16605
- `);
16606
- processRegistry.remove(entry.id);
16607
- if (!exited) {
16608
- return {
16609
- checked: true,
16610
- passed: true,
16611
- failed: 0,
16612
- passedCount: 0,
16613
- detail: "test run timed out after 90s — result unknown",
16614
- command: "bun test"
16615
- };
16616
- }
16617
- const run = detectTestResults(output);
16618
- if (!run) {
16619
- return {
16620
- checked: true,
16621
- passed: entry.exitCode === 0,
16622
- failed: entry.exitCode === 0 ? 0 : 1,
16623
- passedCount: 0,
16624
- detail: output.slice(0, 200).trim(),
16625
- command: "bun test"
16626
- };
16627
- }
16628
- const names = extractFailingNames(output);
16629
- return {
16630
- checked: true,
16631
- passed: run.failed === 0,
16632
- failed: run.failed,
16633
- passedCount: run.passed,
16634
- detail: names.length ? names.join("; ") : run.summary || `${run.failed} failed / ${run.passed} passed`,
16635
- command: "bun test"
16636
- };
16637
- }
16638
- function parseTypecheckErrors(output) {
16639
- const line = output.split(`
16640
- `).find((l) => /error TS\d+/.test(l));
16641
- return line ? line.trim().slice(0, 300) : null;
16642
- }
16643
- function findTypecheckRoot(baseDir, existingFiles = []) {
16644
- const candidates = [baseDir, ...existingFiles];
16645
- let best = null;
16646
- for (const start of candidates) {
16647
- let dir = resolve17(start);
16648
- for (let depth = 0;depth <= 10; depth++) {
16649
- if (existsSync30(join24(dir, "tsconfig.json"))) {
16650
- if (!best || depth < best.depth)
16651
- best = { depth, root: dir };
16652
- break;
16653
- }
16654
- const parent = dirname10(dir);
16655
- if (parent === dir)
16656
- break;
16657
- dir = parent;
16658
- }
16659
- }
16660
- return best?.root ?? null;
16661
- }
16662
- async function runTypecheck(baseDir) {
16663
- const entry = processRegistry.start("npx --no-install tsc --noEmit --skipLibCheck", baseDir);
16664
- const exited = await processRegistry.waitForExit(entry.id, 90000);
16665
- const output = entry.log.join(`
16666
- `);
16667
- processRegistry.remove(entry.id);
16668
- if (!exited)
16669
- return null;
16670
- return parseTypecheckErrors(output);
16671
- }
16672
- var SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE;
16673
- var init_audit_runners = __esm(() => {
16674
- init_bash();
16675
- init_processes();
16676
- SKIP_DIRS = new Set([
16677
- "node_modules",
16678
- ".git",
16679
- ".mma",
16680
- "dist",
16681
- "build",
16682
- "coverage",
16683
- ".next",
16684
- ".nuxt",
16685
- "vendor"
16686
- ]);
16687
- TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
16688
- TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
16689
- });
16690
-
16691
- // src/modules/execution/auditor.ts
16692
- import { existsSync as existsSync31, readdirSync as readdirSync9 } from "fs";
16693
- import { resolve as resolve18, join as join25, basename as basename3 } from "path";
16694
- function findExistingFile(baseDir, filePath) {
16695
- const direct = resolve18(baseDir, filePath);
16696
- if (existsSync31(direct))
16697
- return direct;
16698
- const name = basename3(filePath).toLowerCase();
16699
- const suffix = filePath.replace(/\\/g, "/").toLowerCase();
16700
- let found = null;
16701
- const walk = (dir, depth) => {
16702
- if (found || depth > RESOLVE_MAX_DEPTH)
16703
- return;
16704
- let entries;
16705
- try {
16706
- entries = readdirSync9(dir, { withFileTypes: true });
16707
- } catch {
16708
- return;
16709
- }
16710
- for (const e of entries) {
16711
- if (found)
16712
- return;
16713
- const full = join25(dir, e.name);
16714
- if (e.isDirectory()) {
16715
- if (SKIP_DIRS.has(e.name))
16716
- continue;
16717
- walk(full, depth + 1);
16718
- } else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
16719
- found = full;
16720
- return;
16997
+ baseDir: plan.baseDir,
16998
+ name: plan.name
16999
+ };
17000
+ }
17001
+ static toPromptBlock(plan, currentStepIndex) {
17002
+ const date = plan.createdAt.slice(0, 10);
17003
+ const lines = [
17004
+ `[${plan.id}] ${plan.title}`,
17005
+ `Dir: ${plan.baseDir}`,
17006
+ `Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
17007
+ ``
17008
+ ];
17009
+ for (const step of plan.steps) {
17010
+ const icon = step.status === "done" ? "[x]" : step.status === "in_progress" ? "[*]" : step.status === "failed" ? "[!]" : step.status === "skipped" ? "[-]" : "[ ]";
17011
+ const note = step.note ? ` — ${step.note}` : "";
17012
+ lines.push(`${icon} ${step.id}. ${step.description}${note}`);
17013
+ for (const sub of step.subtasks ?? []) {
17014
+ lines.push(` ${sub.done ? "[x]" : "[ ]"} ${sub.text}`);
16721
17015
  }
16722
17016
  }
16723
- };
16724
- walk(baseDir, 0);
16725
- return found;
17017
+ return lines.join(`
17018
+ `);
17019
+ }
16726
17020
  }
16727
17021
 
16728
- class Auditor {
16729
- baseDir;
16730
- constructor(baseDir) {
16731
- this.baseDir = baseDir;
17022
+ // src/modules/execution/tracker.ts
17023
+ class PlanTracker {
17024
+ plan;
17025
+ currentStepIndex = 0;
17026
+ constructor(plan) {
17027
+ this.plan = plan;
16732
17028
  }
16733
- async audit(plan) {
16734
- const doneCreate = plan.steps.filter((s) => s.status === "done" && s.kind !== "delete");
16735
- const doneDelete = plan.steps.filter((s) => s.status === "done" && s.kind === "delete");
16736
- const createText = doneCreate.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
16737
- const deleteText = doneDelete.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
16738
- const createFiles = extractFileLikeTokens(stripUrls(createText));
16739
- const deleteFiles = extractFileLikeTokens(stripUrls(deleteText));
16740
- const missingFiles = [];
16741
- const existingFiles = [];
16742
- const leftoverFiles = [];
16743
- for (const filePath of createFiles) {
16744
- const resolved = findExistingFile(this.baseDir, filePath);
16745
- if (resolved) {
16746
- existingFiles.push(resolved);
16747
- } else {
16748
- missingFiles.push(filePath);
16749
- }
16750
- }
16751
- for (const filePath of deleteFiles) {
16752
- if (findExistingFile(this.baseDir, filePath)) {
16753
- leftoverFiles.push(filePath);
16754
- }
16755
- }
16756
- let testRun = null;
16757
- if (hasTestStep(plan) && findTestFile(this.baseDir)) {
16758
- try {
16759
- testRun = await runTests(this.baseDir);
16760
- } catch {
16761
- testRun = null;
16762
- }
16763
- }
16764
- let typecheckError = null;
16765
- if (missingFiles.length === 0 && leftoverFiles.length === 0) {
16766
- const root = findTypecheckRoot(this.baseDir, existingFiles);
16767
- if (root) {
16768
- try {
16769
- typecheckError = await runTypecheck(root);
16770
- } catch {
16771
- typecheckError = null;
16772
- }
16773
- }
16774
- }
16775
- const doneSteps = plan.steps.filter((s) => s.status === "done").length;
16776
- const terminalSteps = plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
16777
- const totalSteps = plan.steps.length;
16778
- const auditedFiles = [...new Set([...createFiles, ...deleteFiles])];
16779
- let massEditWarning = null;
16780
- if (auditedFiles.length > MASS_EDIT_THRESHOLD) {
16781
- massEditWarning = t("exec.mass_edit_warning", {
16782
- count: String(auditedFiles.length)
16783
- });
17029
+ getPlan() {
17030
+ return this.plan;
17031
+ }
17032
+ getCurrentStepIndex() {
17033
+ return this.currentStepIndex;
17034
+ }
17035
+ getCurrentStep() {
17036
+ return this.plan.steps[this.currentStepIndex];
17037
+ }
17038
+ getStep(id) {
17039
+ return this.plan.steps.find((s) => s.id === id);
17040
+ }
17041
+ updateStepStatus(id, status) {
17042
+ const step = this.getStep(id);
17043
+ if (step)
17044
+ step.status = status;
17045
+ }
17046
+ addNote(id, note) {
17047
+ const step = this.getStep(id);
17048
+ if (step)
17049
+ step.note = note;
17050
+ }
17051
+ advance() {
17052
+ if (this.currentStepIndex < this.plan.steps.length - 1) {
17053
+ this.currentStepIndex++;
17054
+ this.plan.steps[this.currentStepIndex].status = "in_progress";
17055
+ return true;
16784
17056
  }
16785
- const testsFailing = testRun !== null && !testRun.passed;
16786
- const typecheckFailing = typecheckError !== null;
16787
- const passed = missingFiles.length === 0 && leftoverFiles.length === 0 && !testsFailing && !typecheckFailing;
16788
- const stepsPending = terminalSteps < totalSteps;
16789
- let summary;
16790
- if (missingFiles.length > 0) {
16791
- summary = t("exec.audit_fail", {
16792
- done: doneSteps,
16793
- total: totalSteps,
16794
- files: missingFiles.length
16795
- });
16796
- } else if (leftoverFiles.length > 0) {
16797
- summary = t("exec.audit_leftovers", {
16798
- done: doneSteps,
16799
- total: totalSteps,
16800
- files: leftoverFiles.length
16801
- });
16802
- } else if (testsFailing) {
16803
- summary = t("exec.audit_fail_tests", {
16804
- done: doneSteps,
16805
- total: totalSteps,
16806
- failed: String(testRun.failed),
16807
- passed: String(testRun.passedCount),
16808
- detail: testRun.detail
16809
- });
16810
- } else if (typecheckFailing) {
16811
- summary = t("exec.audit_fail_typecheck", {
16812
- done: doneSteps,
16813
- total: totalSteps,
16814
- missing: String(missingFiles.length),
16815
- typeError: typecheckError
16816
- });
16817
- } else if (stepsPending) {
16818
- summary = t("exec.audit_pending", {
16819
- done: doneSteps,
16820
- total: totalSteps
16821
- });
16822
- } else {
16823
- summary = t("exec.audit_pass", {
16824
- done: doneSteps,
16825
- total: totalSteps,
16826
- files: existingFiles.length
16827
- });
17057
+ return false;
17058
+ }
17059
+ syncCurrentStep() {
17060
+ while (this.currentStepIndex < this.plan.steps.length - 1 && (this.plan.steps[this.currentStepIndex].status === "done" || this.plan.steps[this.currentStepIndex].status === "skipped")) {
17061
+ this.currentStepIndex++;
16828
17062
  }
16829
- return {
16830
- passed,
16831
- missingFiles,
16832
- leftoverFiles,
16833
- createdFiles: existingFiles,
16834
- modifiedFiles: [],
16835
- summary,
16836
- massEditWarning,
16837
- testRun,
16838
- typecheckError
16839
- };
17063
+ }
17064
+ isComplete() {
17065
+ return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
17066
+ }
17067
+ getProgressString() {
17068
+ const done = this.plan.steps.filter((s) => s.status === "done").length;
17069
+ const total = this.plan.steps.length;
17070
+ const pct = total > 0 ? Math.round(done / total * 100) : 0;
17071
+ const barWidth = 10;
17072
+ const filled = Math.round(done / total * barWidth);
17073
+ const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
17074
+ return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
17075
+ }
17076
+ toPromptBlock() {
17077
+ return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
16840
17078
  }
16841
17079
  }
16842
- var MASS_EDIT_THRESHOLD = 10, RESOLVE_MAX_DEPTH = 8;
16843
- var init_auditor = __esm(() => {
16844
- init_i18n();
16845
- init_js_identifiers();
16846
- init_audit_runners();
16847
- init_audit_runners();
16848
- });
17080
+ var init_tracker = () => {};
16849
17081
 
16850
17082
  // src/modules/execution/plan-store.ts
16851
17083
  import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync32, readdirSync as readdirSync10, rmSync } from "fs";
@@ -17279,6 +17511,20 @@ ${display}`,
17279
17511
  return { success: false, output: t("plan.step_not_found") };
17280
17512
  }
17281
17513
  const status = String(args.status || "done");
17514
+ if (tracker.isComplete()) {
17515
+ const plan = tracker.getPlan();
17516
+ deps.store.archivePlan(plan);
17517
+ deps.trackerRef.current = null;
17518
+ const done = plan.steps.filter((s) => s.status === "done").length;
17519
+ return {
17520
+ success: true,
17521
+ output: t("plan.completed_archived", {
17522
+ id: plan.id,
17523
+ done: String(done),
17524
+ total: String(plan.steps.length)
17525
+ })
17526
+ };
17527
+ }
17282
17528
  if (status === "done" && target.status === "done") {
17283
17529
  const progress2 = tracker.getProgressString();
17284
17530
  const display2 = tracker.toPromptBlock();
@@ -17334,6 +17580,21 @@ ${progress2}`,
17334
17580
  tracker.addNote(Number(args.step), String(args.note));
17335
17581
  tracker.syncCurrentStep();
17336
17582
  deps.store.saveActive(tracker.getPlan());
17583
+ if (tracker.isComplete()) {
17584
+ const plan = tracker.getPlan();
17585
+ deps.store.archivePlan(plan);
17586
+ deps.trackerRef.current = null;
17587
+ const done = plan.steps.filter((s) => s.status === "done").length;
17588
+ return {
17589
+ success: true,
17590
+ output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
17591
+ ${t("plan.completed_archived", {
17592
+ id: plan.id,
17593
+ done: String(done),
17594
+ total: String(plan.steps.length)
17595
+ })}`
17596
+ };
17597
+ }
17337
17598
  const progress = tracker.getProgressString();
17338
17599
  const display = tracker.toPromptBlock();
17339
17600
  _ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
@@ -17497,6 +17758,12 @@ ${lines.join(`
17497
17758
  if (!step)
17498
17759
  return { success: false, output: t("plan.step_not_found") };
17499
17760
  const result = await deps.verifier.verifyStep(step.description);
17761
+ if (result.noFiles) {
17762
+ return {
17763
+ success: true,
17764
+ output: t("verify.no_files", { step: String(step.id) })
17765
+ };
17766
+ }
17500
17767
  return {
17501
17768
  success: result.passed,
17502
17769
  output: result.passed ? t("verify.passed") : t("verify.failed", {
@@ -17683,7 +17950,8 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17683
17950
  deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
17684
17951
  }
17685
17952
  const toolText = String(result.output ?? "");
17686
- 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) {
17687
17955
  deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
17688
17956
  }
17689
17957
  if (!result.success) {
@@ -17701,7 +17969,9 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17701
17969
  }
17702
17970
  }
17703
17971
  }
17704
- deps.stuckDetector.recordToolError(call.name, result.output);
17972
+ if (!hasTypeError) {
17973
+ deps.stuckDetector.recordToolError(call.name, result.output);
17974
+ }
17705
17975
  const actionableHints = deps.stuckDetector.getActionableHints();
17706
17976
  const alternative = deps.stuckDetector.getToolAlternative();
17707
17977
  if (actionableHints.length > 0 || alternative) {
@@ -17755,6 +18025,7 @@ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17755
18025
  }
17756
18026
  deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
17757
18027
  }
18028
+ deps.maybeSearchError(ctx, call);
17758
18029
  }
17759
18030
  };
17760
18031
  }
@@ -17780,6 +18051,12 @@ class ExecutionModule {
17780
18051
  _auditSkipsRemaining = 0;
17781
18052
  pendingMessages = [];
17782
18053
  forbiddenBashFailures = new Map;
18054
+ searchRunner;
18055
+ webSearchThreshold;
18056
+ searchesThisSession = 0;
18057
+ retryWebSearchSignatures = new Set;
18058
+ lastErrorSearchAt = 0;
18059
+ errorSearchCooldownMs;
17783
18060
  state = {
17784
18061
  lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
17785
18062
  consecutivePlanWarnings: 0,
@@ -17798,12 +18075,15 @@ class ExecutionModule {
17798
18075
  }
17799
18076
  };
17800
18077
  })();
17801
- constructor(baseDir, stuckThreshold = 6) {
18078
+ constructor(baseDir, stuckThreshold = 6, webSearchThreshold = 5, searchRunner = performWebSearch, errorSearchCooldownMs = ERROR_SEARCH_MIN_INTERVAL_MS) {
17802
18079
  this.baseDir = baseDir;
17803
18080
  this.verifier = new StepVerifier(baseDir);
17804
- this.stuckDetector = new StuckDetector(stuckThreshold);
18081
+ this.stuckDetector = new StuckDetector(stuckThreshold, 3, webSearchThreshold);
17805
18082
  this.auditor = new Auditor(baseDir);
17806
18083
  this.store = new PlanStore(baseDir);
18084
+ this.webSearchThreshold = webSearchThreshold;
18085
+ this.searchRunner = searchRunner;
18086
+ this.errorSearchCooldownMs = errorSearchCooldownMs;
17807
18087
  }
17808
18088
  setPlan(plan) {
17809
18089
  this.tracker = new PlanTracker(plan);
@@ -17814,7 +18094,13 @@ class ExecutionModule {
17814
18094
  const plan = this.store.loadActive();
17815
18095
  if (!plan)
17816
18096
  return false;
17817
- this.tracker = new PlanTracker(plan);
18097
+ const restored = new PlanTracker(plan);
18098
+ if (restored.isComplete()) {
18099
+ this.store.archivePlan(plan);
18100
+ this.tracker = null;
18101
+ return false;
18102
+ }
18103
+ this.tracker = restored;
17818
18104
  this.tracker.syncCurrentStep();
17819
18105
  this._auditSkipsRemaining = 1;
17820
18106
  return true;
@@ -17828,6 +18114,58 @@ class ExecutionModule {
17828
18114
  getStuckDetector() {
17829
18115
  return this.stuckDetector;
17830
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
+ }
17831
18169
  async runFinalAudit() {
17832
18170
  if (!this.tracker)
17833
18171
  return null;
@@ -17850,7 +18188,7 @@ class ExecutionModule {
17850
18188
  };
17851
18189
  }
17852
18190
  getSystemPromptBlock() {
17853
- if (!this.tracker)
18191
+ if (!this.tracker || this.tracker.isComplete())
17854
18192
  return null;
17855
18193
  return {
17856
18194
  content: this.tracker.toPromptBlock(),
@@ -17864,8 +18202,12 @@ class ExecutionModule {
17864
18202
  return null;
17865
18203
  const plan = this.tracker.getPlan();
17866
18204
  const done = plan.steps.filter((s) => s.status === "done").length;
18205
+ const total = plan.steps.length;
18206
+ if (this.tracker.isComplete()) {
18207
+ return `[${plan.id}] "${plan.title}" ${done}/${total} done — plan complete. You may create a new plan for new work (plan create), or reply with your final answer.`;
18208
+ }
17867
18209
  const current = this.tracker.getCurrentStepIndex() + 1;
17868
- return `[${plan.id}] "${plan.title}" ${done}/${plan.steps.length} done, current: step ${current} — continue it (plan show / re-plan), do NOT create a new plan`;
18210
+ return `[${plan.id}] "${plan.title}" ${done}/${total} done, current: step ${current} — continue it (plan show / re-plan), do NOT create a new plan`;
17869
18211
  }
17870
18212
  getToolDefinitions() {
17871
18213
  return createPlanToolDefinitions({
@@ -17889,7 +18231,8 @@ class ExecutionModule {
17889
18231
  forbiddenBashFailures: this.forbiddenBashFailures,
17890
18232
  state: this.state,
17891
18233
  checkPlanAlignment: (call) => this.checkPlanAlignment(call),
17892
- advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl)
18234
+ advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl),
18235
+ maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call)
17893
18236
  });
17894
18237
  }
17895
18238
  preserveActive() {
@@ -18030,6 +18373,12 @@ class ExecutionModule {
18030
18373
  content: `<system-summary>${nextMsg}</system-summary>`
18031
18374
  });
18032
18375
  }
18376
+ if (this.tracker?.isComplete()) {
18377
+ const completed = this.tracker.getPlan();
18378
+ this.store.archivePlan(completed);
18379
+ this.tracker = null;
18380
+ sessionLog?.plan("auto-archive", `Plan ${completed.id} complete — archived`);
18381
+ }
18033
18382
  }
18034
18383
  missingStepDeliverables(step) {
18035
18384
  const tokens = extractFileLikeTokens(stripUrls(step.description));
@@ -18080,6 +18429,7 @@ class ExecutionModule {
18080
18429
  return getMessageText(firstUser.content).trim();
18081
18430
  }
18082
18431
  }
18432
+ var ERROR_SEARCH_MIN_INTERVAL_MS = 30000;
18083
18433
  var init_module = __esm(() => {
18084
18434
  init_i18n();
18085
18435
  init_tracker();
@@ -18088,6 +18438,8 @@ var init_module = __esm(() => {
18088
18438
  init_auditor();
18089
18439
  init_plan_store();
18090
18440
  init_js_identifiers();
18441
+ init_web_search();
18442
+ init_session_isolation();
18091
18443
  init_plan_tool();
18092
18444
  init_execution_plugin();
18093
18445
  });
@@ -19183,7 +19535,7 @@ class LspClient {
19183
19535
  } catch {} finally {
19184
19536
  if (this.process) {
19185
19537
  try {
19186
- this.process.kill();
19538
+ killTree(this.process);
19187
19539
  } catch {}
19188
19540
  this.process = null;
19189
19541
  }
@@ -19228,6 +19580,7 @@ class LspClient {
19228
19580
  var DEFAULT_TIMEOUT = 15000;
19229
19581
  var init_client2 = __esm(() => {
19230
19582
  init_command();
19583
+ init_registry();
19231
19584
  });
19232
19585
 
19233
19586
  // src/modules/lsp/check-tool.ts
@@ -19283,7 +19636,7 @@ var init_check_tool = __esm(() => {
19283
19636
 
19284
19637
  // src/modules/lsp/module.ts
19285
19638
  import { existsSync as existsSync38 } from "fs";
19286
- import { resolve as resolve22 } from "path";
19639
+ import { relative as relative3, resolve as resolve22 } from "path";
19287
19640
 
19288
19641
  class LspModule {
19289
19642
  name = "lsp";
@@ -19418,6 +19771,7 @@ ${items}`;
19418
19771
  const errors = [];
19419
19772
  const warnings = [];
19420
19773
  const notes = [];
19774
+ const checkedFiles = [];
19421
19775
  let checked = 0;
19422
19776
  for (const file of files) {
19423
19777
  const serverConfig = getServerForFile(file, this.config);
@@ -19431,6 +19785,7 @@ ${items}`;
19431
19785
  const diags = await this.client.checkFile(file, ctx.baseDir, serverConfig, projectRoot);
19432
19786
  this.failuresByServer.set(key, 0);
19433
19787
  checked++;
19788
+ checkedFiles.push(relative3(ctx.baseDir, file).replace(/\\/g, "/"));
19434
19789
  for (const d of diags) {
19435
19790
  if (d.severity === 1)
19436
19791
  errors.push({ file, diag: d });
@@ -19453,6 +19808,10 @@ ${items}`;
19453
19808
  }
19454
19809
  }
19455
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
+ }
19456
19815
  if (errors.length > 0) {
19457
19816
  lines.push(`[LSP errors] (${errors.length}):`);
19458
19817
  lines.push(formatCheckDiagnostics(errors, ctx.baseDir));
@@ -19630,7 +19989,7 @@ var init_startup_check = __esm(() => {
19630
19989
 
19631
19990
  // src/modules/indexer/walker.ts
19632
19991
  import { readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync40, watch } from "fs";
19633
- 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";
19634
19993
 
19635
19994
  class Indexer {
19636
19995
  baseDir;
@@ -19669,7 +20028,7 @@ class Indexer {
19669
20028
  if (count >= this.MAX_FILES)
19670
20029
  return;
19671
20030
  const fullPath = join32(dir, entry);
19672
- const relPath = relative3(this.baseDir, fullPath);
20031
+ const relPath = relative4(this.baseDir, fullPath);
19673
20032
  const stat2 = statSync7(fullPath);
19674
20033
  if (stat2.isDirectory()) {
19675
20034
  if (!IGNORE_DIRS.has(entry)) {
@@ -20616,7 +20975,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20616
20975
  toolCtx.toolExecutor = toolExecutor;
20617
20976
  const hallucinationDetector = new HallucinationDetector(baseDir, llmProvider);
20618
20977
  const moduleRegistry = new ModuleRegistry;
20619
- const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
20978
+ const execModule = new ExecutionModule(baseDir, config.stuckThreshold, config.errorWebSearch?.threshold ?? 5);
20620
20979
  const activeMeta = sessionManager.getActiveMeta();
20621
20980
  if (activeMeta && activeMeta.messageCount > 0) {
20622
20981
  execModule.restorePlan();
@@ -20648,9 +21007,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20648
21007
  pluginManager.register(lspPlugin);
20649
21008
  }
20650
21009
  let startupCheckBlock = null;
20651
- if (!exitOnComplete && true) {
20652
- startupCheckBlock = await runStartupHealthCheck(config.lsp ?? DEFAULT_LSP_CONFIG, baseDir);
20653
- }
21010
+ const startupCheckPromise = !exitOnComplete ? runStartupHealthCheck(config.lsp ?? DEFAULT_LSP_CONFIG, baseDir) : Promise.resolve(null);
20654
21011
  const moduleTools = moduleRegistry.collectToolDefinitions();
20655
21012
  for (const tool of moduleTools) {
20656
21013
  toolRegistry.register(tool);
@@ -20733,8 +21090,6 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20733
21090
  ...moduleRegistry.collectPromptBlocks(["indexer"]),
20734
21091
  ...agentsMdBlocks
20735
21092
  ];
20736
- if (startupCheckBlock)
20737
- promptBlocks.push(startupCheckBlock);
20738
21093
  const agentDeps = {
20739
21094
  config,
20740
21095
  llmProvider,
@@ -20769,6 +21124,12 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
20769
21124
  return blocks;
20770
21125
  },
20771
21126
  finalAudit: () => execModule.runFinalAudit(),
21127
+ lazyPromptBlocks: async () => {
21128
+ if (startupCheckBlock)
21129
+ return [startupCheckBlock];
21130
+ startupCheckBlock = await startupCheckPromise ?? null;
21131
+ return startupCheckBlock ? [startupCheckBlock] : [];
21132
+ },
20772
21133
  sessionManager,
20773
21134
  memoryStore,
20774
21135
  exitOnComplete
@@ -30819,9 +31180,9 @@ import * as readline2 from "readline";
30819
31180
 
30820
31181
  // src/ui/line-math.ts
30821
31182
  init_string_width();
30822
- var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
31183
+ var ANSI_RE2 = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
30823
31184
  function stripAnsi2(s) {
30824
- return s.replace(ANSI_RE, "");
31185
+ return s.replace(ANSI_RE2, "");
30825
31186
  }
30826
31187
  function charLen(s) {
30827
31188
  return Array.from(s).length;
@@ -31875,6 +32236,7 @@ init_box();
31875
32236
  init_table();
31876
32237
  init_i18n();
31877
32238
  var GUTTER = " ";
32239
+ var BUSY_TOOLS = new Set(["lsp_check"]);
31878
32240
  function toolMarker(tool) {
31879
32241
  switch (tool) {
31880
32242
  case "write_file":
@@ -31994,6 +32356,9 @@ ${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}$
31994
32356
  this.out.write(`
31995
32357
  ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}
31996
32358
  `);
32359
+ if (BUSY_TOOLS.has(tool)) {
32360
+ this.spinner.start(t("ui.tool_running", { tool: friendlyTool(tool) }));
32361
+ }
31997
32362
  return;
31998
32363
  }
31999
32364
  this.spinner.start(`${pc2.dim("⚙")} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}${step}`);
@@ -32078,6 +32443,7 @@ ${pc2.dim(marker)} ${friendlyTool(tool)}${summary ? ` ${pc2.dim(summary)}` : ""}
32078
32443
 
32079
32444
  // src/cli/repl.ts
32080
32445
  init_box();
32446
+ init_string_width();
32081
32447
  init_i18n();
32082
32448
  init_repl_commands();
32083
32449
 
@@ -32086,12 +32452,15 @@ init_client2();
32086
32452
  init_config();
32087
32453
  init_check_tool();
32088
32454
  init_project_root();
32089
- var DEFAULT_PROBE_TIMEOUT_MS = 20000;
32455
+ var DEFAULT_PROBE_TIMEOUT_MS = 30000;
32456
+ var DEFAULT_PROBE_DEADLINE_MS = 60000;
32457
+ var DEFAULT_MAX_PARALLEL = 3;
32090
32458
  async function probeLspServers(config, baseDir, deps = {}) {
32091
32459
  if (!config.enabled)
32092
32460
  return { ok: [], failed: [], noneApplicable: true };
32093
- const client = deps.client ?? new LspClient;
32094
- const timeoutMs = deps.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
32461
+ const timeoutCap = deps.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
32462
+ const deadlineMs = deps.deadlineMs ?? DEFAULT_PROBE_DEADLINE_MS;
32463
+ const maxParallel = deps.maxParallel ?? DEFAULT_MAX_PARALLEL;
32095
32464
  const collect = deps.collectFiles ?? collectCheckFiles;
32096
32465
  const files = await collect(baseDir, config);
32097
32466
  const seen = new Set;
@@ -32108,26 +32477,37 @@ async function probeLspServers(config, baseDir, deps = {}) {
32108
32477
  }
32109
32478
  if (servers.length === 0)
32110
32479
  return { ok: [], failed: [], noneApplicable: true };
32111
- const results = await Promise.all(servers.map(async ({ language, config: server }) => {
32480
+ const ok = [];
32481
+ const failed = [];
32482
+ const deadline = Date.now() + deadlineMs;
32483
+ const probeOne = async (language, server) => {
32112
32484
  const projectRoot = findProjectRoot(baseDir, baseDir, server.workspaceMarkers ?? []);
32485
+ const serverTimeout = Math.min(server.timeout ?? timeoutCap, timeoutCap);
32113
32486
  const started = Date.now();
32487
+ const probeClient = deps.client ?? new LspClient;
32114
32488
  try {
32115
- await client.probe(server, projectRoot, timeoutMs);
32116
- return { language, ok: true, durationMs: Date.now() - started };
32489
+ await probeClient.probe(server, projectRoot, serverTimeout);
32490
+ ok.push({ language, ok: true, durationMs: Date.now() - started });
32117
32491
  } catch (e) {
32118
- return {
32492
+ failed.push({
32119
32493
  language,
32120
32494
  ok: false,
32121
32495
  error: e instanceof Error ? e.message : String(e),
32122
32496
  durationMs: Date.now() - started
32123
- };
32497
+ });
32124
32498
  }
32125
- }));
32126
- return {
32127
- ok: results.filter((r) => r.ok),
32128
- failed: results.filter((r) => !r.ok),
32129
- noneApplicable: false
32130
32499
  };
32500
+ for (let i = 0;i < servers.length; i += maxParallel) {
32501
+ if (Date.now() >= deadline) {
32502
+ for (const { language } of servers.slice(i)) {
32503
+ failed.push({ language, ok: false, error: "timeout", durationMs: 0 });
32504
+ }
32505
+ break;
32506
+ }
32507
+ const wave = servers.slice(i, i + maxParallel);
32508
+ await Promise.all(wave.map(({ language, config: server }) => probeOne(language, server)));
32509
+ }
32510
+ return { ok, failed, noneApplicable: false };
32131
32511
  }
32132
32512
 
32133
32513
  // src/cli/repl.ts
@@ -32706,10 +33086,6 @@ ${t("image.clipboard_empty")}`));
32706
33086
  const names = enabledServers.map(([name]) => name).join(", ");
32707
33087
  row(t("repl.mcp_label"), `${pc2.white(String(enabledServers.length))} ${pc2.dim(`(${names})`)}`);
32708
33088
  }
32709
- const lspSummary = await this.probeLspBanner();
32710
- if (lspSummary) {
32711
- row(t("repl.lsp_label"), lspSummary);
32712
- }
32713
33089
  const cwd = process.cwd();
32714
33090
  row(t("repl.work_dir"), pc2.dim(cwd));
32715
33091
  if (this.noAgentsMd) {
@@ -32735,14 +33111,39 @@ ${t("image.clipboard_empty")}`));
32735
33111
  row(t("repl.session_label"), `${pc2.cyan(meta.name)} ${pc2.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc2.dim(sessionPath)}`);
32736
33112
  }
32737
33113
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
32738
- for (const line of box(info, {
33114
+ const isTty2 = process.stdout.isTTY === true;
33115
+ const lspEnabled = isTty2 && (this.config.lsp ?? DEFAULT_LSP_CONFIG).enabled !== false;
33116
+ if (lspEnabled) {
33117
+ row(t("repl.lsp_label"), pc2.dim("…"));
33118
+ }
33119
+ const boxLines = box(info, {
32739
33120
  title: t("repl.title", { version: version3 }),
32740
33121
  width: headerWidth
32741
- })) {
33122
+ });
33123
+ for (const line of boxLines) {
32742
33124
  console.log(line);
32743
33125
  }
32744
33126
  console.log();
32745
33127
  this.rl.prompt();
33128
+ if (lspEnabled) {
33129
+ this.probeLspBanner().then((lspSummary) => {
33130
+ if (lspSummary)
33131
+ this.updateLspRow(boxLines.length, headerWidth, lspSummary);
33132
+ }).catch(() => {});
33133
+ }
33134
+ }
33135
+ updateLspRow(boxLineCount, headerWidth, summary) {
33136
+ if (this.agentRunning || !process.stdout.isTTY)
33137
+ return;
33138
+ if ((process.stdout.columns ?? 96) < headerWidth)
33139
+ return;
33140
+ const inner = headerWidth - 4;
33141
+ const rowText = `${pc2.yellow(t("repl.lsp_label"))} ${summary.replace(/\s+/g, " ").trim()}`;
33142
+ if (stringWidth(rowText) > inner)
33143
+ return;
33144
+ const padded = rowText + " ".repeat(Math.max(0, inner - stringWidth(rowText)));
33145
+ const line = pc2.dim("│") + " " + padded + " " + pc2.dim("│");
33146
+ process.stdout.write(`\x1B[2A\r\x1B[2K${line}\x1B[2B\r`);
32746
33147
  }
32747
33148
  async probeLspBanner() {
32748
33149
  const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;