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.
- package/dist/main.js +1362 -961
- 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: [
|
|
2096
|
-
|
|
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: [
|
|
2103
|
-
|
|
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
|
-
|
|
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/
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
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
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
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
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
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
|
|
12054
|
-
import { resolve as
|
|
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
|
|
12585
|
+
return existsSync23(fp);
|
|
12090
12586
|
if (!fp.includes("/") && !fp.includes("\\")) {
|
|
12091
12587
|
return this.bareNameExists(fp);
|
|
12092
12588
|
}
|
|
12093
|
-
return
|
|
12589
|
+
return existsSync23(resolve14(this.baseDir, fp));
|
|
12094
12590
|
}
|
|
12095
12591
|
bareNameExists(name) {
|
|
12096
|
-
if (
|
|
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 =
|
|
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 =
|
|
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
|
|
12308
|
-
import { existsSync as
|
|
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 =
|
|
12320
|
-
if (
|
|
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((
|
|
12422
|
-
execFile(command, args, options, (err) =>
|
|
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
|
|
13288
|
+
import { resolve as resolve15, normalize as normalize4 } from "path";
|
|
12793
13289
|
function isPathInScope2(baseDir, targetPath, scope) {
|
|
12794
|
-
const baseResolved =
|
|
12795
|
-
const targetResolved =
|
|
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(
|
|
12802
|
-
const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(
|
|
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
|
|
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(
|
|
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
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
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:
|
|
13135
|
-
output: t("
|
|
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
|
-
}
|
|
13146
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
14070
|
-
this.pendingResolve =
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
14337
|
-
import { join as
|
|
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 =
|
|
14349
|
-
if (!
|
|
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 =
|
|
14361
|
-
if (
|
|
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
|
|
14382
|
-
import { join as
|
|
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 =
|
|
14391
|
-
if (!
|
|
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 (!
|
|
14915
|
+
if (!existsSync27(this.memoryDir)) {
|
|
14400
14916
|
mkdirSync13(this.memoryDir, { recursive: true });
|
|
14401
14917
|
}
|
|
14402
14918
|
}
|
|
14403
14919
|
read(name) {
|
|
14404
|
-
const path =
|
|
14405
|
-
if (!
|
|
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 =
|
|
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
|
|
14937
|
+
return join18(this.memoryDir, "preferences.json");
|
|
14422
14938
|
}
|
|
14423
14939
|
getPreferences() {
|
|
14424
14940
|
const path = this.prefsPath();
|
|
14425
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
15169
|
+
import { existsSync as existsSync28 } from "fs";
|
|
14654
15170
|
function pickExistingPath(candidates, fallback = candidates[0]) {
|
|
14655
|
-
return candidates.find((p) =>
|
|
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
|
|
15182
|
+
import { dirname as dirname10, join as join21 } from "path";
|
|
14667
15183
|
import { fileURLToPath } from "url";
|
|
14668
15184
|
function bridgeScriptPath() {
|
|
14669
|
-
const dir =
|
|
15185
|
+
const dir = dirname10(fileURLToPath(import.meta.url));
|
|
14670
15186
|
const candidates = [
|
|
14671
|
-
|
|
14672
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
15742
|
+
import { join as join22 } from "path";
|
|
15227
15743
|
|
|
15228
15744
|
class CookieStore {
|
|
15229
15745
|
filePath;
|
|
15230
15746
|
constructor(cookieDir) {
|
|
15231
|
-
this.filePath =
|
|
15747
|
+
this.filePath = join22(cookieDir, "cookies.json");
|
|
15232
15748
|
}
|
|
15233
15749
|
async save(cookies) {
|
|
15234
|
-
await mkdir(
|
|
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
|
|
16105
|
+
import { join as join23 } from "path";
|
|
15590
16106
|
function getSession(ctx) {
|
|
15591
16107
|
if (!session) {
|
|
15592
|
-
const cookieDir =
|
|
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:
|
|
15740
|
-
const tmpPath =
|
|
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
|
|
15821
|
-
import { resolve as
|
|
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 =
|
|
15863
|
-
if (!
|
|
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
|
|
16120
|
-
import { join as
|
|
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 (!
|
|
16640
|
+
if (!existsSync30(dirPath))
|
|
16125
16641
|
return;
|
|
16126
|
-
const entries =
|
|
16642
|
+
const entries = readdirSync9(dirPath).sort();
|
|
16127
16643
|
for (const entry of entries) {
|
|
16128
|
-
const fullPath =
|
|
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}/${
|
|
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 =
|
|
16145
|
-
if (
|
|
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
|
|
16188
|
-
import { resolve as
|
|
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 =
|
|
16235
|
-
if (!
|
|
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 =
|
|
16292
|
-
if (!
|
|
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]: ${
|
|
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 =
|
|
16313
|
-
if (!
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
16725
|
-
|
|
17017
|
+
return lines.join(`
|
|
17018
|
+
`);
|
|
17019
|
+
}
|
|
16726
17020
|
}
|
|
16727
17021
|
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
17022
|
+
// src/modules/execution/tracker.ts
|
|
17023
|
+
class PlanTracker {
|
|
17024
|
+
plan;
|
|
17025
|
+
currentStepIndex = 0;
|
|
17026
|
+
constructor(plan) {
|
|
17027
|
+
this.plan = plan;
|
|
16732
17028
|
}
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
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
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
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
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}/${
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
31183
|
+
var ANSI_RE2 = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
30823
31184
|
function stripAnsi2(s) {
|
|
30824
|
-
return s.replace(
|
|
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 =
|
|
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
|
|
32094
|
-
const
|
|
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
|
|
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
|
|
32116
|
-
|
|
32489
|
+
await probeClient.probe(server, projectRoot, serverTimeout);
|
|
32490
|
+
ok.push({ language, ok: true, durationMs: Date.now() - started });
|
|
32117
32491
|
} catch (e) {
|
|
32118
|
-
|
|
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
|
-
|
|
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;
|