micro-models-agent 0.35.2 → 0.35.5
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 +1710 -1216
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -2096,47 +2096,55 @@ var init_config = __esm(() => {
|
|
|
2096
2096
|
command: "npx",
|
|
2097
2097
|
args: ["typescript-language-server", "--stdio"],
|
|
2098
2098
|
timeout: 20000,
|
|
2099
|
-
autoInstall: true
|
|
2099
|
+
autoInstall: true,
|
|
2100
|
+
workspaceMarkers: ["package.json", "tsconfig.json"]
|
|
2100
2101
|
},
|
|
2101
2102
|
javascript: {
|
|
2102
2103
|
command: "npx",
|
|
2103
2104
|
args: ["typescript-language-server", "--stdio"],
|
|
2104
2105
|
timeout: 15000,
|
|
2105
|
-
autoInstall: true
|
|
2106
|
+
autoInstall: true,
|
|
2107
|
+
workspaceMarkers: ["package.json", "tsconfig.json"]
|
|
2106
2108
|
},
|
|
2107
2109
|
python: {
|
|
2108
2110
|
command: "npx",
|
|
2109
2111
|
args: ["pyright", "--stdio"],
|
|
2110
2112
|
timeout: 15000,
|
|
2111
|
-
autoInstall: true
|
|
2113
|
+
autoInstall: true,
|
|
2114
|
+
workspaceMarkers: ["pyproject.toml", "setup.py", "requirements.txt"]
|
|
2112
2115
|
},
|
|
2113
2116
|
rust: {
|
|
2114
2117
|
command: "rust-analyzer",
|
|
2115
2118
|
timeout: 15000,
|
|
2116
|
-
autoInstall: false
|
|
2119
|
+
autoInstall: false,
|
|
2120
|
+
workspaceMarkers: ["Cargo.toml"]
|
|
2117
2121
|
},
|
|
2118
2122
|
go: {
|
|
2119
2123
|
command: "gopls",
|
|
2120
2124
|
timeout: 15000,
|
|
2121
|
-
autoInstall: false
|
|
2125
|
+
autoInstall: false,
|
|
2126
|
+
workspaceMarkers: ["go.mod"]
|
|
2122
2127
|
},
|
|
2123
2128
|
json: {
|
|
2124
2129
|
command: "npx",
|
|
2125
2130
|
args: ["vscode-json-languageserver", "--stdio"],
|
|
2126
2131
|
timeout: 1e4,
|
|
2127
|
-
autoInstall: true
|
|
2132
|
+
autoInstall: true,
|
|
2133
|
+
workspaceMarkers: ["package.json"]
|
|
2128
2134
|
},
|
|
2129
2135
|
html: {
|
|
2130
2136
|
command: "npx",
|
|
2131
2137
|
args: ["vscode-html-languageserver", "--stdio"],
|
|
2132
2138
|
timeout: 1e4,
|
|
2133
|
-
autoInstall: true
|
|
2139
|
+
autoInstall: true,
|
|
2140
|
+
workspaceMarkers: ["package.json"]
|
|
2134
2141
|
},
|
|
2135
2142
|
css: {
|
|
2136
2143
|
command: "npx",
|
|
2137
2144
|
args: ["vscode-css-languageserver", "--stdio"],
|
|
2138
2145
|
timeout: 1e4,
|
|
2139
|
-
autoInstall: true
|
|
2146
|
+
autoInstall: true,
|
|
2147
|
+
workspaceMarkers: ["package.json"]
|
|
2140
2148
|
}
|
|
2141
2149
|
}
|
|
2142
2150
|
};
|
|
@@ -2464,6 +2472,10 @@ Command: {command}`,
|
|
|
2464
2472
|
"plan.step_already_done": "Step {step} is already done — nothing to do. Plan progress:",
|
|
2465
2473
|
"plan.order_blocked": 'Cannot mark step {step} done: step {first} ("{desc}") is not finished yet. Complete earlier steps first, or mark step {first} status=skipped if it is not needed.',
|
|
2466
2474
|
"plan.deliverables_missing": "Cannot mark step {step} done: the files it names do not exist yet: {files}. Create these files first (or mark the step status=skipped if they are not actually needed).",
|
|
2475
|
+
"plan.kinds_mismatch": "kinds array length must match steps",
|
|
2476
|
+
"plan.kinds_invalid": 'Invalid step kind(s): {kinds}. Use "create" or "delete".',
|
|
2477
|
+
"plan.kinds_not_array": 'kinds must be an array of "create" or "delete"',
|
|
2478
|
+
"plan.deliverables_remain": "Cannot mark step {step} done: the files it names still exist: {files}. This step is kind=delete — delete these files first (or set status=skipped if they should stay).",
|
|
2467
2479
|
"plan.show_header": "Plan status:",
|
|
2468
2480
|
"plan.show_empty": "(plan has no steps)",
|
|
2469
2481
|
"plan.list_header": "Plans:",
|
|
@@ -2487,6 +2499,7 @@ Command: {command}`,
|
|
|
2487
2499
|
"verify.script_passed": "Script '{script}' passed",
|
|
2488
2500
|
"verify.script_failed": "Script '{script}' failed: {message}",
|
|
2489
2501
|
"verify.syntax_error": "Syntax error in: {path}",
|
|
2502
|
+
"lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
|
|
2490
2503
|
"cli.description": "Micro Models Agent — AI coding agent for small models",
|
|
2491
2504
|
"cli.init": "Run interactive setup wizard",
|
|
2492
2505
|
"cli.config_saved": "Configuration saved to ~/.mma/config.json",
|
|
@@ -2759,6 +2772,7 @@ Use this knowledge to answer the user's question.`,
|
|
|
2759
2772
|
"exec.audit_pass": "[✓] Task complete: {done}/{total} steps done, {files} files verified",
|
|
2760
2773
|
"exec.audit_pending": "[✗] Task incomplete: {done}/{total} steps done — remaining steps are not marked done",
|
|
2761
2774
|
"exec.audit_fail": "[✗] Task incomplete: {done}/{total} steps done, {files} files missing",
|
|
2775
|
+
"exec.audit_leftovers": "[✗] Task incomplete: {done}/{total} steps done, {files} files from delete-steps still exist",
|
|
2762
2776
|
"exec.audit_fail_tests": "[✗] Task incomplete: {done}/{total} steps done, tests FAILING: {failed} failed / {passed} passed — {detail}",
|
|
2763
2777
|
"exec.audit_fail_typecheck": "[✗] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
|
|
2764
2778
|
"exec.audit_incomplete": "[⚠ Final audit incomplete: {summary}. Task is NOT finished — continue working. Remaining steps: {steps}]",
|
|
@@ -2770,6 +2784,7 @@ Use this knowledge to answer the user's question.`,
|
|
|
2770
2784
|
[Hints]
|
|
2771
2785
|
{hints}`,
|
|
2772
2786
|
"exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
|
|
2787
|
+
"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.',
|
|
2773
2788
|
"hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
|
|
2774
2789
|
"hall.short_response": "Response too short or empty",
|
|
2775
2790
|
"hall.repetitive": "Response too repetitive ({pct}% overlap)",
|
|
@@ -2830,6 +2845,7 @@ Use this knowledge to answer the user's question.`,
|
|
|
2830
2845
|
"indexer.find_results": `Found {count} matching files:
|
|
2831
2846
|
{results}`,
|
|
2832
2847
|
"indexer.no_matches": 'No matching files for "{query}"',
|
|
2848
|
+
"indexer.duplicates": "Duplicate basenames (verify which file is the real source): {names}",
|
|
2833
2849
|
"indexer.stack_deps": "deps",
|
|
2834
2850
|
"indexer.stack_dev": "dev",
|
|
2835
2851
|
"indexer.stack_scripts": "scripts",
|
|
@@ -3054,6 +3070,10 @@ var init_ru = __esm(() => {
|
|
|
3054
3070
|
"plan.step_already_done": "Шаг {step} уже выполнен — ничего делать не нужно. Прогресс плана:",
|
|
3055
3071
|
"plan.order_blocked": "Нельзя отметить шаг {step} выполненным: шаг {first} («{desc}») ещё не завершён. Сначала завершите предыдущие шаги или отметьте шаг {first} status=skipped, если он не нужен.",
|
|
3056
3072
|
"plan.deliverables_missing": "Нельзя отметить шаг {step} выполненным: указанные в нём файлы ещё не существуют: {files}. Сначала создайте эти файлы (или отметьте шаг status=skipped, если они на самом деле не нужны).",
|
|
3073
|
+
"plan.kinds_mismatch": "Длина kinds должна совпадать с числом шагов",
|
|
3074
|
+
"plan.kinds_invalid": 'Недопустимый kind шага: {kinds}. Используй "create" или "delete".',
|
|
3075
|
+
"plan.kinds_not_array": 'kinds должен быть массивом значений "create" или "delete"',
|
|
3076
|
+
"plan.deliverables_remain": "Нельзя пометить шаг {step} как выполненный: указанные файлы всё ещё существуют: {files}. Шаг имеет kind=delete — сначала удали их (или поставь status=skipped, если они должны остаться).",
|
|
3057
3077
|
"plan.show_header": "Статус плана:",
|
|
3058
3078
|
"plan.show_empty": "(в плане нет шагов)",
|
|
3059
3079
|
"plan.list_header": "Планы:",
|
|
@@ -3077,6 +3097,7 @@ var init_ru = __esm(() => {
|
|
|
3077
3097
|
"verify.script_passed": "Скрипт '{script}' пройден",
|
|
3078
3098
|
"verify.script_failed": "Скрипт '{script}' не пройден: {message}",
|
|
3079
3099
|
"verify.syntax_error": "Синтаксическая ошибка в: {path}",
|
|
3100
|
+
"lsp.unavailable": "статические проверки недоступны (сервер не найден). Проверяйте через собственный build/test проекта.",
|
|
3080
3101
|
"cli.description": "Micro Models Agent — ИИ-агент для кодинга на малых моделях",
|
|
3081
3102
|
"cli.init": "Запустить мастер настройки",
|
|
3082
3103
|
"cli.config_saved": "Конфигурация сохранена в ~/.mma/config.json",
|
|
@@ -3349,6 +3370,7 @@ var init_ru = __esm(() => {
|
|
|
3349
3370
|
"exec.audit_pass": "[✓] Задача выполнена: {done}/{total} шагов, {files} файлов проверено",
|
|
3350
3371
|
"exec.audit_pending": "[✗] Задача не выполнена: {done}/{total} шагов — оставшиеся шаги не отмечены выполненными",
|
|
3351
3372
|
"exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
|
|
3373
|
+
"exec.audit_leftovers": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов из delete-шагов всё ещё существуют",
|
|
3352
3374
|
"exec.audit_fail_tests": "[✗] Задача не выполнена: {done}/{total} шагов, тесты ПАДАЮТ: {failed} failed / {passed} passed — {detail}",
|
|
3353
3375
|
"exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
|
|
3354
3376
|
"exec.audit_incomplete": "[⚠ Финальная проверка не пройдена: {summary}. Задача НЕ завершена — продолжайте работу. Оставшиеся шаги: {steps}]",
|
|
@@ -3360,6 +3382,7 @@ var init_ru = __esm(() => {
|
|
|
3360
3382
|
[Подсказки]
|
|
3361
3383
|
{hints}`,
|
|
3362
3384
|
"exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
|
|
3385
|
+
"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 для этого.',
|
|
3363
3386
|
"hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
|
|
3364
3387
|
"hall.short_response": "Слишком короткий или пустой ответ",
|
|
3365
3388
|
"hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
|
|
@@ -3420,6 +3443,7 @@ var init_ru = __esm(() => {
|
|
|
3420
3443
|
"indexer.find_results": `Найдено {count} совпадающих файлов:
|
|
3421
3444
|
{results}`,
|
|
3422
3445
|
"indexer.no_matches": 'Нет совпадающих файлов для "{query}"',
|
|
3446
|
+
"indexer.duplicates": "Дубликаты basename (проверьте, какой файл реально используется): {names}",
|
|
3423
3447
|
"indexer.stack_deps": "зависимости",
|
|
3424
3448
|
"indexer.stack_dev": "dev",
|
|
3425
3449
|
"indexer.stack_scripts": "скрипты",
|
|
@@ -4890,6 +4914,30 @@ var exports_openai_compat = {};
|
|
|
4890
4914
|
__export(exports_openai_compat, {
|
|
4891
4915
|
OpenAICompatProvider: () => OpenAICompatProvider
|
|
4892
4916
|
});
|
|
4917
|
+
function buildRequestBody(opts) {
|
|
4918
|
+
const body = {
|
|
4919
|
+
model: opts.model,
|
|
4920
|
+
messages: opts.messages,
|
|
4921
|
+
stream: opts.stream
|
|
4922
|
+
};
|
|
4923
|
+
if (opts.maxTokens !== undefined)
|
|
4924
|
+
body.max_tokens = opts.maxTokens;
|
|
4925
|
+
if (opts.reasoningEffort) {
|
|
4926
|
+
body.reasoning_effort = opts.reasoningEffort;
|
|
4927
|
+
}
|
|
4928
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
4929
|
+
body.tools = opts.tools.map((t2) => ({
|
|
4930
|
+
type: "function",
|
|
4931
|
+
function: {
|
|
4932
|
+
name: t2.name,
|
|
4933
|
+
description: t2.description,
|
|
4934
|
+
parameters: t2.parameters
|
|
4935
|
+
}
|
|
4936
|
+
}));
|
|
4937
|
+
body.tool_choice = "auto";
|
|
4938
|
+
}
|
|
4939
|
+
return body;
|
|
4940
|
+
}
|
|
4893
4941
|
|
|
4894
4942
|
class OpenAICompatProvider {
|
|
4895
4943
|
model;
|
|
@@ -4937,26 +4985,14 @@ class OpenAICompatProvider {
|
|
|
4937
4985
|
}
|
|
4938
4986
|
}
|
|
4939
4987
|
async* doStream(messages, tools, signal, options) {
|
|
4940
|
-
const body = {
|
|
4988
|
+
const body = buildRequestBody({
|
|
4941
4989
|
model: this.model,
|
|
4942
4990
|
messages,
|
|
4991
|
+
tools,
|
|
4943
4992
|
stream: true,
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
body.reasoning_effort = options.reasoningEffort;
|
|
4948
|
-
}
|
|
4949
|
-
if (tools && tools.length > 0) {
|
|
4950
|
-
body.tools = tools.map((t2) => ({
|
|
4951
|
-
type: "function",
|
|
4952
|
-
function: {
|
|
4953
|
-
name: t2.name,
|
|
4954
|
-
description: t2.description,
|
|
4955
|
-
parameters: t2.parameters
|
|
4956
|
-
}
|
|
4957
|
-
}));
|
|
4958
|
-
body.tool_choice = "auto";
|
|
4959
|
-
}
|
|
4993
|
+
maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
|
|
4994
|
+
reasoningEffort: options?.reasoningEffort
|
|
4995
|
+
});
|
|
4960
4996
|
const headers = {
|
|
4961
4997
|
"Content-Type": "application/json"
|
|
4962
4998
|
};
|
|
@@ -5082,26 +5118,14 @@ class OpenAICompatProvider {
|
|
|
5082
5118
|
}
|
|
5083
5119
|
}
|
|
5084
5120
|
async doNonStreaming(messages, tools, signal, options) {
|
|
5085
|
-
const body = {
|
|
5121
|
+
const body = buildRequestBody({
|
|
5086
5122
|
model: this.model,
|
|
5087
5123
|
messages,
|
|
5124
|
+
tools,
|
|
5088
5125
|
stream: false,
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
body.reasoning_effort = options.reasoningEffort;
|
|
5093
|
-
}
|
|
5094
|
-
if (tools && tools.length > 0) {
|
|
5095
|
-
body.tools = tools.map((t2) => ({
|
|
5096
|
-
type: "function",
|
|
5097
|
-
function: {
|
|
5098
|
-
name: t2.name,
|
|
5099
|
-
description: t2.description,
|
|
5100
|
-
parameters: t2.parameters
|
|
5101
|
-
}
|
|
5102
|
-
}));
|
|
5103
|
-
body.tool_choice = "auto";
|
|
5104
|
-
}
|
|
5126
|
+
maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
|
|
5127
|
+
reasoningEffort: options?.reasoningEffort
|
|
5128
|
+
});
|
|
5105
5129
|
const headers = {
|
|
5106
5130
|
"Content-Type": "application/json"
|
|
5107
5131
|
};
|
|
@@ -5351,7 +5375,8 @@ class ToolRegistry {
|
|
|
5351
5375
|
return tools.map((t2) => ({
|
|
5352
5376
|
name: t2.name,
|
|
5353
5377
|
description: t2.description,
|
|
5354
|
-
parameters: t2.parameters
|
|
5378
|
+
parameters: t2.parameters,
|
|
5379
|
+
boundedOutput: t2.boundedOutput
|
|
5355
5380
|
}));
|
|
5356
5381
|
}
|
|
5357
5382
|
}
|
|
@@ -6056,6 +6081,7 @@ var init_read_file = __esm(() => {
|
|
|
6056
6081
|
name: "read_file",
|
|
6057
6082
|
description: `Read a file from the filesystem. Reads up to ${DEFAULT_LIMIT} lines at a time by default; use offset to page through large files.`,
|
|
6058
6083
|
tags: ["file", "code"],
|
|
6084
|
+
boundedOutput: true,
|
|
6059
6085
|
parameters: {
|
|
6060
6086
|
type: "object",
|
|
6061
6087
|
properties: {
|
|
@@ -6688,6 +6714,7 @@ var init_list_dir = __esm(() => {
|
|
|
6688
6714
|
name: "list_dir",
|
|
6689
6715
|
description: `List files and directories in a given path. Shows up to ${MAX_PREVIEW_LINES} entries by default.`,
|
|
6690
6716
|
tags: ["file"],
|
|
6717
|
+
boundedOutput: true,
|
|
6691
6718
|
parameters: {
|
|
6692
6719
|
type: "object",
|
|
6693
6720
|
properties: {
|
|
@@ -6932,6 +6959,7 @@ var init_file_info = __esm(() => {
|
|
|
6932
6959
|
name: "file_info",
|
|
6933
6960
|
description: "Get metadata about a file or directory (size, creation date, modification date).",
|
|
6934
6961
|
tags: ["file"],
|
|
6962
|
+
boundedOutput: true,
|
|
6935
6963
|
parameters: {
|
|
6936
6964
|
type: "object",
|
|
6937
6965
|
properties: {
|
|
@@ -7290,7 +7318,7 @@ class ProcessRegistry {
|
|
|
7290
7318
|
entry.status = "killed";
|
|
7291
7319
|
}
|
|
7292
7320
|
entry.spawnError = err.message;
|
|
7293
|
-
|
|
7321
|
+
pushLine(`[process error] ${err.message}`);
|
|
7294
7322
|
resolveExit();
|
|
7295
7323
|
});
|
|
7296
7324
|
child.on("close", (code) => {
|
|
@@ -7689,19 +7717,19 @@ ${output2}`;
|
|
|
7689
7717
|
output2 = `(exit code ${code})`;
|
|
7690
7718
|
}
|
|
7691
7719
|
if (platform2() === "win32") {
|
|
7692
|
-
const
|
|
7693
|
-
if (
|
|
7720
|
+
const originalFirstWord = originalCommand.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
7721
|
+
if (originalFirstWord && originalFirstWord in UNIX_TO_WIN_HINTS) {
|
|
7694
7722
|
if (code === 0) {
|
|
7695
|
-
FAILING_FIRST_WORDS.delete(
|
|
7723
|
+
FAILING_FIRST_WORDS.delete(originalFirstWord);
|
|
7696
7724
|
} else {
|
|
7697
|
-
const failures = (FAILING_FIRST_WORDS.get(
|
|
7698
|
-
FAILING_FIRST_WORDS.set(
|
|
7725
|
+
const failures = (FAILING_FIRST_WORDS.get(originalFirstWord) || 0) + 1;
|
|
7726
|
+
FAILING_FIRST_WORDS.set(originalFirstWord, failures);
|
|
7699
7727
|
if (failures >= HARD_BLOCK_THRESHOLD) {
|
|
7700
|
-
output2 = `STOP using "${
|
|
7728
|
+
output2 = `STOP using "${originalFirstWord}" — it does not work in this cmd.exe shell and has failed ${failures} times in a row. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
|
|
7701
7729
|
} else {
|
|
7702
7730
|
output2 = `${output2}
|
|
7703
7731
|
|
|
7704
|
-
Hint: "${
|
|
7732
|
+
Hint: "${originalFirstWord}" may not work on Windows. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
|
|
7705
7733
|
}
|
|
7706
7734
|
}
|
|
7707
7735
|
}
|
|
@@ -7801,6 +7829,7 @@ var init_process_log = __esm(() => {
|
|
|
7801
7829
|
name: "process_log",
|
|
7802
7830
|
description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
|
|
7803
7831
|
tags: ["shell"],
|
|
7832
|
+
boundedOutput: true,
|
|
7804
7833
|
parameters: {
|
|
7805
7834
|
type: "object",
|
|
7806
7835
|
properties: {
|
|
@@ -9357,13 +9386,14 @@ class StuckDetector {
|
|
|
9357
9386
|
if (desc.includes("deploy") || desc.includes("publish")) {
|
|
9358
9387
|
hints.push("Verify credentials and network access before deploying.");
|
|
9359
9388
|
}
|
|
9360
|
-
|
|
9389
|
+
const primary = this.getPrimaryReason();
|
|
9390
|
+
if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
|
|
9361
9391
|
hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
|
|
9362
9392
|
}
|
|
9363
|
-
if (this.hasConsecutiveFailures()) {
|
|
9393
|
+
if (this.hasConsecutiveFailures() && primary !== "consecutive") {
|
|
9364
9394
|
hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
|
|
9365
9395
|
}
|
|
9366
|
-
if (this.hasReadOnlyLoop()) {
|
|
9396
|
+
if (this.hasReadOnlyLoop() && primary !== "read-only") {
|
|
9367
9397
|
hints.push("You have made many read-only tool calls (read_file/glob/grep/browser) without writing anything. Stop exploring — make the edit/write the task needs, or use the plan tool to decide next steps.");
|
|
9368
9398
|
}
|
|
9369
9399
|
return hints;
|
|
@@ -9489,35 +9519,50 @@ class StuckDetector {
|
|
|
9489
9519
|
return "";
|
|
9490
9520
|
}
|
|
9491
9521
|
getRecoveryMessage() {
|
|
9492
|
-
|
|
9522
|
+
const reason = this.getPrimaryReason();
|
|
9523
|
+
if (reason === "stuck") {
|
|
9493
9524
|
return t("exec.stuck_recovery", {
|
|
9494
9525
|
iterations: this.iterationsOnCurrentStep,
|
|
9495
9526
|
stepId: String(this.currentStepId ?? "?"),
|
|
9496
9527
|
description: this.currentStepDescription
|
|
9497
9528
|
});
|
|
9498
9529
|
}
|
|
9499
|
-
|
|
9500
|
-
|
|
9530
|
+
if (reason === "tool-errors") {
|
|
9531
|
+
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
9501
9532
|
return t("exec.tool_errors_recovery", {
|
|
9502
9533
|
tool: errorTool[0],
|
|
9503
9534
|
count: errorTool[1]
|
|
9504
9535
|
});
|
|
9505
9536
|
}
|
|
9506
|
-
if (
|
|
9537
|
+
if (reason === "consecutive") {
|
|
9507
9538
|
return t("exec.consecutive_failures_recovery", {
|
|
9508
9539
|
count: this.consecutiveFailures
|
|
9509
9540
|
});
|
|
9510
9541
|
}
|
|
9511
|
-
if (
|
|
9542
|
+
if (reason === "repetitive") {
|
|
9512
9543
|
return this.getRepetitiveToolMessage();
|
|
9513
9544
|
}
|
|
9514
|
-
if (
|
|
9545
|
+
if (reason === "read-only") {
|
|
9515
9546
|
return t("exec.read_only_loop_recovery", {
|
|
9516
9547
|
count: String(this.readOnlyStreak)
|
|
9517
9548
|
});
|
|
9518
9549
|
}
|
|
9519
9550
|
return "";
|
|
9520
9551
|
}
|
|
9552
|
+
getPrimaryReason() {
|
|
9553
|
+
if (this.isStuck())
|
|
9554
|
+
return "stuck";
|
|
9555
|
+
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
9556
|
+
if (errorTool)
|
|
9557
|
+
return "tool-errors";
|
|
9558
|
+
if (this.hasConsecutiveFailures())
|
|
9559
|
+
return "consecutive";
|
|
9560
|
+
if (this.hasRepetitiveToolCalls())
|
|
9561
|
+
return "repetitive";
|
|
9562
|
+
if (this.hasReadOnlyLoop())
|
|
9563
|
+
return "read-only";
|
|
9564
|
+
return null;
|
|
9565
|
+
}
|
|
9521
9566
|
reset() {
|
|
9522
9567
|
this.currentStepId = null;
|
|
9523
9568
|
this.iterationsOnCurrentStep = 0;
|
|
@@ -9941,9 +9986,31 @@ var init_moe_executor = __esm(() => {
|
|
|
9941
9986
|
];
|
|
9942
9987
|
});
|
|
9943
9988
|
|
|
9944
|
-
// src/modules/
|
|
9989
|
+
// src/modules/lsp/project-root.ts
|
|
9945
9990
|
import { existsSync as existsSync19 } from "fs";
|
|
9946
|
-
import {
|
|
9991
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
9992
|
+
function findProjectRoot(filePath, baseDir, markers) {
|
|
9993
|
+
if (!markers || markers.length === 0)
|
|
9994
|
+
return baseDir;
|
|
9995
|
+
let dir = dirname6(filePath);
|
|
9996
|
+
const root = baseDir.replace(/[\\/]+$/, "");
|
|
9997
|
+
while (true) {
|
|
9998
|
+
if (markers.some((m) => existsSync19(join10(dir, m)))) {
|
|
9999
|
+
return dir;
|
|
10000
|
+
}
|
|
10001
|
+
if (dir === root)
|
|
10002
|
+
return dir;
|
|
10003
|
+
const parent = dirname6(dir);
|
|
10004
|
+
if (parent === dir)
|
|
10005
|
+
return root;
|
|
10006
|
+
dir = parent;
|
|
10007
|
+
}
|
|
10008
|
+
}
|
|
10009
|
+
var init_project_root = () => {};
|
|
10010
|
+
|
|
10011
|
+
// src/modules/execution/verifier.ts
|
|
10012
|
+
import { existsSync as existsSync20 } from "fs";
|
|
10013
|
+
import { resolve as resolve11, extname as extname2, join as join11 } from "path";
|
|
9947
10014
|
import { spawn as spawn2 } from "child_process";
|
|
9948
10015
|
|
|
9949
10016
|
class StepVerifier {
|
|
@@ -9953,7 +10020,7 @@ class StepVerifier {
|
|
|
9953
10020
|
}
|
|
9954
10021
|
async checkFileExists(path) {
|
|
9955
10022
|
const resolved = resolve11(this.baseDir, path);
|
|
9956
|
-
const exists =
|
|
10023
|
+
const exists = existsSync20(resolved);
|
|
9957
10024
|
return {
|
|
9958
10025
|
passed: exists,
|
|
9959
10026
|
message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path })
|
|
@@ -9969,7 +10036,7 @@ class StepVerifier {
|
|
|
9969
10036
|
}
|
|
9970
10037
|
async runTypeCheck() {
|
|
9971
10038
|
const tsconfigPath = resolve11(this.baseDir, "tsconfig.json");
|
|
9972
|
-
if (!
|
|
10039
|
+
if (!existsSync20(tsconfigPath)) {
|
|
9973
10040
|
return { passed: true, message: "No tsconfig.json found — skipping type check" };
|
|
9974
10041
|
}
|
|
9975
10042
|
try {
|
|
@@ -9980,9 +10047,26 @@ class StepVerifier {
|
|
|
9980
10047
|
return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
|
|
9981
10048
|
}
|
|
9982
10049
|
}
|
|
10050
|
+
async runTypeCheckForFile(filePath) {
|
|
10051
|
+
const projectRoot = findProjectRoot(filePath, this.baseDir, [
|
|
10052
|
+
"tsconfig.json",
|
|
10053
|
+
"package.json"
|
|
10054
|
+
]);
|
|
10055
|
+
const tsconfigPath = join11(projectRoot, "tsconfig.json");
|
|
10056
|
+
if (!existsSync20(tsconfigPath)) {
|
|
10057
|
+
return { passed: true, message: "No tsconfig.json found — skipping type check" };
|
|
10058
|
+
}
|
|
10059
|
+
try {
|
|
10060
|
+
await this.runAsync("npx tsc --noEmit --skipLibCheck", projectRoot, 60000);
|
|
10061
|
+
return { passed: true, message: "TypeScript type check passed" };
|
|
10062
|
+
} catch (e) {
|
|
10063
|
+
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
10064
|
+
return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
|
|
10065
|
+
}
|
|
10066
|
+
}
|
|
9983
10067
|
async runTests() {
|
|
9984
10068
|
const pkgPath = resolve11(this.baseDir, "package.json");
|
|
9985
|
-
if (!
|
|
10069
|
+
if (!existsSync20(pkgPath)) {
|
|
9986
10070
|
return { passed: true, message: "No package.json found — skipping tests" };
|
|
9987
10071
|
}
|
|
9988
10072
|
try {
|
|
@@ -10115,6 +10199,7 @@ class StepVerifier {
|
|
|
10115
10199
|
}
|
|
10116
10200
|
var init_verifier = __esm(() => {
|
|
10117
10201
|
init_i18n();
|
|
10202
|
+
init_project_root();
|
|
10118
10203
|
});
|
|
10119
10204
|
|
|
10120
10205
|
// src/core/agent-moe.ts
|
|
@@ -10231,132 +10316,7 @@ var init_agent_moe = __esm(() => {
|
|
|
10231
10316
|
init_verifier();
|
|
10232
10317
|
});
|
|
10233
10318
|
|
|
10234
|
-
// src/modules/memory/search.ts
|
|
10235
|
-
import { readFileSync as readFileSync10, existsSync as existsSync20 } from "fs";
|
|
10236
|
-
import { join as join10 } from "path";
|
|
10237
|
-
|
|
10238
|
-
class MemorySearch {
|
|
10239
|
-
memoryDir;
|
|
10240
|
-
constructor(memoryDir) {
|
|
10241
|
-
this.memoryDir = memoryDir;
|
|
10242
|
-
}
|
|
10243
|
-
query(query) {
|
|
10244
|
-
const results = [];
|
|
10245
|
-
const lowerQuery = query.toLowerCase();
|
|
10246
|
-
for (const name of MEMORY_FILES) {
|
|
10247
|
-
const path = join10(this.memoryDir, `${name}.md`);
|
|
10248
|
-
if (!existsSync20(path))
|
|
10249
|
-
continue;
|
|
10250
|
-
const content = readFileSync10(path, "utf-8");
|
|
10251
|
-
const lines = content.split(`
|
|
10252
|
-
`);
|
|
10253
|
-
for (const line of lines) {
|
|
10254
|
-
if (line.toLowerCase().includes(lowerQuery)) {
|
|
10255
|
-
results.push({ file: name, match: line.trim() });
|
|
10256
|
-
}
|
|
10257
|
-
}
|
|
10258
|
-
}
|
|
10259
|
-
const prefsPath = join10(this.memoryDir, "preferences.json");
|
|
10260
|
-
if (existsSync20(prefsPath)) {
|
|
10261
|
-
try {
|
|
10262
|
-
const prefs = JSON.parse(readFileSync10(prefsPath, "utf-8"));
|
|
10263
|
-
for (const [key, value] of Object.entries(prefs)) {
|
|
10264
|
-
const searchStr = `${key}=${value}`;
|
|
10265
|
-
if (searchStr.toLowerCase().includes(lowerQuery)) {
|
|
10266
|
-
results.push({ file: "preferences", match: `${key} = ${value}` });
|
|
10267
|
-
}
|
|
10268
|
-
}
|
|
10269
|
-
} catch {}
|
|
10270
|
-
}
|
|
10271
|
-
return results;
|
|
10272
|
-
}
|
|
10273
|
-
}
|
|
10274
|
-
var MEMORY_FILES;
|
|
10275
|
-
var init_search = __esm(() => {
|
|
10276
|
-
MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
|
|
10277
|
-
});
|
|
10278
|
-
|
|
10279
|
-
// src/modules/memory/store.ts
|
|
10280
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, appendFileSync as appendFileSync5, existsSync as existsSync21, mkdirSync as mkdirSync12 } from "fs";
|
|
10281
|
-
import { join as join11 } from "path";
|
|
10282
|
-
|
|
10283
|
-
class MemoryStore {
|
|
10284
|
-
memoryDir;
|
|
10285
|
-
constructor(memoryDir) {
|
|
10286
|
-
this.memoryDir = memoryDir;
|
|
10287
|
-
this.ensureDir();
|
|
10288
|
-
for (const name of MEMORY_FILES2) {
|
|
10289
|
-
const path = join11(this.memoryDir, `${name}.md`);
|
|
10290
|
-
if (!existsSync21(path)) {
|
|
10291
|
-
writeFileSync8(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
|
|
10292
|
-
|
|
10293
|
-
`, "utf-8");
|
|
10294
|
-
}
|
|
10295
|
-
}
|
|
10296
|
-
}
|
|
10297
|
-
ensureDir() {
|
|
10298
|
-
if (!existsSync21(this.memoryDir)) {
|
|
10299
|
-
mkdirSync12(this.memoryDir, { recursive: true });
|
|
10300
|
-
}
|
|
10301
|
-
}
|
|
10302
|
-
read(name) {
|
|
10303
|
-
const path = join11(this.memoryDir, `${name}.md`);
|
|
10304
|
-
if (!existsSync21(path))
|
|
10305
|
-
return "";
|
|
10306
|
-
return readFileSync11(path, "utf-8");
|
|
10307
|
-
}
|
|
10308
|
-
append(name, entry) {
|
|
10309
|
-
const path = join11(this.memoryDir, `${name}.md`);
|
|
10310
|
-
const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
10311
|
-
const formatted = `- **${timestamp}** — ${entry}
|
|
10312
|
-
`;
|
|
10313
|
-
appendFileSync5(path, formatted, "utf-8");
|
|
10314
|
-
}
|
|
10315
|
-
search(query) {
|
|
10316
|
-
const searchModule = new MemorySearch(this.memoryDir);
|
|
10317
|
-
return searchModule.query(query);
|
|
10318
|
-
}
|
|
10319
|
-
prefsPath() {
|
|
10320
|
-
return join11(this.memoryDir, "preferences.json");
|
|
10321
|
-
}
|
|
10322
|
-
getPreferences() {
|
|
10323
|
-
const path = this.prefsPath();
|
|
10324
|
-
if (!existsSync21(path))
|
|
10325
|
-
return {};
|
|
10326
|
-
try {
|
|
10327
|
-
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
10328
|
-
} catch {
|
|
10329
|
-
return {};
|
|
10330
|
-
}
|
|
10331
|
-
}
|
|
10332
|
-
setPreference(key, value) {
|
|
10333
|
-
const prefs = this.getPreferences();
|
|
10334
|
-
prefs[key] = value;
|
|
10335
|
-
writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
|
|
10336
|
-
}
|
|
10337
|
-
deletePreference(key) {
|
|
10338
|
-
const prefs = this.getPreferences();
|
|
10339
|
-
if (!(key in prefs))
|
|
10340
|
-
return false;
|
|
10341
|
-
delete prefs[key];
|
|
10342
|
-
writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
|
|
10343
|
-
return true;
|
|
10344
|
-
}
|
|
10345
|
-
appendRule(category, pattern, cause, solution) {
|
|
10346
|
-
const entry = `**pattern:** ${pattern}
|
|
10347
|
-
**cause:** ${cause}
|
|
10348
|
-
**solution:** ${solution}`;
|
|
10349
|
-
this.append(category, entry);
|
|
10350
|
-
}
|
|
10351
|
-
}
|
|
10352
|
-
var MEMORY_FILES2;
|
|
10353
|
-
var init_store2 = __esm(() => {
|
|
10354
|
-
init_search();
|
|
10355
|
-
MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
|
|
10356
|
-
});
|
|
10357
|
-
|
|
10358
10319
|
// src/core/agent.ts
|
|
10359
|
-
import { join as join12 } from "path";
|
|
10360
10320
|
function isToolCallJson(text) {
|
|
10361
10321
|
const trimmed = text.trim();
|
|
10362
10322
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
@@ -10369,6 +10329,12 @@ function isToolCallJson(text) {
|
|
|
10369
10329
|
}
|
|
10370
10330
|
return false;
|
|
10371
10331
|
}
|
|
10332
|
+
function toolOutputCharLimit(remainingBudget, historyBudget, bounded) {
|
|
10333
|
+
if (bounded)
|
|
10334
|
+
return Number.MAX_SAFE_INTEGER;
|
|
10335
|
+
const maxCharsByRatio = Math.floor(historyBudget * TOOL_RESULT_MAX_TOKENS_RATIO * 2);
|
|
10336
|
+
return Math.min(remainingBudget, maxCharsByRatio, TOOL_RESULT_ABSOLUTE_MAX_CHARS);
|
|
10337
|
+
}
|
|
10372
10338
|
|
|
10373
10339
|
class Agent {
|
|
10374
10340
|
deps;
|
|
@@ -10432,11 +10398,9 @@ class Agent {
|
|
|
10432
10398
|
this.deps.contextManager.updateSystemPrompt?.(prompt);
|
|
10433
10399
|
}
|
|
10434
10400
|
}
|
|
10435
|
-
truncateToolOutput(output, budget, currentTokens) {
|
|
10436
|
-
const remainingBudget = budget.history - currentTokens;
|
|
10437
|
-
const
|
|
10438
|
-
const maxCharsByRatio = Math.floor(budget.history * TOOL_RESULT_MAX_TOKENS_RATIO * 2);
|
|
10439
|
-
const maxChars = Math.min(maxCharsByBudget, maxCharsByRatio, TOOL_RESULT_ABSOLUTE_MAX_CHARS);
|
|
10401
|
+
truncateToolOutput(output, budget, currentTokens, bounded) {
|
|
10402
|
+
const remainingBudget = Math.max(0, budget.history - currentTokens);
|
|
10403
|
+
const maxChars = toolOutputCharLimit(remainingBudget, budget.history, bounded);
|
|
10440
10404
|
if (output.length <= maxChars)
|
|
10441
10405
|
return output;
|
|
10442
10406
|
const truncated = output.slice(0, maxChars);
|
|
@@ -10546,6 +10510,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
10546
10510
|
let repeatedToolCount = 0;
|
|
10547
10511
|
const MAX_REPEATED_TOOL_CALLS = 2;
|
|
10548
10512
|
const allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
|
|
10513
|
+
const boundedToolNames = new Set(allToolsForBudget.filter((t2) => t2.boundedOutput).map((t2) => t2.name));
|
|
10549
10514
|
const toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
|
|
10550
10515
|
contextManager.setToolTokens(toolTokenEstimate);
|
|
10551
10516
|
while (iteration < config.maxToolIterations && !this.shutdownRequested) {
|
|
@@ -10748,7 +10713,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
10748
10713
|
}
|
|
10749
10714
|
const currentTokens2 = contextManager.getEstimatedTokens();
|
|
10750
10715
|
const budget3 = contextManager.getBudget();
|
|
10751
|
-
const truncatedOutput = this.truncateToolOutput(result.output, budget3, currentTokens2);
|
|
10716
|
+
const truncatedOutput = this.truncateToolOutput(result.output, budget3, currentTokens2, boundedToolNames.has(call.name));
|
|
10752
10717
|
contextManager.addMessage({
|
|
10753
10718
|
role: "tool",
|
|
10754
10719
|
content: truncatedOutput,
|
|
@@ -10782,14 +10747,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
10782
10747
|
toolFailureCounts.set(key, prev);
|
|
10783
10748
|
if (prev.count >= MIN_REPEATED_TOOL_FAILURES && !memoryRuleRecorded.has(key)) {
|
|
10784
10749
|
memoryRuleRecorded.add(key);
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
const memStore = new MemoryStore(memDir);
|
|
10790
|
-
memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.error})`, `Repeated ${key} failures suggest a systemic problem (config, environment, or a broken tool), not a one-off`, "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool");
|
|
10791
|
-
logger.warn(`Recorded repeated ${key} failures to memory (${prev.count}x)`);
|
|
10792
|
-
}
|
|
10750
|
+
const memStore = this.deps.memoryStore;
|
|
10751
|
+
if (memStore) {
|
|
10752
|
+
memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.error})`, `Repeated ${key} failures suggest a systemic problem (config, environment, or a broken tool), not a one-off`, "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool");
|
|
10753
|
+
logger.warn(`Recorded repeated ${key} failures to memory (${prev.count}x)`);
|
|
10793
10754
|
}
|
|
10794
10755
|
}
|
|
10795
10756
|
}
|
|
@@ -10811,13 +10772,9 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
10811
10772
|
content: `<system-summary>${recoveryMsg}
|
|
10812
10773
|
${taskReminder}</system-summary>`
|
|
10813
10774
|
});
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
const memDir = join12(baseDir, ".mma", "memory");
|
|
10818
|
-
const memStore = new MemoryStore(memDir);
|
|
10819
|
-
memStore.appendRule("errors", `${consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
|
|
10820
|
-
}
|
|
10775
|
+
const memStore = this.deps.memoryStore;
|
|
10776
|
+
if (memStore) {
|
|
10777
|
+
memStore.appendRule("errors", `${consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
|
|
10821
10778
|
}
|
|
10822
10779
|
}
|
|
10823
10780
|
contextManager.addMessage({
|
|
@@ -11077,10 +11034,108 @@ var init_agent = __esm(() => {
|
|
|
11077
11034
|
init_processes();
|
|
11078
11035
|
init_session_logger();
|
|
11079
11036
|
init_agent_moe();
|
|
11080
|
-
init_store2();
|
|
11081
11037
|
init_verifier();
|
|
11082
11038
|
});
|
|
11083
11039
|
|
|
11040
|
+
// src/modules/context/fact-extractor.ts
|
|
11041
|
+
class FactExtractor {
|
|
11042
|
+
fileFacts = [];
|
|
11043
|
+
deletedFacts = [];
|
|
11044
|
+
decisionFacts = [];
|
|
11045
|
+
errorFacts = [];
|
|
11046
|
+
static MAX_FACTS = 20;
|
|
11047
|
+
get errorCount() {
|
|
11048
|
+
return this.errorFacts.length;
|
|
11049
|
+
}
|
|
11050
|
+
extract(turns) {
|
|
11051
|
+
const DRIVE = "(?:[a-zA-Z]:[\\\\/])?";
|
|
11052
|
+
const createdPatterns = [
|
|
11053
|
+
new RegExp(`(?:Created|Updated|Written|Moved|Replaced in) ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
|
|
11054
|
+
new RegExp(`Файл (?:создан|обновлён|записан|перемещён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
|
|
11055
|
+
new RegExp(`file (?:created|updated|written|moved):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
|
|
11056
|
+
];
|
|
11057
|
+
const deletedPatterns = [
|
|
11058
|
+
new RegExp(`Deleted ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
|
|
11059
|
+
new RegExp(`(?:Удалён|Файл удалён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
|
|
11060
|
+
];
|
|
11061
|
+
const newFiles = [];
|
|
11062
|
+
const newDeleted = [];
|
|
11063
|
+
const newDecisions = [];
|
|
11064
|
+
const newErrors = [];
|
|
11065
|
+
const lastEvent = new Map;
|
|
11066
|
+
for (const msg of turns) {
|
|
11067
|
+
const content = getMessageText(msg.content);
|
|
11068
|
+
if (msg.role === "user" && (content.startsWith("<system-summary>") || content.includes("[Compressed:"))) {
|
|
11069
|
+
continue;
|
|
11070
|
+
}
|
|
11071
|
+
if (msg.role === "tool") {
|
|
11072
|
+
for (const pattern of createdPatterns) {
|
|
11073
|
+
for (const match of content.matchAll(pattern)) {
|
|
11074
|
+
newFiles.push(match[1]);
|
|
11075
|
+
lastEvent.set(match[1], "file");
|
|
11076
|
+
}
|
|
11077
|
+
}
|
|
11078
|
+
for (const pattern of deletedPatterns) {
|
|
11079
|
+
for (const match of content.matchAll(pattern)) {
|
|
11080
|
+
newDeleted.push(match[1]);
|
|
11081
|
+
lastEvent.set(match[1], "deleted");
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
if (content.includes("Plan:") && content.includes("[")) {
|
|
11085
|
+
const planLine = content.split(`
|
|
11086
|
+
`).find((line) => line.includes("Plan:"));
|
|
11087
|
+
if (planLine)
|
|
11088
|
+
newDecisions.push(planLine.trim());
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
if (msg.role === "assistant") {
|
|
11092
|
+
if (content.includes("decided:") || content.includes("decision:")) {
|
|
11093
|
+
const line = content.split(`
|
|
11094
|
+
`).find((l) => l.includes("decided:") || l.includes("decision:"));
|
|
11095
|
+
if (line)
|
|
11096
|
+
newDecisions.push(line.trim().slice(0, 200));
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
11099
|
+
if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
|
|
11100
|
+
const line = content.split(`
|
|
11101
|
+
`).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
|
|
11102
|
+
if (line)
|
|
11103
|
+
newErrors.push(line.trim().slice(0, 250));
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
const dedup = (arr) => [...new Set(arr)];
|
|
11107
|
+
const finalFiles = dedup(newFiles.filter((p) => lastEvent.get(p) === "file"));
|
|
11108
|
+
const finalDeleted = dedup(newDeleted.filter((p) => lastEvent.get(p) === "deleted"));
|
|
11109
|
+
this.fileFacts = dedup([...this.fileFacts, ...finalFiles].filter((p) => !finalDeleted.includes(p))).slice(-FactExtractor.MAX_FACTS);
|
|
11110
|
+
this.deletedFacts = dedup([...this.deletedFacts, ...finalDeleted].filter((p) => !finalFiles.includes(p))).slice(-FactExtractor.MAX_FACTS);
|
|
11111
|
+
this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-FactExtractor.MAX_FACTS);
|
|
11112
|
+
this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-FactExtractor.MAX_FACTS);
|
|
11113
|
+
}
|
|
11114
|
+
filesLine() {
|
|
11115
|
+
const deletedSet = new Set(this.deletedFacts);
|
|
11116
|
+
const liveFiles = this.fileFacts.filter((p) => !deletedSet.has(p));
|
|
11117
|
+
if (liveFiles.length === 0)
|
|
11118
|
+
return null;
|
|
11119
|
+
return `[Files: ${liveFiles.slice(-15).join("; ")}]`;
|
|
11120
|
+
}
|
|
11121
|
+
deletedLine() {
|
|
11122
|
+
if (this.deletedFacts.length === 0)
|
|
11123
|
+
return null;
|
|
11124
|
+
return `[Deleted: ${this.deletedFacts.slice(-10).join("; ")}]`;
|
|
11125
|
+
}
|
|
11126
|
+
decisionsLine() {
|
|
11127
|
+
if (this.decisionFacts.length === 0)
|
|
11128
|
+
return null;
|
|
11129
|
+
return `[Decisions: ${this.decisionFacts.slice(-5).join("; ")}]`;
|
|
11130
|
+
}
|
|
11131
|
+
errorsLine() {
|
|
11132
|
+
if (this.errorFacts.length === 0)
|
|
11133
|
+
return null;
|
|
11134
|
+
return `[Errors: ${this.errorFacts.slice(-3).join("; ")}]`;
|
|
11135
|
+
}
|
|
11136
|
+
}
|
|
11137
|
+
var init_fact_extractor = () => {};
|
|
11138
|
+
|
|
11084
11139
|
// src/modules/context/manager.ts
|
|
11085
11140
|
function summarizeArgs(args) {
|
|
11086
11141
|
if (!args)
|
|
@@ -11099,6 +11154,18 @@ function truncate(s, max) {
|
|
|
11099
11154
|
return s;
|
|
11100
11155
|
return s.slice(0, max - 3) + "...";
|
|
11101
11156
|
}
|
|
11157
|
+
function looksLikeErrorPaste(text) {
|
|
11158
|
+
if (/(?:^|\s)ERROR|Error:|error TS\d+|Transform failed|\[plugin:|SyntaxError|Cannot find|Uncaught|exception/i.test(text)) {
|
|
11159
|
+
return true;
|
|
11160
|
+
}
|
|
11161
|
+
if (/(?:^|\n)\s*\d+\s*\|/.test(text))
|
|
11162
|
+
return true;
|
|
11163
|
+
if (/[\w./\\-]+\.[a-z]{1,6}:\d+:\d+/.test(text))
|
|
11164
|
+
return true;
|
|
11165
|
+
if (/(?:^|\n)\s*at\s+\S+/.test(text))
|
|
11166
|
+
return true;
|
|
11167
|
+
return false;
|
|
11168
|
+
}
|
|
11102
11169
|
function extractTriedAndFailed(messages) {
|
|
11103
11170
|
const failures = new Map;
|
|
11104
11171
|
for (const msg of messages) {
|
|
@@ -11130,11 +11197,10 @@ class ContextManager {
|
|
|
11130
11197
|
peakTokens = 0;
|
|
11131
11198
|
budget;
|
|
11132
11199
|
compactionThreshold;
|
|
11133
|
-
|
|
11134
|
-
decisionFacts = [];
|
|
11135
|
-
errorFacts = [];
|
|
11200
|
+
facts = new FactExtractor;
|
|
11136
11201
|
lastUserTask = "";
|
|
11137
|
-
|
|
11202
|
+
sessionMission = "";
|
|
11203
|
+
lastUserFeedback = "";
|
|
11138
11204
|
tokenCounter;
|
|
11139
11205
|
pendingImageParts = [];
|
|
11140
11206
|
toolTokens = 0;
|
|
@@ -11173,6 +11239,7 @@ class ContextManager {
|
|
|
11173
11239
|
resetUserTurn() {
|
|
11174
11240
|
this.iterationsSinceCompaction = 0;
|
|
11175
11241
|
this.lastUserTask = "";
|
|
11242
|
+
this.lastUserFeedback = "";
|
|
11176
11243
|
}
|
|
11177
11244
|
getQuality() {
|
|
11178
11245
|
const usedTokens = this.getEstimatedTokens();
|
|
@@ -11180,16 +11247,22 @@ class ContextManager {
|
|
|
11180
11247
|
const COMPACTION_PENALTY_FLOOR = 0.5;
|
|
11181
11248
|
const compactionLoss = Math.max(COMPACTION_PENALTY_FLOOR, 1 - this.compactionCount * 0.15);
|
|
11182
11249
|
const msgCount = this.messages.length || 1;
|
|
11183
|
-
const errorDensity = Math.max(0, 1 - Math.min(1, this.
|
|
11250
|
+
const errorDensity = Math.max(0, 1 - Math.min(1, this.facts.errorCount / msgCount));
|
|
11184
11251
|
const freshness = Math.max(0, 1 - this.iterationsSinceCompaction / COMPACTION_INTERVAL);
|
|
11185
11252
|
const score = tokenLoad * 0.4 + compactionLoss * 0.25 + errorDensity * 0.2 + freshness * 0.15;
|
|
11186
11253
|
return Math.round(Math.min(100, Math.max(0, score * 100)));
|
|
11187
11254
|
}
|
|
11188
11255
|
addMessage(msg) {
|
|
11189
|
-
if (msg.role === "user"
|
|
11256
|
+
if (msg.role === "user") {
|
|
11190
11257
|
const text = typeof msg.content === "string" ? msg.content : getMessageText(msg.content);
|
|
11191
11258
|
if (text.trim() && !text.startsWith("<system-summary>") && !text.includes("[Compressed:")) {
|
|
11192
|
-
this.lastUserTask
|
|
11259
|
+
if (looksLikeErrorPaste(text) && (this.lastUserTask || this.sessionMission)) {
|
|
11260
|
+
this.lastUserFeedback = text.trim().slice(0, 400);
|
|
11261
|
+
} else if (!this.lastUserTask && !this.lastUserFeedback) {
|
|
11262
|
+
this.lastUserTask = text.trim();
|
|
11263
|
+
if (!this.sessionMission)
|
|
11264
|
+
this.sessionMission = text.trim();
|
|
11265
|
+
}
|
|
11193
11266
|
}
|
|
11194
11267
|
}
|
|
11195
11268
|
if (msg.role === "user" && this.pendingImageParts.length > 0) {
|
|
@@ -11278,22 +11351,27 @@ class ContextManager {
|
|
|
11278
11351
|
const cutoff = this.messages.length - KEEP_LAST_N * 2;
|
|
11279
11352
|
const oldTurns = this.messages.slice(0, cutoff);
|
|
11280
11353
|
const recentTurns = this.messages.slice(cutoff);
|
|
11281
|
-
this.
|
|
11354
|
+
this.facts.extract(oldTurns);
|
|
11282
11355
|
const parts = [];
|
|
11283
11356
|
parts.push(`[Compressed: ${oldTurns.length} old turns removed]`);
|
|
11284
|
-
|
|
11285
|
-
|
|
11357
|
+
const task = (this.lastUserTask || this.sessionMission).slice(0, 400);
|
|
11358
|
+
if (task)
|
|
11286
11359
|
parts.push(`[Task: ${task}]`);
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
if (
|
|
11292
|
-
parts.push(
|
|
11293
|
-
|
|
11294
|
-
if (
|
|
11295
|
-
parts.push(
|
|
11296
|
-
|
|
11360
|
+
if (this.lastUserFeedback && !task.startsWith(this.lastUserFeedback)) {
|
|
11361
|
+
parts.push(`[User feedback: ${this.lastUserFeedback}]`);
|
|
11362
|
+
}
|
|
11363
|
+
const filesLine = this.facts.filesLine();
|
|
11364
|
+
if (filesLine)
|
|
11365
|
+
parts.push(filesLine);
|
|
11366
|
+
const deletedLine = this.facts.deletedLine();
|
|
11367
|
+
if (deletedLine)
|
|
11368
|
+
parts.push(deletedLine);
|
|
11369
|
+
const decisionsLine = this.facts.decisionsLine();
|
|
11370
|
+
if (decisionsLine)
|
|
11371
|
+
parts.push(decisionsLine);
|
|
11372
|
+
const errorsLine = this.facts.errorsLine();
|
|
11373
|
+
if (errorsLine)
|
|
11374
|
+
parts.push(errorsLine);
|
|
11297
11375
|
const triedAndFailed = extractTriedAndFailed(oldTurns);
|
|
11298
11376
|
if (triedAndFailed.length > 0) {
|
|
11299
11377
|
const lines = triedAndFailed.map((t2) => `- ${t2.tool}(${t2.args}): ${t2.error} (failed ${t2.count}x)`);
|
|
@@ -11323,55 +11401,6 @@ ${lines.join(`
|
|
|
11323
11401
|
this.onCompact(summary);
|
|
11324
11402
|
}
|
|
11325
11403
|
}
|
|
11326
|
-
extractFacts(turns) {
|
|
11327
|
-
const DRIVE = "(?:[a-zA-Z]:[\\\\/])?";
|
|
11328
|
-
const filePatterns = [
|
|
11329
|
-
new RegExp(`(?:Created|Updated|Written|Deleted|Moved) ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
|
|
11330
|
-
new RegExp(`Replaced in ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
|
|
11331
|
-
new RegExp(`Файл (?:создан|обновлён|записан|удалён|перемещён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
|
|
11332
|
-
new RegExp(`file (?:created|updated|written|deleted|moved):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
|
|
11333
|
-
];
|
|
11334
|
-
const newFiles = [];
|
|
11335
|
-
const newDecisions = [];
|
|
11336
|
-
const newErrors = [];
|
|
11337
|
-
for (const msg of turns) {
|
|
11338
|
-
const content = getMessageText(msg.content);
|
|
11339
|
-
if (msg.role === "user" && (content.startsWith("<system-summary>") || content.includes("[Compressed:"))) {
|
|
11340
|
-
continue;
|
|
11341
|
-
}
|
|
11342
|
-
if (msg.role === "tool") {
|
|
11343
|
-
for (const pattern of filePatterns) {
|
|
11344
|
-
for (const match of content.matchAll(pattern)) {
|
|
11345
|
-
newFiles.push(match[1]);
|
|
11346
|
-
}
|
|
11347
|
-
}
|
|
11348
|
-
if (content.includes("Plan:") && content.includes("[")) {
|
|
11349
|
-
const planLine = content.split(`
|
|
11350
|
-
`).find((line) => line.includes("Plan:"));
|
|
11351
|
-
if (planLine)
|
|
11352
|
-
newDecisions.push(planLine.trim());
|
|
11353
|
-
}
|
|
11354
|
-
}
|
|
11355
|
-
if (msg.role === "assistant") {
|
|
11356
|
-
if (content.includes("decided:") || content.includes("decision:")) {
|
|
11357
|
-
const line = content.split(`
|
|
11358
|
-
`).find((l) => l.includes("decided:") || l.includes("decision:"));
|
|
11359
|
-
if (line)
|
|
11360
|
-
newDecisions.push(line.trim().slice(0, 200));
|
|
11361
|
-
}
|
|
11362
|
-
}
|
|
11363
|
-
if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
|
|
11364
|
-
const line = content.split(`
|
|
11365
|
-
`).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
|
|
11366
|
-
if (line)
|
|
11367
|
-
newErrors.push(line.trim().slice(0, 250));
|
|
11368
|
-
}
|
|
11369
|
-
}
|
|
11370
|
-
const dedup = (arr) => [...new Set(arr)];
|
|
11371
|
-
this.fileFacts = dedup([...this.fileFacts, ...newFiles]).slice(-ContextManager.MAX_FACTS);
|
|
11372
|
-
this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-ContextManager.MAX_FACTS);
|
|
11373
|
-
this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-ContextManager.MAX_FACTS);
|
|
11374
|
-
}
|
|
11375
11404
|
updateSystemPrompt(content) {
|
|
11376
11405
|
const idx = this.messages.findIndex((m) => m.role === "system");
|
|
11377
11406
|
if (idx >= 0) {
|
|
@@ -11390,6 +11419,8 @@ ${lines.join(`
|
|
|
11390
11419
|
this.compactionCount = 0;
|
|
11391
11420
|
this.peakTokens = 0;
|
|
11392
11421
|
this.lastUserTask = "";
|
|
11422
|
+
this.lastUserFeedback = "";
|
|
11423
|
+
this.sessionMission = "";
|
|
11393
11424
|
}
|
|
11394
11425
|
getEstimatedTokens() {
|
|
11395
11426
|
return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0) + this.toolTokens;
|
|
@@ -11406,7 +11437,9 @@ ${lines.join(`
|
|
|
11406
11437
|
}
|
|
11407
11438
|
}
|
|
11408
11439
|
var COMPACTION_INTERVAL = 15, KEEP_LAST_N = 6;
|
|
11409
|
-
var init_manager = () => {
|
|
11440
|
+
var init_manager = __esm(() => {
|
|
11441
|
+
init_fact_extractor();
|
|
11442
|
+
});
|
|
11410
11443
|
|
|
11411
11444
|
// src/modules/hallucination/consistency.ts
|
|
11412
11445
|
class ConsistencyCheck {
|
|
@@ -11517,6 +11550,10 @@ function isFilePathLike(s) {
|
|
|
11517
11550
|
return false;
|
|
11518
11551
|
return FILE_EXTENSIONS.has(ext);
|
|
11519
11552
|
}
|
|
11553
|
+
const last = s.split(/[\\/]/).pop() ?? "";
|
|
11554
|
+
const lastDot = last.lastIndexOf(".");
|
|
11555
|
+
if (lastDot <= 0)
|
|
11556
|
+
return false;
|
|
11520
11557
|
return true;
|
|
11521
11558
|
}
|
|
11522
11559
|
function extractFileLikeTokens(text) {
|
|
@@ -11705,8 +11742,8 @@ var init_js_identifiers = __esm(() => {
|
|
|
11705
11742
|
});
|
|
11706
11743
|
|
|
11707
11744
|
// src/modules/hallucination/factual.ts
|
|
11708
|
-
import { existsSync as
|
|
11709
|
-
import { resolve as resolve12, isAbsolute as isAbsolute2, join as
|
|
11745
|
+
import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
|
|
11746
|
+
import { resolve as resolve12, isAbsolute as isAbsolute2, join as join12 } from "path";
|
|
11710
11747
|
|
|
11711
11748
|
class FactualCheck {
|
|
11712
11749
|
baseDir;
|
|
@@ -11741,14 +11778,14 @@ class FactualCheck {
|
|
|
11741
11778
|
}
|
|
11742
11779
|
pathExists(fp) {
|
|
11743
11780
|
if (isAbsolute2(fp))
|
|
11744
|
-
return
|
|
11781
|
+
return existsSync21(fp);
|
|
11745
11782
|
if (!fp.includes("/") && !fp.includes("\\")) {
|
|
11746
11783
|
return this.bareNameExists(fp);
|
|
11747
11784
|
}
|
|
11748
|
-
return
|
|
11785
|
+
return existsSync21(resolve12(this.baseDir, fp));
|
|
11749
11786
|
}
|
|
11750
11787
|
bareNameExists(name) {
|
|
11751
|
-
if (
|
|
11788
|
+
if (existsSync21(resolve12(this.baseDir, name)))
|
|
11752
11789
|
return true;
|
|
11753
11790
|
if (this.indexHas(name))
|
|
11754
11791
|
return true;
|
|
@@ -11788,7 +11825,7 @@ class FactualCheck {
|
|
|
11788
11825
|
for (const entry of entries) {
|
|
11789
11826
|
if (count >= FactualCheck.MAX_INDEXED_FILES)
|
|
11790
11827
|
break;
|
|
11791
|
-
const full =
|
|
11828
|
+
const full = join12(dir, entry.name);
|
|
11792
11829
|
if (entry.isDirectory()) {
|
|
11793
11830
|
if (!IGNORED_DIRS.has(entry.name)) {
|
|
11794
11831
|
count = this.scanDir(full, index, count);
|
|
@@ -12418,7 +12455,7 @@ ${joined}` }
|
|
|
12418
12455
|
var DEFAULT_CHUNK_SYSTEM_PROMPT = 'Answer the query using ONLY the provided text. Be concise. If the text does not contain the answer, say "NO_EVIDENCE".', DEFAULT_SYNTHESIS_SYSTEM_PROMPT = "You are given a query and per-chunk answers over a large text. Produce the final answer to the query, combining evidence from the chunks. If no chunk had evidence, say so.";
|
|
12419
12456
|
|
|
12420
12457
|
// src/tools/chunk-query.ts
|
|
12421
|
-
import { readFileSync as
|
|
12458
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
12422
12459
|
import { resolve as resolve14 } from "node:path";
|
|
12423
12460
|
var chunkQueryTool;
|
|
12424
12461
|
var init_chunk_query = __esm(() => {
|
|
@@ -12487,7 +12524,7 @@ var init_chunk_query = __esm(() => {
|
|
|
12487
12524
|
return { success: false, output: `[SCOPE] ${check.reason || "Path not allowed"}` };
|
|
12488
12525
|
}
|
|
12489
12526
|
try {
|
|
12490
|
-
content =
|
|
12527
|
+
content = readFileSync10(resolve14(ctx.baseDir, inputPath), "utf8");
|
|
12491
12528
|
} catch (e) {
|
|
12492
12529
|
return { success: false, output: `Cannot read ${inputPath}: ${e.message}` };
|
|
12493
12530
|
}
|
|
@@ -12836,8 +12873,8 @@ var init_web_browse = __esm(() => {
|
|
|
12836
12873
|
});
|
|
12837
12874
|
|
|
12838
12875
|
// src/tools/download-file.ts
|
|
12839
|
-
import { writeFileSync as
|
|
12840
|
-
import { dirname as
|
|
12876
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync12 } from "fs";
|
|
12877
|
+
import { dirname as dirname8 } from "path";
|
|
12841
12878
|
var MAX_DOWNLOAD_BYTES, downloadFileTool;
|
|
12842
12879
|
var init_download_file = __esm(() => {
|
|
12843
12880
|
init_i18n();
|
|
@@ -12924,8 +12961,8 @@ var init_download_file = __esm(() => {
|
|
|
12924
12961
|
output: t("tool.download_too_large", { max: String(maxBytes) })
|
|
12925
12962
|
};
|
|
12926
12963
|
}
|
|
12927
|
-
|
|
12928
|
-
|
|
12964
|
+
mkdirSync12(dirname8(resolved), { recursive: true });
|
|
12965
|
+
writeFileSync8(resolved, buffer);
|
|
12929
12966
|
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() || "unknown";
|
|
12930
12967
|
logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Status: ${response.status}`);
|
|
12931
12968
|
logFileWrite(ctx.sessionId, resolved, true, `Downloaded ${buffer.byteLength} bytes`);
|
|
@@ -13760,7 +13797,7 @@ ${JSON.stringify(result, null, 2)}`
|
|
|
13760
13797
|
|
|
13761
13798
|
// src/tools/search-history.ts
|
|
13762
13799
|
import * as fs from "fs";
|
|
13763
|
-
import { join as
|
|
13800
|
+
import { join as join13 } from "path";
|
|
13764
13801
|
import { homedir as homedir5 } from "os";
|
|
13765
13802
|
function searchFile(filePath, query, maxResults, results) {
|
|
13766
13803
|
if (!fs.existsSync(filePath))
|
|
@@ -13804,7 +13841,7 @@ var init_search_history = __esm(() => {
|
|
|
13804
13841
|
const query = String(args.query || "").toLowerCase();
|
|
13805
13842
|
const maxResults = Number(args.maxResults) || 5;
|
|
13806
13843
|
const sessionId = args.sessionId ? String(args.sessionId) : null;
|
|
13807
|
-
const sessionDir =
|
|
13844
|
+
const sessionDir = join13(homedir5(), ".mma", "sessions");
|
|
13808
13845
|
const results = [];
|
|
13809
13846
|
try {
|
|
13810
13847
|
if (!fs.existsSync(sessionDir)) {
|
|
@@ -13819,7 +13856,7 @@ var init_search_history = __esm(() => {
|
|
|
13819
13856
|
continue;
|
|
13820
13857
|
if (sessionId && entry.name !== sessionId)
|
|
13821
13858
|
continue;
|
|
13822
|
-
const historyFile =
|
|
13859
|
+
const historyFile = join13(sessionDir, entry.name, "history.jsonl");
|
|
13823
13860
|
searchFile(historyFile, query, maxResults, results);
|
|
13824
13861
|
if (results.length >= maxResults)
|
|
13825
13862
|
break;
|
|
@@ -13845,9 +13882,133 @@ var init_search_history = __esm(() => {
|
|
|
13845
13882
|
};
|
|
13846
13883
|
});
|
|
13847
13884
|
|
|
13885
|
+
// src/modules/memory/search.ts
|
|
13886
|
+
import { readFileSync as readFileSync12, existsSync as existsSync23 } from "fs";
|
|
13887
|
+
import { join as join14 } from "path";
|
|
13888
|
+
|
|
13889
|
+
class MemorySearch {
|
|
13890
|
+
memoryDir;
|
|
13891
|
+
constructor(memoryDir) {
|
|
13892
|
+
this.memoryDir = memoryDir;
|
|
13893
|
+
}
|
|
13894
|
+
query(query) {
|
|
13895
|
+
const results = [];
|
|
13896
|
+
const lowerQuery = query.toLowerCase();
|
|
13897
|
+
for (const name of MEMORY_FILES) {
|
|
13898
|
+
const path = join14(this.memoryDir, `${name}.md`);
|
|
13899
|
+
if (!existsSync23(path))
|
|
13900
|
+
continue;
|
|
13901
|
+
const content = readFileSync12(path, "utf-8");
|
|
13902
|
+
const lines = content.split(`
|
|
13903
|
+
`);
|
|
13904
|
+
for (const line of lines) {
|
|
13905
|
+
if (line.toLowerCase().includes(lowerQuery)) {
|
|
13906
|
+
results.push({ file: name, match: line.trim() });
|
|
13907
|
+
}
|
|
13908
|
+
}
|
|
13909
|
+
}
|
|
13910
|
+
const prefsPath = join14(this.memoryDir, "preferences.json");
|
|
13911
|
+
if (existsSync23(prefsPath)) {
|
|
13912
|
+
try {
|
|
13913
|
+
const prefs = JSON.parse(readFileSync12(prefsPath, "utf-8"));
|
|
13914
|
+
for (const [key, value] of Object.entries(prefs)) {
|
|
13915
|
+
const searchStr = `${key}=${value}`;
|
|
13916
|
+
if (searchStr.toLowerCase().includes(lowerQuery)) {
|
|
13917
|
+
results.push({ file: "preferences", match: `${key} = ${value}` });
|
|
13918
|
+
}
|
|
13919
|
+
}
|
|
13920
|
+
} catch {}
|
|
13921
|
+
}
|
|
13922
|
+
return results;
|
|
13923
|
+
}
|
|
13924
|
+
}
|
|
13925
|
+
var MEMORY_FILES;
|
|
13926
|
+
var init_search = __esm(() => {
|
|
13927
|
+
MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
|
|
13928
|
+
});
|
|
13929
|
+
|
|
13930
|
+
// src/modules/memory/store.ts
|
|
13931
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync24, mkdirSync as mkdirSync13 } from "fs";
|
|
13932
|
+
import { join as join15 } from "path";
|
|
13933
|
+
|
|
13934
|
+
class MemoryStore {
|
|
13935
|
+
memoryDir;
|
|
13936
|
+
constructor(memoryDir) {
|
|
13937
|
+
this.memoryDir = memoryDir;
|
|
13938
|
+
this.ensureDir();
|
|
13939
|
+
for (const name of MEMORY_FILES2) {
|
|
13940
|
+
const path = join15(this.memoryDir, `${name}.md`);
|
|
13941
|
+
if (!existsSync24(path)) {
|
|
13942
|
+
writeFileSync9(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
|
|
13943
|
+
|
|
13944
|
+
`, "utf-8");
|
|
13945
|
+
}
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13948
|
+
ensureDir() {
|
|
13949
|
+
if (!existsSync24(this.memoryDir)) {
|
|
13950
|
+
mkdirSync13(this.memoryDir, { recursive: true });
|
|
13951
|
+
}
|
|
13952
|
+
}
|
|
13953
|
+
read(name) {
|
|
13954
|
+
const path = join15(this.memoryDir, `${name}.md`);
|
|
13955
|
+
if (!existsSync24(path))
|
|
13956
|
+
return "";
|
|
13957
|
+
return readFileSync13(path, "utf-8");
|
|
13958
|
+
}
|
|
13959
|
+
append(name, entry) {
|
|
13960
|
+
const path = join15(this.memoryDir, `${name}.md`);
|
|
13961
|
+
const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
13962
|
+
const formatted = `- **${timestamp}** — ${entry}
|
|
13963
|
+
`;
|
|
13964
|
+
appendFileSync5(path, formatted, "utf-8");
|
|
13965
|
+
}
|
|
13966
|
+
search(query) {
|
|
13967
|
+
const searchModule = new MemorySearch(this.memoryDir);
|
|
13968
|
+
return searchModule.query(query);
|
|
13969
|
+
}
|
|
13970
|
+
prefsPath() {
|
|
13971
|
+
return join15(this.memoryDir, "preferences.json");
|
|
13972
|
+
}
|
|
13973
|
+
getPreferences() {
|
|
13974
|
+
const path = this.prefsPath();
|
|
13975
|
+
if (!existsSync24(path))
|
|
13976
|
+
return {};
|
|
13977
|
+
try {
|
|
13978
|
+
return JSON.parse(readFileSync13(path, "utf-8"));
|
|
13979
|
+
} catch {
|
|
13980
|
+
return {};
|
|
13981
|
+
}
|
|
13982
|
+
}
|
|
13983
|
+
setPreference(key, value) {
|
|
13984
|
+
const prefs = this.getPreferences();
|
|
13985
|
+
prefs[key] = value;
|
|
13986
|
+
writeFileSync9(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
|
|
13987
|
+
}
|
|
13988
|
+
deletePreference(key) {
|
|
13989
|
+
const prefs = this.getPreferences();
|
|
13990
|
+
if (!(key in prefs))
|
|
13991
|
+
return false;
|
|
13992
|
+
delete prefs[key];
|
|
13993
|
+
writeFileSync9(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
|
|
13994
|
+
return true;
|
|
13995
|
+
}
|
|
13996
|
+
appendRule(category, pattern, cause, solution) {
|
|
13997
|
+
const entry = `**pattern:** ${pattern}
|
|
13998
|
+
**cause:** ${cause}
|
|
13999
|
+
**solution:** ${solution}`;
|
|
14000
|
+
this.append(category, entry);
|
|
14001
|
+
}
|
|
14002
|
+
}
|
|
14003
|
+
var MEMORY_FILES2;
|
|
14004
|
+
var init_store2 = __esm(() => {
|
|
14005
|
+
init_search();
|
|
14006
|
+
MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
|
|
14007
|
+
});
|
|
14008
|
+
|
|
13848
14009
|
// src/tools/remember.ts
|
|
13849
14010
|
import { homedir as homedir6 } from "os";
|
|
13850
|
-
import { join as
|
|
14011
|
+
import { join as join16 } from "path";
|
|
13851
14012
|
var CATEGORIES, rememberTool;
|
|
13852
14013
|
var init_remember = __esm(() => {
|
|
13853
14014
|
init_i18n();
|
|
@@ -13885,7 +14046,7 @@ var init_remember = __esm(() => {
|
|
|
13885
14046
|
if (!CATEGORIES.includes(category)) {
|
|
13886
14047
|
return { success: false, output: t("tool.invalid_params") };
|
|
13887
14048
|
}
|
|
13888
|
-
const memoryDir =
|
|
14049
|
+
const memoryDir = join16(homedir6(), ".mma", "memory");
|
|
13889
14050
|
const store = new MemoryStore(memoryDir);
|
|
13890
14051
|
try {
|
|
13891
14052
|
if (category === "preferences") {
|
|
@@ -13918,7 +14079,7 @@ var init_remember = __esm(() => {
|
|
|
13918
14079
|
|
|
13919
14080
|
// src/tools/recall.ts
|
|
13920
14081
|
import { homedir as homedir7 } from "os";
|
|
13921
|
-
import { join as
|
|
14082
|
+
import { join as join17 } from "path";
|
|
13922
14083
|
function formatAll(store) {
|
|
13923
14084
|
const parts = [];
|
|
13924
14085
|
const prefs = store.getPreferences();
|
|
@@ -14001,7 +14162,7 @@ var init_recall = __esm(() => {
|
|
|
14001
14162
|
handler: async (_ctx, args) => {
|
|
14002
14163
|
const query = args.query ? String(args.query) : "";
|
|
14003
14164
|
const category = args.category ? String(args.category) : "";
|
|
14004
|
-
const memoryDir =
|
|
14165
|
+
const memoryDir = join17(homedir7(), ".mma", "memory");
|
|
14005
14166
|
const store = new MemoryStore(memoryDir);
|
|
14006
14167
|
try {
|
|
14007
14168
|
if (!query && !category) {
|
|
@@ -14039,10 +14200,10 @@ __export(exports_bridge_client, {
|
|
|
14039
14200
|
});
|
|
14040
14201
|
import { spawn as spawn4 } from "child_process";
|
|
14041
14202
|
import { createInterface } from "readline";
|
|
14042
|
-
import { dirname as
|
|
14203
|
+
import { dirname as dirname9, join as join18 } from "path";
|
|
14043
14204
|
import { fileURLToPath } from "url";
|
|
14044
14205
|
function bridgeScriptPath() {
|
|
14045
|
-
return
|
|
14206
|
+
return join18(dirname9(fileURLToPath(import.meta.url)), "bridge-server.mjs");
|
|
14046
14207
|
}
|
|
14047
14208
|
|
|
14048
14209
|
class BridgeDriver {
|
|
@@ -14578,15 +14739,15 @@ function buildTextExtractionScript() {
|
|
|
14578
14739
|
|
|
14579
14740
|
// src/modules/browser/cookie-store.ts
|
|
14580
14741
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
14581
|
-
import { join as
|
|
14742
|
+
import { join as join19 } from "path";
|
|
14582
14743
|
|
|
14583
14744
|
class CookieStore {
|
|
14584
14745
|
filePath;
|
|
14585
14746
|
constructor(cookieDir) {
|
|
14586
|
-
this.filePath =
|
|
14747
|
+
this.filePath = join19(cookieDir, "cookies.json");
|
|
14587
14748
|
}
|
|
14588
14749
|
async save(cookies) {
|
|
14589
|
-
await mkdir(
|
|
14750
|
+
await mkdir(join19(this.filePath, ".."), { recursive: true });
|
|
14590
14751
|
await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
14591
14752
|
}
|
|
14592
14753
|
async load() {
|
|
@@ -14941,10 +15102,10 @@ var init_session = __esm(() => {
|
|
|
14941
15102
|
});
|
|
14942
15103
|
|
|
14943
15104
|
// src/tools/browser.ts
|
|
14944
|
-
import { join as
|
|
15105
|
+
import { join as join20 } from "path";
|
|
14945
15106
|
function getSession(ctx) {
|
|
14946
15107
|
if (!session) {
|
|
14947
|
-
const cookieDir =
|
|
15108
|
+
const cookieDir = join20(ctx.baseDir, ".mma", "browser");
|
|
14948
15109
|
session = new BrowserSession({
|
|
14949
15110
|
...DEFAULT_BROWSER_CONFIG,
|
|
14950
15111
|
headless: ctx.config.browser?.headless ?? true,
|
|
@@ -15080,8 +15241,8 @@ async function readClipboardFallback() {
|
|
|
15080
15241
|
const { platform: platform3 } = await import("os");
|
|
15081
15242
|
const { execSync } = await import("child_process");
|
|
15082
15243
|
const { readFileSync: readFileSync15, unlinkSync: unlinkSync4 } = await import("fs");
|
|
15083
|
-
const { join:
|
|
15084
|
-
const tmpPath =
|
|
15244
|
+
const { join: join21 } = await import("path");
|
|
15245
|
+
const tmpPath = join21(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
|
|
15085
15246
|
try {
|
|
15086
15247
|
if (platform3() === "linux") {
|
|
15087
15248
|
execSync(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
|
|
@@ -15159,7 +15320,7 @@ var init_image_utils = __esm(() => {
|
|
|
15159
15320
|
});
|
|
15160
15321
|
|
|
15161
15322
|
// src/tools/attach-image.ts
|
|
15162
|
-
import { existsSync as
|
|
15323
|
+
import { existsSync as existsSync25 } from "fs";
|
|
15163
15324
|
import { resolve as resolve15 } from "path";
|
|
15164
15325
|
var attachImageTool;
|
|
15165
15326
|
var init_attach_image = __esm(() => {
|
|
@@ -15202,7 +15363,7 @@ var init_attach_image = __esm(() => {
|
|
|
15202
15363
|
dataUrl = result.dataUrl;
|
|
15203
15364
|
} else {
|
|
15204
15365
|
const absPath = resolve15(ctx.baseDir, source);
|
|
15205
|
-
if (!
|
|
15366
|
+
if (!existsSync25(absPath)) {
|
|
15206
15367
|
return {
|
|
15207
15368
|
success: false,
|
|
15208
15369
|
output: t("image.not_found", { path: source })
|
|
@@ -15364,16 +15525,16 @@ class ModuleRegistry {
|
|
|
15364
15525
|
}
|
|
15365
15526
|
|
|
15366
15527
|
// src/modules/plugins/loader.ts
|
|
15367
|
-
import { readdirSync as readdirSync7, existsSync as
|
|
15368
|
-
import { join as
|
|
15528
|
+
import { readdirSync as readdirSync7, existsSync as existsSync26, statSync as statSync5 } from "fs";
|
|
15529
|
+
import { join as join21 } from "path";
|
|
15369
15530
|
|
|
15370
15531
|
class PluginLoader {
|
|
15371
15532
|
loadFromDir(dirPath, pluginManager, logger) {
|
|
15372
|
-
if (!
|
|
15533
|
+
if (!existsSync26(dirPath))
|
|
15373
15534
|
return;
|
|
15374
15535
|
const entries = readdirSync7(dirPath);
|
|
15375
15536
|
for (const entry of entries) {
|
|
15376
|
-
const fullPath =
|
|
15537
|
+
const fullPath = join21(dirPath, entry);
|
|
15377
15538
|
if (!statSync5(fullPath).isFile())
|
|
15378
15539
|
continue;
|
|
15379
15540
|
if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
|
|
@@ -15397,8 +15558,8 @@ var init_loader = __esm(() => {
|
|
|
15397
15558
|
|
|
15398
15559
|
// src/modules/plugins/builtin/lint-on-write.ts
|
|
15399
15560
|
import { spawn as spawn5, execSync } from "child_process";
|
|
15400
|
-
import { existsSync as
|
|
15401
|
-
import { resolve as resolve16, extname as extname4, join as
|
|
15561
|
+
import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
|
|
15562
|
+
import { resolve as resolve16, extname as extname4, join as join22 } from "path";
|
|
15402
15563
|
import { platform as platform3 } from "os";
|
|
15403
15564
|
function contentHash(content) {
|
|
15404
15565
|
let h = 5381;
|
|
@@ -15444,7 +15605,7 @@ class LintOnWritePlugin {
|
|
|
15444
15605
|
if (!path)
|
|
15445
15606
|
return;
|
|
15446
15607
|
const fullPath = resolve16(ctx.baseDir, path);
|
|
15447
|
-
if (!
|
|
15608
|
+
if (!existsSync27(fullPath))
|
|
15448
15609
|
return;
|
|
15449
15610
|
const ext = extname4(fullPath);
|
|
15450
15611
|
const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
|
|
@@ -15455,7 +15616,7 @@ class LintOnWritePlugin {
|
|
|
15455
15616
|
return;
|
|
15456
15617
|
}
|
|
15457
15618
|
await this.runProjectLint(ctx, result);
|
|
15458
|
-
await this.runProjectTypeCheck(ctx, result);
|
|
15619
|
+
await this.runProjectTypeCheck(fullPath, ctx.baseDir, result);
|
|
15459
15620
|
}
|
|
15460
15621
|
async checkSyntax(filePath, ext, baseDir) {
|
|
15461
15622
|
if (ext === ".ts" || ext === ".tsx") {
|
|
@@ -15500,8 +15661,8 @@ class LintOnWritePlugin {
|
|
|
15500
15661
|
}
|
|
15501
15662
|
async runProjectLint(ctx, result) {
|
|
15502
15663
|
try {
|
|
15503
|
-
const packageJsonPath =
|
|
15504
|
-
if (!
|
|
15664
|
+
const packageJsonPath = join22(ctx.baseDir, "package.json");
|
|
15665
|
+
if (!existsSync27(packageJsonPath)) {
|
|
15505
15666
|
return;
|
|
15506
15667
|
}
|
|
15507
15668
|
const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
|
|
@@ -15519,9 +15680,13 @@ class LintOnWritePlugin {
|
|
|
15519
15680
|
[Project lint failed]: ${err.message}`;
|
|
15520
15681
|
}
|
|
15521
15682
|
}
|
|
15522
|
-
async runProjectTypeCheck(
|
|
15523
|
-
const
|
|
15524
|
-
|
|
15683
|
+
async runProjectTypeCheck(filePath, baseDir, result) {
|
|
15684
|
+
const projectRoot = findProjectRoot(filePath, baseDir, [
|
|
15685
|
+
"tsconfig.json",
|
|
15686
|
+
"package.json"
|
|
15687
|
+
]);
|
|
15688
|
+
const tsconfigPath = join22(projectRoot, "tsconfig.json");
|
|
15689
|
+
if (!existsSync27(tsconfigPath)) {
|
|
15525
15690
|
return;
|
|
15526
15691
|
}
|
|
15527
15692
|
const now = Date.now();
|
|
@@ -15535,7 +15700,7 @@ class LintOnWritePlugin {
|
|
|
15535
15700
|
return;
|
|
15536
15701
|
}
|
|
15537
15702
|
this._checkTimestamp = now;
|
|
15538
|
-
this._checkPromise = this.runTscCheck(
|
|
15703
|
+
this._checkPromise = this.runTscCheck(projectRoot);
|
|
15539
15704
|
const error = await this._checkPromise;
|
|
15540
15705
|
if (error) {
|
|
15541
15706
|
result.output += `
|
|
@@ -15604,6 +15769,7 @@ function runAsync(command, cwd, timeoutMs) {
|
|
|
15604
15769
|
}
|
|
15605
15770
|
var TYPE_CHECK_DEBOUNCE_MS = 2000, syntaxCache, _winDecoder, plugin;
|
|
15606
15771
|
var init_lint_on_write = __esm(() => {
|
|
15772
|
+
init_project_root();
|
|
15607
15773
|
syntaxCache = new Map;
|
|
15608
15774
|
plugin = new LintOnWritePlugin;
|
|
15609
15775
|
});
|
|
@@ -15651,7 +15817,7 @@ class PlanCreator {
|
|
|
15651
15817
|
const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
|
|
15652
15818
|
return hasActionWord && words.length > 8;
|
|
15653
15819
|
}
|
|
15654
|
-
static createPlan(title, stepDescriptions, baseDir) {
|
|
15820
|
+
static createPlan(title, stepDescriptions, baseDir, kinds) {
|
|
15655
15821
|
const stepCount = stepDescriptions.length;
|
|
15656
15822
|
return {
|
|
15657
15823
|
id: generatePlanId(),
|
|
@@ -15659,7 +15825,8 @@ class PlanCreator {
|
|
|
15659
15825
|
steps: stepDescriptions.map((desc, i) => ({
|
|
15660
15826
|
id: i + 1,
|
|
15661
15827
|
description: desc,
|
|
15662
|
-
status: "pending"
|
|
15828
|
+
status: "pending",
|
|
15829
|
+
kind: kinds?.[i] ?? "create"
|
|
15663
15830
|
})),
|
|
15664
15831
|
createdAt: new Date().toISOString(),
|
|
15665
15832
|
baseDir
|
|
@@ -15673,7 +15840,8 @@ class PlanCreator {
|
|
|
15673
15840
|
const added = newSteps.map((desc, i) => ({
|
|
15674
15841
|
id: kept.length + i + 1,
|
|
15675
15842
|
description: desc,
|
|
15676
|
-
status: "pending"
|
|
15843
|
+
status: "pending",
|
|
15844
|
+
kind: "create"
|
|
15677
15845
|
}));
|
|
15678
15846
|
return {
|
|
15679
15847
|
id: plan.id,
|
|
@@ -15765,42 +15933,9 @@ class PlanTracker {
|
|
|
15765
15933
|
}
|
|
15766
15934
|
var init_tracker = () => {};
|
|
15767
15935
|
|
|
15768
|
-
// src/modules/execution/
|
|
15769
|
-
import {
|
|
15770
|
-
import {
|
|
15771
|
-
function findExistingFile(baseDir, filePath) {
|
|
15772
|
-
const direct = resolve17(baseDir, filePath);
|
|
15773
|
-
if (existsSync27(direct))
|
|
15774
|
-
return direct;
|
|
15775
|
-
const name = basename2(filePath).toLowerCase();
|
|
15776
|
-
const suffix = filePath.replace(/\\/g, "/").toLowerCase();
|
|
15777
|
-
let found = null;
|
|
15778
|
-
const walk = (dir, depth) => {
|
|
15779
|
-
if (found || depth > RESOLVE_MAX_DEPTH)
|
|
15780
|
-
return;
|
|
15781
|
-
let entries;
|
|
15782
|
-
try {
|
|
15783
|
-
entries = readdirSync8(dir, { withFileTypes: true });
|
|
15784
|
-
} catch {
|
|
15785
|
-
return;
|
|
15786
|
-
}
|
|
15787
|
-
for (const e of entries) {
|
|
15788
|
-
if (found)
|
|
15789
|
-
return;
|
|
15790
|
-
const full = join22(dir, e.name);
|
|
15791
|
-
if (e.isDirectory()) {
|
|
15792
|
-
if (SKIP_DIRS.has(e.name))
|
|
15793
|
-
continue;
|
|
15794
|
-
walk(full, depth + 1);
|
|
15795
|
-
} else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
|
|
15796
|
-
found = full;
|
|
15797
|
-
return;
|
|
15798
|
-
}
|
|
15799
|
-
}
|
|
15800
|
-
};
|
|
15801
|
-
walk(baseDir, 0);
|
|
15802
|
-
return found;
|
|
15803
|
-
}
|
|
15936
|
+
// src/modules/execution/audit-runners.ts
|
|
15937
|
+
import { readdirSync as readdirSync8 } from "fs";
|
|
15938
|
+
import { join as join23 } from "path";
|
|
15804
15939
|
function findTestFile(dir, depth = 0) {
|
|
15805
15940
|
if (depth > 5)
|
|
15806
15941
|
return null;
|
|
@@ -15811,7 +15946,7 @@ function findTestFile(dir, depth = 0) {
|
|
|
15811
15946
|
return null;
|
|
15812
15947
|
}
|
|
15813
15948
|
for (const e of entries) {
|
|
15814
|
-
const full =
|
|
15949
|
+
const full = join23(dir, e.name);
|
|
15815
15950
|
if (e.isDirectory()) {
|
|
15816
15951
|
if (SKIP_DIRS.has(e.name))
|
|
15817
15952
|
continue;
|
|
@@ -15875,6 +16010,76 @@ async function runTests(baseDir) {
|
|
|
15875
16010
|
command: "bun test"
|
|
15876
16011
|
};
|
|
15877
16012
|
}
|
|
16013
|
+
function parseTypecheckErrors(output) {
|
|
16014
|
+
const line = output.split(`
|
|
16015
|
+
`).find((l) => /error TS\d+/.test(l));
|
|
16016
|
+
return line ? line.trim().slice(0, 300) : null;
|
|
16017
|
+
}
|
|
16018
|
+
async function runTypecheck(baseDir) {
|
|
16019
|
+
const entry = processRegistry.start("npx --no-install tsc --noEmit --skipLibCheck", baseDir);
|
|
16020
|
+
const exited = await processRegistry.waitForExit(entry.id, 90000);
|
|
16021
|
+
const output = entry.log.join(`
|
|
16022
|
+
`);
|
|
16023
|
+
processRegistry.remove(entry.id);
|
|
16024
|
+
if (!exited)
|
|
16025
|
+
return null;
|
|
16026
|
+
return parseTypecheckErrors(output);
|
|
16027
|
+
}
|
|
16028
|
+
var SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE;
|
|
16029
|
+
var init_audit_runners = __esm(() => {
|
|
16030
|
+
init_bash();
|
|
16031
|
+
init_processes();
|
|
16032
|
+
SKIP_DIRS = new Set([
|
|
16033
|
+
"node_modules",
|
|
16034
|
+
".git",
|
|
16035
|
+
".mma",
|
|
16036
|
+
"dist",
|
|
16037
|
+
"build",
|
|
16038
|
+
"coverage",
|
|
16039
|
+
".next",
|
|
16040
|
+
".nuxt",
|
|
16041
|
+
"vendor"
|
|
16042
|
+
]);
|
|
16043
|
+
TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
|
|
16044
|
+
TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
|
|
16045
|
+
});
|
|
16046
|
+
|
|
16047
|
+
// src/modules/execution/auditor.ts
|
|
16048
|
+
import { existsSync as existsSync28, readdirSync as readdirSync9 } from "fs";
|
|
16049
|
+
import { resolve as resolve17, join as join24, basename as basename2 } from "path";
|
|
16050
|
+
function findExistingFile(baseDir, filePath) {
|
|
16051
|
+
const direct = resolve17(baseDir, filePath);
|
|
16052
|
+
if (existsSync28(direct))
|
|
16053
|
+
return direct;
|
|
16054
|
+
const name = basename2(filePath).toLowerCase();
|
|
16055
|
+
const suffix = filePath.replace(/\\/g, "/").toLowerCase();
|
|
16056
|
+
let found = null;
|
|
16057
|
+
const walk = (dir, depth) => {
|
|
16058
|
+
if (found || depth > RESOLVE_MAX_DEPTH)
|
|
16059
|
+
return;
|
|
16060
|
+
let entries;
|
|
16061
|
+
try {
|
|
16062
|
+
entries = readdirSync9(dir, { withFileTypes: true });
|
|
16063
|
+
} catch {
|
|
16064
|
+
return;
|
|
16065
|
+
}
|
|
16066
|
+
for (const e of entries) {
|
|
16067
|
+
if (found)
|
|
16068
|
+
return;
|
|
16069
|
+
const full = join24(dir, e.name);
|
|
16070
|
+
if (e.isDirectory()) {
|
|
16071
|
+
if (SKIP_DIRS.has(e.name))
|
|
16072
|
+
continue;
|
|
16073
|
+
walk(full, depth + 1);
|
|
16074
|
+
} else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
|
|
16075
|
+
found = full;
|
|
16076
|
+
return;
|
|
16077
|
+
}
|
|
16078
|
+
}
|
|
16079
|
+
};
|
|
16080
|
+
walk(baseDir, 0);
|
|
16081
|
+
return found;
|
|
16082
|
+
}
|
|
15878
16083
|
|
|
15879
16084
|
class Auditor {
|
|
15880
16085
|
baseDir;
|
|
@@ -15882,18 +16087,27 @@ class Auditor {
|
|
|
15882
16087
|
this.baseDir = baseDir;
|
|
15883
16088
|
}
|
|
15884
16089
|
async audit(plan) {
|
|
15885
|
-
const
|
|
15886
|
-
const
|
|
16090
|
+
const doneCreate = plan.steps.filter((s) => s.status === "done" && s.kind !== "delete");
|
|
16091
|
+
const doneDelete = plan.steps.filter((s) => s.status === "done" && s.kind === "delete");
|
|
16092
|
+
const createText = doneCreate.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
|
|
16093
|
+
const deleteText = doneDelete.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
|
|
16094
|
+
const createFiles = extractFileLikeTokens(stripUrls(createText));
|
|
16095
|
+
const deleteFiles = extractFileLikeTokens(stripUrls(deleteText));
|
|
15887
16096
|
const missingFiles = [];
|
|
15888
16097
|
const existingFiles = [];
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
if (
|
|
16098
|
+
const leftoverFiles = [];
|
|
16099
|
+
for (const filePath of createFiles) {
|
|
16100
|
+
if (findExistingFile(this.baseDir, filePath)) {
|
|
15892
16101
|
existingFiles.push(filePath);
|
|
15893
16102
|
} else {
|
|
15894
16103
|
missingFiles.push(filePath);
|
|
15895
16104
|
}
|
|
15896
16105
|
}
|
|
16106
|
+
for (const filePath of deleteFiles) {
|
|
16107
|
+
if (findExistingFile(this.baseDir, filePath)) {
|
|
16108
|
+
leftoverFiles.push(filePath);
|
|
16109
|
+
}
|
|
16110
|
+
}
|
|
15897
16111
|
let testRun = null;
|
|
15898
16112
|
if (hasTestStep(plan) && findTestFile(this.baseDir)) {
|
|
15899
16113
|
try {
|
|
@@ -15902,17 +16116,28 @@ class Auditor {
|
|
|
15902
16116
|
testRun = null;
|
|
15903
16117
|
}
|
|
15904
16118
|
}
|
|
16119
|
+
let typecheckError = null;
|
|
16120
|
+
if (missingFiles.length === 0 && leftoverFiles.length === 0 && existsSync28(join24(this.baseDir, "tsconfig.json"))) {
|
|
16121
|
+
try {
|
|
16122
|
+
typecheckError = await runTypecheck(this.baseDir);
|
|
16123
|
+
} catch {
|
|
16124
|
+
typecheckError = null;
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
15905
16127
|
const doneSteps = plan.steps.filter((s) => s.status === "done").length;
|
|
16128
|
+
const terminalSteps = plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
|
|
15906
16129
|
const totalSteps = plan.steps.length;
|
|
16130
|
+
const auditedFiles = [...new Set([...createFiles, ...deleteFiles])];
|
|
15907
16131
|
let massEditWarning = null;
|
|
15908
|
-
if (
|
|
16132
|
+
if (auditedFiles.length > MASS_EDIT_THRESHOLD) {
|
|
15909
16133
|
massEditWarning = t("exec.mass_edit_warning", {
|
|
15910
|
-
count: String(
|
|
16134
|
+
count: String(auditedFiles.length)
|
|
15911
16135
|
});
|
|
15912
16136
|
}
|
|
15913
16137
|
const testsFailing = testRun !== null && testRun.failed > 0;
|
|
15914
|
-
const
|
|
15915
|
-
const
|
|
16138
|
+
const typecheckFailing = typecheckError !== null;
|
|
16139
|
+
const passed = missingFiles.length === 0 && leftoverFiles.length === 0 && !testsFailing && !typecheckFailing;
|
|
16140
|
+
const stepsPending = terminalSteps < totalSteps;
|
|
15916
16141
|
let summary;
|
|
15917
16142
|
if (missingFiles.length > 0) {
|
|
15918
16143
|
summary = t("exec.audit_fail", {
|
|
@@ -15920,6 +16145,12 @@ class Auditor {
|
|
|
15920
16145
|
total: totalSteps,
|
|
15921
16146
|
files: missingFiles.length
|
|
15922
16147
|
});
|
|
16148
|
+
} else if (leftoverFiles.length > 0) {
|
|
16149
|
+
summary = t("exec.audit_leftovers", {
|
|
16150
|
+
done: doneSteps,
|
|
16151
|
+
total: totalSteps,
|
|
16152
|
+
files: leftoverFiles.length
|
|
16153
|
+
});
|
|
15923
16154
|
} else if (testsFailing) {
|
|
15924
16155
|
summary = t("exec.audit_fail_tests", {
|
|
15925
16156
|
done: doneSteps,
|
|
@@ -15928,6 +16159,13 @@ class Auditor {
|
|
|
15928
16159
|
passed: String(testRun.passedCount),
|
|
15929
16160
|
detail: testRun.detail
|
|
15930
16161
|
});
|
|
16162
|
+
} else if (typecheckFailing) {
|
|
16163
|
+
summary = t("exec.audit_fail_typecheck", {
|
|
16164
|
+
done: doneSteps,
|
|
16165
|
+
total: totalSteps,
|
|
16166
|
+
missing: String(missingFiles.length),
|
|
16167
|
+
typeError: typecheckError
|
|
16168
|
+
});
|
|
15931
16169
|
} else if (stepsPending) {
|
|
15932
16170
|
summary = t("exec.audit_pending", {
|
|
15933
16171
|
done: doneSteps,
|
|
@@ -15943,38 +16181,27 @@ class Auditor {
|
|
|
15943
16181
|
return {
|
|
15944
16182
|
passed,
|
|
15945
16183
|
missingFiles,
|
|
16184
|
+
leftoverFiles,
|
|
15946
16185
|
createdFiles: existingFiles,
|
|
15947
16186
|
modifiedFiles: [],
|
|
15948
16187
|
summary,
|
|
15949
16188
|
massEditWarning,
|
|
15950
|
-
testRun
|
|
16189
|
+
testRun,
|
|
16190
|
+
typecheckError
|
|
15951
16191
|
};
|
|
15952
16192
|
}
|
|
15953
16193
|
}
|
|
15954
|
-
var MASS_EDIT_THRESHOLD = 10,
|
|
16194
|
+
var MASS_EDIT_THRESHOLD = 10, RESOLVE_MAX_DEPTH = 8;
|
|
15955
16195
|
var init_auditor = __esm(() => {
|
|
15956
16196
|
init_i18n();
|
|
15957
16197
|
init_js_identifiers();
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
SKIP_DIRS = new Set([
|
|
15961
|
-
"node_modules",
|
|
15962
|
-
".git",
|
|
15963
|
-
".mma",
|
|
15964
|
-
"dist",
|
|
15965
|
-
"build",
|
|
15966
|
-
"coverage",
|
|
15967
|
-
".next",
|
|
15968
|
-
".nuxt",
|
|
15969
|
-
"vendor"
|
|
15970
|
-
]);
|
|
15971
|
-
TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
|
|
15972
|
-
TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
|
|
16198
|
+
init_audit_runners();
|
|
16199
|
+
init_audit_runners();
|
|
15973
16200
|
});
|
|
15974
16201
|
|
|
15975
16202
|
// src/modules/execution/plan-store.ts
|
|
15976
|
-
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as
|
|
15977
|
-
import { join as
|
|
16203
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync29, readdirSync as readdirSync10, rmSync } from "fs";
|
|
16204
|
+
import { join as join25 } from "path";
|
|
15978
16205
|
function readPlanFile(path, fallbackBaseDir) {
|
|
15979
16206
|
try {
|
|
15980
16207
|
const raw = readFileSync16(path, "utf-8");
|
|
@@ -15999,10 +16226,10 @@ function writePlanFile(path, plan) {
|
|
|
15999
16226
|
writeFileSync10(path, JSON.stringify(plan, null, 2), "utf-8");
|
|
16000
16227
|
}
|
|
16001
16228
|
function listDir(dir, baseDir) {
|
|
16002
|
-
if (!
|
|
16229
|
+
if (!existsSync29(dir))
|
|
16003
16230
|
return [];
|
|
16004
|
-
const files =
|
|
16005
|
-
return files.map((f) => readPlanFile(
|
|
16231
|
+
const files = readdirSync10(dir).filter((f) => f.endsWith(".json"));
|
|
16232
|
+
return files.map((f) => readPlanFile(join25(dir, f), baseDir)).filter((p) => p !== null);
|
|
16006
16233
|
}
|
|
16007
16234
|
function toMeta(plan, status) {
|
|
16008
16235
|
return {
|
|
@@ -16023,33 +16250,33 @@ class PlanStore {
|
|
|
16023
16250
|
archiveDir;
|
|
16024
16251
|
legacyPath;
|
|
16025
16252
|
constructor(baseDir) {
|
|
16026
|
-
const mmaDir =
|
|
16027
|
-
if (!
|
|
16253
|
+
const mmaDir = join25(baseDir, ".mma");
|
|
16254
|
+
if (!existsSync29(mmaDir))
|
|
16028
16255
|
mkdirSync14(mmaDir, { recursive: true });
|
|
16029
16256
|
this.baseDir = baseDir;
|
|
16030
|
-
this.plansDir =
|
|
16031
|
-
this.draftsDir =
|
|
16032
|
-
this.archiveDir =
|
|
16033
|
-
this.legacyPath =
|
|
16257
|
+
this.plansDir = join25(mmaDir, "plans");
|
|
16258
|
+
this.draftsDir = join25(this.plansDir, "drafts");
|
|
16259
|
+
this.archiveDir = join25(this.plansDir, "archive");
|
|
16260
|
+
this.legacyPath = join25(mmaDir, LEGACY_FILE);
|
|
16034
16261
|
for (const dir of [this.plansDir, this.draftsDir, this.archiveDir]) {
|
|
16035
|
-
if (!
|
|
16262
|
+
if (!existsSync29(dir))
|
|
16036
16263
|
mkdirSync14(dir, { recursive: true });
|
|
16037
16264
|
}
|
|
16038
16265
|
}
|
|
16039
16266
|
activePath() {
|
|
16040
|
-
return
|
|
16267
|
+
return join25(this.plansDir, "active.json");
|
|
16041
16268
|
}
|
|
16042
16269
|
saveActive(plan) {
|
|
16043
16270
|
writePlanFile(this.activePath(), plan);
|
|
16044
16271
|
}
|
|
16045
16272
|
loadActive() {
|
|
16046
16273
|
const activePath = this.activePath();
|
|
16047
|
-
if (
|
|
16274
|
+
if (existsSync29(activePath)) {
|
|
16048
16275
|
const plan = readPlanFile(activePath, this.baseDir);
|
|
16049
16276
|
if (plan)
|
|
16050
16277
|
return plan;
|
|
16051
16278
|
}
|
|
16052
|
-
if (
|
|
16279
|
+
if (existsSync29(this.legacyPath)) {
|
|
16053
16280
|
const legacy = readPlanFile(this.legacyPath, this.baseDir);
|
|
16054
16281
|
if (legacy) {
|
|
16055
16282
|
this.saveActive(legacy);
|
|
@@ -16063,26 +16290,26 @@ class PlanStore {
|
|
|
16063
16290
|
}
|
|
16064
16291
|
clearActive() {
|
|
16065
16292
|
const p = this.activePath();
|
|
16066
|
-
if (
|
|
16293
|
+
if (existsSync29(p))
|
|
16067
16294
|
rmSync(p, { force: true });
|
|
16068
16295
|
}
|
|
16069
16296
|
saveDraft(plan) {
|
|
16070
|
-
writePlanFile(
|
|
16297
|
+
writePlanFile(join25(this.draftsDir, `${plan.id}.json`), plan);
|
|
16071
16298
|
}
|
|
16072
16299
|
loadDraft(id) {
|
|
16073
|
-
const p =
|
|
16074
|
-
return
|
|
16300
|
+
const p = join25(this.draftsDir, `${id}.json`);
|
|
16301
|
+
return existsSync29(p) ? readPlanFile(p, this.baseDir) : null;
|
|
16075
16302
|
}
|
|
16076
16303
|
removeDraft(id) {
|
|
16077
|
-
const p =
|
|
16078
|
-
if (
|
|
16304
|
+
const p = join25(this.draftsDir, `${id}.json`);
|
|
16305
|
+
if (existsSync29(p))
|
|
16079
16306
|
rmSync(p, { force: true });
|
|
16080
16307
|
}
|
|
16081
16308
|
listDrafts() {
|
|
16082
16309
|
return listDir(this.draftsDir, this.baseDir);
|
|
16083
16310
|
}
|
|
16084
16311
|
archivePlan(plan) {
|
|
16085
|
-
writePlanFile(
|
|
16312
|
+
writePlanFile(join25(this.archiveDir, `${plan.id}.json`), plan);
|
|
16086
16313
|
this.removeDraft(plan.id);
|
|
16087
16314
|
const active = this.loadActive();
|
|
16088
16315
|
if (active && active.id === plan.id) {
|
|
@@ -16093,8 +16320,8 @@ class PlanStore {
|
|
|
16093
16320
|
return listDir(this.archiveDir, this.baseDir);
|
|
16094
16321
|
}
|
|
16095
16322
|
removeArchived(id) {
|
|
16096
|
-
const p =
|
|
16097
|
-
if (
|
|
16323
|
+
const p = join25(this.archiveDir, `${id}.json`);
|
|
16324
|
+
if (existsSync29(p))
|
|
16098
16325
|
rmSync(p, { force: true });
|
|
16099
16326
|
}
|
|
16100
16327
|
listAll() {
|
|
@@ -16187,91 +16414,12 @@ var init_plan_coverage = __esm(() => {
|
|
|
16187
16414
|
]);
|
|
16188
16415
|
});
|
|
16189
16416
|
|
|
16190
|
-
// src/modules/execution/
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
tracker = null;
|
|
16197
|
-
verifier;
|
|
16198
|
-
stuckDetector;
|
|
16199
|
-
auditor;
|
|
16200
|
-
store;
|
|
16201
|
-
baseDir;
|
|
16202
|
-
lastRecoveryIteration = -STUCK_RECOVERY_COOLDOWN;
|
|
16203
|
-
consecutivePlanWarnings = 0;
|
|
16204
|
-
lastStepId = -1;
|
|
16205
|
-
depsGateHints = new Map;
|
|
16206
|
-
_auditSkipsRemaining = 0;
|
|
16207
|
-
stuckNotified = false;
|
|
16208
|
-
pendingMessages = [];
|
|
16209
|
-
constructor(baseDir, stuckThreshold = 6) {
|
|
16210
|
-
this.baseDir = baseDir;
|
|
16211
|
-
this.verifier = new StepVerifier(baseDir);
|
|
16212
|
-
this.stuckDetector = new StuckDetector(stuckThreshold);
|
|
16213
|
-
this.auditor = new Auditor(baseDir);
|
|
16214
|
-
this.store = new PlanStore(baseDir);
|
|
16215
|
-
}
|
|
16216
|
-
setPlan(plan) {
|
|
16217
|
-
this.tracker = new PlanTracker(plan);
|
|
16218
|
-
this.store.saveActive(plan);
|
|
16219
|
-
this._auditSkipsRemaining = 0;
|
|
16220
|
-
}
|
|
16221
|
-
restorePlan() {
|
|
16222
|
-
const plan = this.store.loadActive();
|
|
16223
|
-
if (!plan)
|
|
16224
|
-
return false;
|
|
16225
|
-
this.tracker = new PlanTracker(plan);
|
|
16226
|
-
this.tracker.syncCurrentStep();
|
|
16227
|
-
this._auditSkipsRemaining = 1;
|
|
16228
|
-
return true;
|
|
16229
|
-
}
|
|
16230
|
-
getTracker() {
|
|
16231
|
-
return this.tracker;
|
|
16232
|
-
}
|
|
16233
|
-
getStore() {
|
|
16234
|
-
return this.store;
|
|
16235
|
-
}
|
|
16236
|
-
getStuckDetector() {
|
|
16237
|
-
return this.stuckDetector;
|
|
16238
|
-
}
|
|
16239
|
-
async runFinalAudit() {
|
|
16240
|
-
if (!this.tracker)
|
|
16241
|
-
return null;
|
|
16242
|
-
if (this._auditSkipsRemaining > 0) {
|
|
16243
|
-
this._auditSkipsRemaining--;
|
|
16244
|
-
return null;
|
|
16245
|
-
}
|
|
16246
|
-
const plan = this.tracker.getPlan();
|
|
16247
|
-
const audit = await this.auditor.audit(plan);
|
|
16248
|
-
const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
|
|
16249
|
-
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
16250
|
-
const passed = audit.passed && pendingSteps.length === 0;
|
|
16251
|
-
return {
|
|
16252
|
-
passed,
|
|
16253
|
-
done,
|
|
16254
|
-
total: plan.steps.length,
|
|
16255
|
-
pendingSteps,
|
|
16256
|
-
missingFiles: audit.missingFiles,
|
|
16257
|
-
summary: audit.summary
|
|
16258
|
-
};
|
|
16259
|
-
}
|
|
16260
|
-
getSystemPromptBlock() {
|
|
16261
|
-
if (!this.tracker)
|
|
16262
|
-
return null;
|
|
16263
|
-
return {
|
|
16264
|
-
content: this.tracker.toPromptBlock(),
|
|
16265
|
-
priority: "critical",
|
|
16266
|
-
essential: true,
|
|
16267
|
-
estimatedTokens: 200
|
|
16268
|
-
};
|
|
16269
|
-
}
|
|
16270
|
-
getToolDefinitions() {
|
|
16271
|
-
return [
|
|
16272
|
-
{
|
|
16273
|
-
name: "plan",
|
|
16274
|
-
description: `Create, update, show, abort, list, switch, or re-plan multi-step plans.
|
|
16417
|
+
// src/modules/execution/plan-tool.ts
|
|
16418
|
+
function createPlanToolDefinitions(deps) {
|
|
16419
|
+
return [
|
|
16420
|
+
{
|
|
16421
|
+
name: "plan",
|
|
16422
|
+
description: `Create, update, show, abort, list, switch, or re-plan multi-step plans.
|
|
16275
16423
|
|
|
16276
16424
|
Actions:
|
|
16277
16425
|
- create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.
|
|
@@ -16288,198 +16436,222 @@ Write CONCRETE steps with exact file paths and commands:
|
|
|
16288
16436
|
- Specify WHICH CLI commands to run with exact arguments
|
|
16289
16437
|
- Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)
|
|
16290
16438
|
- Good: "Создать src/components/Header.tsx с навигацией и логотипом"
|
|
16291
|
-
- Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
title: { type: "string" },
|
|
16308
|
-
steps: { type: "array", items: { type: "string" } },
|
|
16309
|
-
step: { type: "number" },
|
|
16310
|
-
status: { type: "string", enum: ["done", "failed", "skipped"] },
|
|
16311
|
-
note: { type: "string" },
|
|
16312
|
-
id: { type: "string", description: "Plan id (for switch action)" }
|
|
16439
|
+
- Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)
|
|
16440
|
+
- For steps that REMOVE files, pass kinds: ["delete"] aligned with steps (the done-gate verifies those files are gone instead of demanding they exist).`,
|
|
16441
|
+
parameters: {
|
|
16442
|
+
type: "object",
|
|
16443
|
+
properties: {
|
|
16444
|
+
action: {
|
|
16445
|
+
type: "string",
|
|
16446
|
+
enum: [
|
|
16447
|
+
"create",
|
|
16448
|
+
"update",
|
|
16449
|
+
"show",
|
|
16450
|
+
"abort",
|
|
16451
|
+
"list",
|
|
16452
|
+
"switch",
|
|
16453
|
+
"re-plan"
|
|
16454
|
+
]
|
|
16313
16455
|
},
|
|
16314
|
-
|
|
16456
|
+
title: { type: "string" },
|
|
16457
|
+
steps: { type: "array", items: { type: "string" } },
|
|
16458
|
+
kinds: {
|
|
16459
|
+
type: "array",
|
|
16460
|
+
items: { type: "string", enum: ["create", "delete"] },
|
|
16461
|
+
description: 'Optional per-step intent, aligned 1:1 with steps. Pass "delete" for steps whose purpose is REMOVING files — the done-gate then requires those files to be GONE (not present). Defaults to "create".'
|
|
16462
|
+
},
|
|
16463
|
+
step: { type: "number" },
|
|
16464
|
+
status: { type: "string", enum: ["done", "failed", "skipped"] },
|
|
16465
|
+
note: { type: "string" },
|
|
16466
|
+
id: { type: "string", description: "Plan id (for switch action)" }
|
|
16315
16467
|
},
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
});
|
|
16328
|
-
return {
|
|
16329
|
-
|
|
16330
|
-
|
|
16468
|
+
required: ["action"]
|
|
16469
|
+
},
|
|
16470
|
+
handler: async (_ctx, args) => {
|
|
16471
|
+
const action = String(args.action);
|
|
16472
|
+
if (action === "list") {
|
|
16473
|
+
const metas = deps.store.listAll();
|
|
16474
|
+
if (metas.length === 0) {
|
|
16475
|
+
return { success: true, output: t("plan.list_empty") };
|
|
16476
|
+
}
|
|
16477
|
+
const lines = metas.map((m) => {
|
|
16478
|
+
const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
|
|
16479
|
+
const namePart = m.name ? ` (${m.name})` : "";
|
|
16480
|
+
return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
|
|
16481
|
+
});
|
|
16482
|
+
return {
|
|
16483
|
+
success: true,
|
|
16484
|
+
output: `${t("plan.list_header")}
|
|
16331
16485
|
${lines.join(`
|
|
16332
16486
|
`)}`
|
|
16487
|
+
};
|
|
16488
|
+
}
|
|
16489
|
+
if (action === "switch") {
|
|
16490
|
+
const planId = String(args.id || "");
|
|
16491
|
+
if (!planId) {
|
|
16492
|
+
return { success: false, output: t("plan.switch_no_id") };
|
|
16493
|
+
}
|
|
16494
|
+
const found = deps.store.find(planId);
|
|
16495
|
+
if (!found) {
|
|
16496
|
+
return {
|
|
16497
|
+
success: false,
|
|
16498
|
+
output: t("plan.not_found", { id: planId })
|
|
16333
16499
|
};
|
|
16334
16500
|
}
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16501
|
+
deps.preserveActive();
|
|
16502
|
+
if (found.status === "draft") {
|
|
16503
|
+
deps.store.removeDraft(found.plan.id);
|
|
16504
|
+
} else if (found.status === "archived") {
|
|
16505
|
+
deps.store.removeArchived(found.plan.id);
|
|
16506
|
+
}
|
|
16507
|
+
deps.setPlan(found.plan);
|
|
16508
|
+
const display = PlanCreator.toPromptBlock(found.plan, 0);
|
|
16509
|
+
return {
|
|
16510
|
+
success: true,
|
|
16511
|
+
output: t("plan.switched", {
|
|
16512
|
+
id: found.plan.id,
|
|
16513
|
+
title: found.plan.title
|
|
16514
|
+
}),
|
|
16515
|
+
display
|
|
16516
|
+
};
|
|
16517
|
+
}
|
|
16518
|
+
if (action === "re-plan") {
|
|
16519
|
+
if (!deps.trackerRef.current) {
|
|
16520
|
+
return { success: false, output: t("plan.no_active") };
|
|
16521
|
+
}
|
|
16522
|
+
const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
|
|
16523
|
+
if (newSteps.length === 0) {
|
|
16524
|
+
return { success: false, output: t("plan.replan_no_steps") };
|
|
16525
|
+
}
|
|
16526
|
+
const tracker = deps.trackerRef.current;
|
|
16527
|
+
const oldPlan = tracker.getPlan();
|
|
16528
|
+
const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
|
|
16529
|
+
const keptCount = replanned.steps.length - newSteps.length;
|
|
16530
|
+
deps.setPlan(replanned);
|
|
16531
|
+
const display = PlanCreator.toPromptBlock(replanned, keptCount);
|
|
16532
|
+
return {
|
|
16533
|
+
success: true,
|
|
16534
|
+
output: t("plan.replanned", {
|
|
16535
|
+
kept: String(keptCount),
|
|
16536
|
+
steps: String(newSteps.length)
|
|
16537
|
+
}),
|
|
16538
|
+
display
|
|
16539
|
+
};
|
|
16540
|
+
}
|
|
16541
|
+
if (action === "create") {
|
|
16542
|
+
const title = String(args.title || "Task Plan");
|
|
16543
|
+
const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
|
|
16544
|
+
if (steps.length === 0) {
|
|
16545
|
+
return { success: false, output: t("plan.no_steps") };
|
|
16546
|
+
}
|
|
16547
|
+
const parsed = deps.parseKinds(args, steps);
|
|
16548
|
+
if (parsed.error) {
|
|
16549
|
+
return { success: false, output: parsed.error };
|
|
16550
|
+
}
|
|
16551
|
+
deps.preserveActive();
|
|
16552
|
+
const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
|
|
16553
|
+
deps.setPlan(plan);
|
|
16554
|
+
const display = PlanCreator.toPromptBlock(plan, 0);
|
|
16555
|
+
let output = t("plan.created", {
|
|
16556
|
+
title,
|
|
16557
|
+
steps: String(steps.length)
|
|
16558
|
+
});
|
|
16559
|
+
let displayOut = display;
|
|
16560
|
+
const taskText = deps.getTaskText(_ctx);
|
|
16561
|
+
if (taskText) {
|
|
16562
|
+
const { missing } = checkPlanCoverage(taskText, steps);
|
|
16563
|
+
if (missing.length > 0) {
|
|
16564
|
+
const warn = t("plan.coverage_warning", {
|
|
16565
|
+
missing: missing.join(", ")
|
|
16566
|
+
});
|
|
16567
|
+
output += `
|
|
16568
|
+
⚠️ ${warn}`;
|
|
16569
|
+
displayOut = `${display}
|
|
16570
|
+
⚠️ ${warn}`;
|
|
16339
16571
|
}
|
|
16340
|
-
|
|
16572
|
+
}
|
|
16573
|
+
return {
|
|
16574
|
+
success: true,
|
|
16575
|
+
output,
|
|
16576
|
+
display: displayOut
|
|
16577
|
+
};
|
|
16578
|
+
}
|
|
16579
|
+
if (action === "show") {
|
|
16580
|
+
const planId = args.id ? String(args.id) : "";
|
|
16581
|
+
if (planId && planId !== deps.trackerRef.current?.getPlan()?.id) {
|
|
16582
|
+
const found = deps.store.find(planId);
|
|
16341
16583
|
if (!found) {
|
|
16342
16584
|
return {
|
|
16343
16585
|
success: false,
|
|
16344
16586
|
output: t("plan.not_found", { id: planId })
|
|
16345
16587
|
};
|
|
16346
16588
|
}
|
|
16347
|
-
|
|
16348
|
-
if (found.status === "draft") {
|
|
16349
|
-
this.store.removeDraft(found.plan.id);
|
|
16350
|
-
} else if (found.status === "archived") {
|
|
16351
|
-
this.store.removeArchived(found.plan.id);
|
|
16352
|
-
}
|
|
16353
|
-
this.setPlan(found.plan);
|
|
16354
|
-
const display = PlanCreator.toPromptBlock(found.plan, 0);
|
|
16589
|
+
const display2 = PlanCreator.toPromptBlock(found.plan, 0);
|
|
16355
16590
|
return {
|
|
16356
16591
|
success: true,
|
|
16357
|
-
output: t("plan.
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
}),
|
|
16361
|
-
display
|
|
16592
|
+
output: `${t("plan.show_header")}
|
|
16593
|
+
${display2}`,
|
|
16594
|
+
display: display2
|
|
16362
16595
|
};
|
|
16363
16596
|
}
|
|
16364
|
-
if (
|
|
16365
|
-
|
|
16366
|
-
return { success: false, output: t("plan.no_active") };
|
|
16367
|
-
}
|
|
16368
|
-
const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
|
|
16369
|
-
if (newSteps.length === 0) {
|
|
16370
|
-
return { success: false, output: t("plan.replan_no_steps") };
|
|
16371
|
-
}
|
|
16372
|
-
const oldPlan = this.tracker.getPlan();
|
|
16373
|
-
const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
|
|
16374
|
-
const keptCount = replanned.steps.length - newSteps.length;
|
|
16375
|
-
this.setPlan(replanned);
|
|
16376
|
-
const display = PlanCreator.toPromptBlock(replanned, keptCount);
|
|
16377
|
-
return {
|
|
16378
|
-
success: true,
|
|
16379
|
-
output: t("plan.replanned", {
|
|
16380
|
-
kept: String(keptCount),
|
|
16381
|
-
steps: String(newSteps.length)
|
|
16382
|
-
}),
|
|
16383
|
-
display
|
|
16384
|
-
};
|
|
16597
|
+
if (!deps.trackerRef.current) {
|
|
16598
|
+
return { success: false, output: t("plan.no_active") };
|
|
16385
16599
|
}
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
}
|
|
16400
|
-
let displayOut = display;
|
|
16401
|
-
const taskText = this.getTaskText(_ctx);
|
|
16402
|
-
if (taskText) {
|
|
16403
|
-
const { missing } = checkPlanCoverage(taskText, steps);
|
|
16404
|
-
if (missing.length > 0) {
|
|
16405
|
-
const warn = t("plan.coverage_warning", {
|
|
16406
|
-
missing: missing.join(", ")
|
|
16407
|
-
});
|
|
16408
|
-
output += `
|
|
16409
|
-
⚠️ ${warn}`;
|
|
16410
|
-
displayOut = `${display}
|
|
16411
|
-
⚠️ ${warn}`;
|
|
16412
|
-
}
|
|
16413
|
-
}
|
|
16414
|
-
return {
|
|
16415
|
-
success: true,
|
|
16416
|
-
output,
|
|
16417
|
-
display: displayOut
|
|
16418
|
-
};
|
|
16600
|
+
const display = deps.trackerRef.current.toPromptBlock();
|
|
16601
|
+
return {
|
|
16602
|
+
success: true,
|
|
16603
|
+
output: `${t("plan.show_header")}
|
|
16604
|
+
${display}`,
|
|
16605
|
+
display
|
|
16606
|
+
};
|
|
16607
|
+
}
|
|
16608
|
+
if (action === "update" && args.step && deps.trackerRef.current) {
|
|
16609
|
+
const tracker = deps.trackerRef.current;
|
|
16610
|
+
const stepId = Number(args.step);
|
|
16611
|
+
const target = tracker.getStep(stepId);
|
|
16612
|
+
if (!target) {
|
|
16613
|
+
return { success: false, output: t("plan.step_not_found") };
|
|
16419
16614
|
}
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
if (!found) {
|
|
16425
|
-
return {
|
|
16426
|
-
success: false,
|
|
16427
|
-
output: t("plan.not_found", { id: planId })
|
|
16428
|
-
};
|
|
16429
|
-
}
|
|
16430
|
-
const display2 = PlanCreator.toPromptBlock(found.plan, 0);
|
|
16431
|
-
return {
|
|
16432
|
-
success: true,
|
|
16433
|
-
output: `${t("plan.show_header")}
|
|
16434
|
-
${display2}`,
|
|
16435
|
-
display: display2
|
|
16436
|
-
};
|
|
16437
|
-
}
|
|
16438
|
-
if (!this.tracker) {
|
|
16439
|
-
return { success: false, output: t("plan.no_active") };
|
|
16440
|
-
}
|
|
16441
|
-
const display = this.tracker.toPromptBlock();
|
|
16615
|
+
const status = String(args.status || "done");
|
|
16616
|
+
if (status === "done" && target.status === "done") {
|
|
16617
|
+
const progress2 = tracker.getProgressString();
|
|
16618
|
+
const display2 = tracker.toPromptBlock();
|
|
16442
16619
|
return {
|
|
16443
16620
|
success: true,
|
|
16444
|
-
output: `${t("plan.
|
|
16445
|
-
|
|
16446
|
-
|
|
16621
|
+
output: `${t("plan.step_already_done", {
|
|
16622
|
+
step: String(stepId)
|
|
16623
|
+
})}
|
|
16624
|
+
${progress2}`,
|
|
16625
|
+
display: display2
|
|
16447
16626
|
};
|
|
16448
16627
|
}
|
|
16449
|
-
if (
|
|
16450
|
-
const
|
|
16451
|
-
|
|
16452
|
-
if (!target) {
|
|
16453
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
16454
|
-
}
|
|
16455
|
-
const status = String(args.status || "done");
|
|
16456
|
-
if (status === "done" && target.status === "done") {
|
|
16457
|
-
const progress2 = this.tracker.getProgressString();
|
|
16458
|
-
const display2 = this.tracker.toPromptBlock();
|
|
16628
|
+
if (status === "done") {
|
|
16629
|
+
const blockers = tracker.getPlan().steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
|
|
16630
|
+
if (blockers.length > 0) {
|
|
16459
16631
|
return {
|
|
16460
|
-
success:
|
|
16461
|
-
output:
|
|
16462
|
-
step: String(stepId)
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16632
|
+
success: false,
|
|
16633
|
+
output: t("plan.order_blocked", {
|
|
16634
|
+
step: String(stepId),
|
|
16635
|
+
first: String(blockers[0].id),
|
|
16636
|
+
desc: blockers[0].description
|
|
16637
|
+
})
|
|
16466
16638
|
};
|
|
16467
16639
|
}
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16640
|
+
}
|
|
16641
|
+
if (status === "done") {
|
|
16642
|
+
if (target.kind === "delete") {
|
|
16643
|
+
const leftovers = deps.stillExistingDeliverables(target);
|
|
16644
|
+
if (leftovers.length > 0) {
|
|
16471
16645
|
return {
|
|
16472
16646
|
success: false,
|
|
16473
|
-
output: t("plan.
|
|
16647
|
+
output: t("plan.deliverables_remain", {
|
|
16474
16648
|
step: String(stepId),
|
|
16475
|
-
|
|
16476
|
-
desc: blockers[0].description
|
|
16649
|
+
files: leftovers.join(", ")
|
|
16477
16650
|
})
|
|
16478
16651
|
};
|
|
16479
16652
|
}
|
|
16480
|
-
}
|
|
16481
|
-
|
|
16482
|
-
const missing = this.missingStepDeliverables(target);
|
|
16653
|
+
} else {
|
|
16654
|
+
const missing = deps.missingStepDeliverables(target);
|
|
16483
16655
|
if (missing.length > 0) {
|
|
16484
16656
|
return {
|
|
16485
16657
|
success: false,
|
|
@@ -16490,373 +16662,559 @@ ${progress2}`,
|
|
|
16490
16662
|
};
|
|
16491
16663
|
}
|
|
16492
16664
|
}
|
|
16493
|
-
this.tracker.updateStepStatus(stepId, status);
|
|
16494
|
-
if (args.note)
|
|
16495
|
-
this.tracker.addNote(Number(args.step), String(args.note));
|
|
16496
|
-
this.tracker.syncCurrentStep();
|
|
16497
|
-
this.store.saveActive(this.tracker.getPlan());
|
|
16498
|
-
const progress = this.tracker.getProgressString();
|
|
16499
|
-
const display = this.tracker.toPromptBlock();
|
|
16500
|
-
_ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
16501
|
-
return {
|
|
16502
|
-
success: true,
|
|
16503
|
-
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
|
|
16504
|
-
${progress}`,
|
|
16505
|
-
display
|
|
16506
|
-
};
|
|
16507
16665
|
}
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
return { success: true, output: t("plan.aborted") };
|
|
16531
|
-
}
|
|
16532
|
-
if (!this.tracker) {
|
|
16533
|
-
return { success: false, output: t("plan.no_active") };
|
|
16666
|
+
tracker.updateStepStatus(stepId, status);
|
|
16667
|
+
if (args.note)
|
|
16668
|
+
tracker.addNote(Number(args.step), String(args.note));
|
|
16669
|
+
tracker.syncCurrentStep();
|
|
16670
|
+
deps.store.saveActive(tracker.getPlan());
|
|
16671
|
+
const progress = tracker.getProgressString();
|
|
16672
|
+
const display = tracker.toPromptBlock();
|
|
16673
|
+
_ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
|
|
16674
|
+
return {
|
|
16675
|
+
success: true,
|
|
16676
|
+
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
|
|
16677
|
+
${progress}`,
|
|
16678
|
+
display
|
|
16679
|
+
};
|
|
16680
|
+
}
|
|
16681
|
+
if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && deps.trackerRef.current) {
|
|
16682
|
+
const tracker = deps.trackerRef.current;
|
|
16683
|
+
const title = String(args.title || tracker.getPlan().title);
|
|
16684
|
+
const steps = args.steps.map(String);
|
|
16685
|
+
const parsed = deps.parseKinds(args, steps);
|
|
16686
|
+
if (parsed.error) {
|
|
16687
|
+
return { success: false, output: parsed.error };
|
|
16534
16688
|
}
|
|
16689
|
+
const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
|
|
16690
|
+
deps.setPlan(plan);
|
|
16691
|
+
const display = PlanCreator.toPromptBlock(plan, 0);
|
|
16692
|
+
const output = t("plan.updated", {
|
|
16693
|
+
title,
|
|
16694
|
+
steps: String(steps.length)
|
|
16695
|
+
});
|
|
16535
16696
|
return {
|
|
16536
|
-
success:
|
|
16537
|
-
output
|
|
16697
|
+
success: true,
|
|
16698
|
+
output,
|
|
16699
|
+
display
|
|
16538
16700
|
};
|
|
16539
16701
|
}
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16702
|
+
if (action === "abort") {
|
|
16703
|
+
const tracker = deps.trackerRef.current;
|
|
16704
|
+
if (tracker) {
|
|
16705
|
+
deps.store.archivePlan(tracker.getPlan());
|
|
16706
|
+
}
|
|
16707
|
+
deps.trackerRef.current = null;
|
|
16708
|
+
deps.store.clearActive();
|
|
16709
|
+
return { success: true, output: t("plan.aborted") };
|
|
16710
|
+
}
|
|
16711
|
+
if (!deps.trackerRef.current) {
|
|
16712
|
+
return { success: false, output: t("plan.no_active") };
|
|
16713
|
+
}
|
|
16714
|
+
return {
|
|
16715
|
+
success: false,
|
|
16716
|
+
output: t("plan.unknown_action", { action })
|
|
16717
|
+
};
|
|
16718
|
+
}
|
|
16719
|
+
},
|
|
16720
|
+
{
|
|
16721
|
+
name: "todo",
|
|
16722
|
+
description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
|
|
16723
|
+
parameters: {
|
|
16724
|
+
type: "object",
|
|
16725
|
+
properties: {
|
|
16726
|
+
action: { type: "string", enum: ["add", "done", "list"] },
|
|
16727
|
+
items: { type: "array", items: { type: "string" } }
|
|
16551
16728
|
},
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16729
|
+
required: ["action"]
|
|
16730
|
+
},
|
|
16731
|
+
handler: async (_ctx, args) => {
|
|
16732
|
+
if (!deps.trackerRef.current) {
|
|
16733
|
+
return { success: false, output: t("plan.no_active") };
|
|
16734
|
+
}
|
|
16735
|
+
const tracker = deps.trackerRef.current;
|
|
16736
|
+
const action = String(args.action);
|
|
16737
|
+
const currentStep = tracker.getCurrentStep();
|
|
16738
|
+
if (!currentStep) {
|
|
16739
|
+
return { success: false, output: t("plan.step_not_found") };
|
|
16740
|
+
}
|
|
16741
|
+
if (action === "add" && Array.isArray(args.items)) {
|
|
16742
|
+
const items = args.items.map(String);
|
|
16743
|
+
const existing = currentStep.subtasks ?? [];
|
|
16744
|
+
let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
|
|
16745
|
+
for (const item of items) {
|
|
16746
|
+
existing.push({ id: nextId++, text: item, done: false });
|
|
16555
16747
|
}
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16748
|
+
currentStep.subtasks = existing;
|
|
16749
|
+
deps.store.saveActive(tracker.getPlan());
|
|
16750
|
+
const display = tracker.toPromptBlock();
|
|
16751
|
+
return {
|
|
16752
|
+
success: true,
|
|
16753
|
+
output: t("todo.added", {
|
|
16754
|
+
count: String(items.length),
|
|
16755
|
+
items: items.join(", ")
|
|
16756
|
+
}),
|
|
16757
|
+
display
|
|
16758
|
+
};
|
|
16759
|
+
}
|
|
16760
|
+
if (action === "done") {
|
|
16761
|
+
const items = Array.isArray(args.items) ? args.items.map(String) : [];
|
|
16762
|
+
if (items.length === 0) {
|
|
16763
|
+
return {
|
|
16764
|
+
success: false,
|
|
16765
|
+
output: t("todo.no_items")
|
|
16766
|
+
};
|
|
16560
16767
|
}
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16768
|
+
const subs = currentStep.subtasks ?? [];
|
|
16769
|
+
let marked = 0;
|
|
16770
|
+
for (const item of items) {
|
|
16771
|
+
const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
|
|
16772
|
+
if (sub) {
|
|
16773
|
+
sub.done = true;
|
|
16774
|
+
marked++;
|
|
16567
16775
|
}
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
const display = this.tracker.toPromptBlock();
|
|
16776
|
+
}
|
|
16777
|
+
if (marked === 0) {
|
|
16571
16778
|
return {
|
|
16572
|
-
success:
|
|
16573
|
-
output: t("todo.
|
|
16574
|
-
count: String(items.length),
|
|
16779
|
+
success: false,
|
|
16780
|
+
output: t("todo.subtask_not_found", {
|
|
16575
16781
|
items: items.join(", ")
|
|
16576
|
-
})
|
|
16577
|
-
display
|
|
16782
|
+
})
|
|
16578
16783
|
};
|
|
16579
16784
|
}
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
for (const item of items) {
|
|
16591
|
-
const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
|
|
16592
|
-
if (sub) {
|
|
16593
|
-
sub.done = true;
|
|
16594
|
-
marked++;
|
|
16595
|
-
}
|
|
16596
|
-
}
|
|
16597
|
-
if (marked === 0) {
|
|
16598
|
-
return {
|
|
16599
|
-
success: false,
|
|
16600
|
-
output: t("todo.subtask_not_found", {
|
|
16601
|
-
items: items.join(", ")
|
|
16602
|
-
})
|
|
16603
|
-
};
|
|
16604
|
-
}
|
|
16605
|
-
currentStep.subtasks = subs;
|
|
16606
|
-
this.store.saveActive(this.tracker.getPlan());
|
|
16607
|
-
const doneCount = subs.filter((s) => s.done).length;
|
|
16608
|
-
const display = this.tracker.toPromptBlock();
|
|
16609
|
-
let output = t("todo.marked_done", {
|
|
16610
|
-
count: String(marked)
|
|
16611
|
-
});
|
|
16612
|
-
output += ` (${doneCount}/${subs.length})`;
|
|
16613
|
-
if (doneCount === subs.length) {
|
|
16614
|
-
output += `
|
|
16785
|
+
currentStep.subtasks = subs;
|
|
16786
|
+
deps.store.saveActive(tracker.getPlan());
|
|
16787
|
+
const doneCount = subs.filter((s) => s.done).length;
|
|
16788
|
+
const display = tracker.toPromptBlock();
|
|
16789
|
+
let output = t("todo.marked_done", {
|
|
16790
|
+
count: String(marked)
|
|
16791
|
+
});
|
|
16792
|
+
output += ` (${doneCount}/${subs.length})`;
|
|
16793
|
+
if (doneCount === subs.length) {
|
|
16794
|
+
output += `
|
|
16615
16795
|
All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
|
|
16616
|
-
}
|
|
16617
|
-
return { success: true, output, display };
|
|
16618
16796
|
}
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16797
|
+
return { success: true, output, display };
|
|
16798
|
+
}
|
|
16799
|
+
if (action === "list") {
|
|
16800
|
+
const subs = currentStep.subtasks ?? [];
|
|
16801
|
+
const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
|
|
16802
|
+
return {
|
|
16803
|
+
success: true,
|
|
16804
|
+
output: `Step ${currentStep.id}: ${currentStep.description}
|
|
16625
16805
|
${lines.join(`
|
|
16626
16806
|
`)}`
|
|
16627
|
-
};
|
|
16628
|
-
}
|
|
16629
|
-
return {
|
|
16630
|
-
success: false,
|
|
16631
|
-
output: t("todo.unknown_action", { action })
|
|
16632
16807
|
};
|
|
16633
16808
|
}
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
const step = args.step ? this.tracker.getStep(Number(args.step)) : this.tracker.getCurrentStep();
|
|
16648
|
-
if (!step)
|
|
16649
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
16650
|
-
const result = await this.verifier.verifyStep(step.description);
|
|
16651
|
-
return {
|
|
16652
|
-
success: result.passed,
|
|
16653
|
-
output: result.passed ? t("verify.passed") : t("verify.failed", {
|
|
16654
|
-
details: result.failed.map((f) => f.message).join("; ")
|
|
16655
|
-
})
|
|
16656
|
-
};
|
|
16809
|
+
return {
|
|
16810
|
+
success: false,
|
|
16811
|
+
output: t("todo.unknown_action", { action })
|
|
16812
|
+
};
|
|
16813
|
+
}
|
|
16814
|
+
},
|
|
16815
|
+
{
|
|
16816
|
+
name: "verify",
|
|
16817
|
+
description: "Run verification for the current step. Checks files mentioned in the step description.",
|
|
16818
|
+
parameters: {
|
|
16819
|
+
type: "object",
|
|
16820
|
+
properties: {
|
|
16821
|
+
step: { type: "number", description: "Step number to verify" }
|
|
16657
16822
|
}
|
|
16823
|
+
},
|
|
16824
|
+
handler: async (_ctx, args) => {
|
|
16825
|
+
if (!deps.trackerRef.current)
|
|
16826
|
+
return { success: false, output: t("plan.no_active") };
|
|
16827
|
+
const tracker = deps.trackerRef.current;
|
|
16828
|
+
const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
|
|
16829
|
+
if (!step)
|
|
16830
|
+
return { success: false, output: t("plan.step_not_found") };
|
|
16831
|
+
const result = await deps.verifier.verifyStep(step.description);
|
|
16832
|
+
return {
|
|
16833
|
+
success: result.passed,
|
|
16834
|
+
output: result.passed ? t("verify.passed") : t("verify.failed", {
|
|
16835
|
+
details: result.failed.map((f) => f.message).join("; ")
|
|
16836
|
+
})
|
|
16837
|
+
};
|
|
16658
16838
|
}
|
|
16659
|
-
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16839
|
+
}
|
|
16840
|
+
];
|
|
16841
|
+
}
|
|
16842
|
+
var init_plan_tool = __esm(() => {
|
|
16843
|
+
init_i18n();
|
|
16844
|
+
init_plan_coverage();
|
|
16845
|
+
});
|
|
16846
|
+
|
|
16847
|
+
// src/modules/execution/windows-commands.ts
|
|
16848
|
+
function firstWord(command) {
|
|
16849
|
+
const segs = command.trim().split(/[\s;|&]+/);
|
|
16850
|
+
return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
|
|
16851
|
+
}
|
|
16852
|
+
function forbiddenWindowsCommand(command) {
|
|
16853
|
+
const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
|
|
16854
|
+
if (m)
|
|
16855
|
+
return forbiddenWindowsCommand(m[1]);
|
|
16856
|
+
const word = firstWord(command);
|
|
16857
|
+
if (word && FORBIDDEN_COMMANDS.has(word))
|
|
16858
|
+
return word;
|
|
16859
|
+
return null;
|
|
16860
|
+
}
|
|
16861
|
+
var FORBIDDEN_COMMANDS;
|
|
16862
|
+
var init_windows_commands = __esm(() => {
|
|
16863
|
+
FORBIDDEN_COMMANDS = new Set([
|
|
16864
|
+
"grep",
|
|
16865
|
+
"sed",
|
|
16866
|
+
"ls",
|
|
16867
|
+
"find",
|
|
16868
|
+
"rm",
|
|
16869
|
+
"touch",
|
|
16870
|
+
"which",
|
|
16871
|
+
"diff",
|
|
16872
|
+
"cp",
|
|
16873
|
+
"mv"
|
|
16874
|
+
]);
|
|
16875
|
+
});
|
|
16876
|
+
|
|
16877
|
+
// src/modules/execution/execution-plugin.ts
|
|
16878
|
+
import { platform as platform4 } from "os";
|
|
16879
|
+
function createExecutionPlugin(deps) {
|
|
16880
|
+
return {
|
|
16881
|
+
name: "execution",
|
|
16882
|
+
onBeforeThink: (ctx) => {
|
|
16883
|
+
if (ctx.contextManager && deps.pendingMessages.length > 0) {
|
|
16884
|
+
for (const m of deps.pendingMessages.splice(0)) {
|
|
16885
|
+
ctx.contextManager.addMessage(m);
|
|
16886
|
+
}
|
|
16887
|
+
}
|
|
16888
|
+
if (deps.trackerRef.current?.isComplete()) {
|
|
16889
|
+
deps.stuckDetector.resetStepProgress();
|
|
16890
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
16891
|
+
deps.state.lastStepId = -1;
|
|
16892
|
+
deps.state.stuckNotified = false;
|
|
16893
|
+
} else {
|
|
16894
|
+
const step = deps.trackerRef.current?.getCurrentStep();
|
|
16895
|
+
if (deps.trackerRef.current && step) {
|
|
16896
|
+
if (step.id !== deps.state.lastStepId) {
|
|
16897
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
16898
|
+
deps.state.lastStepId = step.id;
|
|
16899
|
+
deps.state.stuckNotified = false;
|
|
16668
16900
|
}
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
this.stuckDetector.resetStepProgress();
|
|
16672
|
-
this.consecutivePlanWarnings = 0;
|
|
16673
|
-
this.lastStepId = -1;
|
|
16674
|
-
this.stuckNotified = false;
|
|
16901
|
+
deps.stuckDetector.setCurrentStep(step.id, step.description);
|
|
16902
|
+
deps.stuckDetector.recordIteration(step.id);
|
|
16675
16903
|
} else {
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
this.stuckDetector.reset();
|
|
16687
|
-
this.consecutivePlanWarnings = 0;
|
|
16688
|
-
this.lastStepId = -1;
|
|
16689
|
-
this.stuckNotified = false;
|
|
16690
|
-
const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
16691
|
-
if (iter === 3 && !this.tracker && ctx.contextManager) {
|
|
16692
|
-
ctx.contextManager.addMessage({
|
|
16693
|
-
role: "user",
|
|
16694
|
-
content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`
|
|
16695
|
-
});
|
|
16696
|
-
}
|
|
16697
|
-
if (iter >= 6 && !this.tracker && ctx.contextManager) {
|
|
16698
|
-
ctx.contextManager.addMessage({
|
|
16699
|
-
role: "user",
|
|
16700
|
-
content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`
|
|
16701
|
-
});
|
|
16702
|
-
}
|
|
16904
|
+
deps.stuckDetector.reset();
|
|
16905
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
16906
|
+
deps.state.lastStepId = -1;
|
|
16907
|
+
deps.state.stuckNotified = false;
|
|
16908
|
+
const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
16909
|
+
if (iter === 3 && !deps.trackerRef.current && ctx.contextManager) {
|
|
16910
|
+
ctx.contextManager.addMessage({
|
|
16911
|
+
role: "user",
|
|
16912
|
+
content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`
|
|
16913
|
+
});
|
|
16703
16914
|
}
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
ctx.logger?.warn(stuckReason);
|
|
16710
|
-
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
16711
|
-
if (this.stuckDetector.isStuck())
|
|
16712
|
-
this.stuckNotified = true;
|
|
16915
|
+
if (iter >= 6 && !deps.trackerRef.current && ctx.contextManager) {
|
|
16916
|
+
ctx.contextManager.addMessage({
|
|
16917
|
+
role: "user",
|
|
16918
|
+
content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`
|
|
16919
|
+
});
|
|
16713
16920
|
}
|
|
16714
16921
|
}
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16922
|
+
}
|
|
16923
|
+
const stuckReason = deps.stuckDetector.getStuckReason();
|
|
16924
|
+
if (stuckReason) {
|
|
16925
|
+
const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
|
|
16926
|
+
if (logIt) {
|
|
16927
|
+
ctx.logger?.warn(stuckReason);
|
|
16928
|
+
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
16929
|
+
if (deps.stuckDetector.isStuck())
|
|
16930
|
+
deps.state.stuckNotified = true;
|
|
16931
|
+
}
|
|
16932
|
+
}
|
|
16933
|
+
if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
|
|
16934
|
+
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
16935
|
+
if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
|
|
16936
|
+
const recovery = deps.stuckDetector.getRecoveryMessage();
|
|
16937
|
+
if (recovery && ctx.contextManager) {
|
|
16938
|
+
const lastError = deps.stuckDetector.getLastErrorOutput();
|
|
16939
|
+
const skillHint = lastError ? `
|
|
16722
16940
|
If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
|
|
16723
|
-
|
|
16724
|
-
|
|
16941
|
+
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
16942
|
+
const actionableHintStr = actionableHints.length > 0 ? `
|
|
16725
16943
|
${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
|
|
16726
16944
|
`) })}` : "";
|
|
16727
|
-
|
|
16728
|
-
|
|
16729
|
-
Tool "${
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16945
|
+
const alternative = deps.stuckDetector.getToolAlternative();
|
|
16946
|
+
const altHint = alternative ? `
|
|
16947
|
+
Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
|
|
16948
|
+
ctx.contextManager.addMessage({
|
|
16949
|
+
role: "user",
|
|
16950
|
+
content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
|
|
16951
|
+
});
|
|
16952
|
+
}
|
|
16953
|
+
const hints = deps.stuckDetector.getHints();
|
|
16954
|
+
if (hints.length > 0 && ctx.contextManager) {
|
|
16955
|
+
const hintMsg = t("exec.hints", {
|
|
16956
|
+
hints: hints.map((h) => `- ${h}`).join(`
|
|
16739
16957
|
`)
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16958
|
+
});
|
|
16959
|
+
ctx.contextManager.addMessage({
|
|
16960
|
+
role: "user",
|
|
16961
|
+
content: `<system-summary>${hintMsg}</system-summary>`
|
|
16962
|
+
});
|
|
16963
|
+
}
|
|
16964
|
+
deps.stuckDetector.recordEscalation();
|
|
16965
|
+
deps.state.lastRecoveryIteration = currentIter;
|
|
16966
|
+
if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
16967
|
+
const escalation = t("exec.escalation", {
|
|
16968
|
+
stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
16969
|
+
description: deps.trackerRef.current?.getCurrentStep()?.description ?? ""
|
|
16970
|
+
});
|
|
16971
|
+
ctx.onMeta(escalation);
|
|
16972
|
+
}
|
|
16973
|
+
if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
|
|
16974
|
+
const step = deps.trackerRef.current?.getCurrentStep();
|
|
16975
|
+
ctx.contextManager.addMessage({
|
|
16976
|
+
role: "user",
|
|
16977
|
+
content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${deps.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`
|
|
16978
|
+
});
|
|
16979
|
+
}
|
|
16980
|
+
}
|
|
16981
|
+
}
|
|
16982
|
+
},
|
|
16983
|
+
onBeforeTool: (_ctx, call) => {
|
|
16984
|
+
const warning = deps.checkPlanAlignment(call);
|
|
16985
|
+
if (warning) {
|
|
16986
|
+
deps.pendingMessages.push({
|
|
16987
|
+
role: "user",
|
|
16988
|
+
content: `<system-summary>${warning}</system-summary>`
|
|
16989
|
+
});
|
|
16990
|
+
deps.state.consecutivePlanWarnings++;
|
|
16991
|
+
if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
16992
|
+
deps.pendingMessages.push({
|
|
16993
|
+
role: "user",
|
|
16994
|
+
content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
|
|
16995
|
+
});
|
|
16996
|
+
return t("exec.plan_blocked", {
|
|
16997
|
+
step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
16998
|
+
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
|
|
16999
|
+
});
|
|
17000
|
+
}
|
|
17001
|
+
} else {
|
|
17002
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
17003
|
+
}
|
|
17004
|
+
return true;
|
|
17005
|
+
},
|
|
17006
|
+
onToolCall: (ctx) => {
|
|
17007
|
+
const toolName = ctx?.toolName;
|
|
17008
|
+
const args = ctx?.args;
|
|
17009
|
+
if (toolName && args) {
|
|
17010
|
+
deps.stuckDetector.recordToolCall(toolName, args);
|
|
17011
|
+
}
|
|
17012
|
+
},
|
|
17013
|
+
onAfterTool: (ctx, call, result) => {
|
|
17014
|
+
if (call.name === "bash") {
|
|
17015
|
+
deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
|
|
17016
|
+
}
|
|
17017
|
+
const toolText = String(result.output ?? "");
|
|
17018
|
+
if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
|
|
17019
|
+
deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
|
|
17020
|
+
}
|
|
17021
|
+
if (!result.success) {
|
|
17022
|
+
if (call.name === "bash" && platform4() === "win32") {
|
|
17023
|
+
const cmd = String(call.arguments?.command ?? "");
|
|
17024
|
+
const forbidden = forbiddenWindowsCommand(cmd);
|
|
17025
|
+
if (forbidden) {
|
|
17026
|
+
const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
|
|
17027
|
+
deps.forbiddenBashFailures.set(forbidden, n);
|
|
17028
|
+
if (n === 2) {
|
|
17029
|
+
deps.pendingMessages.push({
|
|
16758
17030
|
role: "user",
|
|
16759
|
-
content: `<system-summary
|
|
17031
|
+
content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`
|
|
16760
17032
|
});
|
|
16761
17033
|
}
|
|
16762
17034
|
}
|
|
16763
17035
|
}
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
const
|
|
16767
|
-
if (
|
|
16768
|
-
|
|
17036
|
+
deps.stuckDetector.recordToolError(call.name, result.output);
|
|
17037
|
+
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
17038
|
+
const alternative = deps.stuckDetector.getToolAlternative();
|
|
17039
|
+
if (actionableHints.length > 0 || alternative) {
|
|
17040
|
+
const parts = [...actionableHints];
|
|
17041
|
+
if (alternative) {
|
|
17042
|
+
parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
|
|
17043
|
+
}
|
|
17044
|
+
deps.pendingMessages.push({
|
|
16769
17045
|
role: "user",
|
|
16770
|
-
content: `<system-summary>${
|
|
17046
|
+
content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
|
|
17047
|
+
`) })}</system-summary>`
|
|
16771
17048
|
});
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
17049
|
+
}
|
|
17050
|
+
} else {
|
|
17051
|
+
deps.stuckDetector.recordToolSuccess();
|
|
17052
|
+
if (call.name === "bash" && result.success) {
|
|
17053
|
+
const cmd = String(call.arguments?.command ?? "");
|
|
17054
|
+
const testRun = detectTestResults(String(result.output ?? ""));
|
|
17055
|
+
if (testRun && testRun.failed > 0) {
|
|
17056
|
+
deps.pendingMessages.push({
|
|
16775
17057
|
role: "user",
|
|
16776
|
-
content: `<system-summary>${
|
|
17058
|
+
content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`
|
|
16777
17059
|
});
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
17060
|
+
} else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
17061
|
+
deps.pendingMessages.push({
|
|
17062
|
+
role: "user",
|
|
17063
|
+
content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`
|
|
16781
17064
|
});
|
|
16782
|
-
}
|
|
16783
|
-
|
|
16784
|
-
this.consecutivePlanWarnings = 0;
|
|
16785
|
-
}
|
|
16786
|
-
return true;
|
|
16787
|
-
},
|
|
16788
|
-
onToolCall: (ctx) => {
|
|
16789
|
-
const toolName = ctx?.toolName;
|
|
16790
|
-
const args = ctx?.args;
|
|
16791
|
-
if (toolName && args) {
|
|
16792
|
-
this.stuckDetector.recordToolCall(toolName, args);
|
|
16793
|
-
}
|
|
16794
|
-
},
|
|
16795
|
-
onAfterTool: (ctx, call, result) => {
|
|
16796
|
-
if (call.name === "bash") {
|
|
16797
|
-
this.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
|
|
16798
|
-
}
|
|
16799
|
-
const toolText = String(result.output ?? "");
|
|
16800
|
-
if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
|
|
16801
|
-
this.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
|
|
16802
|
-
}
|
|
16803
|
-
if (!result.success) {
|
|
16804
|
-
this.stuckDetector.recordToolError(call.name, result.output);
|
|
16805
|
-
const actionableHints = this.stuckDetector.getActionableHints();
|
|
16806
|
-
const alternative = this.stuckDetector.getToolAlternative();
|
|
16807
|
-
if (actionableHints.length > 0 || alternative) {
|
|
16808
|
-
const parts = [...actionableHints];
|
|
16809
|
-
if (alternative) {
|
|
16810
|
-
parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
|
|
16811
|
-
}
|
|
16812
|
-
this.pendingMessages.push({
|
|
17065
|
+
} else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
17066
|
+
deps.pendingMessages.push({
|
|
16813
17067
|
role: "user",
|
|
16814
|
-
content: `<system-summary
|
|
16815
|
-
`) })}</system-summary>`
|
|
17068
|
+
content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`
|
|
16816
17069
|
});
|
|
16817
17070
|
}
|
|
16818
|
-
} else {
|
|
16819
|
-
this.stuckDetector.recordToolSuccess();
|
|
16820
|
-
if (call.name === "bash" && result.success) {
|
|
16821
|
-
const cmd = String(call.arguments?.command ?? "");
|
|
16822
|
-
const testRun = detectTestResults(String(result.output ?? ""));
|
|
16823
|
-
if (testRun && testRun.failed > 0) {
|
|
16824
|
-
this.pendingMessages.push({
|
|
16825
|
-
role: "user",
|
|
16826
|
-
content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`
|
|
16827
|
-
});
|
|
16828
|
-
} else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
16829
|
-
this.pendingMessages.push({
|
|
16830
|
-
role: "user",
|
|
16831
|
-
content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`
|
|
16832
|
-
});
|
|
16833
|
-
} else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
16834
|
-
this.pendingMessages.push({
|
|
16835
|
-
role: "user",
|
|
16836
|
-
content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`
|
|
16837
|
-
});
|
|
16838
|
-
}
|
|
16839
|
-
}
|
|
16840
17071
|
}
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
}
|
|
17072
|
+
}
|
|
17073
|
+
if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
|
|
17074
|
+
const filePath = call.arguments?.path;
|
|
17075
|
+
if (filePath) {
|
|
17076
|
+
deps.stuckDetector.recordFileRewrite(filePath);
|
|
17077
|
+
if (deps.stuckDetector.hasExcessiveRewrites()) {
|
|
17078
|
+
const file = deps.stuckDetector.getExcessiveRewriteFile();
|
|
17079
|
+
const count = deps.stuckDetector.getFileRewriteCount(file);
|
|
17080
|
+
if (ctx.onMeta) {
|
|
17081
|
+
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
17082
|
+
file,
|
|
17083
|
+
count: String(count)
|
|
17084
|
+
}));
|
|
16854
17085
|
}
|
|
16855
17086
|
}
|
|
16856
|
-
this.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
16857
17087
|
}
|
|
17088
|
+
deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
17089
|
+
}
|
|
17090
|
+
}
|
|
17091
|
+
};
|
|
17092
|
+
}
|
|
17093
|
+
var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10;
|
|
17094
|
+
var init_execution_plugin = __esm(() => {
|
|
17095
|
+
init_i18n();
|
|
17096
|
+
init_bash();
|
|
17097
|
+
init_windows_commands();
|
|
17098
|
+
});
|
|
17099
|
+
|
|
17100
|
+
// src/modules/execution/module.ts
|
|
17101
|
+
import { existsSync as existsSync30, readFileSync as readFileSync17 } from "fs";
|
|
17102
|
+
import { resolve as resolve18 } from "path";
|
|
17103
|
+
|
|
17104
|
+
class ExecutionModule {
|
|
17105
|
+
name = "execution";
|
|
17106
|
+
tracker = null;
|
|
17107
|
+
verifier;
|
|
17108
|
+
stuckDetector;
|
|
17109
|
+
auditor;
|
|
17110
|
+
store;
|
|
17111
|
+
baseDir;
|
|
17112
|
+
_auditSkipsRemaining = 0;
|
|
17113
|
+
pendingMessages = [];
|
|
17114
|
+
forbiddenBashFailures = new Map;
|
|
17115
|
+
state = {
|
|
17116
|
+
lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
|
|
17117
|
+
consecutivePlanWarnings: 0,
|
|
17118
|
+
lastStepId: -1,
|
|
17119
|
+
stuckNotified: false,
|
|
17120
|
+
depsGateHints: new Map
|
|
17121
|
+
};
|
|
17122
|
+
trackerRef = (() => {
|
|
17123
|
+
const self = this;
|
|
17124
|
+
return {
|
|
17125
|
+
get current() {
|
|
17126
|
+
return self.tracker;
|
|
17127
|
+
},
|
|
17128
|
+
set current(v) {
|
|
17129
|
+
self.tracker = v;
|
|
16858
17130
|
}
|
|
16859
17131
|
};
|
|
17132
|
+
})();
|
|
17133
|
+
constructor(baseDir, stuckThreshold = 6) {
|
|
17134
|
+
this.baseDir = baseDir;
|
|
17135
|
+
this.verifier = new StepVerifier(baseDir);
|
|
17136
|
+
this.stuckDetector = new StuckDetector(stuckThreshold);
|
|
17137
|
+
this.auditor = new Auditor(baseDir);
|
|
17138
|
+
this.store = new PlanStore(baseDir);
|
|
17139
|
+
}
|
|
17140
|
+
setPlan(plan) {
|
|
17141
|
+
this.tracker = new PlanTracker(plan);
|
|
17142
|
+
this.store.saveActive(plan);
|
|
17143
|
+
this._auditSkipsRemaining = 0;
|
|
17144
|
+
}
|
|
17145
|
+
restorePlan() {
|
|
17146
|
+
const plan = this.store.loadActive();
|
|
17147
|
+
if (!plan)
|
|
17148
|
+
return false;
|
|
17149
|
+
this.tracker = new PlanTracker(plan);
|
|
17150
|
+
this.tracker.syncCurrentStep();
|
|
17151
|
+
this._auditSkipsRemaining = 1;
|
|
17152
|
+
return true;
|
|
17153
|
+
}
|
|
17154
|
+
getTracker() {
|
|
17155
|
+
return this.tracker;
|
|
17156
|
+
}
|
|
17157
|
+
getStore() {
|
|
17158
|
+
return this.store;
|
|
17159
|
+
}
|
|
17160
|
+
getStuckDetector() {
|
|
17161
|
+
return this.stuckDetector;
|
|
17162
|
+
}
|
|
17163
|
+
async runFinalAudit() {
|
|
17164
|
+
if (!this.tracker)
|
|
17165
|
+
return null;
|
|
17166
|
+
if (this._auditSkipsRemaining > 0) {
|
|
17167
|
+
this._auditSkipsRemaining--;
|
|
17168
|
+
return null;
|
|
17169
|
+
}
|
|
17170
|
+
const plan = this.tracker.getPlan();
|
|
17171
|
+
const audit = await this.auditor.audit(plan);
|
|
17172
|
+
const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
|
|
17173
|
+
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
17174
|
+
const passed = audit.passed && pendingSteps.length === 0;
|
|
17175
|
+
return {
|
|
17176
|
+
passed,
|
|
17177
|
+
done,
|
|
17178
|
+
total: plan.steps.length,
|
|
17179
|
+
pendingSteps,
|
|
17180
|
+
missingFiles: audit.missingFiles,
|
|
17181
|
+
summary: audit.summary
|
|
17182
|
+
};
|
|
17183
|
+
}
|
|
17184
|
+
getSystemPromptBlock() {
|
|
17185
|
+
if (!this.tracker)
|
|
17186
|
+
return null;
|
|
17187
|
+
return {
|
|
17188
|
+
content: this.tracker.toPromptBlock(),
|
|
17189
|
+
priority: "critical",
|
|
17190
|
+
essential: true,
|
|
17191
|
+
estimatedTokens: 200
|
|
17192
|
+
};
|
|
17193
|
+
}
|
|
17194
|
+
getToolDefinitions() {
|
|
17195
|
+
return createPlanToolDefinitions({
|
|
17196
|
+
store: this.store,
|
|
17197
|
+
verifier: this.verifier,
|
|
17198
|
+
trackerRef: this.trackerRef,
|
|
17199
|
+
preserveActive: () => this.preserveActive(),
|
|
17200
|
+
setPlan: (p) => this.setPlan(p),
|
|
17201
|
+
missingStepDeliverables: (s) => this.missingStepDeliverables(s),
|
|
17202
|
+
stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
|
|
17203
|
+
parseKinds: (a, st) => this.parseKinds(a, st),
|
|
17204
|
+
getTaskText: (c) => this.getTaskText(c),
|
|
17205
|
+
baseDir: this.baseDir
|
|
17206
|
+
});
|
|
17207
|
+
}
|
|
17208
|
+
getPlugin() {
|
|
17209
|
+
return createExecutionPlugin({
|
|
17210
|
+
stuckDetector: this.stuckDetector,
|
|
17211
|
+
trackerRef: this.trackerRef,
|
|
17212
|
+
pendingMessages: this.pendingMessages,
|
|
17213
|
+
forbiddenBashFailures: this.forbiddenBashFailures,
|
|
17214
|
+
state: this.state,
|
|
17215
|
+
checkPlanAlignment: (call) => this.checkPlanAlignment(call),
|
|
17216
|
+
advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl)
|
|
17217
|
+
});
|
|
16860
17218
|
}
|
|
16861
17219
|
preserveActive() {
|
|
16862
17220
|
if (!this.tracker)
|
|
@@ -16887,19 +17245,18 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
16887
17245
|
];
|
|
16888
17246
|
if (allowedAlways.includes(call.name))
|
|
16889
17247
|
return null;
|
|
16890
|
-
const stepPaths = (step.description
|
|
17248
|
+
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map((p) => p.toLowerCase());
|
|
16891
17249
|
if (stepPaths.length === 0)
|
|
16892
17250
|
return null;
|
|
16893
|
-
const argStr = JSON.stringify(call.arguments)
|
|
16894
|
-
const callPaths = (argStr
|
|
17251
|
+
const argStr = JSON.stringify(call.arguments);
|
|
17252
|
+
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map((p) => p.toLowerCase());
|
|
16895
17253
|
if (callPaths.length === 0)
|
|
16896
17254
|
return null;
|
|
16897
17255
|
const plan = this.tracker.getPlan();
|
|
16898
17256
|
const finishedPaths = new Set;
|
|
16899
17257
|
for (const s of plan.steps) {
|
|
16900
17258
|
if (s.status === "done" || s.status === "skipped") {
|
|
16901
|
-
|
|
16902
|
-
ps.forEach((p) => finishedPaths.add(p));
|
|
17259
|
+
extractFileLikeTokens(stripUrls(s.description)).map((p) => p.toLowerCase()).forEach((p) => finishedPaths.add(p));
|
|
16903
17260
|
}
|
|
16904
17261
|
}
|
|
16905
17262
|
const offPath = callPaths.some((p) => {
|
|
@@ -16921,7 +17278,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
16921
17278
|
if (!step)
|
|
16922
17279
|
return;
|
|
16923
17280
|
const stepText = step.description.toLowerCase();
|
|
16924
|
-
const stepPaths = step.description
|
|
17281
|
+
const stepPaths = extractFileLikeTokens(stripUrls(step.description)) || [];
|
|
16925
17282
|
const isDepsStep = stepText.includes("install") || stepText.includes("зависим") || stepText.includes("init") || stepText.includes("инициализац");
|
|
16926
17283
|
if (isDepsStep) {
|
|
16927
17284
|
const lockFiles = [
|
|
@@ -16936,11 +17293,11 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
16936
17293
|
"poetry.lock",
|
|
16937
17294
|
"requirements.txt"
|
|
16938
17295
|
];
|
|
16939
|
-
const hasLockFile = lockFiles.some((f) =>
|
|
17296
|
+
const hasLockFile = lockFiles.some((f) => existsSync30(resolve18(this.baseDir, f)));
|
|
16940
17297
|
if (!hasLockFile) {
|
|
16941
17298
|
if (contextManager) {
|
|
16942
|
-
const hints = this.depsGateHints.get(step.id) || 0;
|
|
16943
|
-
this.depsGateHints.set(step.id, hints + 1);
|
|
17299
|
+
const hints = this.state.depsGateHints.get(step.id) || 0;
|
|
17300
|
+
this.state.depsGateHints.set(step.id, hints + 1);
|
|
16944
17301
|
const force = hints >= 1;
|
|
16945
17302
|
const content = force ? t("exec.step_gate_deps_force", { step: String(step.id) }) : t("exec.step_gate_deps", {
|
|
16946
17303
|
step: String(step.id),
|
|
@@ -16956,24 +17313,28 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
16956
17313
|
}
|
|
16957
17314
|
if (stepPaths.length === 0)
|
|
16958
17315
|
return;
|
|
16959
|
-
const allExist = stepPaths.every((p) =>
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
16963
|
-
for (const p of stepPaths) {
|
|
16964
|
-
try {
|
|
16965
|
-
const content = readFileSync17(resolve18(this.baseDir, p), "utf-8");
|
|
16966
|
-
if (content.trim().length < 10) {
|
|
16967
|
-
emptyFiles.push(p);
|
|
16968
|
-
}
|
|
16969
|
-
} catch {}
|
|
16970
|
-
}
|
|
16971
|
-
if (emptyFiles.length > 0 && contextManager) {
|
|
16972
|
-
contextManager.addMessage({
|
|
16973
|
-
role: "user",
|
|
16974
|
-
content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`
|
|
16975
|
-
});
|
|
17316
|
+
const allExist = stepPaths.every((p) => existsSync30(resolve18(this.baseDir, p)));
|
|
17317
|
+
const allGone = stepPaths.every((p) => !existsSync30(resolve18(this.baseDir, p)));
|
|
17318
|
+
const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
|
|
17319
|
+
if (!satisfied)
|
|
16976
17320
|
return;
|
|
17321
|
+
if (step.kind !== "delete") {
|
|
17322
|
+
const emptyFiles = [];
|
|
17323
|
+
for (const p of stepPaths) {
|
|
17324
|
+
try {
|
|
17325
|
+
const content = readFileSync17(resolve18(this.baseDir, p), "utf-8");
|
|
17326
|
+
if (content.trim().length < 10) {
|
|
17327
|
+
emptyFiles.push(p);
|
|
17328
|
+
}
|
|
17329
|
+
} catch {}
|
|
17330
|
+
}
|
|
17331
|
+
if (emptyFiles.length > 0 && contextManager) {
|
|
17332
|
+
contextManager.addMessage({
|
|
17333
|
+
role: "user",
|
|
17334
|
+
content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`
|
|
17335
|
+
});
|
|
17336
|
+
return;
|
|
17337
|
+
}
|
|
16977
17338
|
}
|
|
16978
17339
|
this.tracker?.updateStepStatus(step.id, "done");
|
|
16979
17340
|
const hadNext = this.tracker?.advance() ?? false;
|
|
@@ -17000,18 +17361,49 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
|
|
|
17000
17361
|
return [];
|
|
17001
17362
|
return tokens.filter((p) => !findExistingFile(this.baseDir, p));
|
|
17002
17363
|
}
|
|
17364
|
+
stillExistingDeliverables(step) {
|
|
17365
|
+
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
17366
|
+
if (tokens.length === 0)
|
|
17367
|
+
return [];
|
|
17368
|
+
return tokens.filter((p) => findExistingFile(this.baseDir, p));
|
|
17369
|
+
}
|
|
17370
|
+
parseKinds(args, steps) {
|
|
17371
|
+
if (args.kinds === undefined)
|
|
17372
|
+
return { kinds: null, error: null };
|
|
17373
|
+
if (!Array.isArray(args.kinds)) {
|
|
17374
|
+
return { kinds: null, error: t("plan.kinds_not_array") };
|
|
17375
|
+
}
|
|
17376
|
+
const kindsRaw = args.kinds.map(String);
|
|
17377
|
+
if (kindsRaw.length !== steps.length) {
|
|
17378
|
+
return { kinds: null, error: t("plan.kinds_mismatch") };
|
|
17379
|
+
}
|
|
17380
|
+
const invalid = kindsRaw.filter((k) => k !== "create" && k !== "delete");
|
|
17381
|
+
if (invalid.length > 0) {
|
|
17382
|
+
return {
|
|
17383
|
+
kinds: null,
|
|
17384
|
+
error: t("plan.kinds_invalid", {
|
|
17385
|
+
kinds: invalid.join(", ")
|
|
17386
|
+
})
|
|
17387
|
+
};
|
|
17388
|
+
}
|
|
17389
|
+
return { kinds: kindsRaw, error: null };
|
|
17390
|
+
}
|
|
17003
17391
|
getTaskText(ctx) {
|
|
17004
17392
|
const manager = ctx?.contextManager;
|
|
17005
17393
|
if (!manager)
|
|
17006
17394
|
return "";
|
|
17007
17395
|
const history = manager.getActiveHistory();
|
|
17008
|
-
const firstUser = history.find((m) =>
|
|
17396
|
+
const firstUser = history.find((m) => {
|
|
17397
|
+
if (m.role !== "user")
|
|
17398
|
+
return false;
|
|
17399
|
+
const text = getMessageText(m.content);
|
|
17400
|
+
return !text.startsWith("<system-summary>") && !text.includes("[Compressed:");
|
|
17401
|
+
});
|
|
17009
17402
|
if (!firstUser)
|
|
17010
17403
|
return "";
|
|
17011
17404
|
return getMessageText(firstUser.content).trim();
|
|
17012
17405
|
}
|
|
17013
17406
|
}
|
|
17014
|
-
var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10;
|
|
17015
17407
|
var init_module = __esm(() => {
|
|
17016
17408
|
init_i18n();
|
|
17017
17409
|
init_tracker();
|
|
@@ -17019,20 +17411,20 @@ var init_module = __esm(() => {
|
|
|
17019
17411
|
init_stuck_detector();
|
|
17020
17412
|
init_auditor();
|
|
17021
17413
|
init_plan_store();
|
|
17022
|
-
init_plan_coverage();
|
|
17023
|
-
init_bash();
|
|
17024
17414
|
init_js_identifiers();
|
|
17415
|
+
init_plan_tool();
|
|
17416
|
+
init_execution_plugin();
|
|
17025
17417
|
});
|
|
17026
17418
|
|
|
17027
17419
|
// src/modules/security/session-encryption.ts
|
|
17028
17420
|
import {
|
|
17029
17421
|
readFileSync as readFileSync18,
|
|
17030
17422
|
writeFileSync as writeFileSync11,
|
|
17031
|
-
existsSync as
|
|
17032
|
-
readdirSync as
|
|
17423
|
+
existsSync as existsSync31,
|
|
17424
|
+
readdirSync as readdirSync11,
|
|
17033
17425
|
unlinkSync as unlinkSync4
|
|
17034
17426
|
} from "fs";
|
|
17035
|
-
import { join as
|
|
17427
|
+
import { join as join26 } from "path";
|
|
17036
17428
|
import { homedir as homedir8 } from "os";
|
|
17037
17429
|
|
|
17038
17430
|
class SessionFileEncryptor {
|
|
@@ -17041,7 +17433,7 @@ class SessionFileEncryptor {
|
|
|
17041
17433
|
constructor(config) {
|
|
17042
17434
|
this.config = { ...DEFAULT_SESSION_ENCRYPTION, ...config };
|
|
17043
17435
|
this.encryptor = new ConfigEncryptor({
|
|
17044
|
-
keyPath: config?.keyPath ||
|
|
17436
|
+
keyPath: config?.keyPath || join26(homedir8(), ".mma", ".session-encryption-key")
|
|
17045
17437
|
});
|
|
17046
17438
|
}
|
|
17047
17439
|
isEnabled() {
|
|
@@ -17125,10 +17517,10 @@ class SessionFileEncryptor {
|
|
|
17125
17517
|
encryptSessionDirectory(sessionDir) {
|
|
17126
17518
|
if (!this.config.enabled)
|
|
17127
17519
|
return;
|
|
17128
|
-
const files =
|
|
17520
|
+
const files = readdirSync11(sessionDir);
|
|
17129
17521
|
for (const file of files) {
|
|
17130
|
-
const filePath =
|
|
17131
|
-
if (
|
|
17522
|
+
const filePath = join26(sessionDir, file);
|
|
17523
|
+
if (existsSync31(filePath) && !file.endsWith(".enc")) {
|
|
17132
17524
|
try {
|
|
17133
17525
|
const content = readFileSync18(filePath, "utf8");
|
|
17134
17526
|
const encrypted = this.encryptFileContent(content);
|
|
@@ -17141,10 +17533,10 @@ class SessionFileEncryptor {
|
|
|
17141
17533
|
decryptSessionDirectory(sessionDir) {
|
|
17142
17534
|
if (!this.config.enabled)
|
|
17143
17535
|
return;
|
|
17144
|
-
const files =
|
|
17536
|
+
const files = readdirSync11(sessionDir);
|
|
17145
17537
|
for (const file of files) {
|
|
17146
17538
|
if (file.endsWith(".enc")) {
|
|
17147
|
-
const encFilePath =
|
|
17539
|
+
const encFilePath = join26(sessionDir, file);
|
|
17148
17540
|
const decFilePath = encFilePath.slice(0, -4);
|
|
17149
17541
|
try {
|
|
17150
17542
|
const content = readFileSync18(encFilePath, "utf8");
|
|
@@ -17169,15 +17561,15 @@ var init_session_encryption = __esm(() => {
|
|
|
17169
17561
|
|
|
17170
17562
|
// src/modules/session/store.ts
|
|
17171
17563
|
import {
|
|
17172
|
-
existsSync as
|
|
17564
|
+
existsSync as existsSync32,
|
|
17173
17565
|
mkdirSync as mkdirSync15,
|
|
17174
|
-
readdirSync as
|
|
17566
|
+
readdirSync as readdirSync12,
|
|
17175
17567
|
readFileSync as readFileSync19,
|
|
17176
17568
|
rmSync as rmSync2,
|
|
17177
17569
|
writeFileSync as writeFileSync12,
|
|
17178
17570
|
appendFileSync as appendFileSync6
|
|
17179
17571
|
} from "fs";
|
|
17180
|
-
import { join as
|
|
17572
|
+
import { join as join27 } from "path";
|
|
17181
17573
|
import { gzipSync } from "zlib";
|
|
17182
17574
|
|
|
17183
17575
|
class SessionStore {
|
|
@@ -17191,7 +17583,7 @@ class SessionStore {
|
|
|
17191
17583
|
}
|
|
17192
17584
|
}
|
|
17193
17585
|
getSessionDir(id) {
|
|
17194
|
-
return
|
|
17586
|
+
return join27(this.baseDir, id);
|
|
17195
17587
|
}
|
|
17196
17588
|
updateEncryption(config) {
|
|
17197
17589
|
if (config?.enabled) {
|
|
@@ -17207,19 +17599,19 @@ class SessionStore {
|
|
|
17207
17599
|
mkdirSync15(this.baseDir, { recursive: true });
|
|
17208
17600
|
}
|
|
17209
17601
|
sessionDir(id) {
|
|
17210
|
-
return
|
|
17602
|
+
return join27(this.baseDir, id);
|
|
17211
17603
|
}
|
|
17212
17604
|
metaPath(id) {
|
|
17213
|
-
return
|
|
17605
|
+
return join27(this.sessionDir(id), "meta.json");
|
|
17214
17606
|
}
|
|
17215
17607
|
historyPath(id) {
|
|
17216
|
-
return
|
|
17608
|
+
return join27(this.sessionDir(id), "history.jsonl");
|
|
17217
17609
|
}
|
|
17218
17610
|
sessionLogPath(id) {
|
|
17219
|
-
return
|
|
17611
|
+
return join27(this.sessionDir(id), "session.jsonl");
|
|
17220
17612
|
}
|
|
17221
17613
|
sessionExists(id) {
|
|
17222
|
-
return
|
|
17614
|
+
return existsSync32(this.metaPath(id));
|
|
17223
17615
|
}
|
|
17224
17616
|
saveMeta(id, meta) {
|
|
17225
17617
|
this._metaCache.set(id, meta);
|
|
@@ -17237,7 +17629,7 @@ class SessionStore {
|
|
|
17237
17629
|
if (cached)
|
|
17238
17630
|
return cached;
|
|
17239
17631
|
const path = this.metaPath(id);
|
|
17240
|
-
if (!
|
|
17632
|
+
if (!existsSync32(path))
|
|
17241
17633
|
return null;
|
|
17242
17634
|
try {
|
|
17243
17635
|
const raw = readFileSync19(path, "utf-8");
|
|
@@ -17269,7 +17661,7 @@ class SessionStore {
|
|
|
17269
17661
|
}
|
|
17270
17662
|
loadHistory(id) {
|
|
17271
17663
|
const path = this.historyPath(id);
|
|
17272
|
-
if (!
|
|
17664
|
+
if (!existsSync32(path))
|
|
17273
17665
|
return [];
|
|
17274
17666
|
try {
|
|
17275
17667
|
const raw = readFileSync19(path, "utf-8");
|
|
@@ -17310,7 +17702,7 @@ class SessionStore {
|
|
|
17310
17702
|
}
|
|
17311
17703
|
loadSessionLog(id) {
|
|
17312
17704
|
const path = this.sessionLogPath(id);
|
|
17313
|
-
if (!
|
|
17705
|
+
if (!existsSync32(path))
|
|
17314
17706
|
return [];
|
|
17315
17707
|
try {
|
|
17316
17708
|
const raw = readFileSync19(path, "utf-8");
|
|
@@ -17338,9 +17730,9 @@ class SessionStore {
|
|
|
17338
17730
|
}
|
|
17339
17731
|
}
|
|
17340
17732
|
listSessions() {
|
|
17341
|
-
if (!
|
|
17733
|
+
if (!existsSync32(this.baseDir))
|
|
17342
17734
|
return [];
|
|
17343
|
-
const entries =
|
|
17735
|
+
const entries = readdirSync12(this.baseDir, { withFileTypes: true });
|
|
17344
17736
|
const sessions = [];
|
|
17345
17737
|
for (const entry of entries) {
|
|
17346
17738
|
if (entry.isDirectory()) {
|
|
@@ -17355,7 +17747,7 @@ class SessionStore {
|
|
|
17355
17747
|
deleteSession(id) {
|
|
17356
17748
|
this._metaCache.delete(id);
|
|
17357
17749
|
const dir = this.sessionDir(id);
|
|
17358
|
-
if (
|
|
17750
|
+
if (existsSync32(dir)) {
|
|
17359
17751
|
rmSync2(dir, { recursive: true, force: true });
|
|
17360
17752
|
}
|
|
17361
17753
|
}
|
|
@@ -17367,10 +17759,10 @@ class SessionStore {
|
|
|
17367
17759
|
const updatedAt = new Date(session2.updatedAt);
|
|
17368
17760
|
if (updatedAt < thirtyDaysAgo) {
|
|
17369
17761
|
const historyPath = this.historyPath(session2.id);
|
|
17370
|
-
if (
|
|
17762
|
+
if (existsSync32(historyPath)) {
|
|
17371
17763
|
const content = readFileSync19(historyPath, "utf-8");
|
|
17372
17764
|
const compressed = gzipSync(content);
|
|
17373
|
-
const gzPath =
|
|
17765
|
+
const gzPath = join27(this.baseDir, `${session2.id}.jsonl.gz`);
|
|
17374
17766
|
writeFileSync12(gzPath, compressed);
|
|
17375
17767
|
rmSync2(historyPath);
|
|
17376
17768
|
}
|
|
@@ -17580,9 +17972,9 @@ class ProfileCompressor {
|
|
|
17580
17972
|
}
|
|
17581
17973
|
|
|
17582
17974
|
// src/modules/user-profile/profile.ts
|
|
17583
|
-
import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as
|
|
17584
|
-
import { join as
|
|
17585
|
-
import { homedir as homedir9, hostname, platform as
|
|
17975
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync33, mkdirSync as mkdirSync16 } from "fs";
|
|
17976
|
+
import { join as join28 } from "path";
|
|
17977
|
+
import { homedir as homedir9, hostname, platform as platform5, type } from "os";
|
|
17586
17978
|
import { env } from "process";
|
|
17587
17979
|
|
|
17588
17980
|
class UserProfile {
|
|
@@ -17594,7 +17986,7 @@ class UserProfile {
|
|
|
17594
17986
|
}
|
|
17595
17987
|
collect() {
|
|
17596
17988
|
this.info = {
|
|
17597
|
-
platform:
|
|
17989
|
+
platform: platform5(),
|
|
17598
17990
|
os: `${type()} ${hostname()}`,
|
|
17599
17991
|
hostname: hostname(),
|
|
17600
17992
|
shell: env.SHELL || env.ComSpec || "unknown",
|
|
@@ -17605,14 +17997,14 @@ class UserProfile {
|
|
|
17605
17997
|
return this.info;
|
|
17606
17998
|
}
|
|
17607
17999
|
save() {
|
|
17608
|
-
if (!
|
|
18000
|
+
if (!existsSync33(this.profileDir)) {
|
|
17609
18001
|
mkdirSync16(this.profileDir, { recursive: true });
|
|
17610
18002
|
}
|
|
17611
|
-
writeFileSync13(
|
|
18003
|
+
writeFileSync13(join28(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
|
|
17612
18004
|
}
|
|
17613
18005
|
load() {
|
|
17614
|
-
const path =
|
|
17615
|
-
if (!
|
|
18006
|
+
const path = join28(this.profileDir, "profile.json");
|
|
18007
|
+
if (!existsSync33(path))
|
|
17616
18008
|
return null;
|
|
17617
18009
|
try {
|
|
17618
18010
|
const data = JSON.parse(readFileSync20(path, "utf-8"));
|
|
@@ -17650,21 +18042,21 @@ class UserProfile {
|
|
|
17650
18042
|
var init_profile = () => {};
|
|
17651
18043
|
|
|
17652
18044
|
// src/modules/skills/loader.ts
|
|
17653
|
-
import { readdirSync as
|
|
17654
|
-
import { join as
|
|
18045
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync21, existsSync as existsSync34, statSync as statSync6 } from "fs";
|
|
18046
|
+
import { join as join29 } from "path";
|
|
17655
18047
|
|
|
17656
18048
|
class SkillsLoader {
|
|
17657
18049
|
loadFromDir(dirPath) {
|
|
17658
|
-
if (!
|
|
18050
|
+
if (!existsSync34(dirPath))
|
|
17659
18051
|
return [];
|
|
17660
18052
|
const skills = [];
|
|
17661
18053
|
this.scanDir(dirPath, skills);
|
|
17662
18054
|
return skills;
|
|
17663
18055
|
}
|
|
17664
18056
|
scanDir(dirPath, skills) {
|
|
17665
|
-
const entries =
|
|
18057
|
+
const entries = readdirSync13(dirPath);
|
|
17666
18058
|
for (const entry of entries) {
|
|
17667
|
-
const fullPath =
|
|
18059
|
+
const fullPath = join29(dirPath, entry);
|
|
17668
18060
|
const stat = statSync6(fullPath);
|
|
17669
18061
|
if (stat.isDirectory()) {
|
|
17670
18062
|
this.scanDir(fullPath, skills);
|
|
@@ -17908,10 +18300,10 @@ var init_browser2 = __esm(() => {
|
|
|
17908
18300
|
});
|
|
17909
18301
|
|
|
17910
18302
|
// src/modules/lsp/command.ts
|
|
17911
|
-
import { delimiter, join as
|
|
17912
|
-
import { existsSync as
|
|
17913
|
-
import { platform as
|
|
17914
|
-
function resolveSpawnCommand(command, platformName =
|
|
18303
|
+
import { delimiter, join as join30 } from "path";
|
|
18304
|
+
import { existsSync as existsSync35 } from "fs";
|
|
18305
|
+
import { platform as platform6 } from "os";
|
|
18306
|
+
function resolveSpawnCommand(command, platformName = platform6(), pathEnv = process.env.PATH ?? "") {
|
|
17915
18307
|
if (platformName !== "win32")
|
|
17916
18308
|
return command;
|
|
17917
18309
|
if (command.includes("/") || command.includes("\\") || WIN_EXTS.some((ext) => command.toLowerCase().endsWith(ext))) {
|
|
@@ -17920,8 +18312,8 @@ function resolveSpawnCommand(command, platformName = platform5(), pathEnv = proc
|
|
|
17920
18312
|
const dirs = pathEnv.split(delimiter).filter(Boolean);
|
|
17921
18313
|
for (const dir of dirs) {
|
|
17922
18314
|
for (const ext of WIN_EXTS) {
|
|
17923
|
-
const candidate =
|
|
17924
|
-
if (
|
|
18315
|
+
const candidate = join30(dir, `${command}${ext}`);
|
|
18316
|
+
if (existsSync35(candidate))
|
|
17925
18317
|
return `${command}${ext}`;
|
|
17926
18318
|
}
|
|
17927
18319
|
}
|
|
@@ -17948,7 +18340,7 @@ import {
|
|
|
17948
18340
|
execSync as execSync2
|
|
17949
18341
|
} from "child_process";
|
|
17950
18342
|
import { resolve as resolve19 } from "path";
|
|
17951
|
-
import { platform as
|
|
18343
|
+
import { platform as platform7 } from "os";
|
|
17952
18344
|
|
|
17953
18345
|
class LspClient {
|
|
17954
18346
|
process = null;
|
|
@@ -17960,11 +18352,11 @@ class LspClient {
|
|
|
17960
18352
|
diagnosticsResolve = null;
|
|
17961
18353
|
diagnosticsTimer = null;
|
|
17962
18354
|
initialized = false;
|
|
17963
|
-
async checkFile(filePath, baseDir, config) {
|
|
18355
|
+
async checkFile(filePath, baseDir, config, projectRoot = baseDir) {
|
|
17964
18356
|
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
17965
18357
|
try {
|
|
17966
|
-
await this.startServer(config,
|
|
17967
|
-
const rootUri = this.pathToUri(
|
|
18358
|
+
await this.startServer(config, projectRoot);
|
|
18359
|
+
const rootUri = this.pathToUri(projectRoot);
|
|
17968
18360
|
const initResult = await this.sendRequest("initialize", {
|
|
17969
18361
|
processId: process.pid,
|
|
17970
18362
|
rootUri,
|
|
@@ -18001,7 +18393,7 @@ class LspClient {
|
|
|
18001
18393
|
await this.shutdown();
|
|
18002
18394
|
}
|
|
18003
18395
|
}
|
|
18004
|
-
async startServer(config,
|
|
18396
|
+
async startServer(config, projectRoot) {
|
|
18005
18397
|
if (config.autoInstall === false) {
|
|
18006
18398
|
try {
|
|
18007
18399
|
execSync2(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
|
|
@@ -18011,13 +18403,13 @@ class LspClient {
|
|
|
18011
18403
|
}
|
|
18012
18404
|
return new Promise((resolve20, reject) => {
|
|
18013
18405
|
const args = config.args ?? [];
|
|
18014
|
-
const isWin =
|
|
18406
|
+
const isWin = platform7() === "win32";
|
|
18015
18407
|
let spawnCommand = resolveSpawnCommand(config.command);
|
|
18016
18408
|
let spawnArgs = args;
|
|
18017
18409
|
const spawnOpts = {
|
|
18018
18410
|
stdio: ["pipe", "pipe", "pipe"],
|
|
18019
18411
|
env: { ...process.env, ...config.env },
|
|
18020
|
-
cwd:
|
|
18412
|
+
cwd: projectRoot
|
|
18021
18413
|
};
|
|
18022
18414
|
if (isWin) {
|
|
18023
18415
|
spawnCommand = buildWinCommandLine(spawnCommand, args);
|
|
@@ -18190,16 +18582,21 @@ var init_client2 = __esm(() => {
|
|
|
18190
18582
|
});
|
|
18191
18583
|
|
|
18192
18584
|
// src/modules/lsp/module.ts
|
|
18193
|
-
import { existsSync as
|
|
18585
|
+
import { existsSync as existsSync36 } from "fs";
|
|
18194
18586
|
import { resolve as resolve20 } from "path";
|
|
18195
18587
|
|
|
18196
18588
|
class LspModule {
|
|
18197
18589
|
name = "lsp";
|
|
18198
18590
|
config;
|
|
18199
18591
|
client = new LspClient;
|
|
18592
|
+
consecutiveFailures = 0;
|
|
18593
|
+
lspDisabled = false;
|
|
18200
18594
|
constructor(config) {
|
|
18201
18595
|
this.config = { ...DEFAULT_LSP_CONFIG, ...config };
|
|
18202
18596
|
}
|
|
18597
|
+
isLspDisabled() {
|
|
18598
|
+
return this.lspDisabled;
|
|
18599
|
+
}
|
|
18203
18600
|
getPlugin() {
|
|
18204
18601
|
const self = this;
|
|
18205
18602
|
return {
|
|
@@ -18208,6 +18605,8 @@ class LspModule {
|
|
|
18208
18605
|
onAfterTool: async (_ctx, call, result) => {
|
|
18209
18606
|
if (!self.config.enabled)
|
|
18210
18607
|
return;
|
|
18608
|
+
if (self.lspDisabled)
|
|
18609
|
+
return;
|
|
18211
18610
|
if (call.name !== "write_file" && call.name !== "edit_file")
|
|
18212
18611
|
return;
|
|
18213
18612
|
if (!result.success)
|
|
@@ -18216,13 +18615,15 @@ class LspModule {
|
|
|
18216
18615
|
if (!filePath)
|
|
18217
18616
|
return;
|
|
18218
18617
|
const fullPath = resolve20(_ctx.baseDir, filePath);
|
|
18219
|
-
if (!
|
|
18618
|
+
if (!existsSync36(fullPath))
|
|
18220
18619
|
return;
|
|
18221
18620
|
const serverConfig = getServerForFile(fullPath, self.config);
|
|
18222
18621
|
if (!serverConfig)
|
|
18223
18622
|
return;
|
|
18623
|
+
const projectRoot = findProjectRoot(fullPath, _ctx.baseDir, serverConfig.workspaceMarkers ?? []);
|
|
18224
18624
|
try {
|
|
18225
|
-
const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig);
|
|
18625
|
+
const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig, projectRoot);
|
|
18626
|
+
self.consecutiveFailures = 0;
|
|
18226
18627
|
const errors = diagnostics.filter((d) => d.severity === 1);
|
|
18227
18628
|
const warnings = diagnostics.filter((d) => d.severity === 2);
|
|
18228
18629
|
if (errors.length > 0) {
|
|
@@ -18241,12 +18642,20 @@ ${items}`;
|
|
|
18241
18642
|
}
|
|
18242
18643
|
} catch (e) {
|
|
18243
18644
|
const msg = e instanceof Error ? e.message : String(e);
|
|
18645
|
+
self.consecutiveFailures++;
|
|
18244
18646
|
try {
|
|
18245
18647
|
_ctx.logger?.warn(`LSP check failed for ${filePath}: ${msg}`);
|
|
18246
18648
|
} catch {}
|
|
18247
|
-
|
|
18649
|
+
if (self.consecutiveFailures >= MAX_CONSECUTIVE_LSP_FAILURES) {
|
|
18650
|
+
self.lspDisabled = true;
|
|
18651
|
+
result.output += `
|
|
18652
|
+
|
|
18653
|
+
[LSP disabled: ${t("lsp.unavailable")}]`;
|
|
18654
|
+
} else {
|
|
18655
|
+
result.output += `
|
|
18248
18656
|
|
|
18249
18657
|
[LSP check failed]: ${msg}`;
|
|
18658
|
+
}
|
|
18250
18659
|
}
|
|
18251
18660
|
}
|
|
18252
18661
|
};
|
|
@@ -18262,10 +18671,12 @@ function formatDiagnostics(diagnostics, filePath) {
|
|
|
18262
18671
|
}).join(`
|
|
18263
18672
|
`);
|
|
18264
18673
|
}
|
|
18265
|
-
var severityLabels;
|
|
18674
|
+
var severityLabels, MAX_CONSECUTIVE_LSP_FAILURES = 3;
|
|
18266
18675
|
var init_module5 = __esm(() => {
|
|
18267
18676
|
init_client2();
|
|
18268
18677
|
init_config();
|
|
18678
|
+
init_project_root();
|
|
18679
|
+
init_i18n();
|
|
18269
18680
|
severityLabels = {
|
|
18270
18681
|
1: "error",
|
|
18271
18682
|
2: "warning",
|
|
@@ -18282,8 +18693,8 @@ var init_lsp = __esm(() => {
|
|
|
18282
18693
|
});
|
|
18283
18694
|
|
|
18284
18695
|
// src/modules/indexer/walker.ts
|
|
18285
|
-
import { readdirSync as
|
|
18286
|
-
import { join as
|
|
18696
|
+
import { readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync37, watch } from "fs";
|
|
18697
|
+
import { join as join31, relative as relative2, extname as extname5 } from "path";
|
|
18287
18698
|
|
|
18288
18699
|
class Indexer {
|
|
18289
18700
|
baseDir;
|
|
@@ -18310,18 +18721,18 @@ class Indexer {
|
|
|
18310
18721
|
let totalSize = 0;
|
|
18311
18722
|
let count = 0;
|
|
18312
18723
|
const walkDir = (dir) => {
|
|
18313
|
-
if (!
|
|
18724
|
+
if (!existsSync37(dir))
|
|
18314
18725
|
return;
|
|
18315
18726
|
let entries;
|
|
18316
18727
|
try {
|
|
18317
|
-
entries =
|
|
18728
|
+
entries = readdirSync14(dir);
|
|
18318
18729
|
} catch {
|
|
18319
18730
|
return;
|
|
18320
18731
|
}
|
|
18321
18732
|
for (const entry of entries) {
|
|
18322
18733
|
if (count >= this.MAX_FILES)
|
|
18323
18734
|
return;
|
|
18324
|
-
const fullPath =
|
|
18735
|
+
const fullPath = join31(dir, entry);
|
|
18325
18736
|
const relPath = relative2(this.baseDir, fullPath);
|
|
18326
18737
|
const stat = statSync7(fullPath);
|
|
18327
18738
|
if (stat.isDirectory()) {
|
|
@@ -18384,19 +18795,19 @@ var init_walker = __esm(() => {
|
|
|
18384
18795
|
});
|
|
18385
18796
|
|
|
18386
18797
|
// src/modules/indexer/cache.ts
|
|
18387
|
-
import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as
|
|
18388
|
-
import { join as
|
|
18798
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync38, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
|
|
18799
|
+
import { join as join32 } from "path";
|
|
18389
18800
|
|
|
18390
18801
|
class IndexCache {
|
|
18391
18802
|
cachePath;
|
|
18392
18803
|
cache = null;
|
|
18393
18804
|
constructor(cacheDir) {
|
|
18394
|
-
this.cachePath =
|
|
18805
|
+
this.cachePath = join32(cacheDir, "index-cache.json");
|
|
18395
18806
|
}
|
|
18396
18807
|
load() {
|
|
18397
18808
|
if (this.cache)
|
|
18398
18809
|
return this.cache;
|
|
18399
|
-
if (!
|
|
18810
|
+
if (!existsSync38(this.cachePath))
|
|
18400
18811
|
return null;
|
|
18401
18812
|
try {
|
|
18402
18813
|
this.cache = JSON.parse(readFileSync23(this.cachePath, "utf-8"));
|
|
@@ -18407,14 +18818,14 @@ class IndexCache {
|
|
|
18407
18818
|
}
|
|
18408
18819
|
save(result) {
|
|
18409
18820
|
this.cache = result;
|
|
18410
|
-
const dir =
|
|
18411
|
-
if (!
|
|
18821
|
+
const dir = join32(this.cachePath, "..");
|
|
18822
|
+
if (!existsSync38(dir))
|
|
18412
18823
|
mkdirSync17(dir, { recursive: true });
|
|
18413
18824
|
writeFileSync14(this.cachePath, JSON.stringify(result), "utf-8");
|
|
18414
18825
|
}
|
|
18415
18826
|
invalidate() {
|
|
18416
18827
|
this.cache = null;
|
|
18417
|
-
if (
|
|
18828
|
+
if (existsSync38(this.cachePath)) {
|
|
18418
18829
|
try {
|
|
18419
18830
|
rmSync3(this.cachePath);
|
|
18420
18831
|
} catch {}
|
|
@@ -18424,11 +18835,11 @@ class IndexCache {
|
|
|
18424
18835
|
var init_cache = () => {};
|
|
18425
18836
|
|
|
18426
18837
|
// src/modules/indexer/project-profile.ts
|
|
18427
|
-
import { readFileSync as readFileSync24, existsSync as
|
|
18428
|
-
import { join as
|
|
18838
|
+
import { readFileSync as readFileSync24, existsSync as existsSync39 } from "fs";
|
|
18839
|
+
import { join as join33 } from "path";
|
|
18429
18840
|
function detectManifest(baseDir) {
|
|
18430
18841
|
for (const manifest of MANIFEST_ORDER) {
|
|
18431
|
-
if (
|
|
18842
|
+
if (existsSync39(join33(baseDir, manifest)))
|
|
18432
18843
|
return manifest;
|
|
18433
18844
|
}
|
|
18434
18845
|
return null;
|
|
@@ -18445,7 +18856,7 @@ function cleanDependency(entry) {
|
|
|
18445
18856
|
}
|
|
18446
18857
|
function readPackageJson(baseDir) {
|
|
18447
18858
|
try {
|
|
18448
|
-
const raw = JSON.parse(readFileSync24(
|
|
18859
|
+
const raw = JSON.parse(readFileSync24(join33(baseDir, "package.json"), "utf-8"));
|
|
18449
18860
|
if (!raw || typeof raw !== "object")
|
|
18450
18861
|
return null;
|
|
18451
18862
|
const profile = {
|
|
@@ -18469,7 +18880,7 @@ function readPackageJson(baseDir) {
|
|
|
18469
18880
|
}
|
|
18470
18881
|
function readPyproject(baseDir) {
|
|
18471
18882
|
try {
|
|
18472
|
-
const content = readFileSync24(
|
|
18883
|
+
const content = readFileSync24(join33(baseDir, "pyproject.toml"), "utf-8");
|
|
18473
18884
|
const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
|
|
18474
18885
|
const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
18475
18886
|
if (nameMatch)
|
|
@@ -18485,7 +18896,7 @@ function readPyproject(baseDir) {
|
|
|
18485
18896
|
}
|
|
18486
18897
|
function readCargo(baseDir) {
|
|
18487
18898
|
try {
|
|
18488
|
-
const content = readFileSync24(
|
|
18899
|
+
const content = readFileSync24(join33(baseDir, "Cargo.toml"), "utf-8");
|
|
18489
18900
|
const profile = { runtime: "rust", deps: [], devDeps: [], scripts: {} };
|
|
18490
18901
|
const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
18491
18902
|
if (nameMatch)
|
|
@@ -18509,7 +18920,7 @@ function readCargo(baseDir) {
|
|
|
18509
18920
|
}
|
|
18510
18921
|
function readGoMod(baseDir) {
|
|
18511
18922
|
try {
|
|
18512
|
-
const content = readFileSync24(
|
|
18923
|
+
const content = readFileSync24(join33(baseDir, "go.mod"), "utf-8");
|
|
18513
18924
|
const profile = { runtime: "go", deps: [], devDeps: [], scripts: {} };
|
|
18514
18925
|
const moduleMatch = content.match(/^module\s+(\S+)/m);
|
|
18515
18926
|
if (moduleMatch)
|
|
@@ -18527,7 +18938,7 @@ function readGoMod(baseDir) {
|
|
|
18527
18938
|
}
|
|
18528
18939
|
function readRequirements(baseDir) {
|
|
18529
18940
|
try {
|
|
18530
|
-
const content = readFileSync24(
|
|
18941
|
+
const content = readFileSync24(join33(baseDir, "requirements.txt"), "utf-8");
|
|
18531
18942
|
const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
|
|
18532
18943
|
for (const line of content.split(`
|
|
18533
18944
|
`)) {
|
|
@@ -18592,7 +19003,7 @@ var init_project_profile = __esm(() => {
|
|
|
18592
19003
|
});
|
|
18593
19004
|
|
|
18594
19005
|
// src/modules/indexer/module.ts
|
|
18595
|
-
import { dirname as
|
|
19006
|
+
import { dirname as dirname11 } from "path";
|
|
18596
19007
|
|
|
18597
19008
|
class IndexerModule {
|
|
18598
19009
|
name = "indexer";
|
|
@@ -18696,22 +19107,54 @@ class IndexerModule {
|
|
|
18696
19107
|
});
|
|
18697
19108
|
const more = result.files.length > 100 ? `
|
|
18698
19109
|
${t("indexer.and_more", { count: result.files.length - 100 })}` : "";
|
|
19110
|
+
const duplicates = this.findDuplicateBasenames(result);
|
|
18699
19111
|
return [
|
|
18700
19112
|
`${t("indexer.map_header")} (${this.baseDir})`,
|
|
18701
19113
|
summary,
|
|
18702
19114
|
...stackLine ? [stackLine] : [],
|
|
18703
19115
|
`${t("indexer.top_directories")}: ${topDirs}`,
|
|
19116
|
+
...duplicates.length > 0 ? [t("indexer.duplicates", { names: duplicates.join(", ") })] : [],
|
|
18704
19117
|
`${t("indexer.files")}:` + (fileLines.length > 0 ? "" : " " + t("indexer.empty")),
|
|
18705
19118
|
...fileLines,
|
|
18706
19119
|
more
|
|
18707
19120
|
].join(`
|
|
18708
19121
|
`);
|
|
18709
19122
|
}
|
|
19123
|
+
findDuplicateBasenames(result) {
|
|
19124
|
+
const byDir = {};
|
|
19125
|
+
for (const f of result.files) {
|
|
19126
|
+
const path = f.path.replace(/\\/g, "/");
|
|
19127
|
+
const idx = path.lastIndexOf("/");
|
|
19128
|
+
const dir = idx >= 0 ? path.slice(0, idx) : "";
|
|
19129
|
+
const name = path.slice(idx + 1);
|
|
19130
|
+
const dot = name.lastIndexOf(".");
|
|
19131
|
+
if (dot <= 0)
|
|
19132
|
+
continue;
|
|
19133
|
+
const base = name.slice(0, dot);
|
|
19134
|
+
const ext = name.slice(dot + 1);
|
|
19135
|
+
if (!byDir[dir])
|
|
19136
|
+
byDir[dir] = new Map;
|
|
19137
|
+
const list = byDir[dir].get(base) ?? [];
|
|
19138
|
+
list.push(ext);
|
|
19139
|
+
byDir[dir].set(base, list);
|
|
19140
|
+
}
|
|
19141
|
+
const dups = [];
|
|
19142
|
+
for (const dir of Object.keys(byDir).sort()) {
|
|
19143
|
+
for (const [base, exts] of byDir[dir]) {
|
|
19144
|
+
if (exts.length < 2)
|
|
19145
|
+
continue;
|
|
19146
|
+
const uniqueExts = [...new Set(exts)].sort();
|
|
19147
|
+
const dirPrefix = dir ? `${dir}/` : "";
|
|
19148
|
+
dups.push(`${dirPrefix}${base}.{${uniqueExts.join(",")}}`);
|
|
19149
|
+
}
|
|
19150
|
+
}
|
|
19151
|
+
return dups;
|
|
19152
|
+
}
|
|
18710
19153
|
getDirectoryCounts(result) {
|
|
18711
19154
|
const counts = {};
|
|
18712
19155
|
for (const f of result.files) {
|
|
18713
19156
|
const normalized = f.path.replace(/\\/g, "/");
|
|
18714
|
-
const dir =
|
|
19157
|
+
const dir = dirname11(normalized);
|
|
18715
19158
|
const key = dir === "." ? "(root)" : dir;
|
|
18716
19159
|
counts[key] = (counts[key] || 0) + 1;
|
|
18717
19160
|
}
|
|
@@ -18949,27 +19392,65 @@ var init_mcp = __esm(() => {
|
|
|
18949
19392
|
|
|
18950
19393
|
// src/modules/memory/module.ts
|
|
18951
19394
|
import { homedir as homedir10 } from "os";
|
|
18952
|
-
import { join as
|
|
19395
|
+
import { join as join34 } from "path";
|
|
18953
19396
|
|
|
18954
19397
|
class MemoryModule {
|
|
18955
19398
|
name = "memory";
|
|
18956
19399
|
store;
|
|
18957
|
-
constructor(
|
|
18958
|
-
|
|
18959
|
-
|
|
19400
|
+
constructor(storeOrDir) {
|
|
19401
|
+
if (storeOrDir instanceof MemoryStore) {
|
|
19402
|
+
this.store = storeOrDir;
|
|
19403
|
+
} else {
|
|
19404
|
+
const dir = storeOrDir || join34(homedir10(), ".mma", "memory");
|
|
19405
|
+
this.store = new MemoryStore(dir);
|
|
19406
|
+
}
|
|
18960
19407
|
}
|
|
18961
19408
|
getSystemPromptBlock() {
|
|
19409
|
+
const parts = [];
|
|
18962
19410
|
const prefs = this.store.getPreferences();
|
|
18963
|
-
if (Object.keys(prefs).length
|
|
19411
|
+
if (Object.keys(prefs).length > 0) {
|
|
19412
|
+
const prefStr = Object.entries(prefs).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
19413
|
+
parts.push(`User preferences: ${prefStr}`);
|
|
19414
|
+
}
|
|
19415
|
+
const memoryTail = this.getMemoryTail();
|
|
19416
|
+
if (memoryTail)
|
|
19417
|
+
parts.push(memoryTail);
|
|
19418
|
+
if (parts.length === 0)
|
|
18964
19419
|
return null;
|
|
18965
|
-
const
|
|
19420
|
+
const content = parts.join(`
|
|
19421
|
+
|
|
19422
|
+
`);
|
|
18966
19423
|
return {
|
|
18967
|
-
content
|
|
19424
|
+
content,
|
|
18968
19425
|
priority: "normal",
|
|
18969
19426
|
essential: false,
|
|
18970
|
-
estimatedTokens: Math.ceil(
|
|
19427
|
+
estimatedTokens: Math.ceil(content.length / 4) + 10
|
|
18971
19428
|
};
|
|
18972
19429
|
}
|
|
19430
|
+
getMemoryTail() {
|
|
19431
|
+
const sections = [];
|
|
19432
|
+
const tailPerFile = 3;
|
|
19433
|
+
for (const name of MEMORY_MD_FILES) {
|
|
19434
|
+
const content = this.store.read(name).trim();
|
|
19435
|
+
if (!content)
|
|
19436
|
+
continue;
|
|
19437
|
+
const entries = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.startsWith("- ")).slice(-tailPerFile);
|
|
19438
|
+
if (entries.length === 0)
|
|
19439
|
+
continue;
|
|
19440
|
+
sections.push(`[Memory ${name}]:
|
|
19441
|
+
${entries.join(`
|
|
19442
|
+
`)}`);
|
|
19443
|
+
}
|
|
19444
|
+
if (sections.length === 0)
|
|
19445
|
+
return null;
|
|
19446
|
+
let joined = sections.join(`
|
|
19447
|
+
|
|
19448
|
+
`);
|
|
19449
|
+
if (joined.length > 1200)
|
|
19450
|
+
joined = joined.slice(0, 1200) + `
|
|
19451
|
+
…`;
|
|
19452
|
+
return joined;
|
|
19453
|
+
}
|
|
18973
19454
|
getToolDefinitions() {
|
|
18974
19455
|
return [rememberTool, recallTool];
|
|
18975
19456
|
}
|
|
@@ -18978,11 +19459,18 @@ class MemoryModule {
|
|
|
18978
19459
|
name: "memory",
|
|
18979
19460
|
isBuiltin: true,
|
|
18980
19461
|
onBuildPrompt: () => {
|
|
19462
|
+
const parts = [];
|
|
18981
19463
|
const prefs = this.store.getPreferences();
|
|
18982
|
-
if (Object.keys(prefs).length
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
19464
|
+
if (Object.keys(prefs).length > 0) {
|
|
19465
|
+
const prefStr = Object.entries(prefs).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
19466
|
+
parts.push(`User preferences: ${prefStr}`);
|
|
19467
|
+
}
|
|
19468
|
+
const memoryTail = this.getMemoryTail();
|
|
19469
|
+
if (memoryTail)
|
|
19470
|
+
parts.push(memoryTail);
|
|
19471
|
+
return parts.length > 0 ? parts.join(`
|
|
19472
|
+
|
|
19473
|
+
`) : null;
|
|
18986
19474
|
}
|
|
18987
19475
|
};
|
|
18988
19476
|
}
|
|
@@ -18990,10 +19478,12 @@ class MemoryModule {
|
|
|
18990
19478
|
return this.store;
|
|
18991
19479
|
}
|
|
18992
19480
|
}
|
|
19481
|
+
var MEMORY_MD_FILES;
|
|
18993
19482
|
var init_module8 = __esm(() => {
|
|
18994
19483
|
init_store2();
|
|
18995
19484
|
init_remember();
|
|
18996
19485
|
init_recall();
|
|
19486
|
+
MEMORY_MD_FILES = ["errors", "conventions", "decisions", "facts"];
|
|
18997
19487
|
});
|
|
18998
19488
|
|
|
18999
19489
|
// src/core/bootstrap.ts
|
|
@@ -19003,8 +19493,8 @@ __export(exports_bootstrap, {
|
|
|
19003
19493
|
bootstrap: () => bootstrap
|
|
19004
19494
|
});
|
|
19005
19495
|
import { homedir as homedir11 } from "os";
|
|
19006
|
-
import { join as
|
|
19007
|
-
import { existsSync as
|
|
19496
|
+
import { join as join35, resolve as resolve21 } from "path";
|
|
19497
|
+
import { existsSync as existsSync40, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "fs";
|
|
19008
19498
|
function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
19009
19499
|
const now = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
19010
19500
|
const isWin = profileCompressed.toLowerCase().includes("win32");
|
|
@@ -19021,6 +19511,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
19021
19511
|
if (config.autoPlan) {
|
|
19022
19512
|
lines.push(`Plan rule (MANDATORY): any task creating files, installing packages, or requiring multiple actions MUST create a plan with the "plan" tool BEFORE starting. Each step = a concrete deliverable.`);
|
|
19023
19513
|
}
|
|
19514
|
+
lines.push(`Runtime verification: when a bug is described in terms of runtime behavior (what happens when the app runs), reproduce it — run the app / build / browser tool — and confirm the fix against actual runtime output before calling "plan update step=N status=done".`);
|
|
19024
19515
|
const hasMCP = config.mcpServers && Object.values(config.mcpServers).some((s) => s.enabled !== false);
|
|
19025
19516
|
if (hasMCP) {
|
|
19026
19517
|
lines.push(`MCP servers are available — use the named MCP tools (prefixed mcp__) to query external services.`);
|
|
@@ -19029,8 +19520,8 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
19029
19520
|
`);
|
|
19030
19521
|
}
|
|
19031
19522
|
async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
19032
|
-
const dir = configDir ||
|
|
19033
|
-
const projectConfigPath = projectDir ?
|
|
19523
|
+
const dir = configDir || join35(homedir11(), ".mma");
|
|
19524
|
+
const projectConfigPath = projectDir ? join35(projectDir, ".mmrc") : join35(process.cwd(), ".mmrc");
|
|
19034
19525
|
const config = loadConfig({ configDir: dir, projectConfigPath });
|
|
19035
19526
|
setLocale(config.locale);
|
|
19036
19527
|
try {
|
|
@@ -19040,7 +19531,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19040
19531
|
}
|
|
19041
19532
|
} catch {}
|
|
19042
19533
|
const logger = new Logger(config.logLevel);
|
|
19043
|
-
logger.setLogDir(
|
|
19534
|
+
logger.setLogDir(join35(dir, "logs"));
|
|
19044
19535
|
logger.debug("MMA bootstrap", {
|
|
19045
19536
|
version: config.version,
|
|
19046
19537
|
model: config.model
|
|
@@ -19062,7 +19553,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19062
19553
|
logger.info(`Model ${config.model} loaded in ${loadResult.loadTime}s`);
|
|
19063
19554
|
}
|
|
19064
19555
|
}
|
|
19065
|
-
const profile = new UserProfile(
|
|
19556
|
+
const profile = new UserProfile(join35(dir));
|
|
19066
19557
|
profile.load() || profile.collect();
|
|
19067
19558
|
profile.save();
|
|
19068
19559
|
const llmProvider = new OpenAICompatProvider({
|
|
@@ -19074,7 +19565,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19074
19565
|
rateLimits: config.security?.rateLimits
|
|
19075
19566
|
});
|
|
19076
19567
|
const baseDir = projectDir ? resolve21(projectDir) : process.cwd();
|
|
19077
|
-
const projectMapCacheDir =
|
|
19568
|
+
const projectMapCacheDir = join35(baseDir, ".mma");
|
|
19078
19569
|
const indexerModule = new IndexerModule({
|
|
19079
19570
|
baseDir,
|
|
19080
19571
|
cacheDir: projectMapCacheDir
|
|
@@ -19085,9 +19576,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19085
19576
|
logger.warn(`Project indexing failed: ${err.message}`);
|
|
19086
19577
|
}
|
|
19087
19578
|
const skillsLoader = new SkillsLoader;
|
|
19088
|
-
const builtinDir =
|
|
19089
|
-
const globalDir =
|
|
19090
|
-
const projectSkillsDir =
|
|
19579
|
+
const builtinDir = join35(import.meta.dirname, "skills", "builtin");
|
|
19580
|
+
const globalDir = join35(homedir11(), ".agents", "skills");
|
|
19581
|
+
const projectSkillsDir = join35(baseDir, ".mma", "skills");
|
|
19091
19582
|
const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
|
|
19092
19583
|
const skillsBudget = Math.floor(config.contextWindow * config.skills.budget);
|
|
19093
19584
|
const skillsModule = new SkillsModule(availableSkills, skillsBudget);
|
|
@@ -19101,11 +19592,11 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19101
19592
|
essential: true,
|
|
19102
19593
|
estimatedTokens: Math.ceil(systemInfoContent.length / 4)
|
|
19103
19594
|
};
|
|
19104
|
-
const agentsMdGlobal =
|
|
19105
|
-
if (!
|
|
19595
|
+
const agentsMdGlobal = join35(dir, "AGENTS.md");
|
|
19596
|
+
if (!existsSync40(agentsMdGlobal)) {
|
|
19106
19597
|
writeFileSync15(agentsMdGlobal, "", "utf-8");
|
|
19107
19598
|
}
|
|
19108
|
-
const sessionDir =
|
|
19599
|
+
const sessionDir = join35(dir, "sessions");
|
|
19109
19600
|
const sessionStore = new SessionStore(sessionDir);
|
|
19110
19601
|
sessionStore.init();
|
|
19111
19602
|
const sessionManager = new SessionManager(sessionStore, {
|
|
@@ -19172,7 +19663,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19172
19663
|
const mcpModule = new MCPModule(config);
|
|
19173
19664
|
await mcpModule.initialize();
|
|
19174
19665
|
moduleRegistry.register(mcpModule);
|
|
19175
|
-
const
|
|
19666
|
+
const memoryStore = new MemoryStore(join35(dir, "memory"));
|
|
19667
|
+
const memoryModule = new MemoryModule(memoryStore);
|
|
19176
19668
|
moduleRegistry.register(memoryModule);
|
|
19177
19669
|
if (config.browser.enabled) {
|
|
19178
19670
|
const browserModule = new BrowserModule;
|
|
@@ -19222,8 +19714,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19222
19714
|
pluginManager.register(plugin);
|
|
19223
19715
|
pluginManager.register(plugin2);
|
|
19224
19716
|
const pluginLoader = new PluginLoader;
|
|
19225
|
-
const globalPluginsDir =
|
|
19226
|
-
const projectPluginsDir =
|
|
19717
|
+
const globalPluginsDir = join35(homedir11(), ".mma", "plugins");
|
|
19718
|
+
const projectPluginsDir = join35(baseDir, ".mma", "plugins");
|
|
19227
19719
|
pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
|
|
19228
19720
|
pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
|
|
19229
19721
|
contextManager.onCompact = (summary) => {
|
|
@@ -19241,12 +19733,12 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19241
19733
|
const skipAgentsMd = noAgentsMd === true;
|
|
19242
19734
|
if (!skipAgentsMd) {
|
|
19243
19735
|
const agentsMdCandidates = [
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19736
|
+
join35(baseDir, "AGENTS.md"),
|
|
19737
|
+
join35(baseDir, ".mma", "AGENTS.md"),
|
|
19738
|
+
join35(dir, "AGENTS.md")
|
|
19247
19739
|
];
|
|
19248
19740
|
for (const p of agentsMdCandidates) {
|
|
19249
|
-
if (
|
|
19741
|
+
if (existsSync40(p)) {
|
|
19250
19742
|
const content = readFileSync25(p, "utf-8").trim();
|
|
19251
19743
|
if (content) {
|
|
19252
19744
|
agentsMdBlocks.push({
|
|
@@ -19289,6 +19781,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
19289
19781
|
},
|
|
19290
19782
|
finalAudit: () => execModule.runFinalAudit(),
|
|
19291
19783
|
sessionManager,
|
|
19784
|
+
memoryStore,
|
|
19292
19785
|
exitOnComplete
|
|
19293
19786
|
};
|
|
19294
19787
|
const agent = new Agent(agentDeps);
|
|
@@ -19325,6 +19818,7 @@ var init_bootstrap = __esm(() => {
|
|
|
19325
19818
|
init_indexer();
|
|
19326
19819
|
init_mcp();
|
|
19327
19820
|
init_module8();
|
|
19821
|
+
init_store2();
|
|
19328
19822
|
init_i18n();
|
|
19329
19823
|
init_agent();
|
|
19330
19824
|
});
|
|
@@ -20099,12 +20593,12 @@ __export(exports_manifest, {
|
|
|
20099
20593
|
getCertMark: () => getCertMark,
|
|
20100
20594
|
MANIFEST_PATH: () => MANIFEST_PATH
|
|
20101
20595
|
});
|
|
20102
|
-
import { existsSync as
|
|
20596
|
+
import { existsSync as existsSync41, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
20103
20597
|
import { homedir as homedir13 } from "os";
|
|
20104
|
-
import { join as
|
|
20598
|
+
import { join as join37 } from "path";
|
|
20105
20599
|
function readManifest(path = MANIFEST_PATH) {
|
|
20106
20600
|
try {
|
|
20107
|
-
if (
|
|
20601
|
+
if (existsSync41(path)) {
|
|
20108
20602
|
const raw = JSON.parse(readFileSync26(path, "utf-8"));
|
|
20109
20603
|
return { version: 1, certifications: raw.certifications ?? [] };
|
|
20110
20604
|
}
|
|
@@ -20112,7 +20606,7 @@ function readManifest(path = MANIFEST_PATH) {
|
|
|
20112
20606
|
return { version: 1, certifications: [] };
|
|
20113
20607
|
}
|
|
20114
20608
|
function saveManifest(m, path = MANIFEST_PATH) {
|
|
20115
|
-
mkdirSync18(
|
|
20609
|
+
mkdirSync18(join37(homedir13(), ".mma"), { recursive: true });
|
|
20116
20610
|
writeFileSync16(path, JSON.stringify(m, null, 2), "utf-8");
|
|
20117
20611
|
}
|
|
20118
20612
|
function upsertCertification(entry, path = MANIFEST_PATH) {
|
|
@@ -20147,7 +20641,7 @@ function getCertMark(model, providerUrl, currentVersion, path = MANIFEST_PATH) {
|
|
|
20147
20641
|
}
|
|
20148
20642
|
var MANIFEST_PATH;
|
|
20149
20643
|
var init_manifest = __esm(() => {
|
|
20150
|
-
MANIFEST_PATH =
|
|
20644
|
+
MANIFEST_PATH = join37(homedir13(), ".mma", "certifications.json");
|
|
20151
20645
|
});
|
|
20152
20646
|
|
|
20153
20647
|
// node_modules/yaml/dist/nodes/identity.js
|
|
@@ -27270,8 +27764,8 @@ var init_scenarios = __esm(() => {
|
|
|
27270
27764
|
});
|
|
27271
27765
|
|
|
27272
27766
|
// src/modules/certification/loader.ts
|
|
27273
|
-
import { existsSync as
|
|
27274
|
-
import { join as
|
|
27767
|
+
import { existsSync as existsSync42, readdirSync as readdirSync15, readFileSync as readFileSync27 } from "fs";
|
|
27768
|
+
import { join as join38 } from "path";
|
|
27275
27769
|
function validateScenario(s) {
|
|
27276
27770
|
const errors2 = [];
|
|
27277
27771
|
const isSkip = s.mode === "skip";
|
|
@@ -27320,12 +27814,12 @@ function loadScenarios(userDir) {
|
|
|
27320
27814
|
else
|
|
27321
27815
|
scenarios.push(s);
|
|
27322
27816
|
}
|
|
27323
|
-
if (userDir &&
|
|
27324
|
-
for (const file of
|
|
27817
|
+
if (userDir && existsSync42(userDir)) {
|
|
27818
|
+
for (const file of readdirSync15(userDir)) {
|
|
27325
27819
|
if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
|
|
27326
27820
|
continue;
|
|
27327
27821
|
try {
|
|
27328
|
-
const raw = readFileSync27(
|
|
27822
|
+
const raw = readFileSync27(join38(userDir, file), "utf-8");
|
|
27329
27823
|
const data = $parse(raw);
|
|
27330
27824
|
const parsed = normalizeScenario(data, file);
|
|
27331
27825
|
const errs = validateScenario(parsed);
|
|
@@ -27378,8 +27872,8 @@ var init_loader3 = __esm(() => {
|
|
|
27378
27872
|
});
|
|
27379
27873
|
|
|
27380
27874
|
// src/modules/certification/fact-checker.ts
|
|
27381
|
-
import { existsSync as
|
|
27382
|
-
import { join as
|
|
27875
|
+
import { existsSync as existsSync43, readFileSync as readFileSync28, statSync as statSync8 } from "fs";
|
|
27876
|
+
import { join as join39 } from "path";
|
|
27383
27877
|
function checkSandbox(sandboxDir, checks, exitCode, output) {
|
|
27384
27878
|
const failures = [];
|
|
27385
27879
|
for (const check of checks) {
|
|
@@ -27396,13 +27890,13 @@ function runCheck(sandboxDir, check, exitCode, output) {
|
|
|
27396
27890
|
case "outputContains":
|
|
27397
27891
|
return output.includes(check.text);
|
|
27398
27892
|
case "fileExists":
|
|
27399
|
-
return isFile(
|
|
27893
|
+
return isFile(join39(sandboxDir, check.path));
|
|
27400
27894
|
case "fileNotExists":
|
|
27401
|
-
return !
|
|
27895
|
+
return !existsSync43(join39(sandboxDir, check.path));
|
|
27402
27896
|
case "dirExists":
|
|
27403
|
-
return isDir(
|
|
27897
|
+
return isDir(join39(sandboxDir, check.path));
|
|
27404
27898
|
case "fileContent": {
|
|
27405
|
-
const abs =
|
|
27899
|
+
const abs = join39(sandboxDir, check.path);
|
|
27406
27900
|
if (!isFile(abs))
|
|
27407
27901
|
return false;
|
|
27408
27902
|
const content = readFileSync28(abs, "utf-8");
|
|
@@ -27413,7 +27907,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
|
|
|
27413
27907
|
return false;
|
|
27414
27908
|
}
|
|
27415
27909
|
case "fileRegex": {
|
|
27416
|
-
const abs =
|
|
27910
|
+
const abs = join39(sandboxDir, check.path);
|
|
27417
27911
|
if (!isFile(abs))
|
|
27418
27912
|
return false;
|
|
27419
27913
|
return new RegExp(check.pattern).test(readFileSync28(abs, "utf-8"));
|
|
@@ -27424,14 +27918,14 @@ function runCheck(sandboxDir, check, exitCode, output) {
|
|
|
27424
27918
|
}
|
|
27425
27919
|
function isFile(p) {
|
|
27426
27920
|
try {
|
|
27427
|
-
return
|
|
27921
|
+
return existsSync43(p) && statSync8(p).isFile();
|
|
27428
27922
|
} catch {
|
|
27429
27923
|
return false;
|
|
27430
27924
|
}
|
|
27431
27925
|
}
|
|
27432
27926
|
function isDir(p) {
|
|
27433
27927
|
try {
|
|
27434
|
-
return
|
|
27928
|
+
return existsSync43(p) && statSync8(p).isDirectory();
|
|
27435
27929
|
} catch {
|
|
27436
27930
|
return false;
|
|
27437
27931
|
}
|
|
@@ -27462,9 +27956,9 @@ var init_fact_checker = () => {};
|
|
|
27462
27956
|
|
|
27463
27957
|
// src/modules/certification/runner.ts
|
|
27464
27958
|
import { spawn as spawn7 } from "child_process";
|
|
27465
|
-
import { existsSync as
|
|
27466
|
-
import { platform as
|
|
27467
|
-
import { join as
|
|
27959
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
|
|
27960
|
+
import { platform as platform8 } from "os";
|
|
27961
|
+
import { join as join40, resolve as resolve22, dirname as dirname12 } from "path";
|
|
27468
27962
|
async function runScenario(scenario, opts) {
|
|
27469
27963
|
if (scenario.mode === "skip") {
|
|
27470
27964
|
return {
|
|
@@ -27483,7 +27977,7 @@ async function runScenario(scenario, opts) {
|
|
|
27483
27977
|
let passed = 0;
|
|
27484
27978
|
let firstError;
|
|
27485
27979
|
for (let i = 1;i <= reps; i++) {
|
|
27486
|
-
const sandbox =
|
|
27980
|
+
const sandbox = join40(opts.sandboxBase, `run-${scenario.id}-${i}`);
|
|
27487
27981
|
let failures = [];
|
|
27488
27982
|
let exitCode = -1;
|
|
27489
27983
|
let output = "";
|
|
@@ -27544,20 +28038,20 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
|
|
|
27544
28038
|
rmSync4(sandbox, { recursive: true, force: true });
|
|
27545
28039
|
mkdirSync19(sandbox, { recursive: true });
|
|
27546
28040
|
for (const f of scenario.fixtures ?? []) {
|
|
27547
|
-
const src =
|
|
27548
|
-
if (!
|
|
28041
|
+
const src = join40(mmaRoot, f.source);
|
|
28042
|
+
if (!existsSync44(src)) {
|
|
27549
28043
|
throw new Error(`fixture missing: ${f.source}`);
|
|
27550
28044
|
}
|
|
27551
|
-
const dest =
|
|
27552
|
-
mkdirSync19(
|
|
28045
|
+
const dest = join40(sandbox, f.dest);
|
|
28046
|
+
mkdirSync19(dirname12(dest), { recursive: true });
|
|
27553
28047
|
cpSync2(src, dest);
|
|
27554
28048
|
}
|
|
27555
28049
|
}
|
|
27556
28050
|
function resolveMmaEntry(mmaRoot) {
|
|
27557
|
-
const dev =
|
|
27558
|
-
if (
|
|
28051
|
+
const dev = join40(mmaRoot, "src", "cli", "main.ts");
|
|
28052
|
+
if (existsSync44(dev))
|
|
27559
28053
|
return dev;
|
|
27560
|
-
return
|
|
28054
|
+
return join40(mmaRoot, "dist", "main.js");
|
|
27561
28055
|
}
|
|
27562
28056
|
function findMmaRoot(fromDir) {
|
|
27563
28057
|
const candidates = [
|
|
@@ -27565,7 +28059,7 @@ function findMmaRoot(fromDir) {
|
|
|
27565
28059
|
resolve22(fromDir, "..")
|
|
27566
28060
|
];
|
|
27567
28061
|
for (const c of candidates) {
|
|
27568
|
-
if (
|
|
28062
|
+
if (existsSync44(join40(c, "package.json")))
|
|
27569
28063
|
return c;
|
|
27570
28064
|
}
|
|
27571
28065
|
return process.cwd();
|
|
@@ -27574,7 +28068,7 @@ function killTree2(child) {
|
|
|
27574
28068
|
const pid = child.pid;
|
|
27575
28069
|
if (!pid)
|
|
27576
28070
|
return;
|
|
27577
|
-
if (
|
|
28071
|
+
if (platform8() === "win32") {
|
|
27578
28072
|
spawn7("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
27579
28073
|
windowsHide: true,
|
|
27580
28074
|
stdio: "ignore"
|
|
@@ -27633,13 +28127,13 @@ __export(exports_cli, {
|
|
|
27633
28127
|
});
|
|
27634
28128
|
import { rmSync as rmSync5 } from "fs";
|
|
27635
28129
|
import { homedir as homedir14 } from "os";
|
|
27636
|
-
import { join as
|
|
28130
|
+
import { join as join41, dirname as dirname13 } from "path";
|
|
27637
28131
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27638
|
-
import { existsSync as
|
|
28132
|
+
import { existsSync as existsSync45, readFileSync as readFileSync29 } from "fs";
|
|
27639
28133
|
function readVersion() {
|
|
27640
|
-
const candidates = [
|
|
28134
|
+
const candidates = [join41(MMA_ROOT, "package.json")];
|
|
27641
28135
|
for (const p of candidates) {
|
|
27642
|
-
if (
|
|
28136
|
+
if (existsSync45(p)) {
|
|
27643
28137
|
try {
|
|
27644
28138
|
const raw = JSON.parse(readFileSync29(p, "utf-8"));
|
|
27645
28139
|
if (raw.version)
|
|
@@ -27677,7 +28171,7 @@ async function certify(opts) {
|
|
|
27677
28171
|
return;
|
|
27678
28172
|
}
|
|
27679
28173
|
console.log(t("cli.cert_started", { model: opts.name, provider: providerUrl }));
|
|
27680
|
-
const sandboxBase =
|
|
28174
|
+
const sandboxBase = join41(process.cwd(), ".mma", "certification");
|
|
27681
28175
|
const results = [];
|
|
27682
28176
|
const total = selected.length;
|
|
27683
28177
|
let idx = 0;
|
|
@@ -27789,9 +28283,9 @@ var init_cli = __esm(() => {
|
|
|
27789
28283
|
init_loader3();
|
|
27790
28284
|
init_runner2();
|
|
27791
28285
|
init_manifest();
|
|
27792
|
-
HERE =
|
|
28286
|
+
HERE = dirname13(fileURLToPath2(import.meta.url));
|
|
27793
28287
|
MMA_ROOT = findMmaRoot(HERE);
|
|
27794
|
-
USER_SCENARIO_DIR =
|
|
28288
|
+
USER_SCENARIO_DIR = join41(homedir14(), ".mma", "certification", "scenarios");
|
|
27795
28289
|
});
|
|
27796
28290
|
|
|
27797
28291
|
// src/cli/repl-commands.ts
|
|
@@ -27800,18 +28294,18 @@ __export(exports_repl_commands, {
|
|
|
27800
28294
|
registerAllCommands: () => registerAllCommands,
|
|
27801
28295
|
COMMAND_GROUPS: () => COMMAND_GROUPS
|
|
27802
28296
|
});
|
|
27803
|
-
import { join as
|
|
28297
|
+
import { join as join43, dirname as dirname15 } from "path";
|
|
27804
28298
|
import { homedir as homedir16 } from "os";
|
|
27805
|
-
import { existsSync as
|
|
28299
|
+
import { existsSync as existsSync47, readFileSync as readFileSync31 } from "fs";
|
|
27806
28300
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
27807
28301
|
function readVersion3() {
|
|
27808
|
-
const here =
|
|
28302
|
+
const here = dirname15(fileURLToPath4(import.meta.url));
|
|
27809
28303
|
const candidates = [
|
|
27810
|
-
|
|
27811
|
-
|
|
28304
|
+
join43(here, "..", "..", "package.json"),
|
|
28305
|
+
join43(here, "..", "package.json")
|
|
27812
28306
|
];
|
|
27813
28307
|
for (const p of candidates) {
|
|
27814
|
-
if (
|
|
28308
|
+
if (existsSync47(p)) {
|
|
27815
28309
|
try {
|
|
27816
28310
|
const raw = JSON.parse(readFileSync31(p, "utf8"));
|
|
27817
28311
|
if (raw.version)
|
|
@@ -27877,7 +28371,7 @@ function registerMmaCommands(ctx) {
|
|
|
27877
28371
|
}
|
|
27878
28372
|
try {
|
|
27879
28373
|
const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
|
|
27880
|
-
const { existsSync:
|
|
28374
|
+
const { existsSync: existsSync48 } = await import("fs");
|
|
27881
28375
|
const { resolve: resolve23 } = await import("path");
|
|
27882
28376
|
let dataUrl;
|
|
27883
28377
|
let label;
|
|
@@ -27897,7 +28391,7 @@ function registerMmaCommands(ctx) {
|
|
|
27897
28391
|
label = source;
|
|
27898
28392
|
} else {
|
|
27899
28393
|
const absPath = resolve23(process.cwd(), source);
|
|
27900
|
-
if (!
|
|
28394
|
+
if (!existsSync48(absPath)) {
|
|
27901
28395
|
console.log(pc2.red(t("image.not_found", { path: source })));
|
|
27902
28396
|
return;
|
|
27903
28397
|
}
|
|
@@ -27976,7 +28470,7 @@ function registerMmaCommands(ctx) {
|
|
|
27976
28470
|
console.log(pc2.yellow(t("repl.wizard_running")));
|
|
27977
28471
|
await ctx.withExclusiveInput(async () => {
|
|
27978
28472
|
const answers = await runSetup(ctx.rl);
|
|
27979
|
-
const configPath =
|
|
28473
|
+
const configPath = join43(homedir16(), ".mma", "config.json");
|
|
27980
28474
|
ctx.config.provider.type = answers.provider;
|
|
27981
28475
|
ctx.config.provider.baseUrl = answers.apiBase;
|
|
27982
28476
|
ctx.config.provider.apiKey = answers.apiKey;
|
|
@@ -28030,7 +28524,7 @@ Excluded blocks: ${info.excluded.length}`));
|
|
|
28030
28524
|
return;
|
|
28031
28525
|
}
|
|
28032
28526
|
ctx.config.provider.type = name;
|
|
28033
|
-
const configPath =
|
|
28527
|
+
const configPath = join43(homedir16(), ".mma", "config.json");
|
|
28034
28528
|
saveConfig(ctx.config, configPath);
|
|
28035
28529
|
await ctx.agent.reconfigure(ctx.config);
|
|
28036
28530
|
console.log(pc2.green(t("repl.provider_set", { name })));
|
|
@@ -28086,7 +28580,7 @@ Excluded blocks: ${info.excluded.length}`));
|
|
|
28086
28580
|
return;
|
|
28087
28581
|
}
|
|
28088
28582
|
ctx.config.model = name;
|
|
28089
|
-
const configPath =
|
|
28583
|
+
const configPath = join43(homedir16(), ".mma", "config.json");
|
|
28090
28584
|
saveConfig(ctx.config, configPath);
|
|
28091
28585
|
await ctx.agent.reconfigure(ctx.config);
|
|
28092
28586
|
console.log(pc2.green(t("repl.model_set", { name })));
|
|
@@ -28111,7 +28605,7 @@ Excluded blocks: ${info.excluded.length}`));
|
|
|
28111
28605
|
return;
|
|
28112
28606
|
}
|
|
28113
28607
|
ctx.config.contextWindow = size;
|
|
28114
|
-
const configPath =
|
|
28608
|
+
const configPath = join43(homedir16(), ".mma", "config.json");
|
|
28115
28609
|
saveConfig(ctx.config, configPath);
|
|
28116
28610
|
await ctx.agent.reconfigure(ctx.config);
|
|
28117
28611
|
console.log(pc2.green(t("cli.context_set", { size })));
|
|
@@ -28130,10 +28624,10 @@ Excluded blocks: ${info.excluded.length}`));
|
|
|
28130
28624
|
ctx.agent.shutdown();
|
|
28131
28625
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
|
|
28132
28626
|
const { homedir: homedir17 } = await import("os");
|
|
28133
|
-
const { join:
|
|
28627
|
+
const { join: join44 } = await import("path");
|
|
28134
28628
|
const configDir = ctx.configDir;
|
|
28135
28629
|
const baseDir = ctx.baseDir;
|
|
28136
|
-
const projectConfigPath =
|
|
28630
|
+
const projectConfigPath = join44(baseDir, ".mmrc");
|
|
28137
28631
|
const freshConfig = loadConfig2({ configDir, projectConfigPath });
|
|
28138
28632
|
Object.assign(ctx.config, freshConfig);
|
|
28139
28633
|
const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
|
|
@@ -28433,14 +28927,14 @@ init_bootstrap();
|
|
|
28433
28927
|
init_config2();
|
|
28434
28928
|
init_setup();
|
|
28435
28929
|
init_i18n();
|
|
28436
|
-
import { join as
|
|
28930
|
+
import { join as join42, dirname as dirname14 } from "path";
|
|
28437
28931
|
import { homedir as homedir15 } from "os";
|
|
28438
|
-
import { existsSync as
|
|
28932
|
+
import { existsSync as existsSync46, readFileSync as readFileSync30 } from "fs";
|
|
28439
28933
|
|
|
28440
28934
|
// src/cli/security-commands.ts
|
|
28441
28935
|
init_bootstrap();
|
|
28442
28936
|
init_config2();
|
|
28443
|
-
import { join as
|
|
28937
|
+
import { join as join36 } from "path";
|
|
28444
28938
|
import { homedir as homedir12 } from "os";
|
|
28445
28939
|
|
|
28446
28940
|
// src/modules/security/security-policies.ts
|
|
@@ -28970,7 +29464,7 @@ function createSecurityCommand(program2) {
|
|
|
28970
29464
|
}
|
|
28971
29465
|
});
|
|
28972
29466
|
securityCmd.command("set-policy").argument("<preset>", t("cli.security.preset")).description(t("cli.security.set_policy")).action(async (preset) => {
|
|
28973
|
-
const configPath =
|
|
29467
|
+
const configPath = join36(homedir12(), ".mma", "config.json");
|
|
28974
29468
|
const { config: appConfig } = await bootstrap();
|
|
28975
29469
|
const validPresets = ["strict", "balanced", "permissive"];
|
|
28976
29470
|
if (!validPresets.includes(preset)) {
|
|
@@ -28985,7 +29479,7 @@ function createSecurityCommand(program2) {
|
|
|
28985
29479
|
console.log(t("cli.security.policy_description", { description: policy.description }));
|
|
28986
29480
|
});
|
|
28987
29481
|
securityCmd.command("enable-encryption").description(t("cli.security.enable_encryption")).action(async () => {
|
|
28988
|
-
const configPath =
|
|
29482
|
+
const configPath = join36(homedir12(), ".mma", "config.json");
|
|
28989
29483
|
const { config: appConfig } = await bootstrap();
|
|
28990
29484
|
appConfig.security = appConfig.security || {};
|
|
28991
29485
|
appConfig.security.sessionEncryption = {
|
|
@@ -28997,7 +29491,7 @@ function createSecurityCommand(program2) {
|
|
|
28997
29491
|
console.log(t("cli.security.encryption_enabled"));
|
|
28998
29492
|
});
|
|
28999
29493
|
securityCmd.command("disable-encryption").description(t("cli.security.disable_encryption")).action(async () => {
|
|
29000
|
-
const configPath =
|
|
29494
|
+
const configPath = join36(homedir12(), ".mma", "config.json");
|
|
29001
29495
|
const { config: appConfig } = await bootstrap();
|
|
29002
29496
|
appConfig.security = appConfig.security || {};
|
|
29003
29497
|
appConfig.security.sessionEncryption = {
|
|
@@ -29009,7 +29503,7 @@ function createSecurityCommand(program2) {
|
|
|
29009
29503
|
console.log(t("cli.security.encryption_disabled"));
|
|
29010
29504
|
});
|
|
29011
29505
|
securityCmd.command("enable-audit").description(t("cli.security.enable_audit")).action(async () => {
|
|
29012
|
-
const configPath =
|
|
29506
|
+
const configPath = join36(homedir12(), ".mma", "config.json");
|
|
29013
29507
|
const { config: appConfig } = await bootstrap();
|
|
29014
29508
|
appConfig.security = appConfig.security || {};
|
|
29015
29509
|
appConfig.security.auditNotifier = {
|
|
@@ -29023,7 +29517,7 @@ function createSecurityCommand(program2) {
|
|
|
29023
29517
|
console.log(t("cli.security.audit_enabled"));
|
|
29024
29518
|
});
|
|
29025
29519
|
securityCmd.command("disable-audit").description(t("cli.security.disable_audit")).action(async () => {
|
|
29026
|
-
const configPath =
|
|
29520
|
+
const configPath = join36(homedir12(), ".mma", "config.json");
|
|
29027
29521
|
const { config: appConfig } = await bootstrap();
|
|
29028
29522
|
appConfig.security = appConfig.security || {};
|
|
29029
29523
|
appConfig.security.auditNotifier = {
|
|
@@ -29057,13 +29551,13 @@ function createSecurityCommand(program2) {
|
|
|
29057
29551
|
// src/cli/commands.ts
|
|
29058
29552
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
29059
29553
|
function readVersion2() {
|
|
29060
|
-
const here =
|
|
29554
|
+
const here = dirname14(fileURLToPath3(import.meta.url));
|
|
29061
29555
|
const candidates = [
|
|
29062
|
-
|
|
29063
|
-
|
|
29556
|
+
join42(here, "..", "..", "package.json"),
|
|
29557
|
+
join42(here, "..", "package.json")
|
|
29064
29558
|
];
|
|
29065
29559
|
for (const p of candidates) {
|
|
29066
|
-
if (
|
|
29560
|
+
if (existsSync46(p)) {
|
|
29067
29561
|
try {
|
|
29068
29562
|
const raw = JSON.parse(readFileSync30(p, "utf8"));
|
|
29069
29563
|
if (raw.version)
|
|
@@ -29078,7 +29572,7 @@ function createProgram() {
|
|
|
29078
29572
|
const program2 = new Command().name("mma").description(t("cli.description")).version(version).option("--no-agents-md", t("cli.no_agents_md")).option("-d, --dir <path>", t("cli.dir")).option("-e, --exit-on-complete", t("cli.exit_on_complete")).option("-j, --json", t("cli.json"));
|
|
29079
29573
|
program2.command("init").description(t("cli.init")).action(async () => {
|
|
29080
29574
|
const answers = await runSetup();
|
|
29081
|
-
const configPath =
|
|
29575
|
+
const configPath = join42(homedir15(), ".mma", "config.json");
|
|
29082
29576
|
const { config } = await bootstrap();
|
|
29083
29577
|
config.provider.type = answers.provider;
|
|
29084
29578
|
config.provider.baseUrl = answers.apiBase;
|
|
@@ -29123,7 +29617,7 @@ function createProgram() {
|
|
|
29123
29617
|
});
|
|
29124
29618
|
const configCmd = program2.command("config").description(t("cli.manage_config"));
|
|
29125
29619
|
configCmd.command("set").argument("<key>", t("cli.config_key")).argument("<value>", "Config value").description(t("cli.set_value")).action(async (key, value) => {
|
|
29126
|
-
const configPath =
|
|
29620
|
+
const configPath = join42(homedir15(), ".mma", "config.json");
|
|
29127
29621
|
const { config } = await bootstrap();
|
|
29128
29622
|
const keys = key.split(".");
|
|
29129
29623
|
let obj = config;
|
|
@@ -29186,7 +29680,7 @@ function createProgram() {
|
|
|
29186
29680
|
console.log(t("cli.model_hint"));
|
|
29187
29681
|
});
|
|
29188
29682
|
model.command("use").argument("<name>", "Model name").description(t("cli.set_model")).action(async (name) => {
|
|
29189
|
-
const configPath =
|
|
29683
|
+
const configPath = join42(homedir15(), ".mma", "config.json");
|
|
29190
29684
|
const { config } = await bootstrap();
|
|
29191
29685
|
config.model = name;
|
|
29192
29686
|
saveConfig(config, configPath);
|
|
@@ -29222,7 +29716,7 @@ function createProgram() {
|
|
|
29222
29716
|
await uncertify2(name, config);
|
|
29223
29717
|
});
|
|
29224
29718
|
program2.command("context").description(t("cli.manage_context")).argument("<size>", "Context window size in tokens").action(async (size) => {
|
|
29225
|
-
const configPath =
|
|
29719
|
+
const configPath = join42(homedir15(), ".mma", "config.json");
|
|
29226
29720
|
const { config } = await bootstrap();
|
|
29227
29721
|
const contextWindow = parseInt(size, 10);
|
|
29228
29722
|
if (isNaN(contextWindow) || contextWindow < 1024) {
|
|
@@ -29240,7 +29734,7 @@ function createProgram() {
|
|
|
29240
29734
|
console.log(t("cli.base_url"), config.provider.baseUrl);
|
|
29241
29735
|
});
|
|
29242
29736
|
provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
|
|
29243
|
-
const configPath =
|
|
29737
|
+
const configPath = join42(homedir15(), ".mma", "config.json");
|
|
29244
29738
|
const { config } = await bootstrap();
|
|
29245
29739
|
config.provider.type = name;
|
|
29246
29740
|
saveConfig(config, configPath);
|
|
@@ -30010,8 +30504,8 @@ class LineEditor {
|
|
|
30010
30504
|
}
|
|
30011
30505
|
|
|
30012
30506
|
// src/cli/repl.ts
|
|
30013
|
-
import { existsSync as
|
|
30014
|
-
import { join as
|
|
30507
|
+
import { existsSync as existsSync48, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
|
|
30508
|
+
import { join as join44, dirname as dirname16 } from "path";
|
|
30015
30509
|
import { homedir as homedir17 } from "os";
|
|
30016
30510
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
30017
30511
|
|
|
@@ -30515,13 +31009,13 @@ init_box();
|
|
|
30515
31009
|
init_i18n();
|
|
30516
31010
|
init_repl_commands();
|
|
30517
31011
|
function readVersion4() {
|
|
30518
|
-
const here =
|
|
31012
|
+
const here = dirname16(fileURLToPath5(import.meta.url));
|
|
30519
31013
|
const candidates = [
|
|
30520
|
-
|
|
30521
|
-
|
|
31014
|
+
join44(here, "..", "..", "package.json"),
|
|
31015
|
+
join44(here, "..", "package.json")
|
|
30522
31016
|
];
|
|
30523
31017
|
for (const p of candidates) {
|
|
30524
|
-
if (
|
|
31018
|
+
if (existsSync48(p)) {
|
|
30525
31019
|
try {
|
|
30526
31020
|
const raw = JSON.parse(readFileSync32(p, "utf8"));
|
|
30527
31021
|
if (raw.version)
|
|
@@ -30579,10 +31073,10 @@ class Repl {
|
|
|
30579
31073
|
this.skillsModule = skillsModule;
|
|
30580
31074
|
this.pluginManager = pluginManager;
|
|
30581
31075
|
this.logger = logger;
|
|
30582
|
-
this.configDir = configDir ||
|
|
31076
|
+
this.configDir = configDir || join44(homedir17(), ".mma");
|
|
30583
31077
|
this.baseDir = baseDir || process.cwd();
|
|
30584
31078
|
this.noAgentsMd = noAgentsMd === true;
|
|
30585
|
-
this.historyPath =
|
|
31079
|
+
this.historyPath = join44(homedir17(), ".mma", "repl-history");
|
|
30586
31080
|
this.loadHistory();
|
|
30587
31081
|
this.rl = process.stdin.isTTY ? new LineEditor({
|
|
30588
31082
|
input: process.stdin,
|
|
@@ -30615,7 +31109,7 @@ class Repl {
|
|
|
30615
31109
|
this.setupListeners();
|
|
30616
31110
|
}
|
|
30617
31111
|
loadHistory() {
|
|
30618
|
-
if (
|
|
31112
|
+
if (existsSync48(this.historyPath)) {
|
|
30619
31113
|
try {
|
|
30620
31114
|
const raw = readFileSync32(this.historyPath, "utf-8");
|
|
30621
31115
|
this.history = raw.split(`
|
|
@@ -30974,11 +31468,11 @@ ${t("image.clipboard_empty")}`));
|
|
|
30974
31468
|
row(t("repl.agents_label"), pc2.red(t("repl.disabled")));
|
|
30975
31469
|
} else {
|
|
30976
31470
|
const agentsMdCandidates = [
|
|
30977
|
-
|
|
30978
|
-
|
|
30979
|
-
|
|
31471
|
+
join44(this.baseDir, "AGENTS.md"),
|
|
31472
|
+
join44(this.baseDir, ".mma", "AGENTS.md"),
|
|
31473
|
+
join44(this.configDir, "AGENTS.md")
|
|
30980
31474
|
];
|
|
30981
|
-
const foundAgents = agentsMdCandidates.filter((p) =>
|
|
31475
|
+
const foundAgents = agentsMdCandidates.filter((p) => existsSync48(p));
|
|
30982
31476
|
if (foundAgents.length > 0) {
|
|
30983
31477
|
for (const p of foundAgents) {
|
|
30984
31478
|
row(t("repl.agents_label"), pc2.dim(p));
|
|
@@ -30989,7 +31483,7 @@ ${t("image.clipboard_empty")}`));
|
|
|
30989
31483
|
}
|
|
30990
31484
|
const meta = this.sessionManager?.getActiveMeta();
|
|
30991
31485
|
if (meta) {
|
|
30992
|
-
const sessionPath =
|
|
31486
|
+
const sessionPath = join44(this.configDir, "sessions", meta.id);
|
|
30993
31487
|
row(t("repl.session_label"), `${pc2.cyan(meta.name)} ${pc2.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc2.dim(sessionPath)}`);
|
|
30994
31488
|
}
|
|
30995
31489
|
const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
|
|
@@ -31030,14 +31524,14 @@ init_setup();
|
|
|
31030
31524
|
init_config2();
|
|
31031
31525
|
init_i18n();
|
|
31032
31526
|
init_colors();
|
|
31033
|
-
import { existsSync as
|
|
31034
|
-
import { join as
|
|
31527
|
+
import { existsSync as existsSync49, readFileSync as readFileSync33 } from "fs";
|
|
31528
|
+
import { join as join45, dirname as dirname17 } from "path";
|
|
31035
31529
|
import { homedir as homedir18 } from "os";
|
|
31036
31530
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
31037
31531
|
|
|
31038
31532
|
// src/modules/updater/checker.ts
|
|
31039
31533
|
init_command();
|
|
31040
|
-
import { platform as
|
|
31534
|
+
import { platform as platform9 } from "os";
|
|
31041
31535
|
var defaultRunner2 = async (command, args, options) => {
|
|
31042
31536
|
const { execFile } = await import("child_process");
|
|
31043
31537
|
return new Promise((resolve23) => {
|
|
@@ -31099,7 +31593,7 @@ class Updater {
|
|
|
31099
31593
|
buildInstallCommand(latest) {
|
|
31100
31594
|
const npmArgs = ["install", "-g", `${this.packageName}@${latest}`];
|
|
31101
31595
|
const command = resolveSpawnCommand("npm");
|
|
31102
|
-
if (
|
|
31596
|
+
if (platform9() === "win32" && /\.(cmd|bat)$/i.test(command)) {
|
|
31103
31597
|
return { command: "cmd.exe", args: ["/c", command, ...npmArgs] };
|
|
31104
31598
|
}
|
|
31105
31599
|
return { command, args: npmArgs };
|
|
@@ -31193,13 +31687,13 @@ class UpdaterModule {
|
|
|
31193
31687
|
}
|
|
31194
31688
|
// src/cli/main.ts
|
|
31195
31689
|
function readVersion5() {
|
|
31196
|
-
const here =
|
|
31690
|
+
const here = dirname17(fileURLToPath6(import.meta.url));
|
|
31197
31691
|
const candidates = [
|
|
31198
|
-
|
|
31199
|
-
|
|
31692
|
+
join45(here, "..", "..", "package.json"),
|
|
31693
|
+
join45(here, "..", "package.json")
|
|
31200
31694
|
];
|
|
31201
31695
|
for (const p of candidates) {
|
|
31202
|
-
if (
|
|
31696
|
+
if (existsSync49(p)) {
|
|
31203
31697
|
try {
|
|
31204
31698
|
const raw = JSON.parse(readFileSync33(p, "utf8"));
|
|
31205
31699
|
if (raw.version)
|
|
@@ -31272,15 +31766,15 @@ async function main() {
|
|
|
31272
31766
|
agent.shutdown();
|
|
31273
31767
|
process.exit(exitCode);
|
|
31274
31768
|
} else {
|
|
31275
|
-
const configPath =
|
|
31276
|
-
if (!
|
|
31769
|
+
const configPath = join45(homedir18(), ".mma", "config.json");
|
|
31770
|
+
if (!existsSync49(configPath)) {
|
|
31277
31771
|
console.log(pc2.yellow(`
|
|
31278
31772
|
` + t("cli.first_run") + `
|
|
31279
31773
|
`));
|
|
31280
31774
|
const answers = await runSetup();
|
|
31281
31775
|
const config2 = loadConfig({
|
|
31282
|
-
configDir:
|
|
31283
|
-
projectConfigPath: projectDir ?
|
|
31776
|
+
configDir: join45(homedir18(), ".mma"),
|
|
31777
|
+
projectConfigPath: projectDir ? join45(projectDir, ".mmrc") : join45(process.cwd(), ".mmrc")
|
|
31284
31778
|
});
|
|
31285
31779
|
config2.provider.type = answers.provider;
|
|
31286
31780
|
config2.provider.baseUrl = answers.apiBase;
|