micro-models-agent 0.43.1 → 0.43.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +366 -270
  2. 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:",
@@ -2575,6 +2580,7 @@ Command: {command}`,
2575
2580
  "verify.script_passed": "Script '{script}' passed",
2576
2581
  "verify.script_failed": "Script '{script}' failed: {message}",
2577
2582
  "verify.syntax_error": "Syntax error in: {path}",
2583
+ "verify.file_still_exists": "File should be deleted but still exists: {path}",
2578
2584
  "verify.no_files": "Step {step} has no named files to verify — run a real check instead (bash: bun run build / bun test / lsp_check) and confirm the result.",
2579
2585
  "lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
2580
2586
  "lsp.check_disabled": "LSP checks are disabled in the config.",
@@ -3012,7 +3018,9 @@ var init_ru = __esm(() => {
3012
3018
  "file.is_directory": "Это каталог, используйте rmdir: {path}",
3013
3019
  "file.replaced_in": "Заменено в {path}",
3014
3020
  "file.string_not_found": `Строка не найдена в файле:
3015
- {str}`,
3021
+ {str}
3022
+
3023
+ Используй read_file для {path}, чтобы увидеть текущее содержимое файла перед редактированием — искомая строка может отличаться (пробелы, переводы строк) или уже была заменена.`,
3016
3024
  "file.moved": "Перемещён {from} → {to}",
3017
3025
  "file.not_found_short": "Не найдено: {path}",
3018
3026
  "file.empty": "(пусто)",
@@ -3192,6 +3200,9 @@ var init_ru = __esm(() => {
3192
3200
  "plan.kinds_invalid": 'Недопустимый kind шага: {kinds}. Используй "create" или "delete".',
3193
3201
  "plan.kinds_not_array": 'kinds должен быть массивом значений "create" или "delete"',
3194
3202
  "plan.deliverables_remain": "Нельзя пометить шаг {step} как выполненный: указанные файлы всё ещё существуют: {files}. Шаг имеет kind=delete — сначала удали их (или поставь status=skipped, если они должны остаться).",
3203
+ "plan.typecheck_failed": `Нельзя отметить шаг {step} выполненным: последняя проверка всё ещё сообщает об ошибке компиляции:
3204
+ {error}
3205
+ Исправь ошибку и снова отредактируй файл (чистая запись снимает блокировку), либо отметь шаг status=skipped, если он не нужен.`,
3195
3206
  "plan.show_header": "Статус плана:",
3196
3207
  "plan.show_empty": "(в плане нет шагов)",
3197
3208
  "plan.list_header": "Планы:",
@@ -3227,6 +3238,7 @@ var init_ru = __esm(() => {
3227
3238
  "verify.script_passed": "Скрипт '{script}' пройден",
3228
3239
  "verify.script_failed": "Скрипт '{script}' не пройден: {message}",
3229
3240
  "verify.syntax_error": "Синтаксическая ошибка в: {path}",
3241
+ "verify.file_still_exists": "Файл должен быть удалён, но всё ещё существует: {path}",
3230
3242
  "verify.no_files": "Шаг {step} не содержит именованных файлов для проверки — выполни реальную проверку (bun run build / bun test / lsp_check) и подтверди результат.",
3231
3243
  "lsp.unavailable": "статические проверки недоступны (сервер не найден). Проверяйте через собственный build/test проекта.",
3232
3244
  "lsp.check_disabled": "LSP-проверки отключены в конфигурации.",
@@ -6764,7 +6776,7 @@ var init_edit_file = __esm(() => {
6764
6776
  if (!content.includes(oldStr)) {
6765
6777
  return {
6766
6778
  success: false,
6767
- output: t("file.string_not_found", { str: oldStr })
6779
+ output: t("file.string_not_found", { str: oldStr, path })
6768
6780
  };
6769
6781
  }
6770
6782
  const updated = content.replace(oldStr, newStr);
@@ -9660,6 +9672,9 @@ class StuckDetector {
9660
9672
  this.consecutiveFailures = 0;
9661
9673
  this.lastFailedTool = "";
9662
9674
  this.escalationCount = 0;
9675
+ if (this.iterationsOnCurrentStep > 0) {
9676
+ this.iterationsOnCurrentStep = Math.max(0, this.iterationsOnCurrentStep - 1);
9677
+ }
9663
9678
  }
9664
9679
  setCurrentStep(stepId, description) {
9665
9680
  if (stepId !== this.currentStepId) {
@@ -11051,7 +11066,7 @@ class StepVerifier {
11051
11066
  details
11052
11067
  };
11053
11068
  }
11054
- async verifyStep(stepDescription) {
11069
+ async verifyStep(stepDescription, kind) {
11055
11070
  const fileMatches = extractFileLikeTokens(stripUrls(stepDescription));
11056
11071
  if (fileMatches.length === 0) {
11057
11072
  return { passed: false, syntaxValid: true, failed: [], noFiles: true };
@@ -11060,7 +11075,15 @@ class StepVerifier {
11060
11075
  let syntaxValid = true;
11061
11076
  for (const filePath of fileMatches) {
11062
11077
  const resolved = findExistingFile(this.baseDir, filePath);
11063
- if (!resolved) {
11078
+ const exists = !!resolved;
11079
+ if (kind === "delete") {
11080
+ results.push({
11081
+ passed: !exists,
11082
+ message: exists ? t("verify.file_still_exists", { path: filePath }) : t("verify.file_not_found", { path: filePath })
11083
+ });
11084
+ continue;
11085
+ }
11086
+ if (!exists) {
11064
11087
  results.push({ passed: false, message: t("verify.file_not_found", { path: filePath }) });
11065
11088
  continue;
11066
11089
  }
@@ -17402,6 +17425,317 @@ var init_plan_coverage = __esm(() => {
17402
17425
  ]);
17403
17426
  });
17404
17427
 
17428
+ // src/modules/execution/windows-commands.ts
17429
+ function firstWord(command) {
17430
+ const segs = command.trim().split(/[\s;|&]+/);
17431
+ return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
17432
+ }
17433
+ function forbiddenWindowsCommand(command) {
17434
+ const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
17435
+ if (m)
17436
+ return forbiddenWindowsCommand(m[1]);
17437
+ const word = firstWord(command);
17438
+ if (word && FORBIDDEN_COMMANDS.has(word))
17439
+ return word;
17440
+ return null;
17441
+ }
17442
+ var FORBIDDEN_COMMANDS;
17443
+ var init_windows_commands = __esm(() => {
17444
+ FORBIDDEN_COMMANDS = new Set([
17445
+ "grep",
17446
+ "sed",
17447
+ "ls",
17448
+ "find",
17449
+ "rm",
17450
+ "touch",
17451
+ "which",
17452
+ "diff",
17453
+ "cp",
17454
+ "mv"
17455
+ ]);
17456
+ });
17457
+
17458
+ // src/modules/execution/execution-plugin.ts
17459
+ import { platform as platform6 } from "os";
17460
+ function normalizeBrokenPath(p) {
17461
+ return p.replace(/\\/g, "/").replace(/^\.\//, "");
17462
+ }
17463
+ function parseBrokenFile(line) {
17464
+ const trimmed = line.trim();
17465
+ const tsc = /^(.+?)\s*\(\d+,\d+\)\s*:\s*error TS\d+/.exec(trimmed);
17466
+ if (tsc)
17467
+ return normalizeBrokenPath(tsc[1]);
17468
+ const bn = /^(.+?):\d+:\d+\s*:\s*(?:error|SyntaxError)/.exec(trimmed);
17469
+ if (bn)
17470
+ return normalizeBrokenPath(bn[1]);
17471
+ return null;
17472
+ }
17473
+ function extractBrokenFiles(output) {
17474
+ const out = [];
17475
+ for (const line of output.split(`
17476
+ `)) {
17477
+ const marker = /\[(?:Project typecheck failed|Syntax check failed)\]:\s*(.+)$/.exec(line);
17478
+ if (!marker)
17479
+ continue;
17480
+ const errLine = marker[1].trim();
17481
+ out.push({ file: parseBrokenFile(errLine) ?? "", error: errLine.slice(0, 300) });
17482
+ }
17483
+ return out;
17484
+ }
17485
+ function stepTypecheckGate(failures, stepDescription) {
17486
+ if (failures.size === 0)
17487
+ return null;
17488
+ const projectError = failures.get("");
17489
+ if (projectError)
17490
+ return projectError;
17491
+ const stepTokens = extractFileLikeTokens(stripUrls(stepDescription)).map((p) => normalizeBrokenPath(p).toLowerCase());
17492
+ if (stepTokens.length === 0)
17493
+ return null;
17494
+ for (const [file, err] of failures) {
17495
+ const f = normalizeBrokenPath(file).toLowerCase();
17496
+ if (stepTokens.some((tok) => f.endsWith(tok) || tok.endsWith(f)))
17497
+ return err;
17498
+ }
17499
+ return null;
17500
+ }
17501
+ function createExecutionPlugin(deps) {
17502
+ return {
17503
+ name: "execution",
17504
+ onBeforeThink: (ctx) => {
17505
+ if (ctx.contextManager && deps.pendingMessages.length > 0) {
17506
+ for (const m of deps.pendingMessages.splice(0)) {
17507
+ ctx.contextManager.addMessage(m);
17508
+ }
17509
+ }
17510
+ if (deps.trackerRef.current?.isComplete()) {
17511
+ deps.stuckDetector.resetStepProgress();
17512
+ deps.state.consecutivePlanWarnings = 0;
17513
+ deps.state.lastStepId = -1;
17514
+ deps.state.stuckNotified = false;
17515
+ deps.state.mutationsWithoutPlan = 0;
17516
+ deps.state.planNudgeSent = false;
17517
+ } else {
17518
+ const step = deps.trackerRef.current?.getCurrentStep();
17519
+ if (deps.trackerRef.current && step) {
17520
+ if (step.id !== deps.state.lastStepId) {
17521
+ deps.state.consecutivePlanWarnings = 0;
17522
+ deps.state.lastStepId = step.id;
17523
+ deps.state.stuckNotified = false;
17524
+ }
17525
+ deps.stuckDetector.setCurrentStep(step.id, step.description);
17526
+ deps.stuckDetector.recordIteration(step.id);
17527
+ deps.state.mutationsWithoutPlan = 0;
17528
+ deps.state.planNudgeSent = false;
17529
+ } else {
17530
+ deps.stuckDetector.reset();
17531
+ deps.state.consecutivePlanWarnings = 0;
17532
+ deps.state.lastStepId = -1;
17533
+ deps.state.stuckNotified = false;
17534
+ const mutations = deps.state.mutationsWithoutPlan;
17535
+ if (mutations >= PLAN_NUDGE_THRESHOLD && !deps.state.planNudgeSent && ctx.contextManager) {
17536
+ deps.state.planNudgeSent = true;
17537
+ ctx.contextManager.addMessage({
17538
+ role: "user",
17539
+ content: `<system-summary>${t("exec.plan_nudge", {
17540
+ count: String(mutations)
17541
+ })}</system-summary>`
17542
+ });
17543
+ }
17544
+ }
17545
+ }
17546
+ const stuckReason = deps.stuckDetector.getStuckReason();
17547
+ if (stuckReason) {
17548
+ const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
17549
+ if (logIt) {
17550
+ ctx.logger?.warn(stuckReason);
17551
+ ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
17552
+ if (deps.stuckDetector.isStuck())
17553
+ deps.state.stuckNotified = true;
17554
+ }
17555
+ }
17556
+ if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
17557
+ const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
17558
+ if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
17559
+ const recovery = deps.stuckDetector.getRecoveryMessage();
17560
+ if (recovery && ctx.contextManager) {
17561
+ let tscContext = "";
17562
+ if (deps.state.typecheckFailures.size > 0) {
17563
+ const currentStep = deps.trackerRef.current?.getCurrentStep();
17564
+ if (currentStep) {
17565
+ const stepFiles = extractFileLikeTokens(stripUrls(currentStep.description));
17566
+ for (const [file, error] of deps.state.typecheckFailures) {
17567
+ if (!file || stepFiles.some((f) => file.endsWith(f) || f.endsWith(file))) {
17568
+ tscContext = `
17569
+ Last compile error: ${error}`;
17570
+ break;
17571
+ }
17572
+ }
17573
+ if (!tscContext) {
17574
+ const first = deps.state.typecheckFailures.entries().next().value;
17575
+ if (first)
17576
+ tscContext = `
17577
+ Last compile error: ${first[1]}`;
17578
+ }
17579
+ }
17580
+ }
17581
+ ctx.contextManager.addMessage({
17582
+ role: "user",
17583
+ content: `<system-summary>${recovery}${tscContext}</system-summary>`
17584
+ });
17585
+ }
17586
+ deps.stuckDetector.recordEscalation();
17587
+ deps.state.lastRecoveryIteration = currentIter;
17588
+ if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
17589
+ const escalation = t("exec.escalation", {
17590
+ stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
17591
+ description: deps.trackerRef.current?.getCurrentStep()?.description ?? ""
17592
+ });
17593
+ ctx.onMeta(escalation);
17594
+ }
17595
+ if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
17596
+ const step = deps.trackerRef.current?.getCurrentStep();
17597
+ ctx.contextManager.addMessage({
17598
+ role: "user",
17599
+ 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>`
17600
+ });
17601
+ }
17602
+ }
17603
+ }
17604
+ },
17605
+ onBeforeTool: (_ctx, call) => {
17606
+ const warning = deps.checkPlanAlignment(call);
17607
+ if (warning) {
17608
+ deps.pendingMessages.push({
17609
+ role: "user",
17610
+ content: `<system-summary>${warning}</system-summary>`
17611
+ });
17612
+ deps.state.consecutivePlanWarnings++;
17613
+ if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
17614
+ deps.pendingMessages.push({
17615
+ role: "user",
17616
+ content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
17617
+ });
17618
+ return t("exec.plan_blocked", {
17619
+ step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
17620
+ max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
17621
+ });
17622
+ }
17623
+ } else {
17624
+ deps.state.consecutivePlanWarnings = 0;
17625
+ }
17626
+ return true;
17627
+ },
17628
+ onToolCall: (ctx) => {
17629
+ const toolName = ctx?.toolName;
17630
+ const args = ctx?.args;
17631
+ if (toolName && args) {
17632
+ deps.stuckDetector.recordToolCall(toolName, args);
17633
+ if (!deps.trackerRef.current && (toolName === "write_file" || toolName === "edit_file" || toolName === "bash" || toolName === "download_file")) {
17634
+ deps.state.mutationsWithoutPlan++;
17635
+ }
17636
+ }
17637
+ },
17638
+ onAfterTool: (ctx, call, result) => {
17639
+ if (call.name === "bash") {
17640
+ deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
17641
+ }
17642
+ if (call.name === "delete_file" && result.success && deps.trackerRef.current) {
17643
+ const deletedPath = String(call.arguments?.path ?? "");
17644
+ if (deletedPath) {
17645
+ const plan = deps.trackerRef.current.getPlan();
17646
+ const tokens = extractFileLikeTokens(stripUrls(deletedPath));
17647
+ for (const step of plan.steps) {
17648
+ if (step.status !== "pending" && step.status !== "in_progress")
17649
+ continue;
17650
+ const stepTokens = extractFileLikeTokens(stripUrls(step.description));
17651
+ const overlap = tokens.some((t2) => stepTokens.some((st) => t2.endsWith(st) || st.endsWith(t2)));
17652
+ if (overlap && step.kind !== "delete") {
17653
+ step.kind = "delete";
17654
+ deps.store.saveActive(plan);
17655
+ break;
17656
+ }
17657
+ }
17658
+ }
17659
+ }
17660
+ const toolText = String(result.output ?? "");
17661
+ const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
17662
+ if (hasTypeError) {
17663
+ deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
17664
+ }
17665
+ if (call.name === "write_file" || call.name === "edit_file") {
17666
+ const broken = extractBrokenFiles(toolText);
17667
+ if (broken.length > 0) {
17668
+ for (const b of broken)
17669
+ deps.state.typecheckFailures.set(b.file, b.error);
17670
+ } else if (result.success) {
17671
+ deps.state.typecheckFailures.clear();
17672
+ }
17673
+ }
17674
+ if (!result.success) {
17675
+ if (call.name === "bash" && platform6() === "win32") {
17676
+ const cmd = String(call.arguments?.command ?? "");
17677
+ const forbidden = forbiddenWindowsCommand(cmd);
17678
+ if (forbidden) {
17679
+ const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
17680
+ deps.forbiddenBashFailures.set(forbidden, n);
17681
+ if (n === 2) {
17682
+ deps.pendingMessages.push({
17683
+ role: "user",
17684
+ content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`
17685
+ });
17686
+ }
17687
+ }
17688
+ }
17689
+ if (!hasTypeError) {
17690
+ deps.stuckDetector.recordToolError(call.name, result.output);
17691
+ }
17692
+ } else {
17693
+ deps.stuckDetector.recordToolSuccess();
17694
+ if (call.name === "bash" && result.success) {
17695
+ const cmd = String(call.arguments?.command ?? "");
17696
+ const testRun = detectTestResults(String(result.output ?? ""));
17697
+ if (testRun && testRun.failed > 0) {
17698
+ deps.pendingMessages.push({
17699
+ role: "user",
17700
+ 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>`
17701
+ });
17702
+ } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
17703
+ deps.pendingMessages.push({
17704
+ role: "user",
17705
+ 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>`
17706
+ });
17707
+ }
17708
+ }
17709
+ }
17710
+ if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
17711
+ const filePath = call.arguments?.path;
17712
+ if (filePath) {
17713
+ deps.stuckDetector.recordFileRewrite(filePath);
17714
+ if (deps.stuckDetector.hasExcessiveRewrites()) {
17715
+ const file = deps.stuckDetector.getExcessiveRewriteFile();
17716
+ const count = deps.stuckDetector.getFileRewriteCount(file);
17717
+ if (ctx.onMeta) {
17718
+ ctx.onMeta(t("exec.file_rewrite_warning", {
17719
+ file,
17720
+ count: String(count)
17721
+ }));
17722
+ }
17723
+ }
17724
+ }
17725
+ deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
17726
+ }
17727
+ deps.maybeSearchError(ctx, call);
17728
+ }
17729
+ };
17730
+ }
17731
+ var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, PLAN_NUDGE_THRESHOLD = 2;
17732
+ var init_execution_plugin = __esm(() => {
17733
+ init_i18n();
17734
+ init_bash();
17735
+ init_windows_commands();
17736
+ init_js_identifiers();
17737
+ });
17738
+
17405
17739
  // src/modules/execution/plan-tool.ts
17406
17740
  function createPlanToolDefinitions(deps) {
17407
17741
  return [
@@ -17708,6 +18042,18 @@ ${progress2}`,
17708
18042
  }
17709
18043
  }
17710
18044
  }
18045
+ if (status === "done") {
18046
+ const blocked = stepTypecheckGate(deps.typecheckFailures, target.description);
18047
+ if (blocked) {
18048
+ return {
18049
+ success: false,
18050
+ output: t("plan.typecheck_failed", {
18051
+ step: String(stepId),
18052
+ error: blocked
18053
+ })
18054
+ };
18055
+ }
18056
+ }
17711
18057
  tracker.updateStepStatus(stepId, status);
17712
18058
  if (args.note)
17713
18059
  tracker.addNote(Number(args.step), String(args.note));
@@ -17951,7 +18297,7 @@ ${lines.join(`
17951
18297
  const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
17952
18298
  if (!step)
17953
18299
  return { success: false, output: t("plan.step_not_found") };
17954
- const result = await deps.verifier.verifyStep(step.description);
18300
+ const result = await deps.verifier.verifyStep(step.description, step.kind);
17955
18301
  if (result.noFiles) {
17956
18302
  return {
17957
18303
  success: true,
@@ -17971,267 +18317,7 @@ ${lines.join(`
17971
18317
  var init_plan_tool = __esm(() => {
17972
18318
  init_i18n();
17973
18319
  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
- });
18109
- }
18110
- }
18111
- }
18112
- },
18113
- onBeforeTool: (_ctx, call) => {
18114
- const warning = deps.checkPlanAlignment(call);
18115
- if (warning) {
18116
- deps.pendingMessages.push({
18117
- role: "user",
18118
- content: `<system-summary>${warning}</system-summary>`
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
- });
18130
- }
18131
- } else {
18132
- deps.state.consecutivePlanWarnings = 0;
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++;
18143
- }
18144
- }
18145
- },
18146
- onAfterTool: (ctx, call, result) => {
18147
- if (call.name === "bash") {
18148
- deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
18149
- }
18150
- const toolText = String(result.output ?? "");
18151
- const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
18152
- if (hasTypeError) {
18153
- deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
18154
- }
18155
- if (!result.success) {
18156
- if (call.name === "bash" && platform6() === "win32") {
18157
- const cmd = String(call.arguments?.command ?? "");
18158
- const forbidden = forbiddenWindowsCommand(cmd);
18159
- if (forbidden) {
18160
- const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
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
- }
18169
- }
18170
- if (!hasTypeError) {
18171
- deps.stuckDetector.recordToolError(call.name, result.output);
18172
- }
18173
- const actionableHints = deps.stuckDetector.getActionableHints();
18174
- const alternative = deps.stuckDetector.getToolAlternative();
18175
- if (actionableHints.length > 0 || alternative) {
18176
- const parts = [...actionableHints];
18177
- if (alternative) {
18178
- parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
18179
- }
18180
- deps.pendingMessages.push({
18181
- role: "user",
18182
- content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
18183
- `) })}</system-summary>`
18184
- });
18185
- }
18186
- } else {
18187
- deps.stuckDetector.recordToolSuccess();
18188
- if (call.name === "bash" && result.success) {
18189
- const cmd = String(call.arguments?.command ?? "");
18190
- const testRun = detectTestResults(String(result.output ?? ""));
18191
- if (testRun && testRun.failed > 0) {
18192
- deps.pendingMessages.push({
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
- });
18206
- }
18207
- }
18208
- }
18209
- if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
18210
- const filePath = call.arguments?.path;
18211
- if (filePath) {
18212
- deps.stuckDetector.recordFileRewrite(filePath);
18213
- if (deps.stuckDetector.hasExcessiveRewrites()) {
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
- }));
18221
- }
18222
- }
18223
- }
18224
- deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
18225
- }
18226
- deps.maybeSearchError(ctx, call);
18227
- }
18228
- };
18229
- }
18230
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10, PLAN_NUDGE_THRESHOLD = 2;
18231
- var init_execution_plugin = __esm(() => {
18232
- init_i18n();
18233
- init_bash();
18234
- init_windows_commands();
18320
+ init_execution_plugin();
18235
18321
  });
18236
18322
 
18237
18323
  // src/modules/execution/module.ts
@@ -18263,7 +18349,8 @@ class ExecutionModule {
18263
18349
  stuckNotified: false,
18264
18350
  depsGateHints: new Map,
18265
18351
  mutationsWithoutPlan: 0,
18266
- planNudgeSent: false
18352
+ planNudgeSent: false,
18353
+ typecheckFailures: new Map
18267
18354
  };
18268
18355
  trackerRef = (() => {
18269
18356
  const self = this;
@@ -18446,6 +18533,7 @@ class ExecutionModule {
18446
18533
  stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
18447
18534
  parseKinds: (a, st) => this.parseKinds(a, st),
18448
18535
  getTaskText: (c) => this.getTaskText(c),
18536
+ typecheckFailures: this.state.typecheckFailures,
18449
18537
  baseDir: this.baseDir
18450
18538
  });
18451
18539
  }
@@ -18456,6 +18544,7 @@ class ExecutionModule {
18456
18544
  pendingMessages: this.pendingMessages,
18457
18545
  forbiddenBashFailures: this.forbiddenBashFailures,
18458
18546
  state: this.state,
18547
+ store: this.store,
18459
18548
  checkPlanAlignment: (call) => this.checkPlanAlignment(call),
18460
18549
  advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl),
18461
18550
  maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call)
@@ -21192,8 +21281,6 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
21192
21281
  exitOnComplete,
21193
21282
  contextManager,
21194
21283
  activeToolTags,
21195
- sessionId: sessionManager.getActiveMeta()?.id,
21196
- sessionContext: sessionManager.getSessionContext() ?? undefined,
21197
21284
  recursionDepth: 0,
21198
21285
  fileOperationsCount: 0,
21199
21286
  sessionHistory: {
@@ -21221,6 +21308,14 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
21221
21308
  }
21222
21309
  }
21223
21310
  };
21311
+ Object.defineProperty(toolCtx, "sessionId", {
21312
+ get: () => sessionManager.getActiveMeta()?.id,
21313
+ configurable: true
21314
+ });
21315
+ Object.defineProperty(toolCtx, "sessionContext", {
21316
+ get: () => sessionManager.getSessionContext() ?? undefined,
21317
+ configurable: true
21318
+ });
21224
21319
  const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
21225
21320
  toolCtx.llmProvider = llmProvider;
21226
21321
  toolCtx.toolExecutor = toolExecutor;
@@ -21393,6 +21488,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
21393
21488
  sessionManager,
21394
21489
  skillsModule,
21395
21490
  pluginManager,
21491
+ toolCtx,
21396
21492
  configDir: dir,
21397
21493
  baseDir,
21398
21494
  noAgentsMd: skipAgentsMd
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.43.1",
3
+ "version": "0.43.3",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {