micro-models-agent 0.2.91 → 0.3.0
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/error-escalation.d.ts +18 -0
- package/dist/agent/error-escalation.d.ts.map +1 -0
- package/dist/agent/error-escalation.js +122 -0
- package/dist/agent/error-escalation.js.map +1 -0
- package/dist/agent/loop.d.ts +2 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +22 -274
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/modes/applier.d.ts +2 -2
- package/dist/agent/modes/applier.d.ts.map +1 -1
- package/dist/agent/modes/registry.d.ts.map +1 -1
- package/dist/agent/modes/registry.js.map +1 -1
- package/dist/agent/modes/types.d.ts +12 -2
- package/dist/agent/modes/types.d.ts.map +1 -1
- package/dist/agent/react-loop.d.ts +4 -0
- package/dist/agent/react-loop.d.ts.map +1 -1
- package/dist/agent/react-loop.js +5 -23
- package/dist/agent/react-loop.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +2 -1
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +23 -32
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-executor.d.ts +2 -0
- package/dist/agent/tool-executor.d.ts.map +1 -1
- package/dist/agent/tool-executor.js +1 -1
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/write-enforcer.d.ts +11 -0
- package/dist/agent/write-enforcer.d.ts.map +1 -0
- package/dist/agent/write-enforcer.js +92 -0
- package/dist/agent/write-enforcer.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -16
- package/dist/cli.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +4 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +5 -0
- package/dist/logger.js.map +1 -1
- package/dist/repl/commands.js +0 -5
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/history.d.ts +2 -1
- package/dist/repl/history.d.ts.map +1 -1
- package/dist/repl/history.js.map +1 -1
- package/dist/repl/index.d.ts +2 -0
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +2 -2
- package/dist/repl/index.js.map +1 -1
- package/dist/skills/loader.d.ts +16 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +110 -9
- package/dist/skills/loader.js.map +1 -1
- package/dist/tools/id-gen.d.ts +4 -0
- package/dist/tools/id-gen.d.ts.map +1 -0
- package/dist/tools/id-gen.js +12 -0
- package/dist/tools/id-gen.js.map +1 -0
- package/dist/tools/native/theme.d.ts.map +1 -1
- package/dist/tools/native/theme.js +0 -5
- package/dist/tools/native/theme.js.map +1 -1
- package/dist/tools/parse-tool-call.d.ts +11 -0
- package/dist/tools/parse-tool-call.d.ts.map +1 -0
- package/dist/tools/parse-tool-call.js +85 -0
- package/dist/tools/parse-tool-call.js.map +1 -0
- package/dist/tools/plan.d.ts +2 -0
- package/dist/tools/plan.d.ts.map +1 -1
- package/dist/tools/plan.js +8 -2
- package/dist/tools/plan.js.map +1 -1
- package/dist/tools/subagent.d.ts +0 -8
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +15 -94
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/todo.d.ts +2 -0
- package/dist/tools/todo.d.ts.map +1 -1
- package/dist/tools/todo.js +8 -2
- package/dist/tools/todo.js.map +1 -1
- package/dist/ui/index.d.ts +1 -15
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -12
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/renderer.d.ts +2 -2
- package/dist/ui/renderer.d.ts.map +1 -1
- package/dist/ui/renderer.js +1 -1
- package/package.json +47 -51
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LLMProvider, ChatMessage } from '../llm/provider.js';
|
|
2
|
+
import type { ToolRegistry } from '../tools/registry.js';
|
|
3
|
+
import type { ToolExecutor } from './tool-executor.js';
|
|
4
|
+
import type { OutputRenderer } from '../ui/renderer.js';
|
|
5
|
+
/**
|
|
6
|
+
* Управляет эскалацией ошибок инструментов: отслеживание,
|
|
7
|
+
* вызов specialist'а, переключение стратегии и блокировка.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ErrorEscalator {
|
|
10
|
+
consecutiveToolErrors: Map<string, number>;
|
|
11
|
+
totalToolErrors: number;
|
|
12
|
+
specialistSpawnedFor: string | null;
|
|
13
|
+
errorStrategy: 'normal' | 'subagent-hint' | 'force-subagent';
|
|
14
|
+
resetForTool(toolName: string): void;
|
|
15
|
+
reset(): void;
|
|
16
|
+
handleToolError(toolName: string, toolOutput: string, toolArgs: Record<string, unknown>, toolId: string, messages: ChatMessage[], createdFiles: string[], enrichedPrompt: string, renderer: OutputRenderer | undefined, quiet: boolean, provider: LLMProvider, registry: ToolRegistry, toolExecutor: ToolExecutor, modeConfig: any, llmCfg: any): Promise<'continue' | 'block'>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=error-escalation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-escalation.d.ts","sourceRoot":"","sources":["../../src/agent/error-escalation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAoDxD;;;GAGG;AACH,qBAAa,cAAc;IACzB,qBAAqB,sBAA6B;IAClD,eAAe,SAAK;IACpB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,aAAa,EAAE,QAAQ,GAAG,eAAe,GAAG,gBAAgB,CAAY;IAExE,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOpC,KAAK,IAAI,IAAI;IAOP,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,WAAW,EAAE,EACvB,YAAY,EAAE,MAAM,EAAE,EACtB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,GAAG,EACf,MAAM,EAAE,GAAG,GACV,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC;CAyDjC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { generateToolCallId } from '../tools/id-gen.js';
|
|
2
|
+
import { logInfo, logError } from '../logger.js';
|
|
3
|
+
import { loadConfig, getSpecialistConfig } from '../config/config.js';
|
|
4
|
+
import { buildSpecialistTask, buildSubagentContext, isTrivialError, runIsolatedLoop } from '../tools/subagent.js';
|
|
5
|
+
import { SubagentRenderer } from '../ui/subagent-renderer.js';
|
|
6
|
+
import { truncateOutput } from './tool-executor.js';
|
|
7
|
+
/**
|
|
8
|
+
* Запускает specialist'а для решения повторяющейся ошибки.
|
|
9
|
+
*/
|
|
10
|
+
async function spawnSpecialist(p) {
|
|
11
|
+
if (!p.quiet) {
|
|
12
|
+
p.renderer?.writeBracketTag('specialist', `solving '${p.errorOutput.slice(0, 60).replace(/\n/g, ' ')}' error...`);
|
|
13
|
+
}
|
|
14
|
+
const task = buildSpecialistTask(p.toolName, p.toolArgs, p.errorOutput, p.enrichedPrompt, p.errorCount);
|
|
15
|
+
const ctx = buildSubagentContext(process.cwd());
|
|
16
|
+
const sub = new SubagentRenderer();
|
|
17
|
+
try {
|
|
18
|
+
logInfo('LOOP', `spawning specialist for ${p.toolName} (attempt #${p.errorCount})`);
|
|
19
|
+
const r = await runIsolatedLoop(p.provider, p.registry, task, ctx, process.cwd(), p.quiet, sub, p.toolExecutor, p.modeConfig.subagentMode, p.llmCfg.toolModel, p.specialistCfg.maxSteps);
|
|
20
|
+
for (let i = p.messages.length - 1; i >= 0; i--) {
|
|
21
|
+
if (p.messages[i].role === 'tool' && p.messages[i].tool_call_id === p.toolId) {
|
|
22
|
+
const text = r.errors.length > 0
|
|
23
|
+
? `[Specialist attempted to fix "${p.toolName}" but failed:\n${r.errors.join('\n')}\n${r.summary}]\nTry a different approach.`
|
|
24
|
+
: `[Specialist resolved "${p.toolName}" error.\n${r.summary}]\nContinue with the task.`;
|
|
25
|
+
p.messages[i].content = truncateOutput(p.toolName, text, 0);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const f of r.createdFiles) {
|
|
30
|
+
if (!p.createdFiles.includes(f))
|
|
31
|
+
p.createdFiles.push(f);
|
|
32
|
+
}
|
|
33
|
+
const buf = sub.getBuffer();
|
|
34
|
+
if (!p.quiet && p.renderer?.writeToolBuffer)
|
|
35
|
+
p.renderer.writeToolBuffer({ lines: buf.lines, toolCalls: buf.toolCalls });
|
|
36
|
+
logInfo('LOOP', `specialist done | files: ${r.createdFiles.length} | turns: ${r.turns} | errors: ${r.errors.length}`);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
logError('LOOP', `specialist error: ${e.message}`);
|
|
41
|
+
if (!p.quiet)
|
|
42
|
+
p.renderer?.writeError(` [specialist failed: ${e.message}]`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Управляет эскалацией ошибок инструментов: отслеживание,
|
|
48
|
+
* вызов specialist'а, переключение стратегии и блокировка.
|
|
49
|
+
*/
|
|
50
|
+
export class ErrorEscalator {
|
|
51
|
+
consecutiveToolErrors = new Map();
|
|
52
|
+
totalToolErrors = 0;
|
|
53
|
+
specialistSpawnedFor = null;
|
|
54
|
+
errorStrategy = 'normal';
|
|
55
|
+
resetForTool(toolName) {
|
|
56
|
+
for (const [key] of this.consecutiveToolErrors) {
|
|
57
|
+
if (key.startsWith(toolName + '::'))
|
|
58
|
+
this.consecutiveToolErrors.delete(key);
|
|
59
|
+
}
|
|
60
|
+
this.specialistSpawnedFor = null;
|
|
61
|
+
}
|
|
62
|
+
reset() {
|
|
63
|
+
this.consecutiveToolErrors.clear();
|
|
64
|
+
this.totalToolErrors = 0;
|
|
65
|
+
this.specialistSpawnedFor = null;
|
|
66
|
+
this.errorStrategy = 'normal';
|
|
67
|
+
}
|
|
68
|
+
async handleToolError(toolName, toolOutput, toolArgs, toolId, messages, createdFiles, enrichedPrompt, renderer, quiet, provider, registry, toolExecutor, modeConfig, llmCfg) {
|
|
69
|
+
this.totalToolErrors++;
|
|
70
|
+
const errKey = `${toolName}::${toolOutput.slice(0, 100).replace(/[A-Z]:\\[^\s"']+/gi, '<PATH>').replace(/\/[^\s"']+/g, '<PATH>')}`;
|
|
71
|
+
const prevCount = this.consecutiveToolErrors.get(errKey) ?? 0;
|
|
72
|
+
this.consecutiveToolErrors.set(errKey, prevCount + 1);
|
|
73
|
+
const errorCount = prevCount + 1;
|
|
74
|
+
const config = loadConfig();
|
|
75
|
+
const specialistCfg = getSpecialistConfig(config);
|
|
76
|
+
if (specialistCfg.enabled &&
|
|
77
|
+
errorCount === specialistCfg.triggerThreshold &&
|
|
78
|
+
this.specialistSpawnedFor !== errKey &&
|
|
79
|
+
!isTrivialError(toolName, toolOutput) &&
|
|
80
|
+
toolOutput.length >= specialistCfg.minErrorLength) {
|
|
81
|
+
this.specialistSpawnedFor = errKey;
|
|
82
|
+
const spawned = await spawnSpecialist({
|
|
83
|
+
provider, registry, toolExecutor, modeConfig, llmCfg, specialistCfg,
|
|
84
|
+
quiet, renderer, messages, toolId, createdFiles,
|
|
85
|
+
toolName, toolArgs,
|
|
86
|
+
errorOutput: toolOutput, errorCount, enrichedPrompt,
|
|
87
|
+
});
|
|
88
|
+
if (spawned)
|
|
89
|
+
return 'continue';
|
|
90
|
+
}
|
|
91
|
+
if (errorCount > specialistCfg.triggerThreshold) {
|
|
92
|
+
let webHint = '';
|
|
93
|
+
if (toolName === 'web_browse' && String(toolArgs?.action) === 'evaluate') {
|
|
94
|
+
webHint = ' For web scraping: use web_browse(action:"discover") to find real CSS selectors, or web_browse(action:"get_links") to see all links, or web_browse(action:"extract", format:"html") to inspect the page structure.';
|
|
95
|
+
}
|
|
96
|
+
else if (toolName === 'web_browse' && String(toolArgs?.action) === 'navigate' && toolOutput.includes('Status: 40')) {
|
|
97
|
+
webHint = ' Page returned HTTP error. Try navigating to the site homepage first, then use web_browse(action:"get_links") or web_browse(action:"discover") to explore available categories and sections instead of guessing URLs.';
|
|
98
|
+
}
|
|
99
|
+
const msg = errorCount === specialistCfg.triggerThreshold + 1
|
|
100
|
+
? `[System: Tool "${toolName}" failed with the SAME error ${errorCount} times. Read the error message carefully. The input is likely wrong — try a completely different approach, or use subagent for a fresh context.]${webHint}`
|
|
101
|
+
: `[System: Tool "${toolName}" failed ${errorCount} times with the same error. STOP. Do NOT retry the same call. Use subagent(task="...") for a fresh context, or tell the user you're stuck.]${webHint}`;
|
|
102
|
+
messages.push({ role: 'user', content: msg });
|
|
103
|
+
if (!quiet)
|
|
104
|
+
renderer?.writeWarning(` [error escalation: ${toolName} failed ${errorCount} times]`);
|
|
105
|
+
}
|
|
106
|
+
if (this.totalToolErrors >= 5 && this.errorStrategy === 'normal') {
|
|
107
|
+
this.errorStrategy = 'subagent-hint';
|
|
108
|
+
messages.push({ role: 'user', content: `[System: ${this.totalToolErrors} tool errors. Use subagent(task="...") for a fresh context.]` });
|
|
109
|
+
if (!quiet)
|
|
110
|
+
renderer?.writeWarning(` [strategy: subagent-hint after ${this.totalToolErrors} errors]`);
|
|
111
|
+
}
|
|
112
|
+
if (errorCount >= 4) {
|
|
113
|
+
const blockId = generateToolCallId();
|
|
114
|
+
messages.push({ role: 'tool', content: `BLOCKED: Tool "${toolName}" failed ${errorCount} times. Temporarily blocked.`, tool_call_id: blockId });
|
|
115
|
+
if (!quiet)
|
|
116
|
+
renderer?.writeWarning(` [blocked ${toolName}: too many repeated errors]`);
|
|
117
|
+
return 'block';
|
|
118
|
+
}
|
|
119
|
+
return 'block';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=error-escalation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-escalation.js","sourceRoot":"","sources":["../../src/agent/error-escalation.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/loop.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { LLMProvider, ChatMessage } from '../llm/provider.js';
|
|
|
2
2
|
import { ToolRegistry } from '../tools/registry.js';
|
|
3
3
|
import { ContextManager } from './context.js';
|
|
4
4
|
import type { OutputRenderer } from '../ui/renderer.js';
|
|
5
|
-
import type { AgentMode } from '../config/types.js';
|
|
5
|
+
import type { AgentMode, RuntimeFlags } from '../config/types.js';
|
|
6
6
|
/**
|
|
7
7
|
* Результат выполнения цикла агента.
|
|
8
8
|
* Содержит всю историю сообщений и опциональный pending-вопрос
|
|
@@ -41,5 +41,6 @@ export declare function runAgentLoop(provider: LLMProvider, registry: ToolRegist
|
|
|
41
41
|
quiet?: boolean;
|
|
42
42
|
showThinking?: boolean;
|
|
43
43
|
mode?: AgentMode;
|
|
44
|
+
flags?: RuntimeFlags;
|
|
44
45
|
}): Promise<AgentLoopResult>;
|
|
45
46
|
//# sourceMappingURL=loop.d.ts.map
|
package/dist/agent/loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAa9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAgFlE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,cAAc,EAC/B,gBAAgB,CAAC,EAAE,WAAW,EAAE,EAChC,QAAQ,CAAC,EAAE,cAAc,EACzB,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,YAAY,CAAA;CAAE,GAC5F,OAAO,CAAC,eAAe,CAAC,CA64B1B"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
2
|
+
import { join, isAbsolute, relative } from 'node:path';
|
|
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
6
|
import { autoLearnFromError } from './memory.js';
|
|
@@ -11,13 +11,16 @@ import { loadSkill, loadSkillsFromRefs, enforceSkillBudget, preloadSkillsByKeywo
|
|
|
11
11
|
import { runPreToolUseHooks, runPostToolUseHooks } from '../tools/hooks.js';
|
|
12
12
|
import { isMCPCommand, parseMCPCommand, callMCPTool } from '../tools/mcp.js';
|
|
13
13
|
import { showPlan, getPlanPendingCount, clearPlan, getNextPendingStep, updatePlanStep, extractDirFromStepTitle } from '../tools/plan.js';
|
|
14
|
+
import { generateToolCallId } from '../tools/id-gen.js';
|
|
14
15
|
import { checkFileSyntax } from '../tools/syntax-check.js';
|
|
15
|
-
import { loadConfig, getLLMConfig,
|
|
16
|
+
import { loadConfig, getLLMConfig, getGroundingConfig } from '../config/config.js';
|
|
16
17
|
import { buildSystemPrompt } from './system-prompt.js';
|
|
17
18
|
import { logInfo, logWarn, logError, logDebug, logToolCall, setStatusWriter, writeStatus } from '../logger.js';
|
|
18
|
-
import { buildSubagentContext,
|
|
19
|
+
import { buildSubagentContext, runIsolatedLoop, doChat } from '../tools/subagent.js';
|
|
19
20
|
import { SubagentRenderer } from '../ui/subagent-renderer.js';
|
|
20
21
|
import { ToolExecutor } from './tool-executor.js';
|
|
22
|
+
import { ErrorEscalator } from './error-escalation.js';
|
|
23
|
+
import { WriteEnforcer } from './write-enforcer.js';
|
|
21
24
|
import { PluginManager } from '../plugins/manager.js';
|
|
22
25
|
import { loadAllPluginsWithInfo } from '../plugins/loader.js';
|
|
23
26
|
import { handleToolAfter as lintHandleToolAfter } from '../plugins/builtin/lint-on-write/index.js';
|
|
@@ -46,40 +49,7 @@ import { treeHandler } from '../tools/fs/tree.js';
|
|
|
46
49
|
import { webSearchHandler } from '../tools/web-search.js';
|
|
47
50
|
import { webFetchHandler } from '../tools/web-fetch.js';
|
|
48
51
|
import { webBrowseHandler } from '../tools/web-browse.js';
|
|
49
|
-
|
|
50
|
-
* Умная обрезка tool-результатов для экономии токенов в контексте.
|
|
51
|
-
*
|
|
52
|
-
* Полные результаты сохраняются в файл сессии, в контексте — только
|
|
53
|
-
* достаточно информации для модели чтобы продолжить работу.
|
|
54
|
-
*
|
|
55
|
-
* Лимиты подобраны эмпирически: достаточно для маленьких моделей
|
|
56
|
-
* понять что произошло, но не занимают весь контекст.
|
|
57
|
-
*/
|
|
58
|
-
function truncateToolOutput(toolName, output, exitCode) {
|
|
59
|
-
// Ошибки обрезаем мягче — модели нужен контекст для диагностики
|
|
60
|
-
if (exitCode !== 0) {
|
|
61
|
-
return output.length > 3000 ? output.slice(0, 3000) + '\n...[truncated]' : output;
|
|
62
|
-
}
|
|
63
|
-
// Лимиты для успешных операций (в символах)
|
|
64
|
-
const LIMITS = {
|
|
65
|
-
read_file: 2000, // модель может ссылаться на строки
|
|
66
|
-
write_file: 200, // достаточно "Written X bytes"
|
|
67
|
-
edit_file: 300, // нужно знать что изменилось
|
|
68
|
-
bash: 1500, // build output, git status
|
|
69
|
-
list_dir: 1500, // структура обычно компактна
|
|
70
|
-
glob_search: 1000, // список файлов
|
|
71
|
-
grep_search: 1500, // результаты поиска
|
|
72
|
-
tree: 1500, // дерево файлов
|
|
73
|
-
subagent: 500, // краткое резюме от субагента
|
|
74
|
-
web_search: 1000, // результаты поиска
|
|
75
|
-
web_fetch: 1500, // контент страницы
|
|
76
|
-
web_browse: 2000, // browser interaction results
|
|
77
|
-
};
|
|
78
|
-
const limit = LIMITS[toolName] ?? 800;
|
|
79
|
-
if (output.length <= limit)
|
|
80
|
-
return output;
|
|
81
|
-
return output.slice(0, limit) + '\n...[truncated]';
|
|
82
|
-
}
|
|
52
|
+
import { truncateOutput } from './tool-executor.js';
|
|
83
53
|
/**
|
|
84
54
|
* Основной цикл ReAct-агента.
|
|
85
55
|
*
|
|
@@ -121,58 +91,6 @@ function extractToolCallFallback(response) {
|
|
|
121
91
|
}
|
|
122
92
|
return null;
|
|
123
93
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Запускает specialist'а для решения повторяющейся ошибки.
|
|
126
|
-
*
|
|
127
|
-
* Specialist — это изолированный суб-агент со свежим контекстом,
|
|
128
|
-
* который получает задачу "диагностируй и исправь ошибку инструмента X".
|
|
129
|
-
*
|
|
130
|
-
* Алгоритм:
|
|
131
|
-
* 1. Формирует задачу через buildSpecialistTask()
|
|
132
|
-
* 2. Собирает контекст проекта через buildSubagentContext()
|
|
133
|
-
* 3. Запускает runIsolatedLoop() с ограничением maxSteps
|
|
134
|
-
* 4. Заменяет результат ошибки в messages[] на результат specialist'а
|
|
135
|
-
* 5. Трекает созданные specialist'ом файлы
|
|
136
|
-
*
|
|
137
|
-
* @returns true если specialist отработал (основной цикл должен continue),
|
|
138
|
-
* false если specialist упал (ошибка остаётся в messages)
|
|
139
|
-
*/
|
|
140
|
-
async function spawnSpecialist(p) {
|
|
141
|
-
if (!p.quiet) {
|
|
142
|
-
p.renderer?.writeBracketTag('specialist', `solving '${p.errorOutput.slice(0, 60).replace(/\n/g, ' ')}' error...`);
|
|
143
|
-
}
|
|
144
|
-
const task = buildSpecialistTask(p.toolName, p.toolArgs, p.errorOutput, p.enrichedPrompt, p.errorCount);
|
|
145
|
-
const ctx = buildSubagentContext(process.cwd());
|
|
146
|
-
const sub = new SubagentRenderer();
|
|
147
|
-
try {
|
|
148
|
-
logInfo('LOOP', `spawning specialist for ${p.toolName} (attempt #${p.errorCount})`);
|
|
149
|
-
const r = await runIsolatedLoop(p.provider, p.registry, task, ctx, process.cwd(), p.quiet, sub, p.toolExecutor, p.modeConfig.subagentMode, p.llmCfg.toolModel, p.specialistCfg.maxSteps);
|
|
150
|
-
for (let i = p.messages.length - 1; i >= 0; i--) {
|
|
151
|
-
if (p.messages[i].role === 'tool' && p.messages[i].tool_call_id === p.toolId) {
|
|
152
|
-
const text = r.errors.length > 0
|
|
153
|
-
? `[Specialist attempted to fix "${p.toolName}" but failed:\n${r.errors.join('\n')}\n${r.summary}]\nTry a different approach.`
|
|
154
|
-
: `[Specialist resolved "${p.toolName}" error.\n${r.summary}]\nContinue with the task.`;
|
|
155
|
-
p.messages[i].content = truncateToolOutput(p.toolName, text, 0);
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
for (const f of r.createdFiles) {
|
|
160
|
-
if (!p.createdFiles.includes(f))
|
|
161
|
-
p.createdFiles.push(f);
|
|
162
|
-
}
|
|
163
|
-
const buf = sub.getBuffer();
|
|
164
|
-
if (!p.quiet && p.renderer?.writeToolBuffer)
|
|
165
|
-
p.renderer.writeToolBuffer({ lines: buf.lines, toolCalls: buf.toolCalls });
|
|
166
|
-
logInfo('LOOP', `specialist done | files: ${r.createdFiles.length} | turns: ${r.turns} | errors: ${r.errors.length}`);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
catch (e) {
|
|
170
|
-
logError('LOOP', `specialist error: ${e.message}`);
|
|
171
|
-
if (!p.quiet)
|
|
172
|
-
p.renderer?.writeError(` [specialist failed: ${e.message}]`);
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
94
|
/**
|
|
177
95
|
* Запускает основной цикл ReAct-агента.
|
|
178
96
|
*
|
|
@@ -315,7 +233,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
315
233
|
const pluginSections = await pluginManager.getPromptSections();
|
|
316
234
|
// Сборка system prompt (включает инструкции, информацию о проекте, скилы, память, плагины)
|
|
317
235
|
const sysStart = performance.now();
|
|
318
|
-
const systemPrompt = buildSystemPrompt(loadedSkills.length > 0 ? loadedSkills : undefined, options?.mode, pluginSections);
|
|
236
|
+
const systemPrompt = buildSystemPrompt(loadedSkills.length > 0 ? loadedSkills : undefined, options?.mode, pluginSections, options?.flags);
|
|
319
237
|
const sysElapsed = ((performance.now() - sysStart) / 1000).toFixed(1);
|
|
320
238
|
writeStatus(`Done (${sysElapsed}s)`);
|
|
321
239
|
setStatusWriter(null);
|
|
@@ -332,15 +250,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
332
250
|
let aborted = false;
|
|
333
251
|
let continuationNudges = 0; // счётчик подсказок "продолжай работать"
|
|
334
252
|
let consecutiveMissingGrounding = 0; // счётчик пропущенных grounding-маркеров подряд
|
|
335
|
-
let directWriteStreak = 0; // количество прямых write_file без subagent
|
|
336
|
-
/** Трекер повторяющихся tool errors: { normalizedError → count } */
|
|
337
|
-
const consecutiveToolErrors = new Map();
|
|
338
|
-
/** Общий счётчик ошибок за сессию */
|
|
339
|
-
let totalToolErrors = 0;
|
|
340
|
-
/** Стратегия обработки ошибок: 'normal' | 'subagent-hint' | 'force-subagent' */
|
|
341
|
-
let errorStrategy = 'normal';
|
|
342
|
-
/** Флаг: specialist уже был запущен для текущей ошибки (чтобы не дублировать) */
|
|
343
|
-
let specialistSpawnedFor = null;
|
|
344
253
|
/** Счётчик бесполезных действий (list_dir, glob_search, todo list без результата) */
|
|
345
254
|
let uselessActionCount = 0;
|
|
346
255
|
/** История последних чтений файлов для.detect повторных чтений */
|
|
@@ -360,6 +269,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
360
269
|
fileInfoHandler, globSearchHandler, grepSearchHandler, treeHandler,
|
|
361
270
|
webSearchHandler, webFetchHandler, webBrowseHandler,
|
|
362
271
|
]);
|
|
272
|
+
const errorEscalator = new ErrorEscalator();
|
|
273
|
+
const writeEnforcer = new WriteEnforcer();
|
|
363
274
|
// Reset abort signal for this run
|
|
364
275
|
if (renderer?.abortSignal === undefined) {
|
|
365
276
|
// Renderer doesn't support abort — fine
|
|
@@ -460,7 +371,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
460
371
|
}
|
|
461
372
|
}
|
|
462
373
|
renderer?.writeInfo(` Auto-loaded ${loadedSkills.filter(s => newSkillNames.includes(s.name)).length} skill(s): ${newSkillNames.join(', ')}`);
|
|
463
|
-
messages[0] = { role: 'system', content: buildSystemPrompt(loadedSkills, options?.mode, pluginSections) };
|
|
374
|
+
messages[0] = { role: 'system', content: buildSystemPrompt(loadedSkills, options?.mode, pluginSections, options?.flags) };
|
|
464
375
|
// Не удаляем ответ ассистента — skills загружаются для следующих шагов цикла.
|
|
465
376
|
// Модель увидит обновлённый system prompt на следующей итерации и сможет
|
|
466
377
|
// использовать загруженные скилы без retry (экономит ~один LLM-запрос).
|
|
@@ -509,42 +420,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
509
420
|
}
|
|
510
421
|
consecutiveEmpty = 0;
|
|
511
422
|
messages.push({ role: 'assistant', content: response });
|
|
512
|
-
|
|
513
|
-
// Извлекаем JSON из тегов <tool_call>...</tool_call> и валидируем
|
|
514
|
-
const allJsonRaw = extractAllToolCalls(response);
|
|
515
|
-
const validCalls = [];
|
|
516
|
-
const invalidCalls = [];
|
|
517
|
-
for (const jsonRaw of allJsonRaw) {
|
|
518
|
-
let p = null;
|
|
519
|
-
try {
|
|
520
|
-
p = JSON.parse(jsonrepair(jsonRaw));
|
|
521
|
-
}
|
|
522
|
-
catch {
|
|
523
|
-
try {
|
|
524
|
-
p = JSON.parse(jsonRaw);
|
|
525
|
-
}
|
|
526
|
-
catch { /* continue */ }
|
|
527
|
-
}
|
|
528
|
-
if (p) {
|
|
529
|
-
const v = validateToolCall(p);
|
|
530
|
-
if (v.valid) {
|
|
531
|
-
validCalls.push({ parsed: p, cmd: v.cmd });
|
|
532
|
-
}
|
|
533
|
-
else if (v.error) {
|
|
534
|
-
invalidCalls.push(v.error);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// ─── Fallback: regex-извлечение при невалидном JSON ───
|
|
539
|
-
if (validCalls.length === 0) {
|
|
540
|
-
const fallback = extractToolCallFallback(response);
|
|
541
|
-
if (fallback) {
|
|
542
|
-
validCalls.push({ parsed: { name: 'bash', arguments: { cmd: fallback.cmd } }, cmd: fallback.cmd });
|
|
543
|
-
if (!quiet) {
|
|
544
|
-
renderer?.writeBracketTag('fallback', 'regex extracted command');
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
423
|
+
const { validCalls, invalidCalls } = parseToolCalls(response);
|
|
548
424
|
if (validCalls.length === 0) {
|
|
549
425
|
if (invalidCalls.length > 0) {
|
|
550
426
|
// If the response has substantial text outside tool_call tags,
|
|
@@ -764,81 +640,9 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
764
640
|
// ─── Enforce: лимиты на запись файлов ────────────────
|
|
765
641
|
// Отслеживание повторных чтений одного файла для консолидации
|
|
766
642
|
let lastReadFile = null;
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if (hasMultiStepPlan) {
|
|
771
|
-
const directWrites = validCalls.filter(c => c.parsed.name === 'write_file' || c.parsed.name === 'edit_file');
|
|
772
|
-
const hasSubagent = validCalls.some(c => c.parsed.name === 'subagent');
|
|
773
|
-
const hasBash = validCalls.some(c => c.parsed.name === 'bash');
|
|
774
|
-
if (directWrites.length > 0 && !hasSubagent) {
|
|
775
|
-
// Block direct writes — model must use subagent
|
|
776
|
-
for (const wc of directWrites) {
|
|
777
|
-
const idx = validCalls.indexOf(wc);
|
|
778
|
-
if (idx !== -1)
|
|
779
|
-
validCalls.splice(idx, 1);
|
|
780
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
781
|
-
messages.push({
|
|
782
|
-
role: 'tool',
|
|
783
|
-
content: `BLOCKED: Multi-step plan active. Use subagent for each step. Example: subagent({task:"Create file X", existingFiles:"Y.ts"}). After subagent completes, mark step done: plan({action:"done", step:N})`,
|
|
784
|
-
tool_call_id: blockId,
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
if (!quiet) {
|
|
788
|
-
renderer?.writeWarning(` [blocked ${directWrites.length} direct writes — use subagent for multi-step plans]`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
// Allow bash for verification (tsc --noEmit, npm run build) — don't block these
|
|
792
|
-
}
|
|
793
|
-
// ESCALATION: block write_file when creating N+ files without subagent (no plan case)
|
|
794
|
-
if (directWriteStreakMax < Infinity && !hasMultiStepPlan) {
|
|
795
|
-
const hasWrite = validCalls.some(c => c.parsed.name === 'write_file');
|
|
796
|
-
const hasSubagentNow = validCalls.some(c => c.parsed.name === 'subagent');
|
|
797
|
-
if (hasSubagentNow) {
|
|
798
|
-
directWriteStreak = 0;
|
|
799
|
-
}
|
|
800
|
-
else if (hasWrite) {
|
|
801
|
-
const writeCount = validCalls.filter(c => c.parsed.name === 'write_file').length;
|
|
802
|
-
directWriteStreak += writeCount;
|
|
803
|
-
if (directWriteStreak >= directWriteStreakMax) {
|
|
804
|
-
const directWrites = validCalls.filter(c => c.parsed.name === 'write_file');
|
|
805
|
-
for (const wc of directWrites) {
|
|
806
|
-
const idx = validCalls.indexOf(wc);
|
|
807
|
-
if (idx !== -1)
|
|
808
|
-
validCalls.splice(idx, 1);
|
|
809
|
-
}
|
|
810
|
-
if (directWrites.length > 0) {
|
|
811
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
812
|
-
messages.push({
|
|
813
|
-
role: 'tool',
|
|
814
|
-
content: `BLOCKED: You're creating file #${directWriteStreak} without subagent. For 3+ new files, use subagent for each one. Example: subagent("Create file X. Context: Y already exists.")`,
|
|
815
|
-
tool_call_id: blockId,
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
if (!quiet) {
|
|
819
|
-
renderer?.writeWarning(` [blocked ${directWrites.length} writes: ${directWriteStreak} files without subagent — use subagent for multi-file projects]`);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
// Блокировка нескольких записей файлов за один ответ — максимум одна
|
|
825
|
-
const writeCalls = validCalls.filter(c => c.parsed.name === 'write_file' || c.parsed.name === 'edit_file');
|
|
826
|
-
if (writeCalls.length > 1) {
|
|
827
|
-
// Keep only the first write call, block the rest
|
|
828
|
-
const blocked = writeCalls.slice(1);
|
|
829
|
-
validCalls.splice(validCalls.indexOf(blocked[0]), blocked.length);
|
|
830
|
-
for (const bc of blocked) {
|
|
831
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
832
|
-
messages.push({
|
|
833
|
-
role: 'tool',
|
|
834
|
-
content: `BLOCKED: Only one file write per response is allowed. You wrote ${writeCalls.length} files at once. Complete the first file, then write the next one.`,
|
|
835
|
-
tool_call_id: blockId,
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
if (!quiet) {
|
|
839
|
-
renderer?.writeWarning(` [blocked ${blocked.length} extra file writes — one at a time]`);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
643
|
+
writeEnforcer.enforcePlan(validCalls, messages, planEnforcement, quiet, renderer);
|
|
644
|
+
writeEnforcer.enforceWriteStreak(validCalls, messages, directWriteStreakMax, quiet, renderer);
|
|
645
|
+
writeEnforcer.enforceSingleWrite(validCalls, messages, quiet, renderer);
|
|
842
646
|
// ─── Исполнение tool calls ──────────────────────────
|
|
843
647
|
for (const call of validCalls) {
|
|
844
648
|
const { parsed, cmd } = call;
|
|
@@ -846,7 +650,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
846
650
|
const preview = isBash
|
|
847
651
|
? (cmd.length > 200 ? cmd.slice(0, 200) + '...' : cmd)
|
|
848
652
|
: JSON.stringify(parsed.arguments);
|
|
849
|
-
const toolId =
|
|
653
|
+
const toolId = generateToolCallId();
|
|
850
654
|
logToolCall(parsed.name, preview);
|
|
851
655
|
if (!quiet) {
|
|
852
656
|
renderer?.writeToolHeader(parsed.name);
|
|
@@ -1051,7 +855,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
1051
855
|
? cmd.match(/-Path\s+['"]?([^'"]+\.(?:ts|tsx|js|jsx|json))['"]?/i)?.[1]?.replace(/\\/g, '/')
|
|
1052
856
|
: String(parsed.arguments.path ?? '');
|
|
1053
857
|
if (rawPath && /\.(ts|tsx|js|jsx|json)$/.test(rawPath)) {
|
|
1054
|
-
const { isAbsolute, relative } = await import('node:path');
|
|
1055
858
|
const filePath = isAbsolute(rawPath) ? relative(process.cwd(), rawPath).replace(/\\/g, '/') : rawPath;
|
|
1056
859
|
const syntaxResult = checkFileSyntax(filePath, process.cwd());
|
|
1057
860
|
if (syntaxResult !== 'OK') {
|
|
@@ -1120,72 +923,17 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
|
|
|
1120
923
|
}
|
|
1121
924
|
messages.push({
|
|
1122
925
|
role: 'tool',
|
|
1123
|
-
content:
|
|
926
|
+
content: truncateOutput(parsed.name, toolOutput, toolExitCode),
|
|
1124
927
|
tool_call_id: toolId,
|
|
1125
928
|
});
|
|
1126
929
|
// ─── Error escalation + specialist auto-delegation ──────────
|
|
1127
930
|
if (toolExitCode !== 0 && toolOutput) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
consecutiveToolErrors.set(errKey, prevCount + 1);
|
|
1132
|
-
const errorCount = prevCount + 1;
|
|
1133
|
-
// ── Specialist: авто-делегация при повторяющихся ошибках ──
|
|
1134
|
-
const specialistCfg = getSpecialistConfig(config);
|
|
1135
|
-
if (specialistCfg.enabled &&
|
|
1136
|
-
errorCount === specialistCfg.triggerThreshold &&
|
|
1137
|
-
specialistSpawnedFor !== errKey &&
|
|
1138
|
-
!isTrivialError(parsed.name, toolOutput) &&
|
|
1139
|
-
toolOutput.length >= specialistCfg.minErrorLength) {
|
|
1140
|
-
specialistSpawnedFor = errKey;
|
|
1141
|
-
const spawned = await spawnSpecialist({
|
|
1142
|
-
provider, registry, toolExecutor, modeConfig, llmCfg, specialistCfg,
|
|
1143
|
-
quiet, renderer, messages, toolId, createdFiles,
|
|
1144
|
-
toolName: parsed.name, toolArgs: parsed.arguments,
|
|
1145
|
-
errorOutput: toolOutput, errorCount, enrichedPrompt,
|
|
1146
|
-
});
|
|
1147
|
-
if (spawned)
|
|
1148
|
-
continue;
|
|
1149
|
-
}
|
|
1150
|
-
// ── Escalation: hint / strategy switch / block ──
|
|
1151
|
-
if (errorCount > specialistCfg.triggerThreshold) {
|
|
1152
|
-
// Веб-специфичные подсказки
|
|
1153
|
-
let webHint = '';
|
|
1154
|
-
if (parsed.name === 'web_browse' && String(parsed.arguments?.action) === 'evaluate') {
|
|
1155
|
-
webHint = ' For web scraping: use web_browse(action:"discover") to find real CSS selectors, or web_browse(action:"get_links") to see all links, or web_browse(action:"extract", format:"html") to inspect the page structure.';
|
|
1156
|
-
}
|
|
1157
|
-
else if (parsed.name === 'web_browse' && String(parsed.arguments?.action) === 'navigate' && toolOutput.includes('Status: 40')) {
|
|
1158
|
-
webHint = ' Page returned HTTP error. Try navigating to the site homepage first, then use web_browse(action:"get_links") or web_browse(action:"discover") to explore available categories and sections instead of guessing URLs.';
|
|
1159
|
-
}
|
|
1160
|
-
const msg = errorCount === specialistCfg.triggerThreshold + 1
|
|
1161
|
-
? `[System: Tool "${parsed.name}" failed with the SAME error ${errorCount} times. Read the error message carefully. The input is likely wrong — try a completely different approach, or use subagent for a fresh context.]${webHint}`
|
|
1162
|
-
: `[System: Tool "${parsed.name}" failed ${errorCount} times with the same error. STOP. Do NOT retry the same call. Use subagent(task="...") for a fresh context, or tell the user you're stuck.]${webHint}`;
|
|
1163
|
-
messages.push({ role: 'user', content: msg });
|
|
1164
|
-
if (!quiet)
|
|
1165
|
-
renderer?.writeWarning(` [error escalation: ${parsed.name} failed ${errorCount} times]`);
|
|
1166
|
-
}
|
|
1167
|
-
if (totalToolErrors >= 5 && errorStrategy === 'normal') {
|
|
1168
|
-
errorStrategy = 'subagent-hint';
|
|
1169
|
-
messages.push({ role: 'user', content: `[System: ${totalToolErrors} tool errors. Use subagent(task="...") for a fresh context.]` });
|
|
1170
|
-
if (!quiet)
|
|
1171
|
-
renderer?.writeWarning(` [strategy: subagent-hint after ${totalToolErrors} errors]`);
|
|
1172
|
-
}
|
|
1173
|
-
if (errorCount >= 4) {
|
|
1174
|
-
const blockId = `call-${Date.now()}-${step}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1175
|
-
messages.push({ role: 'tool', content: `BLOCKED: Tool "${parsed.name}" failed ${errorCount} times. Temporarily blocked.`, tool_call_id: blockId });
|
|
1176
|
-
const idx = validCalls.indexOf(call);
|
|
1177
|
-
if (idx !== -1)
|
|
1178
|
-
validCalls.splice(idx, 1);
|
|
1179
|
-
if (!quiet)
|
|
1180
|
-
renderer?.writeWarning(` [blocked ${parsed.name}: too many repeated errors]`);
|
|
1181
|
-
}
|
|
931
|
+
const action = await errorEscalator.handleToolError(parsed.name, toolOutput, parsed.arguments, toolId, messages, createdFiles, enrichedPrompt, renderer, quiet, provider, registry, toolExecutor, modeConfig, llmCfg);
|
|
932
|
+
if (action === 'continue')
|
|
933
|
+
continue;
|
|
1182
934
|
}
|
|
1183
935
|
else {
|
|
1184
|
-
|
|
1185
|
-
if (key.startsWith(parsed.name + '::'))
|
|
1186
|
-
consecutiveToolErrors.delete(key);
|
|
1187
|
-
}
|
|
1188
|
-
specialistSpawnedFor = null;
|
|
936
|
+
errorEscalator.resetForTool(parsed.name);
|
|
1189
937
|
}
|
|
1190
938
|
// НЕ добавляем "Continue." после каждого tool-вызова — это нарушает
|
|
1191
939
|
// структуру диалога. Bounded nudge (строка 418) уже покрывает
|