micro-models-agent 0.43.0 → 0.43.2
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 +650 -537
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2358,7 +2358,9 @@ var init_en = __esm(() => {
|
|
|
2358
2358
|
"file.is_directory": "Is a directory, use rmdir: {path}",
|
|
2359
2359
|
"file.replaced_in": "Replaced in {path}",
|
|
2360
2360
|
"file.string_not_found": `String not found in file:
|
|
2361
|
-
{str}
|
|
2361
|
+
{str}
|
|
2362
|
+
|
|
2363
|
+
Use read_file on {path} to see the current content before editing — the target may differ (whitespace, line endings) or was already replaced.`,
|
|
2362
2364
|
"file.moved": "Moved {from} → {to}",
|
|
2363
2365
|
"file.not_found_short": "Not found: {path}",
|
|
2364
2366
|
"file.notfound_resolved": "File not found: {path} (resolved to {resolved})",
|
|
@@ -2540,6 +2542,9 @@ Command: {command}`,
|
|
|
2540
2542
|
"plan.kinds_invalid": 'Invalid step kind(s): {kinds}. Use "create" or "delete".',
|
|
2541
2543
|
"plan.kinds_not_array": 'kinds must be an array of "create" or "delete"',
|
|
2542
2544
|
"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).",
|
|
2545
|
+
"plan.typecheck_failed": `Cannot mark step {step} done: the last check still reports a compile error:
|
|
2546
|
+
{error}
|
|
2547
|
+
Fix the error and re-edit the file (a clean write clears the failure), or mark the step status=skipped if it is not needed.`,
|
|
2543
2548
|
"plan.show_header": "Plan status:",
|
|
2544
2549
|
"plan.show_empty": "(plan has no steps)",
|
|
2545
2550
|
"plan.list_header": "Plans:",
|
|
@@ -3012,7 +3017,9 @@ var init_ru = __esm(() => {
|
|
|
3012
3017
|
"file.is_directory": "Это каталог, используйте rmdir: {path}",
|
|
3013
3018
|
"file.replaced_in": "Заменено в {path}",
|
|
3014
3019
|
"file.string_not_found": `Строка не найдена в файле:
|
|
3015
|
-
{str}
|
|
3020
|
+
{str}
|
|
3021
|
+
|
|
3022
|
+
Используй read_file для {path}, чтобы увидеть текущее содержимое файла перед редактированием — искомая строка может отличаться (пробелы, переводы строк) или уже была заменена.`,
|
|
3016
3023
|
"file.moved": "Перемещён {from} → {to}",
|
|
3017
3024
|
"file.not_found_short": "Не найдено: {path}",
|
|
3018
3025
|
"file.empty": "(пусто)",
|
|
@@ -3192,6 +3199,9 @@ var init_ru = __esm(() => {
|
|
|
3192
3199
|
"plan.kinds_invalid": 'Недопустимый kind шага: {kinds}. Используй "create" или "delete".',
|
|
3193
3200
|
"plan.kinds_not_array": 'kinds должен быть массивом значений "create" или "delete"',
|
|
3194
3201
|
"plan.deliverables_remain": "Нельзя пометить шаг {step} как выполненный: указанные файлы всё ещё существуют: {files}. Шаг имеет kind=delete — сначала удали их (или поставь status=skipped, если они должны остаться).",
|
|
3202
|
+
"plan.typecheck_failed": `Нельзя отметить шаг {step} выполненным: последняя проверка всё ещё сообщает об ошибке компиляции:
|
|
3203
|
+
{error}
|
|
3204
|
+
Исправь ошибку и снова отредактируй файл (чистая запись снимает блокировку), либо отметь шаг status=skipped, если он не нужен.`,
|
|
3195
3205
|
"plan.show_header": "Статус плана:",
|
|
3196
3206
|
"plan.show_empty": "(в плане нет шагов)",
|
|
3197
3207
|
"plan.list_header": "Планы:",
|
|
@@ -5697,12 +5707,17 @@ class ToolExecutor {
|
|
|
5697
5707
|
toolCallId: call.id
|
|
5698
5708
|
};
|
|
5699
5709
|
}
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5710
|
+
if (signal?.aborted) {
|
|
5711
|
+
this.ctx.logger.debug(`Tool ${call.name}: abort, skipping onAfterTool plugins`);
|
|
5712
|
+
} else {
|
|
5713
|
+
const pluginCtx = signal ? { ...this.ctx, signal } : this.ctx;
|
|
5714
|
+
for (const plugin of this.pluginManager.getAllPlugins()) {
|
|
5715
|
+
if (plugin.onAfterTool) {
|
|
5716
|
+
try {
|
|
5717
|
+
await plugin.onAfterTool(pluginCtx, call, result);
|
|
5718
|
+
} catch (e) {
|
|
5719
|
+
this.ctx.logger.warn(`Plugin onAfterTool error: ${e.message}`);
|
|
5720
|
+
}
|
|
5706
5721
|
}
|
|
5707
5722
|
}
|
|
5708
5723
|
}
|
|
@@ -6759,7 +6774,7 @@ var init_edit_file = __esm(() => {
|
|
|
6759
6774
|
if (!content.includes(oldStr)) {
|
|
6760
6775
|
return {
|
|
6761
6776
|
success: false,
|
|
6762
|
-
output: t("file.string_not_found", { str: oldStr })
|
|
6777
|
+
output: t("file.string_not_found", { str: oldStr, path })
|
|
6763
6778
|
};
|
|
6764
6779
|
}
|
|
6765
6780
|
const updated = content.replace(oldStr, newStr);
|
|
@@ -11697,6 +11712,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
11697
11712
|
slog.logToolCall(call, iteration);
|
|
11698
11713
|
const tokensBeforeTool = contextManager.getEstimatedTokens();
|
|
11699
11714
|
const result = await toolExecutor.execute(call, this.abortController?.signal);
|
|
11715
|
+
if (this.shutdownRequested)
|
|
11716
|
+
break;
|
|
11700
11717
|
const duration = Date.now() - startTime;
|
|
11701
11718
|
if (!result.success)
|
|
11702
11719
|
anyToolFailed = true;
|
|
@@ -11775,6 +11792,8 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
|
|
|
11775
11792
|
}
|
|
11776
11793
|
}
|
|
11777
11794
|
}
|
|
11795
|
+
if (this.shutdownRequested)
|
|
11796
|
+
break;
|
|
11778
11797
|
if (anyToolFailed) {
|
|
11779
11798
|
consecutiveToolFailures++;
|
|
11780
11799
|
} else {
|
|
@@ -16808,18 +16827,25 @@ class LintOnWritePlugin {
|
|
|
16808
16827
|
const fullPath = resolve18(ctx.baseDir, path);
|
|
16809
16828
|
if (!existsSync31(fullPath))
|
|
16810
16829
|
return;
|
|
16830
|
+
const signal = ctx.signal;
|
|
16831
|
+
if (signal?.aborted)
|
|
16832
|
+
return;
|
|
16811
16833
|
const ext = extname4(fullPath);
|
|
16812
|
-
const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
|
|
16834
|
+
const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir, signal);
|
|
16835
|
+
if (signal?.aborted)
|
|
16836
|
+
return;
|
|
16813
16837
|
if (syntaxError) {
|
|
16814
16838
|
result.output += `
|
|
16815
16839
|
|
|
16816
16840
|
[Syntax check failed]: ${syntaxError}`;
|
|
16817
16841
|
return;
|
|
16818
16842
|
}
|
|
16819
|
-
await this.runProjectLint(ctx, result);
|
|
16820
|
-
|
|
16843
|
+
await this.runProjectLint(ctx, result, signal);
|
|
16844
|
+
if (signal?.aborted)
|
|
16845
|
+
return;
|
|
16846
|
+
await this.runProjectTypeCheck(fullPath, ctx.baseDir, result, signal);
|
|
16821
16847
|
}
|
|
16822
|
-
async checkSyntax(filePath, ext, baseDir) {
|
|
16848
|
+
async checkSyntax(filePath, ext, baseDir, signal) {
|
|
16823
16849
|
if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
|
|
16824
16850
|
let content = "";
|
|
16825
16851
|
try {
|
|
@@ -16833,7 +16859,9 @@ class LintOnWritePlugin {
|
|
|
16833
16859
|
return cached.error;
|
|
16834
16860
|
}
|
|
16835
16861
|
try {
|
|
16836
|
-
await runAsync(`bun build --no-bundle --target=bun "${filePath}"`, baseDir, 1e4);
|
|
16862
|
+
await runAsync(`bun build --no-bundle --target=bun "${filePath}"`, baseDir, 1e4, signal);
|
|
16863
|
+
if (signal?.aborted)
|
|
16864
|
+
return null;
|
|
16837
16865
|
syntaxCache.set(filePath, { hash, error: null });
|
|
16838
16866
|
return null;
|
|
16839
16867
|
} catch (err) {
|
|
@@ -16849,7 +16877,7 @@ class LintOnWritePlugin {
|
|
|
16849
16877
|
}
|
|
16850
16878
|
if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
|
|
16851
16879
|
try {
|
|
16852
|
-
await runAsync(`node --check "${filePath}"`, baseDir, 5000);
|
|
16880
|
+
await runAsync(`node --check "${filePath}"`, baseDir, 5000, signal);
|
|
16853
16881
|
return null;
|
|
16854
16882
|
} catch (err) {
|
|
16855
16883
|
const stderr = err.stderr?.toString() || "";
|
|
@@ -16860,7 +16888,7 @@ class LintOnWritePlugin {
|
|
|
16860
16888
|
}
|
|
16861
16889
|
return null;
|
|
16862
16890
|
}
|
|
16863
|
-
async runProjectLint(ctx, result) {
|
|
16891
|
+
async runProjectLint(ctx, result, signal) {
|
|
16864
16892
|
try {
|
|
16865
16893
|
const packageJsonPath = join25(ctx.baseDir, "package.json");
|
|
16866
16894
|
if (!existsSync31(packageJsonPath)) {
|
|
@@ -16872,7 +16900,7 @@ class LintOnWritePlugin {
|
|
|
16872
16900
|
return;
|
|
16873
16901
|
}
|
|
16874
16902
|
ctx.logger.debug(`Running lint: ${lintScript}`);
|
|
16875
|
-
await runAsync(lintScript, ctx.baseDir, 30000);
|
|
16903
|
+
await runAsync(lintScript, ctx.baseDir, 30000, signal);
|
|
16876
16904
|
ctx.logger.debug("Lint passed");
|
|
16877
16905
|
} catch (err) {
|
|
16878
16906
|
const stderr = (err.stderr?.toString?.() || "").trim();
|
|
@@ -16886,7 +16914,7 @@ class LintOnWritePlugin {
|
|
|
16886
16914
|
[Project lint failed]: ${detail}`;
|
|
16887
16915
|
}
|
|
16888
16916
|
}
|
|
16889
|
-
async runProjectTypeCheck(filePath, baseDir, result) {
|
|
16917
|
+
async runProjectTypeCheck(filePath, baseDir, result, signal) {
|
|
16890
16918
|
const projectRoot = findProjectRoot(filePath, baseDir, ["tsconfig.json", "package.json"]);
|
|
16891
16919
|
const tsconfigPath = join25(projectRoot, "tsconfig.json");
|
|
16892
16920
|
if (!existsSync31(tsconfigPath)) {
|
|
@@ -16903,7 +16931,7 @@ class LintOnWritePlugin {
|
|
|
16903
16931
|
return;
|
|
16904
16932
|
}
|
|
16905
16933
|
this._checkTimestamp = now;
|
|
16906
|
-
this._checkPromise = this.runTscCheck(projectRoot);
|
|
16934
|
+
this._checkPromise = this.runTscCheck(projectRoot, signal);
|
|
16907
16935
|
const error = await this._checkPromise;
|
|
16908
16936
|
if (error) {
|
|
16909
16937
|
result.output += `
|
|
@@ -16911,9 +16939,9 @@ class LintOnWritePlugin {
|
|
|
16911
16939
|
[Project typecheck failed]: ${error}`;
|
|
16912
16940
|
}
|
|
16913
16941
|
}
|
|
16914
|
-
async runTscCheck(baseDir) {
|
|
16942
|
+
async runTscCheck(baseDir, signal) {
|
|
16915
16943
|
try {
|
|
16916
|
-
await runAsync(`npx tsc --noEmit --skipLibCheck`, baseDir, 30000);
|
|
16944
|
+
await runAsync(`npx tsc --noEmit --skipLibCheck`, baseDir, 30000, signal);
|
|
16917
16945
|
return null;
|
|
16918
16946
|
} catch (err) {
|
|
16919
16947
|
if (err.status === 127 || err.message.includes("not found") || err.message.includes("ENOENT")) {
|
|
@@ -16929,7 +16957,7 @@ class LintOnWritePlugin {
|
|
|
16929
16957
|
}
|
|
16930
16958
|
}
|
|
16931
16959
|
}
|
|
16932
|
-
function runAsync(command, cwd, timeoutMs) {
|
|
16960
|
+
function runAsync(command, cwd, timeoutMs, signal) {
|
|
16933
16961
|
return new Promise((resolve19, reject) => {
|
|
16934
16962
|
const child = spawn5(command, {
|
|
16935
16963
|
cwd,
|
|
@@ -16950,15 +16978,27 @@ function runAsync(command, cwd, timeoutMs) {
|
|
|
16950
16978
|
child.kill();
|
|
16951
16979
|
reject(new Error(`Command timed out after ${timeoutMs}ms`));
|
|
16952
16980
|
}, timeoutMs);
|
|
16981
|
+
const onAbort = () => child.kill();
|
|
16982
|
+
if (signal?.aborted) {
|
|
16983
|
+
child.kill();
|
|
16984
|
+
} else {
|
|
16985
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16986
|
+
}
|
|
16953
16987
|
child.on("error", (err) => {
|
|
16954
16988
|
clearTimeout(timer);
|
|
16955
|
-
|
|
16989
|
+
signal?.removeEventListener("abort", onAbort);
|
|
16990
|
+
if (signal?.aborted) {
|
|
16991
|
+
resolve19({ stdout, stderr });
|
|
16992
|
+
} else {
|
|
16993
|
+
reject(err);
|
|
16994
|
+
}
|
|
16956
16995
|
});
|
|
16957
16996
|
child.on("close", (code) => {
|
|
16958
16997
|
clearTimeout(timer);
|
|
16998
|
+
signal?.removeEventListener("abort", onAbort);
|
|
16959
16999
|
stdout += decoder.decode();
|
|
16960
17000
|
stderr += decoder.decode();
|
|
16961
|
-
if (code === 0) {
|
|
17001
|
+
if (signal?.aborted || code === 0) {
|
|
16962
17002
|
resolve19({ stdout, stderr });
|
|
16963
17003
|
} else {
|
|
16964
17004
|
const err = new Error(`Command failed with exit code ${code}`);
|
|
@@ -17372,114 +17412,426 @@ var init_plan_coverage = __esm(() => {
|
|
|
17372
17412
|
]);
|
|
17373
17413
|
});
|
|
17374
17414
|
|
|
17375
|
-
// src/modules/execution/
|
|
17376
|
-
function
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17415
|
+
// src/modules/execution/windows-commands.ts
|
|
17416
|
+
function firstWord(command) {
|
|
17417
|
+
const segs = command.trim().split(/[\s;|&]+/);
|
|
17418
|
+
return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
|
|
17419
|
+
}
|
|
17420
|
+
function forbiddenWindowsCommand(command) {
|
|
17421
|
+
const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
|
|
17422
|
+
if (m)
|
|
17423
|
+
return forbiddenWindowsCommand(m[1]);
|
|
17424
|
+
const word = firstWord(command);
|
|
17425
|
+
if (word && FORBIDDEN_COMMANDS.has(word))
|
|
17426
|
+
return word;
|
|
17427
|
+
return null;
|
|
17428
|
+
}
|
|
17429
|
+
var FORBIDDEN_COMMANDS;
|
|
17430
|
+
var init_windows_commands = __esm(() => {
|
|
17431
|
+
FORBIDDEN_COMMANDS = new Set([
|
|
17432
|
+
"grep",
|
|
17433
|
+
"sed",
|
|
17434
|
+
"ls",
|
|
17435
|
+
"find",
|
|
17436
|
+
"rm",
|
|
17437
|
+
"touch",
|
|
17438
|
+
"which",
|
|
17439
|
+
"diff",
|
|
17440
|
+
"cp",
|
|
17441
|
+
"mv"
|
|
17442
|
+
]);
|
|
17443
|
+
});
|
|
17393
17444
|
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17400
|
-
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17406
|
-
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
});
|
|
17445
|
-
lines.push("");
|
|
17446
|
-
lines.push(t("plan.list_legend"));
|
|
17447
|
-
return {
|
|
17448
|
-
success: true,
|
|
17449
|
-
output: `${t("plan.list_header")}
|
|
17450
|
-
${lines.join(`
|
|
17451
|
-
`)}`
|
|
17452
|
-
};
|
|
17445
|
+
// src/modules/execution/execution-plugin.ts
|
|
17446
|
+
import { platform as platform6 } from "os";
|
|
17447
|
+
function normalizeBrokenPath(p) {
|
|
17448
|
+
return p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
17449
|
+
}
|
|
17450
|
+
function parseBrokenFile(line) {
|
|
17451
|
+
const trimmed = line.trim();
|
|
17452
|
+
const tsc = /^(.+?)\s*\(\d+,\d+\)\s*:\s*error TS\d+/.exec(trimmed);
|
|
17453
|
+
if (tsc)
|
|
17454
|
+
return normalizeBrokenPath(tsc[1]);
|
|
17455
|
+
const bn = /^(.+?):\d+:\d+\s*:\s*(?:error|SyntaxError)/.exec(trimmed);
|
|
17456
|
+
if (bn)
|
|
17457
|
+
return normalizeBrokenPath(bn[1]);
|
|
17458
|
+
return null;
|
|
17459
|
+
}
|
|
17460
|
+
function extractBrokenFiles(output) {
|
|
17461
|
+
const out = [];
|
|
17462
|
+
for (const line of output.split(`
|
|
17463
|
+
`)) {
|
|
17464
|
+
const marker = /\[(?:Project typecheck failed|Syntax check failed)\]:\s*(.+)$/.exec(line);
|
|
17465
|
+
if (!marker)
|
|
17466
|
+
continue;
|
|
17467
|
+
const errLine = marker[1].trim();
|
|
17468
|
+
out.push({ file: parseBrokenFile(errLine) ?? "", error: errLine.slice(0, 300) });
|
|
17469
|
+
}
|
|
17470
|
+
return out;
|
|
17471
|
+
}
|
|
17472
|
+
function stepTypecheckGate(failures, stepDescription) {
|
|
17473
|
+
if (failures.size === 0)
|
|
17474
|
+
return null;
|
|
17475
|
+
const projectError = failures.get("");
|
|
17476
|
+
if (projectError)
|
|
17477
|
+
return projectError;
|
|
17478
|
+
const stepTokens = extractFileLikeTokens(stripUrls(stepDescription)).map((p) => normalizeBrokenPath(p).toLowerCase());
|
|
17479
|
+
if (stepTokens.length === 0)
|
|
17480
|
+
return null;
|
|
17481
|
+
for (const [file, err] of failures) {
|
|
17482
|
+
const f = normalizeBrokenPath(file).toLowerCase();
|
|
17483
|
+
if (stepTokens.some((tok) => f.endsWith(tok) || tok.endsWith(f)))
|
|
17484
|
+
return err;
|
|
17485
|
+
}
|
|
17486
|
+
return null;
|
|
17487
|
+
}
|
|
17488
|
+
function createExecutionPlugin(deps) {
|
|
17489
|
+
return {
|
|
17490
|
+
name: "execution",
|
|
17491
|
+
onBeforeThink: (ctx) => {
|
|
17492
|
+
if (ctx.contextManager && deps.pendingMessages.length > 0) {
|
|
17493
|
+
for (const m of deps.pendingMessages.splice(0)) {
|
|
17494
|
+
ctx.contextManager.addMessage(m);
|
|
17453
17495
|
}
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17496
|
+
}
|
|
17497
|
+
if (deps.trackerRef.current?.isComplete()) {
|
|
17498
|
+
deps.stuckDetector.resetStepProgress();
|
|
17499
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
17500
|
+
deps.state.lastStepId = -1;
|
|
17501
|
+
deps.state.stuckNotified = false;
|
|
17502
|
+
deps.state.mutationsWithoutPlan = 0;
|
|
17503
|
+
deps.state.planNudgeSent = false;
|
|
17504
|
+
} else {
|
|
17505
|
+
const step = deps.trackerRef.current?.getCurrentStep();
|
|
17506
|
+
if (deps.trackerRef.current && step) {
|
|
17507
|
+
if (step.id !== deps.state.lastStepId) {
|
|
17508
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
17509
|
+
deps.state.lastStepId = step.id;
|
|
17510
|
+
deps.state.stuckNotified = false;
|
|
17458
17511
|
}
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17512
|
+
deps.stuckDetector.setCurrentStep(step.id, step.description);
|
|
17513
|
+
deps.stuckDetector.recordIteration(step.id);
|
|
17514
|
+
deps.state.mutationsWithoutPlan = 0;
|
|
17515
|
+
deps.state.planNudgeSent = false;
|
|
17516
|
+
} else {
|
|
17517
|
+
deps.stuckDetector.reset();
|
|
17518
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
17519
|
+
deps.state.lastStepId = -1;
|
|
17520
|
+
deps.state.stuckNotified = false;
|
|
17521
|
+
const mutations = deps.state.mutationsWithoutPlan;
|
|
17522
|
+
if (mutations >= PLAN_NUDGE_THRESHOLD && !deps.state.planNudgeSent && ctx.contextManager) {
|
|
17523
|
+
deps.state.planNudgeSent = true;
|
|
17524
|
+
ctx.contextManager.addMessage({
|
|
17525
|
+
role: "user",
|
|
17526
|
+
content: `<system-summary>${t("exec.plan_nudge", {
|
|
17527
|
+
count: String(mutations)
|
|
17528
|
+
})}</system-summary>`
|
|
17529
|
+
});
|
|
17465
17530
|
}
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17531
|
+
}
|
|
17532
|
+
}
|
|
17533
|
+
const stuckReason = deps.stuckDetector.getStuckReason();
|
|
17534
|
+
if (stuckReason) {
|
|
17535
|
+
const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
|
|
17536
|
+
if (logIt) {
|
|
17537
|
+
ctx.logger?.warn(stuckReason);
|
|
17538
|
+
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
17539
|
+
if (deps.stuckDetector.isStuck())
|
|
17540
|
+
deps.state.stuckNotified = true;
|
|
17541
|
+
}
|
|
17542
|
+
}
|
|
17543
|
+
if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
|
|
17544
|
+
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
17545
|
+
if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
|
|
17546
|
+
const recovery = deps.stuckDetector.getRecoveryMessage();
|
|
17547
|
+
if (recovery && ctx.contextManager) {
|
|
17548
|
+
const lastError = deps.stuckDetector.getLastErrorOutput();
|
|
17549
|
+
const skillHint = lastError ? `
|
|
17550
|
+
If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
|
|
17551
|
+
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
17552
|
+
const actionableHintStr = actionableHints.length > 0 ? `
|
|
17553
|
+
${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
|
|
17554
|
+
`) })}` : "";
|
|
17555
|
+
const alternative = deps.stuckDetector.getToolAlternative();
|
|
17556
|
+
const altHint = alternative ? `
|
|
17557
|
+
Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
|
|
17558
|
+
ctx.contextManager.addMessage({
|
|
17559
|
+
role: "user",
|
|
17560
|
+
content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
|
|
17561
|
+
});
|
|
17475
17562
|
}
|
|
17476
|
-
deps.
|
|
17477
|
-
if (
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17563
|
+
const hints = deps.stuckDetector.getHints();
|
|
17564
|
+
if (hints.length > 0 && ctx.contextManager) {
|
|
17565
|
+
const hintMsg = t("exec.hints", {
|
|
17566
|
+
hints: hints.map((h) => `- ${h}`).join(`
|
|
17567
|
+
`)
|
|
17568
|
+
});
|
|
17569
|
+
ctx.contextManager.addMessage({
|
|
17570
|
+
role: "user",
|
|
17571
|
+
content: `<system-summary>${hintMsg}</system-summary>`
|
|
17572
|
+
});
|
|
17481
17573
|
}
|
|
17482
|
-
deps.
|
|
17574
|
+
deps.stuckDetector.recordEscalation();
|
|
17575
|
+
deps.state.lastRecoveryIteration = currentIter;
|
|
17576
|
+
if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
17577
|
+
const escalation = t("exec.escalation", {
|
|
17578
|
+
stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
17579
|
+
description: deps.trackerRef.current?.getCurrentStep()?.description ?? ""
|
|
17580
|
+
});
|
|
17581
|
+
ctx.onMeta(escalation);
|
|
17582
|
+
}
|
|
17583
|
+
if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
|
|
17584
|
+
const step = deps.trackerRef.current?.getCurrentStep();
|
|
17585
|
+
ctx.contextManager.addMessage({
|
|
17586
|
+
role: "user",
|
|
17587
|
+
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>`
|
|
17588
|
+
});
|
|
17589
|
+
}
|
|
17590
|
+
}
|
|
17591
|
+
}
|
|
17592
|
+
},
|
|
17593
|
+
onBeforeTool: (_ctx, call) => {
|
|
17594
|
+
const warning = deps.checkPlanAlignment(call);
|
|
17595
|
+
if (warning) {
|
|
17596
|
+
deps.pendingMessages.push({
|
|
17597
|
+
role: "user",
|
|
17598
|
+
content: `<system-summary>${warning}</system-summary>`
|
|
17599
|
+
});
|
|
17600
|
+
deps.state.consecutivePlanWarnings++;
|
|
17601
|
+
if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
17602
|
+
deps.pendingMessages.push({
|
|
17603
|
+
role: "user",
|
|
17604
|
+
content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
|
|
17605
|
+
});
|
|
17606
|
+
return t("exec.plan_blocked", {
|
|
17607
|
+
step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
17608
|
+
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
|
|
17609
|
+
});
|
|
17610
|
+
}
|
|
17611
|
+
} else {
|
|
17612
|
+
deps.state.consecutivePlanWarnings = 0;
|
|
17613
|
+
}
|
|
17614
|
+
return true;
|
|
17615
|
+
},
|
|
17616
|
+
onToolCall: (ctx) => {
|
|
17617
|
+
const toolName = ctx?.toolName;
|
|
17618
|
+
const args = ctx?.args;
|
|
17619
|
+
if (toolName && args) {
|
|
17620
|
+
deps.stuckDetector.recordToolCall(toolName, args);
|
|
17621
|
+
if (!deps.trackerRef.current && (toolName === "write_file" || toolName === "edit_file" || toolName === "bash" || toolName === "download_file")) {
|
|
17622
|
+
deps.state.mutationsWithoutPlan++;
|
|
17623
|
+
}
|
|
17624
|
+
}
|
|
17625
|
+
},
|
|
17626
|
+
onAfterTool: (ctx, call, result) => {
|
|
17627
|
+
if (call.name === "bash") {
|
|
17628
|
+
deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
|
|
17629
|
+
}
|
|
17630
|
+
const toolText = String(result.output ?? "");
|
|
17631
|
+
const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
|
|
17632
|
+
if (hasTypeError) {
|
|
17633
|
+
deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
|
|
17634
|
+
}
|
|
17635
|
+
if (call.name === "write_file" || call.name === "edit_file") {
|
|
17636
|
+
const broken = extractBrokenFiles(toolText);
|
|
17637
|
+
if (broken.length > 0) {
|
|
17638
|
+
for (const b of broken)
|
|
17639
|
+
deps.state.typecheckFailures.set(b.file, b.error);
|
|
17640
|
+
} else if (result.success) {
|
|
17641
|
+
deps.state.typecheckFailures.clear();
|
|
17642
|
+
}
|
|
17643
|
+
}
|
|
17644
|
+
if (!result.success) {
|
|
17645
|
+
if (call.name === "bash" && platform6() === "win32") {
|
|
17646
|
+
const cmd = String(call.arguments?.command ?? "");
|
|
17647
|
+
const forbidden = forbiddenWindowsCommand(cmd);
|
|
17648
|
+
if (forbidden) {
|
|
17649
|
+
const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
|
|
17650
|
+
deps.forbiddenBashFailures.set(forbidden, n);
|
|
17651
|
+
if (n === 2) {
|
|
17652
|
+
deps.pendingMessages.push({
|
|
17653
|
+
role: "user",
|
|
17654
|
+
content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`
|
|
17655
|
+
});
|
|
17656
|
+
}
|
|
17657
|
+
}
|
|
17658
|
+
}
|
|
17659
|
+
if (!hasTypeError) {
|
|
17660
|
+
deps.stuckDetector.recordToolError(call.name, result.output);
|
|
17661
|
+
}
|
|
17662
|
+
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
17663
|
+
const alternative = deps.stuckDetector.getToolAlternative();
|
|
17664
|
+
if (actionableHints.length > 0 || alternative) {
|
|
17665
|
+
const parts = [...actionableHints];
|
|
17666
|
+
if (alternative) {
|
|
17667
|
+
parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
|
|
17668
|
+
}
|
|
17669
|
+
deps.pendingMessages.push({
|
|
17670
|
+
role: "user",
|
|
17671
|
+
content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
|
|
17672
|
+
`) })}</system-summary>`
|
|
17673
|
+
});
|
|
17674
|
+
}
|
|
17675
|
+
} else {
|
|
17676
|
+
deps.stuckDetector.recordToolSuccess();
|
|
17677
|
+
if (call.name === "bash" && result.success) {
|
|
17678
|
+
const cmd = String(call.arguments?.command ?? "");
|
|
17679
|
+
const testRun = detectTestResults(String(result.output ?? ""));
|
|
17680
|
+
if (testRun && testRun.failed > 0) {
|
|
17681
|
+
deps.pendingMessages.push({
|
|
17682
|
+
role: "user",
|
|
17683
|
+
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>`
|
|
17684
|
+
});
|
|
17685
|
+
} else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
17686
|
+
deps.pendingMessages.push({
|
|
17687
|
+
role: "user",
|
|
17688
|
+
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>`
|
|
17689
|
+
});
|
|
17690
|
+
} else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
17691
|
+
deps.pendingMessages.push({
|
|
17692
|
+
role: "user",
|
|
17693
|
+
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>`
|
|
17694
|
+
});
|
|
17695
|
+
}
|
|
17696
|
+
}
|
|
17697
|
+
}
|
|
17698
|
+
if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
|
|
17699
|
+
const filePath = call.arguments?.path;
|
|
17700
|
+
if (filePath) {
|
|
17701
|
+
deps.stuckDetector.recordFileRewrite(filePath);
|
|
17702
|
+
if (deps.stuckDetector.hasExcessiveRewrites()) {
|
|
17703
|
+
const file = deps.stuckDetector.getExcessiveRewriteFile();
|
|
17704
|
+
const count = deps.stuckDetector.getFileRewriteCount(file);
|
|
17705
|
+
if (ctx.onMeta) {
|
|
17706
|
+
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
17707
|
+
file,
|
|
17708
|
+
count: String(count)
|
|
17709
|
+
}));
|
|
17710
|
+
}
|
|
17711
|
+
}
|
|
17712
|
+
}
|
|
17713
|
+
deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
17714
|
+
}
|
|
17715
|
+
deps.maybeSearchError(ctx, call);
|
|
17716
|
+
}
|
|
17717
|
+
};
|
|
17718
|
+
}
|
|
17719
|
+
var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, PLAN_NUDGE_THRESHOLD = 2;
|
|
17720
|
+
var init_execution_plugin = __esm(() => {
|
|
17721
|
+
init_i18n();
|
|
17722
|
+
init_bash();
|
|
17723
|
+
init_windows_commands();
|
|
17724
|
+
init_js_identifiers();
|
|
17725
|
+
});
|
|
17726
|
+
|
|
17727
|
+
// src/modules/execution/plan-tool.ts
|
|
17728
|
+
function createPlanToolDefinitions(deps) {
|
|
17729
|
+
return [
|
|
17730
|
+
{
|
|
17731
|
+
name: "plan",
|
|
17732
|
+
alwaysOn: true,
|
|
17733
|
+
description: `Create, update, show, abort, list, switch, delete, purge, or re-plan multi-step plans.
|
|
17734
|
+
|
|
17735
|
+
Actions:
|
|
17736
|
+
- create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.
|
|
17737
|
+
- update: Mark step status (done/failed/skipped), or rebuild plan with new steps.
|
|
17738
|
+
- show: Print current plan checklist.
|
|
17739
|
+
- abort: Archive the current plan (or the plan given by id) and clear the active slot.
|
|
17740
|
+
- list: Show all plans (active, drafts, archived) with progress.
|
|
17741
|
+
- switch: Make a different plan active (by plan id).
|
|
17742
|
+
- re-plan: Iterative replanning: keep completed steps, replace remaining with new steps.
|
|
17743
|
+
- delete: Permanently delete a plan by id (plan delete id=plan_xxx).
|
|
17744
|
+
- purge: Delete ALL plans (active, drafts, archived).
|
|
17745
|
+
|
|
17746
|
+
Write CONCRETE steps with exact file paths and commands:
|
|
17747
|
+
- Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")
|
|
17748
|
+
- Specify WHICH packages to install (e.g. "run npm install react react-dom")
|
|
17749
|
+
- Specify WHICH CLI commands to run with exact arguments
|
|
17750
|
+
- Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)
|
|
17751
|
+
- Good: "Создать src/components/Header.tsx с навигацией и логотипом"
|
|
17752
|
+
- Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)
|
|
17753
|
+
- For steps that REMOVE files, pass kinds: ["delete"] aligned with steps (the done-gate verifies those files are gone instead of demanding they exist).`,
|
|
17754
|
+
parameters: {
|
|
17755
|
+
type: "object",
|
|
17756
|
+
properties: {
|
|
17757
|
+
action: {
|
|
17758
|
+
type: "string",
|
|
17759
|
+
enum: [
|
|
17760
|
+
"create",
|
|
17761
|
+
"update",
|
|
17762
|
+
"show",
|
|
17763
|
+
"abort",
|
|
17764
|
+
"list",
|
|
17765
|
+
"switch",
|
|
17766
|
+
"re-plan",
|
|
17767
|
+
"delete",
|
|
17768
|
+
"purge"
|
|
17769
|
+
]
|
|
17770
|
+
},
|
|
17771
|
+
title: { type: "string" },
|
|
17772
|
+
steps: { type: "array", items: { type: "string" } },
|
|
17773
|
+
kinds: {
|
|
17774
|
+
type: "array",
|
|
17775
|
+
items: { type: "string", enum: ["create", "delete"] },
|
|
17776
|
+
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".'
|
|
17777
|
+
},
|
|
17778
|
+
step: { type: "number" },
|
|
17779
|
+
status: { type: "string", enum: ["done", "failed", "skipped"] },
|
|
17780
|
+
note: { type: "string" },
|
|
17781
|
+
id: { type: "string", description: "Plan id (for switch action)" }
|
|
17782
|
+
},
|
|
17783
|
+
required: ["action"]
|
|
17784
|
+
},
|
|
17785
|
+
handler: async (_ctx, args) => {
|
|
17786
|
+
const action = String(args.action);
|
|
17787
|
+
if (action === "list") {
|
|
17788
|
+
const metas = deps.store.listAll();
|
|
17789
|
+
if (metas.length === 0) {
|
|
17790
|
+
return { success: true, output: t("plan.list_empty") };
|
|
17791
|
+
}
|
|
17792
|
+
const lines = metas.map((m) => {
|
|
17793
|
+
const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
|
|
17794
|
+
const namePart = m.name ? ` (${m.name})` : "";
|
|
17795
|
+
return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
|
|
17796
|
+
});
|
|
17797
|
+
lines.push("");
|
|
17798
|
+
lines.push(t("plan.list_legend"));
|
|
17799
|
+
return {
|
|
17800
|
+
success: true,
|
|
17801
|
+
output: `${t("plan.list_header")}
|
|
17802
|
+
${lines.join(`
|
|
17803
|
+
`)}`
|
|
17804
|
+
};
|
|
17805
|
+
}
|
|
17806
|
+
if (action === "switch") {
|
|
17807
|
+
const planId = String(args.id || "");
|
|
17808
|
+
if (!planId) {
|
|
17809
|
+
return { success: false, output: t("plan.switch_no_id") };
|
|
17810
|
+
}
|
|
17811
|
+
const found = deps.store.find(planId);
|
|
17812
|
+
if (!found) {
|
|
17813
|
+
return {
|
|
17814
|
+
success: false,
|
|
17815
|
+
output: t("plan.not_found", { id: planId })
|
|
17816
|
+
};
|
|
17817
|
+
}
|
|
17818
|
+
if (found.status === "active") {
|
|
17819
|
+
return {
|
|
17820
|
+
success: true,
|
|
17821
|
+
output: t("plan.already_active", {
|
|
17822
|
+
id: found.plan.id,
|
|
17823
|
+
title: found.plan.title
|
|
17824
|
+
}),
|
|
17825
|
+
display: deps.trackerRef.current?.toPromptBlock()
|
|
17826
|
+
};
|
|
17827
|
+
}
|
|
17828
|
+
deps.preserveActive();
|
|
17829
|
+
if (found.status === "draft") {
|
|
17830
|
+
deps.store.removeDraft(found.plan.id);
|
|
17831
|
+
} else if (found.status === "archived") {
|
|
17832
|
+
deps.store.removeArchived(found.plan.id);
|
|
17833
|
+
}
|
|
17834
|
+
deps.setPlan(found.plan);
|
|
17483
17835
|
const display = deps.trackerRef.current?.toPromptBlock();
|
|
17484
17836
|
return {
|
|
17485
17837
|
success: true,
|
|
@@ -17678,8 +18030,20 @@ ${progress2}`,
|
|
|
17678
18030
|
}
|
|
17679
18031
|
}
|
|
17680
18032
|
}
|
|
17681
|
-
|
|
17682
|
-
|
|
18033
|
+
if (status === "done") {
|
|
18034
|
+
const blocked = stepTypecheckGate(deps.typecheckFailures, target.description);
|
|
18035
|
+
if (blocked) {
|
|
18036
|
+
return {
|
|
18037
|
+
success: false,
|
|
18038
|
+
output: t("plan.typecheck_failed", {
|
|
18039
|
+
step: String(stepId),
|
|
18040
|
+
error: blocked
|
|
18041
|
+
})
|
|
18042
|
+
};
|
|
18043
|
+
}
|
|
18044
|
+
}
|
|
18045
|
+
tracker.updateStepStatus(stepId, status);
|
|
18046
|
+
if (args.note)
|
|
17683
18047
|
tracker.addNote(Number(args.step), String(args.note));
|
|
17684
18048
|
tracker.syncCurrentStep();
|
|
17685
18049
|
deps.store.saveActive(tracker.getPlan());
|
|
@@ -17778,430 +18142,170 @@ ${progress}${vacuousNote}`,
|
|
|
17778
18142
|
if (!deleted) {
|
|
17779
18143
|
return {
|
|
17780
18144
|
success: false,
|
|
17781
|
-
output: t("plan.not_found", { id: planId })
|
|
17782
|
-
};
|
|
17783
|
-
}
|
|
17784
|
-
if (deleted === "active") {
|
|
17785
|
-
deps.trackerRef.current = null;
|
|
17786
|
-
deps.clearCompleted();
|
|
17787
|
-
}
|
|
17788
|
-
return {
|
|
17789
|
-
success: true,
|
|
17790
|
-
output: t("plan.deleted", { id: planId })
|
|
17791
|
-
};
|
|
17792
|
-
}
|
|
17793
|
-
if (action === "purge") {
|
|
17794
|
-
const count = deps.store.purgeAll();
|
|
17795
|
-
deps.trackerRef.current = null;
|
|
17796
|
-
deps.clearCompleted();
|
|
17797
|
-
return {
|
|
17798
|
-
success: true,
|
|
17799
|
-
output: t("plan.purged", { count: String(count) })
|
|
17800
|
-
};
|
|
17801
|
-
}
|
|
17802
|
-
if (!deps.trackerRef.current) {
|
|
17803
|
-
return { success: false, output: t("plan.no_active") };
|
|
17804
|
-
}
|
|
17805
|
-
return {
|
|
17806
|
-
success: false,
|
|
17807
|
-
output: t("plan.unknown_action", { action })
|
|
17808
|
-
};
|
|
17809
|
-
}
|
|
17810
|
-
},
|
|
17811
|
-
{
|
|
17812
|
-
name: "todo",
|
|
17813
|
-
alwaysOn: true,
|
|
17814
|
-
description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
|
|
17815
|
-
parameters: {
|
|
17816
|
-
type: "object",
|
|
17817
|
-
properties: {
|
|
17818
|
-
action: { type: "string", enum: ["add", "done", "list"] },
|
|
17819
|
-
items: { type: "array", items: { type: "string" } }
|
|
17820
|
-
},
|
|
17821
|
-
required: ["action"]
|
|
17822
|
-
},
|
|
17823
|
-
handler: async (_ctx, args) => {
|
|
17824
|
-
if (!deps.trackerRef.current) {
|
|
17825
|
-
return { success: false, output: t("plan.no_active") };
|
|
17826
|
-
}
|
|
17827
|
-
const tracker = deps.trackerRef.current;
|
|
17828
|
-
const action = String(args.action);
|
|
17829
|
-
const currentStep = tracker.getCurrentStep();
|
|
17830
|
-
if (!currentStep) {
|
|
17831
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
17832
|
-
}
|
|
17833
|
-
if (action === "add" && Array.isArray(args.items)) {
|
|
17834
|
-
const items = args.items.map(String);
|
|
17835
|
-
const existing = currentStep.subtasks ?? [];
|
|
17836
|
-
let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
|
|
17837
|
-
for (const item of items) {
|
|
17838
|
-
existing.push({ id: nextId++, text: item, done: false });
|
|
17839
|
-
}
|
|
17840
|
-
currentStep.subtasks = existing;
|
|
17841
|
-
deps.store.saveActive(tracker.getPlan());
|
|
17842
|
-
const display = tracker.toPromptBlock();
|
|
17843
|
-
return {
|
|
17844
|
-
success: true,
|
|
17845
|
-
output: t("todo.added", {
|
|
17846
|
-
count: String(items.length),
|
|
17847
|
-
items: items.join(", ")
|
|
17848
|
-
}),
|
|
17849
|
-
display
|
|
17850
|
-
};
|
|
17851
|
-
}
|
|
17852
|
-
if (action === "done") {
|
|
17853
|
-
const items = Array.isArray(args.items) ? args.items.map(String) : [];
|
|
17854
|
-
if (items.length === 0) {
|
|
17855
|
-
return {
|
|
17856
|
-
success: false,
|
|
17857
|
-
output: t("todo.no_items")
|
|
17858
|
-
};
|
|
17859
|
-
}
|
|
17860
|
-
const subs = currentStep.subtasks ?? [];
|
|
17861
|
-
let marked = 0;
|
|
17862
|
-
for (const item of items) {
|
|
17863
|
-
const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
|
|
17864
|
-
if (sub) {
|
|
17865
|
-
sub.done = true;
|
|
17866
|
-
marked++;
|
|
17867
|
-
}
|
|
17868
|
-
}
|
|
17869
|
-
if (marked === 0) {
|
|
17870
|
-
return {
|
|
17871
|
-
success: false,
|
|
17872
|
-
output: t("todo.subtask_not_found", {
|
|
17873
|
-
items: items.join(", ")
|
|
17874
|
-
})
|
|
17875
|
-
};
|
|
17876
|
-
}
|
|
17877
|
-
currentStep.subtasks = subs;
|
|
17878
|
-
deps.store.saveActive(tracker.getPlan());
|
|
17879
|
-
const doneCount = subs.filter((s) => s.done).length;
|
|
17880
|
-
const display = tracker.toPromptBlock();
|
|
17881
|
-
let output = t("todo.marked_done", {
|
|
17882
|
-
count: String(marked)
|
|
17883
|
-
});
|
|
17884
|
-
output += ` (${doneCount}/${subs.length})`;
|
|
17885
|
-
if (doneCount === subs.length) {
|
|
17886
|
-
output += `
|
|
17887
|
-
All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
|
|
17888
|
-
}
|
|
17889
|
-
return { success: true, output, display };
|
|
17890
|
-
}
|
|
17891
|
-
if (action === "list") {
|
|
17892
|
-
const subs = currentStep.subtasks ?? [];
|
|
17893
|
-
const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
|
|
17894
|
-
return {
|
|
17895
|
-
success: true,
|
|
17896
|
-
output: `Step ${currentStep.id}: ${currentStep.description}
|
|
17897
|
-
${lines.join(`
|
|
17898
|
-
`)}`
|
|
17899
|
-
};
|
|
17900
|
-
}
|
|
17901
|
-
return {
|
|
17902
|
-
success: false,
|
|
17903
|
-
output: t("todo.unknown_action", { action })
|
|
17904
|
-
};
|
|
17905
|
-
}
|
|
17906
|
-
},
|
|
17907
|
-
{
|
|
17908
|
-
name: "verify",
|
|
17909
|
-
alwaysOn: true,
|
|
17910
|
-
description: "Run verification for the current step. Checks files mentioned in the step description.",
|
|
17911
|
-
parameters: {
|
|
17912
|
-
type: "object",
|
|
17913
|
-
properties: {
|
|
17914
|
-
step: { type: "number", description: "Step number to verify" }
|
|
17915
|
-
}
|
|
17916
|
-
},
|
|
17917
|
-
handler: async (_ctx, args) => {
|
|
17918
|
-
if (!deps.trackerRef.current)
|
|
17919
|
-
return { success: false, output: t("plan.no_active") };
|
|
17920
|
-
const tracker = deps.trackerRef.current;
|
|
17921
|
-
const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
|
|
17922
|
-
if (!step)
|
|
17923
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
17924
|
-
const result = await deps.verifier.verifyStep(step.description);
|
|
17925
|
-
if (result.noFiles) {
|
|
17926
|
-
return {
|
|
17927
|
-
success: true,
|
|
17928
|
-
output: t("verify.no_files", { step: String(step.id) })
|
|
17929
|
-
};
|
|
17930
|
-
}
|
|
17931
|
-
return {
|
|
17932
|
-
success: result.passed,
|
|
17933
|
-
output: result.passed ? t("verify.passed") : t("verify.failed", {
|
|
17934
|
-
details: result.failed.map((f) => f.message).join("; ")
|
|
17935
|
-
})
|
|
17936
|
-
};
|
|
17937
|
-
}
|
|
17938
|
-
}
|
|
17939
|
-
];
|
|
17940
|
-
}
|
|
17941
|
-
var init_plan_tool = __esm(() => {
|
|
17942
|
-
init_i18n();
|
|
17943
|
-
init_plan_coverage();
|
|
17944
|
-
});
|
|
17945
|
-
|
|
17946
|
-
// src/modules/execution/windows-commands.ts
|
|
17947
|
-
function firstWord(command) {
|
|
17948
|
-
const segs = command.trim().split(/[\s;|&]+/);
|
|
17949
|
-
return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
|
|
17950
|
-
}
|
|
17951
|
-
function forbiddenWindowsCommand(command) {
|
|
17952
|
-
const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
|
|
17953
|
-
if (m)
|
|
17954
|
-
return forbiddenWindowsCommand(m[1]);
|
|
17955
|
-
const word = firstWord(command);
|
|
17956
|
-
if (word && FORBIDDEN_COMMANDS.has(word))
|
|
17957
|
-
return word;
|
|
17958
|
-
return null;
|
|
17959
|
-
}
|
|
17960
|
-
var FORBIDDEN_COMMANDS;
|
|
17961
|
-
var init_windows_commands = __esm(() => {
|
|
17962
|
-
FORBIDDEN_COMMANDS = new Set([
|
|
17963
|
-
"grep",
|
|
17964
|
-
"sed",
|
|
17965
|
-
"ls",
|
|
17966
|
-
"find",
|
|
17967
|
-
"rm",
|
|
17968
|
-
"touch",
|
|
17969
|
-
"which",
|
|
17970
|
-
"diff",
|
|
17971
|
-
"cp",
|
|
17972
|
-
"mv"
|
|
17973
|
-
]);
|
|
17974
|
-
});
|
|
17975
|
-
|
|
17976
|
-
// src/modules/execution/execution-plugin.ts
|
|
17977
|
-
import { platform as platform6 } from "os";
|
|
17978
|
-
function createExecutionPlugin(deps) {
|
|
17979
|
-
return {
|
|
17980
|
-
name: "execution",
|
|
17981
|
-
onBeforeThink: (ctx) => {
|
|
17982
|
-
if (ctx.contextManager && deps.pendingMessages.length > 0) {
|
|
17983
|
-
for (const m of deps.pendingMessages.splice(0)) {
|
|
17984
|
-
ctx.contextManager.addMessage(m);
|
|
17985
|
-
}
|
|
17986
|
-
}
|
|
17987
|
-
if (deps.trackerRef.current?.isComplete()) {
|
|
17988
|
-
deps.stuckDetector.resetStepProgress();
|
|
17989
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
17990
|
-
deps.state.lastStepId = -1;
|
|
17991
|
-
deps.state.stuckNotified = false;
|
|
17992
|
-
deps.state.mutationsWithoutPlan = 0;
|
|
17993
|
-
deps.state.planNudgeSent = false;
|
|
17994
|
-
} else {
|
|
17995
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
17996
|
-
if (deps.trackerRef.current && step) {
|
|
17997
|
-
if (step.id !== deps.state.lastStepId) {
|
|
17998
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
17999
|
-
deps.state.lastStepId = step.id;
|
|
18000
|
-
deps.state.stuckNotified = false;
|
|
18001
|
-
}
|
|
18002
|
-
deps.stuckDetector.setCurrentStep(step.id, step.description);
|
|
18003
|
-
deps.stuckDetector.recordIteration(step.id);
|
|
18004
|
-
deps.state.mutationsWithoutPlan = 0;
|
|
18005
|
-
deps.state.planNudgeSent = false;
|
|
18006
|
-
} else {
|
|
18007
|
-
deps.stuckDetector.reset();
|
|
18008
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
18009
|
-
deps.state.lastStepId = -1;
|
|
18010
|
-
deps.state.stuckNotified = false;
|
|
18011
|
-
const mutations = deps.state.mutationsWithoutPlan;
|
|
18012
|
-
if (mutations >= PLAN_NUDGE_THRESHOLD && !deps.state.planNudgeSent && ctx.contextManager) {
|
|
18013
|
-
deps.state.planNudgeSent = true;
|
|
18014
|
-
ctx.contextManager.addMessage({
|
|
18015
|
-
role: "user",
|
|
18016
|
-
content: `<system-summary>${t("exec.plan_nudge", {
|
|
18017
|
-
count: String(mutations)
|
|
18018
|
-
})}</system-summary>`
|
|
18019
|
-
});
|
|
18020
|
-
}
|
|
18021
|
-
}
|
|
18022
|
-
}
|
|
18023
|
-
const stuckReason = deps.stuckDetector.getStuckReason();
|
|
18024
|
-
if (stuckReason) {
|
|
18025
|
-
const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
|
|
18026
|
-
if (logIt) {
|
|
18027
|
-
ctx.logger?.warn(stuckReason);
|
|
18028
|
-
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
18029
|
-
if (deps.stuckDetector.isStuck())
|
|
18030
|
-
deps.state.stuckNotified = true;
|
|
18031
|
-
}
|
|
18032
|
-
}
|
|
18033
|
-
if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
|
|
18034
|
-
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
18035
|
-
if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
|
|
18036
|
-
const recovery = deps.stuckDetector.getRecoveryMessage();
|
|
18037
|
-
if (recovery && ctx.contextManager) {
|
|
18038
|
-
const lastError = deps.stuckDetector.getLastErrorOutput();
|
|
18039
|
-
const skillHint = lastError ? `
|
|
18040
|
-
If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
|
|
18041
|
-
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
18042
|
-
const actionableHintStr = actionableHints.length > 0 ? `
|
|
18043
|
-
${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
|
|
18044
|
-
`) })}` : "";
|
|
18045
|
-
const alternative = deps.stuckDetector.getToolAlternative();
|
|
18046
|
-
const altHint = alternative ? `
|
|
18047
|
-
Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
|
|
18048
|
-
ctx.contextManager.addMessage({
|
|
18049
|
-
role: "user",
|
|
18050
|
-
content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
|
|
18051
|
-
});
|
|
18052
|
-
}
|
|
18053
|
-
const hints = deps.stuckDetector.getHints();
|
|
18054
|
-
if (hints.length > 0 && ctx.contextManager) {
|
|
18055
|
-
const hintMsg = t("exec.hints", {
|
|
18056
|
-
hints: hints.map((h) => `- ${h}`).join(`
|
|
18057
|
-
`)
|
|
18058
|
-
});
|
|
18059
|
-
ctx.contextManager.addMessage({
|
|
18060
|
-
role: "user",
|
|
18061
|
-
content: `<system-summary>${hintMsg}</system-summary>`
|
|
18062
|
-
});
|
|
18063
|
-
}
|
|
18064
|
-
deps.stuckDetector.recordEscalation();
|
|
18065
|
-
deps.state.lastRecoveryIteration = currentIter;
|
|
18066
|
-
if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
18067
|
-
const escalation = t("exec.escalation", {
|
|
18068
|
-
stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
18069
|
-
description: deps.trackerRef.current?.getCurrentStep()?.description ?? ""
|
|
18070
|
-
});
|
|
18071
|
-
ctx.onMeta(escalation);
|
|
18072
|
-
}
|
|
18073
|
-
if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
|
|
18074
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
18075
|
-
ctx.contextManager.addMessage({
|
|
18076
|
-
role: "user",
|
|
18077
|
-
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>`
|
|
18078
|
-
});
|
|
18145
|
+
output: t("plan.not_found", { id: planId })
|
|
18146
|
+
};
|
|
18147
|
+
}
|
|
18148
|
+
if (deleted === "active") {
|
|
18149
|
+
deps.trackerRef.current = null;
|
|
18150
|
+
deps.clearCompleted();
|
|
18079
18151
|
}
|
|
18152
|
+
return {
|
|
18153
|
+
success: true,
|
|
18154
|
+
output: t("plan.deleted", { id: planId })
|
|
18155
|
+
};
|
|
18080
18156
|
}
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18086
|
-
|
|
18087
|
-
|
|
18088
|
-
|
|
18089
|
-
});
|
|
18090
|
-
deps.state.consecutivePlanWarnings++;
|
|
18091
|
-
if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
18092
|
-
deps.pendingMessages.push({
|
|
18093
|
-
role: "user",
|
|
18094
|
-
content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
|
|
18095
|
-
});
|
|
18096
|
-
return t("exec.plan_blocked", {
|
|
18097
|
-
step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
18098
|
-
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
|
|
18099
|
-
});
|
|
18157
|
+
if (action === "purge") {
|
|
18158
|
+
const count = deps.store.purgeAll();
|
|
18159
|
+
deps.trackerRef.current = null;
|
|
18160
|
+
deps.clearCompleted();
|
|
18161
|
+
return {
|
|
18162
|
+
success: true,
|
|
18163
|
+
output: t("plan.purged", { count: String(count) })
|
|
18164
|
+
};
|
|
18100
18165
|
}
|
|
18101
|
-
|
|
18102
|
-
|
|
18103
|
-
}
|
|
18104
|
-
return true;
|
|
18105
|
-
},
|
|
18106
|
-
onToolCall: (ctx) => {
|
|
18107
|
-
const toolName = ctx?.toolName;
|
|
18108
|
-
const args = ctx?.args;
|
|
18109
|
-
if (toolName && args) {
|
|
18110
|
-
deps.stuckDetector.recordToolCall(toolName, args);
|
|
18111
|
-
if (!deps.trackerRef.current && (toolName === "write_file" || toolName === "edit_file" || toolName === "bash" || toolName === "download_file")) {
|
|
18112
|
-
deps.state.mutationsWithoutPlan++;
|
|
18166
|
+
if (!deps.trackerRef.current) {
|
|
18167
|
+
return { success: false, output: t("plan.no_active") };
|
|
18113
18168
|
}
|
|
18169
|
+
return {
|
|
18170
|
+
success: false,
|
|
18171
|
+
output: t("plan.unknown_action", { action })
|
|
18172
|
+
};
|
|
18114
18173
|
}
|
|
18115
18174
|
},
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
|
|
18131
|
-
deps.forbiddenBashFailures.set(forbidden, n);
|
|
18132
|
-
if (n === 2) {
|
|
18133
|
-
deps.pendingMessages.push({
|
|
18134
|
-
role: "user",
|
|
18135
|
-
content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`
|
|
18136
|
-
});
|
|
18137
|
-
}
|
|
18138
|
-
}
|
|
18175
|
+
{
|
|
18176
|
+
name: "todo",
|
|
18177
|
+
alwaysOn: true,
|
|
18178
|
+
description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
|
|
18179
|
+
parameters: {
|
|
18180
|
+
type: "object",
|
|
18181
|
+
properties: {
|
|
18182
|
+
action: { type: "string", enum: ["add", "done", "list"] },
|
|
18183
|
+
items: { type: "array", items: { type: "string" } }
|
|
18184
|
+
},
|
|
18185
|
+
required: ["action"]
|
|
18186
|
+
},
|
|
18187
|
+
handler: async (_ctx, args) => {
|
|
18188
|
+
if (!deps.trackerRef.current) {
|
|
18189
|
+
return { success: false, output: t("plan.no_active") };
|
|
18139
18190
|
}
|
|
18140
|
-
|
|
18141
|
-
|
|
18191
|
+
const tracker = deps.trackerRef.current;
|
|
18192
|
+
const action = String(args.action);
|
|
18193
|
+
const currentStep = tracker.getCurrentStep();
|
|
18194
|
+
if (!currentStep) {
|
|
18195
|
+
return { success: false, output: t("plan.step_not_found") };
|
|
18142
18196
|
}
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18197
|
+
if (action === "add" && Array.isArray(args.items)) {
|
|
18198
|
+
const items = args.items.map(String);
|
|
18199
|
+
const existing = currentStep.subtasks ?? [];
|
|
18200
|
+
let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
|
|
18201
|
+
for (const item of items) {
|
|
18202
|
+
existing.push({ id: nextId++, text: item, done: false });
|
|
18149
18203
|
}
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18204
|
+
currentStep.subtasks = existing;
|
|
18205
|
+
deps.store.saveActive(tracker.getPlan());
|
|
18206
|
+
const display = tracker.toPromptBlock();
|
|
18207
|
+
return {
|
|
18208
|
+
success: true,
|
|
18209
|
+
output: t("todo.added", {
|
|
18210
|
+
count: String(items.length),
|
|
18211
|
+
items: items.join(", ")
|
|
18212
|
+
}),
|
|
18213
|
+
display
|
|
18214
|
+
};
|
|
18155
18215
|
}
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
|
|
18162
|
-
|
|
18163
|
-
role: "user",
|
|
18164
|
-
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>`
|
|
18165
|
-
});
|
|
18166
|
-
} else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
18167
|
-
deps.pendingMessages.push({
|
|
18168
|
-
role: "user",
|
|
18169
|
-
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>`
|
|
18170
|
-
});
|
|
18171
|
-
} else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
18172
|
-
deps.pendingMessages.push({
|
|
18173
|
-
role: "user",
|
|
18174
|
-
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>`
|
|
18175
|
-
});
|
|
18216
|
+
if (action === "done") {
|
|
18217
|
+
const items = Array.isArray(args.items) ? args.items.map(String) : [];
|
|
18218
|
+
if (items.length === 0) {
|
|
18219
|
+
return {
|
|
18220
|
+
success: false,
|
|
18221
|
+
output: t("todo.no_items")
|
|
18222
|
+
};
|
|
18176
18223
|
}
|
|
18177
|
-
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
const file = deps.stuckDetector.getExcessiveRewriteFile();
|
|
18185
|
-
const count = deps.stuckDetector.getFileRewriteCount(file);
|
|
18186
|
-
if (ctx.onMeta) {
|
|
18187
|
-
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
18188
|
-
file,
|
|
18189
|
-
count: String(count)
|
|
18190
|
-
}));
|
|
18224
|
+
const subs = currentStep.subtasks ?? [];
|
|
18225
|
+
let marked = 0;
|
|
18226
|
+
for (const item of items) {
|
|
18227
|
+
const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
|
|
18228
|
+
if (sub) {
|
|
18229
|
+
sub.done = true;
|
|
18230
|
+
marked++;
|
|
18191
18231
|
}
|
|
18192
18232
|
}
|
|
18233
|
+
if (marked === 0) {
|
|
18234
|
+
return {
|
|
18235
|
+
success: false,
|
|
18236
|
+
output: t("todo.subtask_not_found", {
|
|
18237
|
+
items: items.join(", ")
|
|
18238
|
+
})
|
|
18239
|
+
};
|
|
18240
|
+
}
|
|
18241
|
+
currentStep.subtasks = subs;
|
|
18242
|
+
deps.store.saveActive(tracker.getPlan());
|
|
18243
|
+
const doneCount = subs.filter((s) => s.done).length;
|
|
18244
|
+
const display = tracker.toPromptBlock();
|
|
18245
|
+
let output = t("todo.marked_done", {
|
|
18246
|
+
count: String(marked)
|
|
18247
|
+
});
|
|
18248
|
+
output += ` (${doneCount}/${subs.length})`;
|
|
18249
|
+
if (doneCount === subs.length) {
|
|
18250
|
+
output += `
|
|
18251
|
+
All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
|
|
18252
|
+
}
|
|
18253
|
+
return { success: true, output, display };
|
|
18193
18254
|
}
|
|
18194
|
-
|
|
18255
|
+
if (action === "list") {
|
|
18256
|
+
const subs = currentStep.subtasks ?? [];
|
|
18257
|
+
const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
|
|
18258
|
+
return {
|
|
18259
|
+
success: true,
|
|
18260
|
+
output: `Step ${currentStep.id}: ${currentStep.description}
|
|
18261
|
+
${lines.join(`
|
|
18262
|
+
`)}`
|
|
18263
|
+
};
|
|
18264
|
+
}
|
|
18265
|
+
return {
|
|
18266
|
+
success: false,
|
|
18267
|
+
output: t("todo.unknown_action", { action })
|
|
18268
|
+
};
|
|
18269
|
+
}
|
|
18270
|
+
},
|
|
18271
|
+
{
|
|
18272
|
+
name: "verify",
|
|
18273
|
+
alwaysOn: true,
|
|
18274
|
+
description: "Run verification for the current step. Checks files mentioned in the step description.",
|
|
18275
|
+
parameters: {
|
|
18276
|
+
type: "object",
|
|
18277
|
+
properties: {
|
|
18278
|
+
step: { type: "number", description: "Step number to verify" }
|
|
18279
|
+
}
|
|
18280
|
+
},
|
|
18281
|
+
handler: async (_ctx, args) => {
|
|
18282
|
+
if (!deps.trackerRef.current)
|
|
18283
|
+
return { success: false, output: t("plan.no_active") };
|
|
18284
|
+
const tracker = deps.trackerRef.current;
|
|
18285
|
+
const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
|
|
18286
|
+
if (!step)
|
|
18287
|
+
return { success: false, output: t("plan.step_not_found") };
|
|
18288
|
+
const result = await deps.verifier.verifyStep(step.description);
|
|
18289
|
+
if (result.noFiles) {
|
|
18290
|
+
return {
|
|
18291
|
+
success: true,
|
|
18292
|
+
output: t("verify.no_files", { step: String(step.id) })
|
|
18293
|
+
};
|
|
18294
|
+
}
|
|
18295
|
+
return {
|
|
18296
|
+
success: result.passed,
|
|
18297
|
+
output: result.passed ? t("verify.passed") : t("verify.failed", {
|
|
18298
|
+
details: result.failed.map((f) => f.message).join("; ")
|
|
18299
|
+
})
|
|
18300
|
+
};
|
|
18195
18301
|
}
|
|
18196
|
-
deps.maybeSearchError(ctx, call);
|
|
18197
18302
|
}
|
|
18198
|
-
|
|
18303
|
+
];
|
|
18199
18304
|
}
|
|
18200
|
-
var
|
|
18201
|
-
var init_execution_plugin = __esm(() => {
|
|
18305
|
+
var init_plan_tool = __esm(() => {
|
|
18202
18306
|
init_i18n();
|
|
18203
|
-
|
|
18204
|
-
|
|
18307
|
+
init_plan_coverage();
|
|
18308
|
+
init_execution_plugin();
|
|
18205
18309
|
});
|
|
18206
18310
|
|
|
18207
18311
|
// src/modules/execution/module.ts
|
|
@@ -18233,7 +18337,8 @@ class ExecutionModule {
|
|
|
18233
18337
|
stuckNotified: false,
|
|
18234
18338
|
depsGateHints: new Map,
|
|
18235
18339
|
mutationsWithoutPlan: 0,
|
|
18236
|
-
planNudgeSent: false
|
|
18340
|
+
planNudgeSent: false,
|
|
18341
|
+
typecheckFailures: new Map
|
|
18237
18342
|
};
|
|
18238
18343
|
trackerRef = (() => {
|
|
18239
18344
|
const self = this;
|
|
@@ -18416,6 +18521,7 @@ class ExecutionModule {
|
|
|
18416
18521
|
stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
|
|
18417
18522
|
parseKinds: (a, st) => this.parseKinds(a, st),
|
|
18418
18523
|
getTaskText: (c) => this.getTaskText(c),
|
|
18524
|
+
typecheckFailures: this.state.typecheckFailures,
|
|
18419
18525
|
baseDir: this.baseDir
|
|
18420
18526
|
});
|
|
18421
18527
|
}
|
|
@@ -21162,8 +21268,6 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21162
21268
|
exitOnComplete,
|
|
21163
21269
|
contextManager,
|
|
21164
21270
|
activeToolTags,
|
|
21165
|
-
sessionId: sessionManager.getActiveMeta()?.id,
|
|
21166
|
-
sessionContext: sessionManager.getSessionContext() ?? undefined,
|
|
21167
21271
|
recursionDepth: 0,
|
|
21168
21272
|
fileOperationsCount: 0,
|
|
21169
21273
|
sessionHistory: {
|
|
@@ -21191,6 +21295,14 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21191
21295
|
}
|
|
21192
21296
|
}
|
|
21193
21297
|
};
|
|
21298
|
+
Object.defineProperty(toolCtx, "sessionId", {
|
|
21299
|
+
get: () => sessionManager.getActiveMeta()?.id,
|
|
21300
|
+
configurable: true
|
|
21301
|
+
});
|
|
21302
|
+
Object.defineProperty(toolCtx, "sessionContext", {
|
|
21303
|
+
get: () => sessionManager.getSessionContext() ?? undefined,
|
|
21304
|
+
configurable: true
|
|
21305
|
+
});
|
|
21194
21306
|
const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
|
|
21195
21307
|
toolCtx.llmProvider = llmProvider;
|
|
21196
21308
|
toolCtx.toolExecutor = toolExecutor;
|
|
@@ -21363,6 +21475,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21363
21475
|
sessionManager,
|
|
21364
21476
|
skillsModule,
|
|
21365
21477
|
pluginManager,
|
|
21478
|
+
toolCtx,
|
|
21366
21479
|
configDir: dir,
|
|
21367
21480
|
baseDir,
|
|
21368
21481
|
noAgentsMd: skipAgentsMd
|