micro-models-agent 0.43.1 → 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 +606 -523
- 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": "Планы:",
|
|
@@ -6764,7 +6774,7 @@ var init_edit_file = __esm(() => {
|
|
|
6764
6774
|
if (!content.includes(oldStr)) {
|
|
6765
6775
|
return {
|
|
6766
6776
|
success: false,
|
|
6767
|
-
output: t("file.string_not_found", { str: oldStr })
|
|
6777
|
+
output: t("file.string_not_found", { str: oldStr, path })
|
|
6768
6778
|
};
|
|
6769
6779
|
}
|
|
6770
6780
|
const updated = content.replace(oldStr, newStr);
|
|
@@ -17402,125 +17412,437 @@ var init_plan_coverage = __esm(() => {
|
|
|
17402
17412
|
]);
|
|
17403
17413
|
});
|
|
17404
17414
|
|
|
17405
|
-
// src/modules/execution/
|
|
17406
|
-
function
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
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
|
+
});
|
|
17423
17444
|
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17427
|
-
|
|
17428
|
-
|
|
17429
|
-
|
|
17430
|
-
|
|
17431
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
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);
|
|
17495
|
+
}
|
|
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;
|
|
17511
|
+
}
|
|
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
|
+
});
|
|
17469
17530
|
}
|
|
17470
|
-
const lines = metas.map((m) => {
|
|
17471
|
-
const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
|
|
17472
|
-
const namePart = m.name ? ` (${m.name})` : "";
|
|
17473
|
-
return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
|
|
17474
|
-
});
|
|
17475
|
-
lines.push("");
|
|
17476
|
-
lines.push(t("plan.list_legend"));
|
|
17477
|
-
return {
|
|
17478
|
-
success: true,
|
|
17479
|
-
output: `${t("plan.list_header")}
|
|
17480
|
-
${lines.join(`
|
|
17481
|
-
`)}`
|
|
17482
|
-
};
|
|
17483
17531
|
}
|
|
17484
|
-
|
|
17485
|
-
|
|
17486
|
-
|
|
17487
|
-
|
|
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
|
+
});
|
|
17488
17562
|
}
|
|
17489
|
-
const
|
|
17490
|
-
if (
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
};
|
|
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
|
+
});
|
|
17495
17573
|
}
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
};
|
|
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);
|
|
17505
17582
|
}
|
|
17506
|
-
deps.
|
|
17507
|
-
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
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
|
+
});
|
|
17511
17589
|
}
|
|
17512
|
-
deps.setPlan(found.plan);
|
|
17513
|
-
const display = deps.trackerRef.current?.toPromptBlock();
|
|
17514
|
-
return {
|
|
17515
|
-
success: true,
|
|
17516
|
-
output: t("plan.switched", {
|
|
17517
|
-
id: found.plan.id,
|
|
17518
|
-
title: found.plan.title
|
|
17519
|
-
}),
|
|
17520
|
-
display
|
|
17521
|
-
};
|
|
17522
17590
|
}
|
|
17523
|
-
|
|
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);
|
|
17835
|
+
const display = deps.trackerRef.current?.toPromptBlock();
|
|
17836
|
+
return {
|
|
17837
|
+
success: true,
|
|
17838
|
+
output: t("plan.switched", {
|
|
17839
|
+
id: found.plan.id,
|
|
17840
|
+
title: found.plan.title
|
|
17841
|
+
}),
|
|
17842
|
+
display
|
|
17843
|
+
};
|
|
17844
|
+
}
|
|
17845
|
+
if (action === "re-plan") {
|
|
17524
17846
|
if (!deps.trackerRef.current) {
|
|
17525
17847
|
return { success: false, output: t("plan.no_active") };
|
|
17526
17848
|
}
|
|
@@ -17708,6 +18030,18 @@ ${progress2}`,
|
|
|
17708
18030
|
}
|
|
17709
18031
|
}
|
|
17710
18032
|
}
|
|
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
|
+
}
|
|
17711
18045
|
tracker.updateStepStatus(stepId, status);
|
|
17712
18046
|
if (args.note)
|
|
17713
18047
|
tracker.addNote(Number(args.step), String(args.note));
|
|
@@ -17808,430 +18142,170 @@ ${progress}${vacuousNote}`,
|
|
|
17808
18142
|
if (!deleted) {
|
|
17809
18143
|
return {
|
|
17810
18144
|
success: false,
|
|
17811
|
-
output: t("plan.not_found", { id: planId })
|
|
17812
|
-
};
|
|
17813
|
-
}
|
|
17814
|
-
if (deleted === "active") {
|
|
17815
|
-
deps.trackerRef.current = null;
|
|
17816
|
-
deps.clearCompleted();
|
|
17817
|
-
}
|
|
17818
|
-
return {
|
|
17819
|
-
success: true,
|
|
17820
|
-
output: t("plan.deleted", { id: planId })
|
|
17821
|
-
};
|
|
17822
|
-
}
|
|
17823
|
-
if (action === "purge") {
|
|
17824
|
-
const count = deps.store.purgeAll();
|
|
17825
|
-
deps.trackerRef.current = null;
|
|
17826
|
-
deps.clearCompleted();
|
|
17827
|
-
return {
|
|
17828
|
-
success: true,
|
|
17829
|
-
output: t("plan.purged", { count: String(count) })
|
|
17830
|
-
};
|
|
17831
|
-
}
|
|
17832
|
-
if (!deps.trackerRef.current) {
|
|
17833
|
-
return { success: false, output: t("plan.no_active") };
|
|
17834
|
-
}
|
|
17835
|
-
return {
|
|
17836
|
-
success: false,
|
|
17837
|
-
output: t("plan.unknown_action", { action })
|
|
17838
|
-
};
|
|
17839
|
-
}
|
|
17840
|
-
},
|
|
17841
|
-
{
|
|
17842
|
-
name: "todo",
|
|
17843
|
-
alwaysOn: true,
|
|
17844
|
-
description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
|
|
17845
|
-
parameters: {
|
|
17846
|
-
type: "object",
|
|
17847
|
-
properties: {
|
|
17848
|
-
action: { type: "string", enum: ["add", "done", "list"] },
|
|
17849
|
-
items: { type: "array", items: { type: "string" } }
|
|
17850
|
-
},
|
|
17851
|
-
required: ["action"]
|
|
17852
|
-
},
|
|
17853
|
-
handler: async (_ctx, args) => {
|
|
17854
|
-
if (!deps.trackerRef.current) {
|
|
17855
|
-
return { success: false, output: t("plan.no_active") };
|
|
17856
|
-
}
|
|
17857
|
-
const tracker = deps.trackerRef.current;
|
|
17858
|
-
const action = String(args.action);
|
|
17859
|
-
const currentStep = tracker.getCurrentStep();
|
|
17860
|
-
if (!currentStep) {
|
|
17861
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
17862
|
-
}
|
|
17863
|
-
if (action === "add" && Array.isArray(args.items)) {
|
|
17864
|
-
const items = args.items.map(String);
|
|
17865
|
-
const existing = currentStep.subtasks ?? [];
|
|
17866
|
-
let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
|
|
17867
|
-
for (const item of items) {
|
|
17868
|
-
existing.push({ id: nextId++, text: item, done: false });
|
|
17869
|
-
}
|
|
17870
|
-
currentStep.subtasks = existing;
|
|
17871
|
-
deps.store.saveActive(tracker.getPlan());
|
|
17872
|
-
const display = tracker.toPromptBlock();
|
|
17873
|
-
return {
|
|
17874
|
-
success: true,
|
|
17875
|
-
output: t("todo.added", {
|
|
17876
|
-
count: String(items.length),
|
|
17877
|
-
items: items.join(", ")
|
|
17878
|
-
}),
|
|
17879
|
-
display
|
|
17880
|
-
};
|
|
17881
|
-
}
|
|
17882
|
-
if (action === "done") {
|
|
17883
|
-
const items = Array.isArray(args.items) ? args.items.map(String) : [];
|
|
17884
|
-
if (items.length === 0) {
|
|
17885
|
-
return {
|
|
17886
|
-
success: false,
|
|
17887
|
-
output: t("todo.no_items")
|
|
17888
|
-
};
|
|
17889
|
-
}
|
|
17890
|
-
const subs = currentStep.subtasks ?? [];
|
|
17891
|
-
let marked = 0;
|
|
17892
|
-
for (const item of items) {
|
|
17893
|
-
const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
|
|
17894
|
-
if (sub) {
|
|
17895
|
-
sub.done = true;
|
|
17896
|
-
marked++;
|
|
17897
|
-
}
|
|
17898
|
-
}
|
|
17899
|
-
if (marked === 0) {
|
|
17900
|
-
return {
|
|
17901
|
-
success: false,
|
|
17902
|
-
output: t("todo.subtask_not_found", {
|
|
17903
|
-
items: items.join(", ")
|
|
17904
|
-
})
|
|
17905
|
-
};
|
|
17906
|
-
}
|
|
17907
|
-
currentStep.subtasks = subs;
|
|
17908
|
-
deps.store.saveActive(tracker.getPlan());
|
|
17909
|
-
const doneCount = subs.filter((s) => s.done).length;
|
|
17910
|
-
const display = tracker.toPromptBlock();
|
|
17911
|
-
let output = t("todo.marked_done", {
|
|
17912
|
-
count: String(marked)
|
|
17913
|
-
});
|
|
17914
|
-
output += ` (${doneCount}/${subs.length})`;
|
|
17915
|
-
if (doneCount === subs.length) {
|
|
17916
|
-
output += `
|
|
17917
|
-
All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
|
|
17918
|
-
}
|
|
17919
|
-
return { success: true, output, display };
|
|
17920
|
-
}
|
|
17921
|
-
if (action === "list") {
|
|
17922
|
-
const subs = currentStep.subtasks ?? [];
|
|
17923
|
-
const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
|
|
17924
|
-
return {
|
|
17925
|
-
success: true,
|
|
17926
|
-
output: `Step ${currentStep.id}: ${currentStep.description}
|
|
17927
|
-
${lines.join(`
|
|
17928
|
-
`)}`
|
|
17929
|
-
};
|
|
17930
|
-
}
|
|
17931
|
-
return {
|
|
17932
|
-
success: false,
|
|
17933
|
-
output: t("todo.unknown_action", { action })
|
|
17934
|
-
};
|
|
17935
|
-
}
|
|
17936
|
-
},
|
|
17937
|
-
{
|
|
17938
|
-
name: "verify",
|
|
17939
|
-
alwaysOn: true,
|
|
17940
|
-
description: "Run verification for the current step. Checks files mentioned in the step description.",
|
|
17941
|
-
parameters: {
|
|
17942
|
-
type: "object",
|
|
17943
|
-
properties: {
|
|
17944
|
-
step: { type: "number", description: "Step number to verify" }
|
|
17945
|
-
}
|
|
17946
|
-
},
|
|
17947
|
-
handler: async (_ctx, args) => {
|
|
17948
|
-
if (!deps.trackerRef.current)
|
|
17949
|
-
return { success: false, output: t("plan.no_active") };
|
|
17950
|
-
const tracker = deps.trackerRef.current;
|
|
17951
|
-
const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
|
|
17952
|
-
if (!step)
|
|
17953
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
17954
|
-
const result = await deps.verifier.verifyStep(step.description);
|
|
17955
|
-
if (result.noFiles) {
|
|
17956
|
-
return {
|
|
17957
|
-
success: true,
|
|
17958
|
-
output: t("verify.no_files", { step: String(step.id) })
|
|
17959
|
-
};
|
|
17960
|
-
}
|
|
17961
|
-
return {
|
|
17962
|
-
success: result.passed,
|
|
17963
|
-
output: result.passed ? t("verify.passed") : t("verify.failed", {
|
|
17964
|
-
details: result.failed.map((f) => f.message).join("; ")
|
|
17965
|
-
})
|
|
17966
|
-
};
|
|
17967
|
-
}
|
|
17968
|
-
}
|
|
17969
|
-
];
|
|
17970
|
-
}
|
|
17971
|
-
var init_plan_tool = __esm(() => {
|
|
17972
|
-
init_i18n();
|
|
17973
|
-
init_plan_coverage();
|
|
17974
|
-
});
|
|
17975
|
-
|
|
17976
|
-
// src/modules/execution/windows-commands.ts
|
|
17977
|
-
function firstWord(command) {
|
|
17978
|
-
const segs = command.trim().split(/[\s;|&]+/);
|
|
17979
|
-
return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
|
|
17980
|
-
}
|
|
17981
|
-
function forbiddenWindowsCommand(command) {
|
|
17982
|
-
const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
|
|
17983
|
-
if (m)
|
|
17984
|
-
return forbiddenWindowsCommand(m[1]);
|
|
17985
|
-
const word = firstWord(command);
|
|
17986
|
-
if (word && FORBIDDEN_COMMANDS.has(word))
|
|
17987
|
-
return word;
|
|
17988
|
-
return null;
|
|
17989
|
-
}
|
|
17990
|
-
var FORBIDDEN_COMMANDS;
|
|
17991
|
-
var init_windows_commands = __esm(() => {
|
|
17992
|
-
FORBIDDEN_COMMANDS = new Set([
|
|
17993
|
-
"grep",
|
|
17994
|
-
"sed",
|
|
17995
|
-
"ls",
|
|
17996
|
-
"find",
|
|
17997
|
-
"rm",
|
|
17998
|
-
"touch",
|
|
17999
|
-
"which",
|
|
18000
|
-
"diff",
|
|
18001
|
-
"cp",
|
|
18002
|
-
"mv"
|
|
18003
|
-
]);
|
|
18004
|
-
});
|
|
18005
|
-
|
|
18006
|
-
// src/modules/execution/execution-plugin.ts
|
|
18007
|
-
import { platform as platform6 } from "os";
|
|
18008
|
-
function createExecutionPlugin(deps) {
|
|
18009
|
-
return {
|
|
18010
|
-
name: "execution",
|
|
18011
|
-
onBeforeThink: (ctx) => {
|
|
18012
|
-
if (ctx.contextManager && deps.pendingMessages.length > 0) {
|
|
18013
|
-
for (const m of deps.pendingMessages.splice(0)) {
|
|
18014
|
-
ctx.contextManager.addMessage(m);
|
|
18015
|
-
}
|
|
18016
|
-
}
|
|
18017
|
-
if (deps.trackerRef.current?.isComplete()) {
|
|
18018
|
-
deps.stuckDetector.resetStepProgress();
|
|
18019
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
18020
|
-
deps.state.lastStepId = -1;
|
|
18021
|
-
deps.state.stuckNotified = false;
|
|
18022
|
-
deps.state.mutationsWithoutPlan = 0;
|
|
18023
|
-
deps.state.planNudgeSent = false;
|
|
18024
|
-
} else {
|
|
18025
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
18026
|
-
if (deps.trackerRef.current && step) {
|
|
18027
|
-
if (step.id !== deps.state.lastStepId) {
|
|
18028
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
18029
|
-
deps.state.lastStepId = step.id;
|
|
18030
|
-
deps.state.stuckNotified = false;
|
|
18031
|
-
}
|
|
18032
|
-
deps.stuckDetector.setCurrentStep(step.id, step.description);
|
|
18033
|
-
deps.stuckDetector.recordIteration(step.id);
|
|
18034
|
-
deps.state.mutationsWithoutPlan = 0;
|
|
18035
|
-
deps.state.planNudgeSent = false;
|
|
18036
|
-
} else {
|
|
18037
|
-
deps.stuckDetector.reset();
|
|
18038
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
18039
|
-
deps.state.lastStepId = -1;
|
|
18040
|
-
deps.state.stuckNotified = false;
|
|
18041
|
-
const mutations = deps.state.mutationsWithoutPlan;
|
|
18042
|
-
if (mutations >= PLAN_NUDGE_THRESHOLD && !deps.state.planNudgeSent && ctx.contextManager) {
|
|
18043
|
-
deps.state.planNudgeSent = true;
|
|
18044
|
-
ctx.contextManager.addMessage({
|
|
18045
|
-
role: "user",
|
|
18046
|
-
content: `<system-summary>${t("exec.plan_nudge", {
|
|
18047
|
-
count: String(mutations)
|
|
18048
|
-
})}</system-summary>`
|
|
18049
|
-
});
|
|
18050
|
-
}
|
|
18051
|
-
}
|
|
18052
|
-
}
|
|
18053
|
-
const stuckReason = deps.stuckDetector.getStuckReason();
|
|
18054
|
-
if (stuckReason) {
|
|
18055
|
-
const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
|
|
18056
|
-
if (logIt) {
|
|
18057
|
-
ctx.logger?.warn(stuckReason);
|
|
18058
|
-
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
18059
|
-
if (deps.stuckDetector.isStuck())
|
|
18060
|
-
deps.state.stuckNotified = true;
|
|
18061
|
-
}
|
|
18062
|
-
}
|
|
18063
|
-
if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
|
|
18064
|
-
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
18065
|
-
if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
|
|
18066
|
-
const recovery = deps.stuckDetector.getRecoveryMessage();
|
|
18067
|
-
if (recovery && ctx.contextManager) {
|
|
18068
|
-
const lastError = deps.stuckDetector.getLastErrorOutput();
|
|
18069
|
-
const skillHint = lastError ? `
|
|
18070
|
-
If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
|
|
18071
|
-
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
18072
|
-
const actionableHintStr = actionableHints.length > 0 ? `
|
|
18073
|
-
${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
|
|
18074
|
-
`) })}` : "";
|
|
18075
|
-
const alternative = deps.stuckDetector.getToolAlternative();
|
|
18076
|
-
const altHint = alternative ? `
|
|
18077
|
-
Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
|
|
18078
|
-
ctx.contextManager.addMessage({
|
|
18079
|
-
role: "user",
|
|
18080
|
-
content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
|
|
18081
|
-
});
|
|
18082
|
-
}
|
|
18083
|
-
const hints = deps.stuckDetector.getHints();
|
|
18084
|
-
if (hints.length > 0 && ctx.contextManager) {
|
|
18085
|
-
const hintMsg = t("exec.hints", {
|
|
18086
|
-
hints: hints.map((h) => `- ${h}`).join(`
|
|
18087
|
-
`)
|
|
18088
|
-
});
|
|
18089
|
-
ctx.contextManager.addMessage({
|
|
18090
|
-
role: "user",
|
|
18091
|
-
content: `<system-summary>${hintMsg}</system-summary>`
|
|
18092
|
-
});
|
|
18093
|
-
}
|
|
18094
|
-
deps.stuckDetector.recordEscalation();
|
|
18095
|
-
deps.state.lastRecoveryIteration = currentIter;
|
|
18096
|
-
if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
18097
|
-
const escalation = t("exec.escalation", {
|
|
18098
|
-
stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
18099
|
-
description: deps.trackerRef.current?.getCurrentStep()?.description ?? ""
|
|
18100
|
-
});
|
|
18101
|
-
ctx.onMeta(escalation);
|
|
18102
|
-
}
|
|
18103
|
-
if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
|
|
18104
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
18105
|
-
ctx.contextManager.addMessage({
|
|
18106
|
-
role: "user",
|
|
18107
|
-
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>`
|
|
18108
|
-
});
|
|
18145
|
+
output: t("plan.not_found", { id: planId })
|
|
18146
|
+
};
|
|
18147
|
+
}
|
|
18148
|
+
if (deleted === "active") {
|
|
18149
|
+
deps.trackerRef.current = null;
|
|
18150
|
+
deps.clearCompleted();
|
|
18109
18151
|
}
|
|
18152
|
+
return {
|
|
18153
|
+
success: true,
|
|
18154
|
+
output: t("plan.deleted", { id: planId })
|
|
18155
|
+
};
|
|
18110
18156
|
}
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
});
|
|
18120
|
-
deps.state.consecutivePlanWarnings++;
|
|
18121
|
-
if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
18122
|
-
deps.pendingMessages.push({
|
|
18123
|
-
role: "user",
|
|
18124
|
-
content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
|
|
18125
|
-
});
|
|
18126
|
-
return t("exec.plan_blocked", {
|
|
18127
|
-
step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
18128
|
-
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
|
|
18129
|
-
});
|
|
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
|
+
};
|
|
18130
18165
|
}
|
|
18131
|
-
|
|
18132
|
-
|
|
18133
|
-
}
|
|
18134
|
-
return true;
|
|
18135
|
-
},
|
|
18136
|
-
onToolCall: (ctx) => {
|
|
18137
|
-
const toolName = ctx?.toolName;
|
|
18138
|
-
const args = ctx?.args;
|
|
18139
|
-
if (toolName && args) {
|
|
18140
|
-
deps.stuckDetector.recordToolCall(toolName, args);
|
|
18141
|
-
if (!deps.trackerRef.current && (toolName === "write_file" || toolName === "edit_file" || toolName === "bash" || toolName === "download_file")) {
|
|
18142
|
-
deps.state.mutationsWithoutPlan++;
|
|
18166
|
+
if (!deps.trackerRef.current) {
|
|
18167
|
+
return { success: false, output: t("plan.no_active") };
|
|
18143
18168
|
}
|
|
18169
|
+
return {
|
|
18170
|
+
success: false,
|
|
18171
|
+
output: t("plan.unknown_action", { action })
|
|
18172
|
+
};
|
|
18144
18173
|
}
|
|
18145
18174
|
},
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
18155
|
-
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
deps.forbiddenBashFailures.set(forbidden, n);
|
|
18162
|
-
if (n === 2) {
|
|
18163
|
-
deps.pendingMessages.push({
|
|
18164
|
-
role: "user",
|
|
18165
|
-
content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`
|
|
18166
|
-
});
|
|
18167
|
-
}
|
|
18168
|
-
}
|
|
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") };
|
|
18169
18190
|
}
|
|
18170
|
-
|
|
18171
|
-
|
|
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") };
|
|
18172
18196
|
}
|
|
18173
|
-
|
|
18174
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
|
|
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 });
|
|
18179
18203
|
}
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
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
|
+
};
|
|
18185
18215
|
}
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
role: "user",
|
|
18194
|
-
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>`
|
|
18195
|
-
});
|
|
18196
|
-
} else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
18197
|
-
deps.pendingMessages.push({
|
|
18198
|
-
role: "user",
|
|
18199
|
-
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>`
|
|
18200
|
-
});
|
|
18201
|
-
} else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
18202
|
-
deps.pendingMessages.push({
|
|
18203
|
-
role: "user",
|
|
18204
|
-
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>`
|
|
18205
|
-
});
|
|
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
|
+
};
|
|
18206
18223
|
}
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
const file = deps.stuckDetector.getExcessiveRewriteFile();
|
|
18215
|
-
const count = deps.stuckDetector.getFileRewriteCount(file);
|
|
18216
|
-
if (ctx.onMeta) {
|
|
18217
|
-
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
18218
|
-
file,
|
|
18219
|
-
count: String(count)
|
|
18220
|
-
}));
|
|
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++;
|
|
18221
18231
|
}
|
|
18222
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 };
|
|
18223
18254
|
}
|
|
18224
|
-
|
|
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
|
+
};
|
|
18225
18301
|
}
|
|
18226
|
-
deps.maybeSearchError(ctx, call);
|
|
18227
18302
|
}
|
|
18228
|
-
|
|
18303
|
+
];
|
|
18229
18304
|
}
|
|
18230
|
-
var
|
|
18231
|
-
var init_execution_plugin = __esm(() => {
|
|
18305
|
+
var init_plan_tool = __esm(() => {
|
|
18232
18306
|
init_i18n();
|
|
18233
|
-
|
|
18234
|
-
|
|
18307
|
+
init_plan_coverage();
|
|
18308
|
+
init_execution_plugin();
|
|
18235
18309
|
});
|
|
18236
18310
|
|
|
18237
18311
|
// src/modules/execution/module.ts
|
|
@@ -18263,7 +18337,8 @@ class ExecutionModule {
|
|
|
18263
18337
|
stuckNotified: false,
|
|
18264
18338
|
depsGateHints: new Map,
|
|
18265
18339
|
mutationsWithoutPlan: 0,
|
|
18266
|
-
planNudgeSent: false
|
|
18340
|
+
planNudgeSent: false,
|
|
18341
|
+
typecheckFailures: new Map
|
|
18267
18342
|
};
|
|
18268
18343
|
trackerRef = (() => {
|
|
18269
18344
|
const self = this;
|
|
@@ -18446,6 +18521,7 @@ class ExecutionModule {
|
|
|
18446
18521
|
stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
|
|
18447
18522
|
parseKinds: (a, st) => this.parseKinds(a, st),
|
|
18448
18523
|
getTaskText: (c) => this.getTaskText(c),
|
|
18524
|
+
typecheckFailures: this.state.typecheckFailures,
|
|
18449
18525
|
baseDir: this.baseDir
|
|
18450
18526
|
});
|
|
18451
18527
|
}
|
|
@@ -21192,8 +21268,6 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21192
21268
|
exitOnComplete,
|
|
21193
21269
|
contextManager,
|
|
21194
21270
|
activeToolTags,
|
|
21195
|
-
sessionId: sessionManager.getActiveMeta()?.id,
|
|
21196
|
-
sessionContext: sessionManager.getSessionContext() ?? undefined,
|
|
21197
21271
|
recursionDepth: 0,
|
|
21198
21272
|
fileOperationsCount: 0,
|
|
21199
21273
|
sessionHistory: {
|
|
@@ -21221,6 +21295,14 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21221
21295
|
}
|
|
21222
21296
|
}
|
|
21223
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
|
+
});
|
|
21224
21306
|
const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
|
|
21225
21307
|
toolCtx.llmProvider = llmProvider;
|
|
21226
21308
|
toolCtx.toolExecutor = toolExecutor;
|
|
@@ -21393,6 +21475,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
|
|
|
21393
21475
|
sessionManager,
|
|
21394
21476
|
skillsModule,
|
|
21395
21477
|
pluginManager,
|
|
21478
|
+
toolCtx,
|
|
21396
21479
|
configDir: dir,
|
|
21397
21480
|
baseDir,
|
|
21398
21481
|
noAgentsMd: skipAgentsMd
|