micro-models-agent 0.5.0 → 0.6.1

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 (115) hide show
  1. package/dist/agent/context.d.ts +3 -6
  2. package/dist/agent/context.d.ts.map +1 -1
  3. package/dist/agent/context.js +8 -41
  4. package/dist/agent/context.js.map +1 -1
  5. package/dist/agent/error-escalation.d.ts +7 -6
  6. package/dist/agent/error-escalation.d.ts.map +1 -1
  7. package/dist/agent/error-escalation.js +82 -36
  8. package/dist/agent/error-escalation.js.map +1 -1
  9. package/dist/agent/loop.d.ts.map +1 -1
  10. package/dist/agent/loop.js +151 -210
  11. package/dist/agent/loop.js.map +1 -1
  12. package/dist/agent/memory.d.ts +0 -29
  13. package/dist/agent/memory.d.ts.map +1 -1
  14. package/dist/agent/memory.js +1 -78
  15. package/dist/agent/memory.js.map +1 -1
  16. package/dist/agent/modes/registry.d.ts +1 -1
  17. package/dist/agent/modes/registry.d.ts.map +1 -1
  18. package/dist/agent/modes/registry.js +26 -27
  19. package/dist/agent/modes/registry.js.map +1 -1
  20. package/dist/agent/modes/types.d.ts +0 -5
  21. package/dist/agent/modes/types.d.ts.map +1 -1
  22. package/dist/agent/system-prompt.d.ts +1 -1
  23. package/dist/agent/system-prompt.d.ts.map +1 -1
  24. package/dist/agent/system-prompt.js +95 -147
  25. package/dist/agent/system-prompt.js.map +1 -1
  26. package/dist/agent/tool-executor.d.ts +2 -6
  27. package/dist/agent/tool-executor.d.ts.map +1 -1
  28. package/dist/agent/tool-executor.js +79 -65
  29. package/dist/agent/tool-executor.js.map +1 -1
  30. package/dist/agent/user-profile.d.ts +1 -21
  31. package/dist/agent/user-profile.d.ts.map +1 -1
  32. package/dist/agent/user-profile.js +41 -174
  33. package/dist/agent/user-profile.js.map +1 -1
  34. package/dist/agent/write-enforcer.d.ts +4 -8
  35. package/dist/agent/write-enforcer.d.ts.map +1 -1
  36. package/dist/agent/write-enforcer.js +4 -88
  37. package/dist/agent/write-enforcer.js.map +1 -1
  38. package/dist/cli/config.d.ts.map +1 -1
  39. package/dist/cli/config.js +2 -25
  40. package/dist/cli/config.js.map +1 -1
  41. package/dist/cli.d.ts +1 -1
  42. package/dist/cli.d.ts.map +1 -1
  43. package/dist/cli.js +102 -139
  44. package/dist/cli.js.map +1 -1
  45. package/dist/config/config.d.ts +15 -0
  46. package/dist/config/config.d.ts.map +1 -1
  47. package/dist/config/config.js +4 -38
  48. package/dist/config/config.js.map +1 -1
  49. package/dist/config/types.d.ts +0 -2
  50. package/dist/config/types.d.ts.map +1 -1
  51. package/dist/repl/commands.d.ts +16 -16
  52. package/dist/repl/commands.d.ts.map +1 -1
  53. package/dist/repl/commands.js +474 -321
  54. package/dist/repl/commands.js.map +1 -1
  55. package/dist/repl/index.d.ts +4 -4
  56. package/dist/repl/index.d.ts.map +1 -1
  57. package/dist/repl/index.js +60 -56
  58. package/dist/repl/index.js.map +1 -1
  59. package/dist/session/store.d.ts +4 -7
  60. package/dist/session/store.d.ts.map +1 -1
  61. package/dist/session/store.js +35 -71
  62. package/dist/session/store.js.map +1 -1
  63. package/dist/setup-wizard.d.ts.map +1 -1
  64. package/dist/setup-wizard.js +5 -23
  65. package/dist/setup-wizard.js.map +1 -1
  66. package/dist/styling/engine.d.ts +7 -12
  67. package/dist/styling/engine.d.ts.map +1 -1
  68. package/dist/styling/engine.js +85 -134
  69. package/dist/styling/engine.js.map +1 -1
  70. package/dist/styling/index.d.ts +2 -2
  71. package/dist/styling/index.d.ts.map +1 -1
  72. package/dist/styling/index.js +0 -1
  73. package/dist/styling/index.js.map +1 -1
  74. package/dist/styling/loader.d.ts +2 -2
  75. package/dist/styling/loader.d.ts.map +1 -1
  76. package/dist/styling/loader.js +99 -47
  77. package/dist/styling/loader.js.map +1 -1
  78. package/dist/subagent/chat.d.ts +12 -0
  79. package/dist/subagent/chat.d.ts.map +1 -1
  80. package/dist/subagent/chat.js +28 -6
  81. package/dist/subagent/chat.js.map +1 -1
  82. package/dist/subagent/context.d.ts +7 -0
  83. package/dist/subagent/context.d.ts.map +1 -1
  84. package/dist/subagent/context.js +11 -0
  85. package/dist/subagent/context.js.map +1 -1
  86. package/dist/subagent/definition.d.ts +4 -0
  87. package/dist/subagent/definition.d.ts.map +1 -1
  88. package/dist/subagent/definition.js +4 -0
  89. package/dist/subagent/definition.js.map +1 -1
  90. package/dist/subagent/errors.d.ts +17 -0
  91. package/dist/subagent/errors.d.ts.map +1 -1
  92. package/dist/subagent/errors.js +21 -0
  93. package/dist/subagent/errors.js.map +1 -1
  94. package/dist/subagent/validation.d.ts +6 -0
  95. package/dist/subagent/validation.d.ts.map +1 -1
  96. package/dist/subagent/validation.js +6 -0
  97. package/dist/subagent/validation.js.map +1 -1
  98. package/dist/tools/hooks.d.ts +2 -0
  99. package/dist/tools/hooks.d.ts.map +1 -1
  100. package/dist/tools/hooks.js +118 -16
  101. package/dist/tools/hooks.js.map +1 -1
  102. package/dist/tools/parse-tool-call.d.ts.map +1 -1
  103. package/dist/tools/parse-tool-call.js +16 -14
  104. package/dist/tools/parse-tool-call.js.map +1 -1
  105. package/dist/tools/registry.d.ts +5 -3
  106. package/dist/tools/registry.d.ts.map +1 -1
  107. package/dist/tools/registry.js +47 -28
  108. package/dist/tools/registry.js.map +1 -1
  109. package/dist/tools/subagent.d.ts +0 -10
  110. package/dist/tools/subagent.d.ts.map +1 -1
  111. package/dist/tools/subagent.js +47 -9
  112. package/dist/tools/subagent.js.map +1 -1
  113. package/dist/tools/types.d.ts +2 -10
  114. package/dist/tools/types.d.ts.map +1 -1
  115. package/package.json +1 -1
@@ -1,10 +1,8 @@
1
- import { readFileSync, existsSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import { join, isAbsolute, relative } from "node:path";
3
3
  import { parseToolCalls } from "../tools/parse-tool-call.js";
4
4
  import { parseFileRefs } from "../file-loader.js";
5
5
  import { ContextManager } from "./context.js";
6
- import { autoLearnFromError } from "./memory.js";
7
- import { collectCodingStyle } from "./user-profile.js";
8
6
  import { ModeRegistry } from "./modes/registry.js";
9
7
  import { applyMode } from "./modes/applier.js";
10
8
  import { loadSkill, loadSkillsFromRefs, enforceSkillBudget, preloadSkillsByKeywords, isSkillRequest, listInstalledSkills, } from "../skills/loader.js";
@@ -16,15 +14,14 @@ import { checkFileSyntax } from "../tools/syntax-check.js";
16
14
  import { loadConfig, getLLMConfig, getGroundingConfig, } from "../config/config.js";
17
15
  import { buildSystemPrompt } from "./system-prompt.js";
18
16
  import { logInfo, logWarn, logError, logDebug, logToolCall, logToolOutput, setStatusWriter, writeStatus, } from "../logger.js";
19
- import { buildSubagentContext, runIsolatedLoop, doChat, } from "../tools/subagent.js";
17
+ import { runIsolatedLoop } from "../tools/subagent.js";
18
+ import { buildSubagentContext } from "../subagent/context.js";
19
+ import { doChat } from "../subagent/chat.js";
20
20
  import { SubagentRenderer } from "../ui/subagent-renderer.js";
21
21
  import { ToolExecutor } from "./tool-executor.js";
22
22
  import { ErrorEscalator } from "./error-escalation.js";
23
23
  import { WriteEnforcer } from "./write-enforcer.js";
24
- import { PluginManager } from "../plugins/manager.js";
25
- import { loadAllPluginsWithInfo } from "../plugins/loader.js";
26
- import { handleToolAfter as lintHandleToolAfter } from "../plugins/builtin/lint-on-write/index.js";
27
- import { handleAgentDone, handleToolBlocked, handleToolAfter as notifyHandleToolAfter, } from "../plugins/builtin/notify/index.js";
24
+ import { sendNotification } from "../tools/hooks.js";
28
25
  import { planHandler } from "../tools/native/plan.js";
29
26
  import { todoHandler } from "../tools/native/todo.js";
30
27
  import { questionHandler } from "../tools/native/question.js";
@@ -133,12 +130,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
133
130
  });
134
131
  // ─── Behaviors из ModeConfig ──────────────────────────
135
132
  const behaviors = modeConfig.behaviors;
136
- const planEnforcement = behaviors.planEnforcement !== false;
137
- const directWriteStreakMax = typeof behaviors.directWriteStreakLimit === "number"
138
- ? behaviors.directWriteStreakLimit
139
- : behaviors.directWriteStreak !== false
140
- ? 3
141
- : Infinity;
142
133
  const continuationNudgeMax = typeof behaviors.continuationNudgeLimit === "number"
143
134
  ? behaviors.continuationNudgeLimit
144
135
  : behaviors.continuationNudge !== false
@@ -211,50 +202,9 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
211
202
  }
212
203
  setStatusWriter(renderer ? (msg) => renderer.writeDim(` ${msg}`) : null);
213
204
  writeStatus("Initializing...");
214
- // ─── PluginManager: загрузка и активация плагинов ──
215
- const pluginManager = new PluginManager();
216
- const pluginLogger = {
217
- info: logInfo.bind(null, "PLUGIN"),
218
- warn: logWarn.bind(null, "PLUGIN"),
219
- error: logError.bind(null, "PLUGIN"),
220
- debug: logDebug.bind(null, "PLUGIN"),
221
- };
222
- const pluginCtx = { cwd: process.cwd(), config: {}, logger: pluginLogger };
223
- // Загружаем все плагины (builtin + project + user)
224
- const pluginInfos = await loadAllPluginsWithInfo(pluginManager);
225
- // Активируем плагины из modeConfig (кастомные)
226
- if (modeConfig.plugins?.length) {
227
- await pluginManager.activateAll(modeConfig.plugins, pluginCtx);
228
- }
229
- // Авто-активация builtin плагинов (все моды)
230
- const builtinPlugins = pluginInfos.filter((p) => p.source === "builtin");
231
- for (const bp of builtinPlugins) {
232
- if (!pluginManager.isActive(bp.name)) {
233
- await pluginManager.activate(bp.name, pluginCtx);
234
- }
235
- }
236
- // Активация плагинов из config.agent.plugins
237
- const configPlugins = config.agent?.plugins || [];
238
- for (const pluginName of configPlugins) {
239
- if (!pluginManager.isActive(pluginName)) {
240
- await pluginManager.activate(pluginName, pluginCtx);
241
- }
242
- }
243
- // Регистрация обработчиков событий для всех активных плагинов
244
- if (pluginManager.isActive("lint-on-write")) {
245
- pluginManager.on("tool:after", "lint-on-write", (toolName, args, result) => {
246
- return lintHandleToolAfter(toolName, args, result, process.cwd(), ".lint-detected");
247
- });
248
- }
249
- if (pluginManager.isActive("notify")) {
250
- pluginManager.on("agent:done", "notify", () => handleAgentDone());
251
- pluginManager.on("tool:blocked", "notify", (toolName, args) => handleToolBlocked(toolName, args));
252
- pluginManager.on("tool:after", "notify", (toolName, args, result) => notifyHandleToolAfter(toolName, args, result));
253
- }
254
- const pluginSections = await pluginManager.getPromptSections();
255
- // Сборка system prompt (включает инструкции, информацию о проекте, скилы, память, плагины)
205
+ // Сборка system prompt (включает инструкции, информацию о проекте, скилы, память)
256
206
  const sysStart = performance.now();
257
- const systemPrompt = buildSystemPrompt(loadedSkills.length > 0 ? loadedSkills : undefined, options?.mode, pluginSections, options?.flags);
207
+ const systemPrompt = buildSystemPrompt(loadedSkills.length > 0 ? loadedSkills : undefined, options?.mode, options?.flags);
258
208
  const sysElapsed = ((performance.now() - sysStart) / 1000).toFixed(1);
259
209
  writeStatus(`Done (${sysElapsed}s)`);
260
210
  setStatusWriter(null);
@@ -275,6 +225,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
275
225
  let consecutiveInvalidCalls = 0; // счётчик невалидных tool call подряд
276
226
  /** Счётчик бесполезных действий (list_dir, glob_search, todo list без результата) */
277
227
  let uselessActionCount = 0;
228
+ /** Сколько шагов подряд модель не вызывает инструменты */
229
+ let consecutiveNoToolSteps = 0;
278
230
  /** История последних чтений файлов для.detect повторных чтений */
279
231
  const recentReadFiles = [];
280
232
  /** История последних grep-запросов для detect повторных поисков */
@@ -285,7 +237,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
285
237
  const recentBashCommands = [];
286
238
  // ─── ToolExecutor: единый диспетчер инструментов ──
287
239
  const toolExecutor = new ToolExecutor();
288
- toolExecutor.setPluginManager(pluginManager);
289
240
  toolExecutor.registerAll([
290
241
  planHandler,
291
242
  todoHandler,
@@ -314,6 +265,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
314
265
  ]);
315
266
  const errorEscalator = new ErrorEscalator();
316
267
  const writeEnforcer = new WriteEnforcer();
268
+ /** Отслеживание созданных/изменённых файлов за всю сессию */
269
+ const createdFiles = [];
317
270
  // Reset abort signal for this run
318
271
  if (renderer?.abortSignal === undefined) {
319
272
  // Renderer doesn't support abort — fine
@@ -332,7 +285,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
332
285
  break;
333
286
  }
334
287
  let skillsAutoLoaded = false;
335
- const createdFiles = []; // отслеживание созданных/изменённых файлов
336
288
  logDebug("LOOP", `step ${step + 1}/${maxSteps} starting, ${messages.length} messages`);
337
289
  // Инкрементальный подсчёт — считаем только новые сообщения (быстрее в ~100x на больших историях)
338
290
  if (step === 0) {
@@ -425,7 +377,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
425
377
  renderer?.writeInfo(` Auto-loaded ${loadedSkills.filter((s) => newSkillNames.includes(s.name)).length} skill(s): ${newSkillNames.join(", ")}`);
426
378
  messages[0] = {
427
379
  role: "system",
428
- content: buildSystemPrompt(loadedSkills, options?.mode, pluginSections, options?.flags),
380
+ content: buildSystemPrompt(loadedSkills, options?.mode, options?.flags),
429
381
  };
430
382
  // Не удаляем ответ ассистента — skills загружаются для следующих шагов цикла.
431
383
  // Модель увидит обновлённый system prompt на следующей итерации и сможет
@@ -520,141 +472,122 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
520
472
  continue;
521
473
  }
522
474
  }
523
- if (!skillsAutoLoaded) {
524
- const textOutside = response
525
- .replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
526
- .trim();
527
- const looksLikeDone = /done|готово|создан|выполн|completed|finished/i.test(textOutside);
528
- // Проверка: план с незавершёнными шагами — не даём агенту остановиться
529
- const pendingSteps = getPlanPendingCount();
530
- if (pendingSteps > 0 && looksLikeDone) {
531
- const planText = showPlan();
532
- const pendingLines = planText
533
- .split("\n")
534
- .filter((l) => l.includes("○") || l.includes("►"));
535
- const nextStep = pendingLines[0] || "";
536
- const nextStepMatch = nextStep.match(/\d+\.\s*(.+)/);
537
- const nextStepTitle = nextStepMatch ? nextStepMatch[1].trim() : "";
475
+ // ─── Определение завершения: только по поведению, не по тексту ──────
476
+ // Смотрим: были ли хоть какие-то вызовы инструментов за сессию.
477
+ // Сообщения с role:tool — это результаты выполненных инструментов
478
+ // (кроме invalid — они с tool_call_id вида "invalid-...").
479
+ const hasDoneAnyWork = messages.some((m) => m.role === "tool" &&
480
+ m.tool_call_id &&
481
+ !m.tool_call_id.startsWith("invalid-"));
482
+ const planStatus = showPlan();
483
+ const hasActivePlan = !planStatus.startsWith("No active plan");
484
+ const pendingSteps = getPlanPendingCount();
485
+ // === 1. Незавершённый план ===
486
+ if (hasActivePlan && pendingSteps > 0) {
487
+ const planText = planStatus;
488
+ const pendingLines = planText
489
+ .split("\n")
490
+ .filter((l) => l.includes("○") || l.includes("►"));
491
+ const nextStep = pendingLines[0] || "";
492
+ const nextStepMatch = nextStep.match(/\d+\.\s*(.+)/);
493
+ const nextStepTitle = nextStepMatch ? nextStepMatch[1].trim() : "";
494
+ if (!quiet) {
495
+ renderer?.writeWarning(` [plan has ${pendingSteps} incomplete steps — pushing agent to continue]`);
496
+ }
497
+ const nudge = nextStepTitle
498
+ ? `Plan is NOT complete! ${pendingSteps} step(s) remaining.\nNext step: ${nextStepTitle}\nUse subagent to execute this step, then mark done: plan({action:"done", step:N})`
499
+ : `Plan is NOT complete! ${pendingSteps} step(s) remaining:\n${pendingLines.join("\n")}\n\nContinue with the next step using subagent.`;
500
+ messages.push({ role: "user", content: nudge });
501
+ continuationNudges = 0;
502
+ continue;
503
+ }
504
+ // === 2. План завершён — аудит файлов ===
505
+ // Проверка по createdFiles: если план выполнен, но ни одного файла
506
+ // не создано — скорее всего модель галлюцинирует выполнение шагов.
507
+ if (hasActivePlan && pendingSteps === 0) {
508
+ if (createdFiles.length === 0) {
538
509
  if (!quiet) {
539
- renderer?.writeWarning(` [plan has ${pendingSteps} incomplete steps — pushing agent to continue]`);
510
+ renderer?.writeWarning(` [plan complete but no files created]`);
540
511
  }
541
- const nudge = nextStepTitle
542
- ? `Plan is NOT complete! ${pendingSteps} step(s) remaining.\nNext step: ${nextStepTitle}\nUse subagent to execute this step, then mark done: plan({action:"done", step:N})`
543
- : `Plan is NOT complete! ${pendingSteps} step(s) remaining:\n${pendingLines.join("\n")}\n\nContinue with the next step using subagent.`;
544
- messages.push({ role: "user", content: nudge });
512
+ messages.push({
513
+ role: "user",
514
+ content: `Plan claims all steps done, but no files were created via write_file or subagent. Either execute the steps properly or explain why files aren't needed.`,
515
+ });
545
516
  continuationNudges = 0;
546
517
  continue;
547
518
  }
548
- // Phase 4.1: Аудит файлов при завершении плана
549
- if (pendingSteps === 0 && looksLikeDone) {
550
- const plan = showPlan();
551
- if (!plan.startsWith("No active plan")) {
552
- // Извлекаем заголовки шагов и проверяем наличие файлов
553
- const stepLines = plan
554
- .split("\n")
555
- .filter((l) => l.match(/\d+\.\s/));
556
- const missingFiles = [];
557
- for (const line of stepLines) {
558
- const titleMatch = line.match(/\d+\.\s*(.+)/);
559
- if (!titleMatch)
560
- continue;
561
- const title = titleMatch[1].trim();
562
- // Извлекаем имя файла из заголовка (паттерн: "Create file.ext" или "file.ext")
563
- const fileMatch = title.match(/(?:Create|Создать|Add|Добавить)\s+(\S+\.(?:ts|tsx|js|jsx|json|html|css|md|yaml|yml))/i) ||
564
- title.match(/(\w+\.(?:ts|tsx|js|jsx|json|html|css|md|yaml|yml))/i);
565
- if (fileMatch) {
566
- const fileName = fileMatch[1];
567
- if (!existsSync(fileName) &&
568
- !createdFiles.some((f) => f.endsWith(fileName))) {
569
- missingFiles.push(fileName);
570
- }
571
- }
572
- }
573
- if (missingFiles.length > 0) {
574
- if (!quiet) {
575
- renderer?.writeWarning(` [plan complete but missing files: ${missingFiles.join(", ")}]`);
576
- }
577
- messages.push({
578
- role: "user",
579
- content: `Plan claims all steps done, but these files are missing: ${missingFiles.join(", ")}. Create them before finishing.`,
580
- });
581
- continuationNudges = 0;
582
- continue;
583
- }
584
- }
519
+ // Файлы созданы — выходим
520
+ return { messages };
521
+ }
522
+ // === 3. Нет активного плана ===
523
+ // Stuck detection (без изменений)
524
+ const isUselessAction = validCalls.some((c) => c.parsed.name === "list_dir" ||
525
+ c.parsed.name === "tree" ||
526
+ (c.parsed.name === "todo" && c.parsed.arguments?.action === "list") ||
527
+ (c.parsed.name === "question" && !c.parsed.arguments?.question));
528
+ let isRepeatedRead = false;
529
+ let isRepeatedSearch = false;
530
+ let isRepeatedQuestion = false;
531
+ let isRepeatedBash = false;
532
+ for (const c of validCalls) {
533
+ if (c.parsed.name === "read_file") {
534
+ const filePath = String(c.parsed.arguments?.path || "");
535
+ if (recentReadFiles.includes(filePath))
536
+ isRepeatedRead = true;
537
+ recentReadFiles.push(filePath);
538
+ if (recentReadFiles.length > 5)
539
+ recentReadFiles.shift();
585
540
  }
586
- // Stuck detection: если модель делает бесполезные действия
587
- const isUselessAction = validCalls.some((c) => c.parsed.name === "list_dir" ||
588
- c.parsed.name === "tree" ||
589
- (c.parsed.name === "todo" &&
590
- c.parsed.arguments?.action === "list") ||
591
- (c.parsed.name === "question" && !c.parsed.arguments?.question));
592
- // Phase 5.1: Расширенное detect — повторные чтения, поиски, вопросы и bash
593
- let isRepeatedRead = false;
594
- let isRepeatedSearch = false;
595
- let isRepeatedQuestion = false;
596
- let isRepeatedBash = false;
597
- for (const c of validCalls) {
598
- if (c.parsed.name === "read_file") {
599
- const filePath = String(c.parsed.arguments?.path || "");
600
- if (recentReadFiles.includes(filePath)) {
601
- isRepeatedRead = true;
602
- }
603
- recentReadFiles.push(filePath);
604
- if (recentReadFiles.length > 5)
605
- recentReadFiles.shift();
606
- }
607
- if (c.parsed.name === "grep_search") {
608
- const pattern = String(c.parsed.arguments?.pattern || "");
609
- if (recentGrepPatterns.includes(pattern)) {
610
- isRepeatedSearch = true;
611
- }
612
- recentGrepPatterns.push(pattern);
613
- if (recentGrepPatterns.length > 5)
614
- recentGrepPatterns.shift();
615
- }
616
- if (c.parsed.name === "question") {
617
- const questionText = String(c.parsed.arguments?.question || "");
618
- if (recentQuestions.includes(questionText)) {
619
- isRepeatedQuestion = true;
620
- }
621
- recentQuestions.push(questionText);
622
- if (recentQuestions.length > 3)
623
- recentQuestions.shift();
624
- }
625
- if (c.parsed.name === "bash") {
626
- const cmdText = String(c.parsed.arguments?.cmd || "");
627
- const normalized = cmdText.trim().replace(/\s+/g, " ");
628
- if (recentBashCommands.includes(normalized)) {
629
- isRepeatedBash = true;
630
- }
631
- recentBashCommands.push(normalized);
632
- if (recentBashCommands.length > 5)
633
- recentBashCommands.shift();
634
- }
541
+ if (c.parsed.name === "grep_search") {
542
+ const pattern = String(c.parsed.arguments?.pattern || "");
543
+ if (recentGrepPatterns.includes(pattern))
544
+ isRepeatedSearch = true;
545
+ recentGrepPatterns.push(pattern);
546
+ if (recentGrepPatterns.length > 5)
547
+ recentGrepPatterns.shift();
635
548
  }
636
- const isStuckAction = isUselessAction ||
637
- isRepeatedRead ||
638
- isRepeatedSearch ||
639
- isRepeatedQuestion ||
640
- isRepeatedBash;
641
- if (isStuckAction) {
642
- uselessActionCount++;
643
- if (uselessActionCount >= 3) {
644
- if (!quiet) {
645
- renderer?.writeWarning(` [agent stuck: ${uselessActionCount} useless actions]`);
646
- }
647
- clearPlan();
648
- messages.push({
649
- role: "user",
650
- content: `[System: You are stuck in a loop. STOP calling the same tool. The user already answered your question — use their answer and proceed with the task. Do NOT ask the same question again.]`,
651
- });
652
- uselessActionCount = 0;
653
- continuationNudges = 0;
654
- continue;
549
+ if (c.parsed.name === "question") {
550
+ const questionText = String(c.parsed.arguments?.question || "");
551
+ if (recentQuestions.includes(questionText))
552
+ isRepeatedQuestion = true;
553
+ recentQuestions.push(questionText);
554
+ if (recentQuestions.length > 3)
555
+ recentQuestions.shift();
556
+ }
557
+ if (c.parsed.name === "bash") {
558
+ const cmdText = String(c.parsed.arguments?.cmd || "");
559
+ const normalized = cmdText.trim().replace(/\s+/g, " ");
560
+ if (recentBashCommands.includes(normalized))
561
+ isRepeatedBash = true;
562
+ recentBashCommands.push(normalized);
563
+ if (recentBashCommands.length > 5)
564
+ recentBashCommands.shift();
565
+ }
566
+ }
567
+ const isStuckAction = isUselessAction ||
568
+ isRepeatedRead ||
569
+ isRepeatedSearch ||
570
+ isRepeatedQuestion ||
571
+ isRepeatedBash;
572
+ if (isStuckAction) {
573
+ uselessActionCount++;
574
+ if (uselessActionCount >= 3) {
575
+ if (!quiet) {
576
+ renderer?.writeWarning(` [agent stuck: ${uselessActionCount} useless actions]`);
655
577
  }
578
+ clearPlan();
579
+ messages.push({
580
+ role: "user",
581
+ content: `[System: You are stuck in a loop. STOP calling the same tool. The user already answered your question — use their answer and proceed with the task. Do NOT ask the same question again.]`,
582
+ });
583
+ uselessActionCount = 0;
584
+ continuationNudges = 0;
585
+ continue;
656
586
  }
657
- else if (validCalls.some((c) => c.parsed.name === "subagent" ||
587
+ }
588
+ else {
589
+ consecutiveNoToolSteps = 0;
590
+ if (validCalls.some((c) => c.parsed.name === "subagent" ||
658
591
  c.parsed.name === "write_file" ||
659
592
  c.parsed.name === "bash")) {
660
593
  uselessActionCount = 0;
@@ -663,18 +596,39 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
663
596
  recentQuestions.length = 0;
664
597
  recentBashCommands.length = 0;
665
598
  }
666
- // Если модель claim-ит done но есть созданные файлы — напоминаем о полноте
667
- if (createdFiles.length > 0 && looksLikeDone) {
599
+ }
600
+ // === 4. Выход: решение по поведению, а не по тексту ===
601
+ // 4a. Модель ничего не делала — поведенческий контроль
602
+ if (!hasDoneAnyWork) {
603
+ if (step > 0)
604
+ consecutiveNoToolSteps++;
605
+ if (consecutiveNoToolSteps >= 2) {
606
+ if (!quiet) {
607
+ renderer?.writeWarning(` [idle: ${consecutiveNoToolSteps} steps without any tool calls]`);
608
+ }
609
+ const idleSteps = consecutiveNoToolSteps;
610
+ consecutiveNoToolSteps = 0;
668
611
  messages.push({
669
612
  role: "user",
670
- content: `Files created: ${createdFiles.join(", ")}. Before finishing — verify all files are complete and connected to each other. If any file is missing or not imported, continue working.`,
613
+ content: `[System: ты уже ${idleSteps} шага подряд не вызываешь инструменты. Если задача требует создания/изменения файлов — вызови write_file или edit_file. Если задача уже выполнена — напиши результат пользователю.]`,
671
614
  });
672
615
  continuationNudges = 0;
673
616
  continue;
674
617
  }
618
+ // Первый ответ (step === 0) без инструментов — обычный диалог
675
619
  return { messages };
676
620
  }
677
- continue;
621
+ // 4b. Модель что-то делала — проверяем созданные файлы перед выходом
622
+ if (createdFiles.length > 0) {
623
+ messages.push({
624
+ role: "user",
625
+ content: `Files created: ${createdFiles.join(", ")}. Before finishing — verify all files are complete and connected to each other. If any file is missing or not imported, continue working.`,
626
+ });
627
+ continuationNudges = 0;
628
+ continue;
629
+ }
630
+ // 4c. Модель работала, но не создавала файлы (читала, искала и т.д.)
631
+ return { messages };
678
632
  }
679
633
  // ─── Grounding check ────────────────────────────────
680
634
  // Проверка наличия маркера @grounding[файл: path] перед tool_call
@@ -762,8 +716,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
762
716
  // ─── Enforce: лимиты на запись файлов ────────────────
763
717
  // Отслеживание повторных чтений одного файла для консолидации
764
718
  let lastReadFile = null;
765
- writeEnforcer.enforcePlan(validCalls, messages, planEnforcement, quiet, renderer);
766
- writeEnforcer.enforceWriteStreak(validCalls, messages, directWriteStreakMax, quiet, renderer);
767
719
  writeEnforcer.enforceSingleWrite(validCalls, messages, quiet, renderer);
768
720
  // ─── Исполнение tool calls ──────────────────────────
769
721
  for (const call of validCalls) {
@@ -957,7 +909,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
957
909
  renderer?.writeError(` ${line}`);
958
910
  }
959
911
  }
960
- autoLearnFromError(hookCmd, toolExitCode !== 0 ? toolOutput : "", "");
961
912
  }
962
913
  runPostToolUseHooks(process.cwd(), parsed.name, hookCmd, toolOutput, toolExitCode);
963
914
  // Track sequential read_file calls for consolidation
@@ -1080,23 +1031,15 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
1080
1031
  pendingSyntaxError = "";
1081
1032
  }
1082
1033
  }
1083
- // Автодетекция стиля кодирования из содержимого файла
1084
- if (toolExitCode === 0 && filePath) {
1085
- try {
1086
- const fullContent = readFileSync(join(process.cwd(), filePath), "utf-8");
1087
- collectCodingStyle(filePath, fullContent);
1088
- }
1089
- catch {
1090
- /* ignore */
1091
- }
1092
- // Track created/modified files for completion reporting
1093
- if (!hasSyntaxError && !createdFiles.includes(filePath)) {
1094
- createdFiles.push(filePath);
1095
- }
1034
+ // Track created/modified files for completion reporting
1035
+ if (!hasSyntaxError && !createdFiles.includes(filePath)) {
1036
+ createdFiles.push(filePath);
1096
1037
  }
1097
1038
  }
1098
1039
  }
1099
1040
  }
1041
+ // Любой выполненный tool call сбрасывает счётчик бездействия
1042
+ consecutiveNoToolSteps = 0;
1100
1043
  messages.push({
1101
1044
  role: "tool",
1102
1045
  content: truncateOutput(parsed.name, toolOutput, toolExitCode),
@@ -1123,10 +1066,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
1123
1066
  renderer?.writeWarning("Agent stopped (double ESC)");
1124
1067
  }
1125
1068
  logInfo("LOOP", `runAgentLoop done: ${messages.length} total messages, aborted=${aborted}`);
1126
- // Agent:done event for notify plugin
1127
- if (pluginManager.isActive("notify")) {
1128
- pluginManager.emit("agent:done").catch(() => { });
1129
- }
1069
+ // Agent:done notification
1070
+ sendNotification("MMA", "Agent finished the task");
1130
1071
  return { messages };
1131
1072
  }
1132
1073
  //# sourceMappingURL=loop.js.map