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.
- package/dist/agent/context.d.ts +3 -6
- package/dist/agent/context.d.ts.map +1 -1
- package/dist/agent/context.js +8 -41
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/error-escalation.d.ts +7 -6
- package/dist/agent/error-escalation.d.ts.map +1 -1
- package/dist/agent/error-escalation.js +82 -36
- package/dist/agent/error-escalation.js.map +1 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +151 -210
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory.d.ts +0 -29
- package/dist/agent/memory.d.ts.map +1 -1
- package/dist/agent/memory.js +1 -78
- package/dist/agent/memory.js.map +1 -1
- package/dist/agent/modes/registry.d.ts +1 -1
- package/dist/agent/modes/registry.d.ts.map +1 -1
- package/dist/agent/modes/registry.js +26 -27
- package/dist/agent/modes/registry.js.map +1 -1
- package/dist/agent/modes/types.d.ts +0 -5
- package/dist/agent/modes/types.d.ts.map +1 -1
- package/dist/agent/system-prompt.d.ts +1 -1
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +95 -147
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-executor.d.ts +2 -6
- package/dist/agent/tool-executor.d.ts.map +1 -1
- package/dist/agent/tool-executor.js +79 -65
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/user-profile.d.ts +1 -21
- package/dist/agent/user-profile.d.ts.map +1 -1
- package/dist/agent/user-profile.js +41 -174
- package/dist/agent/user-profile.js.map +1 -1
- package/dist/agent/write-enforcer.d.ts +4 -8
- package/dist/agent/write-enforcer.d.ts.map +1 -1
- package/dist/agent/write-enforcer.js +4 -88
- package/dist/agent/write-enforcer.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +2 -25
- package/dist/cli/config.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +102 -139
- package/dist/cli.js.map +1 -1
- package/dist/config/config.d.ts +15 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +4 -38
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +0 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/repl/commands.d.ts +16 -16
- package/dist/repl/commands.d.ts.map +1 -1
- package/dist/repl/commands.js +474 -321
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.d.ts +4 -4
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +60 -56
- package/dist/repl/index.js.map +1 -1
- package/dist/session/store.d.ts +4 -7
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +35 -71
- package/dist/session/store.js.map +1 -1
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +5 -23
- package/dist/setup-wizard.js.map +1 -1
- package/dist/styling/engine.d.ts +7 -12
- package/dist/styling/engine.d.ts.map +1 -1
- package/dist/styling/engine.js +85 -134
- package/dist/styling/engine.js.map +1 -1
- package/dist/styling/index.d.ts +2 -2
- package/dist/styling/index.d.ts.map +1 -1
- package/dist/styling/index.js +0 -1
- package/dist/styling/index.js.map +1 -1
- package/dist/styling/loader.d.ts +2 -2
- package/dist/styling/loader.d.ts.map +1 -1
- package/dist/styling/loader.js +99 -47
- package/dist/styling/loader.js.map +1 -1
- package/dist/subagent/chat.d.ts +12 -0
- package/dist/subagent/chat.d.ts.map +1 -1
- package/dist/subagent/chat.js +28 -6
- package/dist/subagent/chat.js.map +1 -1
- package/dist/subagent/context.d.ts +7 -0
- package/dist/subagent/context.d.ts.map +1 -1
- package/dist/subagent/context.js +11 -0
- package/dist/subagent/context.js.map +1 -1
- package/dist/subagent/definition.d.ts +4 -0
- package/dist/subagent/definition.d.ts.map +1 -1
- package/dist/subagent/definition.js +4 -0
- package/dist/subagent/definition.js.map +1 -1
- package/dist/subagent/errors.d.ts +17 -0
- package/dist/subagent/errors.d.ts.map +1 -1
- package/dist/subagent/errors.js +21 -0
- package/dist/subagent/errors.js.map +1 -1
- package/dist/subagent/validation.d.ts +6 -0
- package/dist/subagent/validation.d.ts.map +1 -1
- package/dist/subagent/validation.js +6 -0
- package/dist/subagent/validation.js.map +1 -1
- package/dist/tools/hooks.d.ts +2 -0
- package/dist/tools/hooks.d.ts.map +1 -1
- package/dist/tools/hooks.js +118 -16
- package/dist/tools/hooks.js.map +1 -1
- package/dist/tools/parse-tool-call.d.ts.map +1 -1
- package/dist/tools/parse-tool-call.js +16 -14
- package/dist/tools/parse-tool-call.js.map +1 -1
- package/dist/tools/registry.d.ts +5 -3
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +47 -28
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/subagent.d.ts +0 -10
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +47 -9
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/types.d.ts +2 -10
- package/dist/tools/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/agent/loop.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { readFileSync
|
|
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 {
|
|
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 {
|
|
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
|
-
//
|
|
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,
|
|
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,
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
|
510
|
+
renderer?.writeWarning(` [plan complete but no files created]`);
|
|
540
511
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
: `Plan
|
|
544
|
-
|
|
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
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
(
|
|
592
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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: `
|
|
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
|
-
|
|
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 (
|
|
1085
|
-
|
|
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
|
|
1127
|
-
|
|
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
|