micro-models-agent 0.4.0 → 0.6.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/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 +6 -6
- package/dist/agent/error-escalation.d.ts.map +1 -1
- package/dist/agent/error-escalation.js +67 -34
- package/dist/agent/error-escalation.js.map +1 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +14 -79
- 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 +3 -7
- package/dist/agent/write-enforcer.d.ts.map +1 -1
- package/dist/agent/write-enforcer.js +7 -71
- 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/logger.d.ts +10 -3
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +89 -38
- package/dist/logger.js.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 +19 -0
- package/dist/subagent/chat.d.ts.map +1 -0
- package/dist/subagent/chat.js +279 -0
- package/dist/subagent/chat.js.map +1 -0
- package/dist/subagent/context.d.ts +9 -0
- package/dist/subagent/context.d.ts.map +1 -0
- package/dist/subagent/context.js +75 -0
- package/dist/subagent/context.js.map +1 -0
- package/dist/subagent/definition.d.ts +7 -0
- package/dist/subagent/definition.d.ts.map +1 -0
- package/dist/subagent/definition.js +26 -0
- package/dist/subagent/definition.js.map +1 -0
- package/dist/subagent/errors.d.ts +24 -0
- package/dist/subagent/errors.d.ts.map +1 -0
- package/dist/subagent/errors.js +85 -0
- package/dist/subagent/errors.js.map +1 -0
- package/dist/subagent/validation.d.ts +17 -0
- package/dist/subagent/validation.d.ts.map +1 -0
- package/dist/subagent/validation.js +83 -0
- package/dist/subagent/validation.js.map +1 -0
- 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 +4 -57
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +112 -556
- 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/tools/subagent.js
CHANGED
|
@@ -1,466 +1,90 @@
|
|
|
1
|
-
import { generateSubagentToolCallId } from
|
|
2
|
-
import { parseToolCalls } from
|
|
3
|
-
import { LMStudioNative } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { getLLMConfig } from '../config/config.js';
|
|
10
|
-
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
11
|
-
import { join } from 'node:path';
|
|
1
|
+
import { generateSubagentToolCallId } from "./id-gen.js";
|
|
2
|
+
import { parseToolCalls } from "./parse-tool-call.js";
|
|
3
|
+
import { LMStudioNative } from "../llm/lmstudio-native.js";
|
|
4
|
+
import { ContextManager } from "../agent/context.js";
|
|
5
|
+
import { buildSystemPrompt } from "../agent/system-prompt.js";
|
|
6
|
+
import { checkFileSyntax } from "./syntax-check.js";
|
|
7
|
+
import { logInfo, logDebug, logWarn, logToolCall, logToolOutput, } from "../logger.js";
|
|
8
|
+
import { getLLMConfig } from "../config/config.js";
|
|
12
9
|
// ─── Context builder ──────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
* Собирает контекст для субагента: список файлов, зависимости, tsconfig.
|
|
15
|
-
* Сканирует все директории проекта (не только src/).
|
|
16
|
-
*
|
|
17
|
-
* @param cwd - корневая директория проекта
|
|
18
|
-
* @returns Текстовый контекст для system prompt субагента
|
|
19
|
-
*/
|
|
20
|
-
export function buildSubagentContext(cwd) {
|
|
21
|
-
const lines = [];
|
|
22
|
-
// Scan ALL directories for existing source files (not just src/)
|
|
23
|
-
try {
|
|
24
|
-
const entries = readdirSync(cwd, { withFileTypes: true });
|
|
25
|
-
for (const entry of entries) {
|
|
26
|
-
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
27
|
-
continue;
|
|
28
|
-
const dirPath = join(cwd, entry.name);
|
|
29
|
-
try {
|
|
30
|
-
const files = readdirSync(dirPath, { recursive: true })
|
|
31
|
-
.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f))
|
|
32
|
-
.slice(0, 20);
|
|
33
|
-
if (files.length > 0) {
|
|
34
|
-
lines.push(`Files in ${entry.name}/: ${files.join(', ')}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch { /* skip unreadable dirs */ }
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch { /* no entries */ }
|
|
41
|
-
// Include root-level .ts/.js files too
|
|
42
|
-
try {
|
|
43
|
-
const rootFiles = readdirSync(cwd)
|
|
44
|
-
.filter((f) => /\.(ts|js)$/.test(f) && !f.startsWith('.'));
|
|
45
|
-
if (rootFiles.length > 0) {
|
|
46
|
-
lines.push('Root files: ' + rootFiles.join(', '));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch { /* skip */ }
|
|
50
|
-
try {
|
|
51
|
-
const pkgPath = join(cwd, 'package.json');
|
|
52
|
-
if (existsSync(pkgPath)) {
|
|
53
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
54
|
-
const deps = Object.keys(pkg.dependencies || {});
|
|
55
|
-
const devDeps = Object.keys(pkg.devDependencies || {});
|
|
56
|
-
if (deps.length > 0)
|
|
57
|
-
lines.push('Dependencies: ' + deps.join(', '));
|
|
58
|
-
if (devDeps.length > 0)
|
|
59
|
-
lines.push('DevDependencies: ' + devDeps.join(', '));
|
|
60
|
-
lines.push('Module type: ' + (pkg.type || 'commonjs'));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch { /* no package.json */ }
|
|
64
|
-
try {
|
|
65
|
-
const tsconfigPath = join(cwd, 'tsconfig.json');
|
|
66
|
-
if (existsSync(tsconfigPath)) {
|
|
67
|
-
lines.push('Has tsconfig.json');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch { /* no tsconfig */ }
|
|
71
|
-
return lines.join('\n');
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Контекст для specialist'а — фокусированное описание ошибки.
|
|
75
|
-
*
|
|
76
|
-
* @param toolName - имя упавшего инструмента
|
|
77
|
-
* @param toolArgs - аргументы которые были переданы
|
|
78
|
-
* @param errorOutput - текст ошибки
|
|
79
|
-
* @param userTask - исходный запрос пользователя (краткое описание)
|
|
80
|
-
* @param attemptCount - сколько раз ошибка повторилась
|
|
81
|
-
* @returns Текст задачи для specialist'а
|
|
82
|
-
*/
|
|
83
|
-
export function buildSpecialistTask(toolName, toolArgs, errorOutput, userTask, attemptCount) {
|
|
84
|
-
const argsStr = JSON.stringify(toolArgs, null, 2);
|
|
85
|
-
// Специфичные подсказки для web_browse evaluate
|
|
86
|
-
let webHints = '';
|
|
87
|
-
if (toolName === 'web_browse' && String(toolArgs?.action) === 'evaluate') {
|
|
88
|
-
const code = String(toolArgs?.value ?? toolArgs?.code ?? '');
|
|
89
|
-
webHints = [
|
|
90
|
-
'',
|
|
91
|
-
'web_browse evaluate: "value" is JS code executed in browser context.',
|
|
92
|
-
'- Expressions work: document.title, document.querySelectorAll(".item").length',
|
|
93
|
-
'- return statements work: return document.title',
|
|
94
|
-
'- Multi-line with return: return Array.from(document.querySelectorAll("a")).map(a => a.href)',
|
|
95
|
-
`- Current code that failed: ${code.slice(0, 300)}`,
|
|
96
|
-
].join('\n');
|
|
97
|
-
}
|
|
98
|
-
return [
|
|
99
|
-
`The tool "${toolName}" has failed ${attemptCount} times with the same error while working on: "${userTask.slice(0, 200)}"`,
|
|
100
|
-
'',
|
|
101
|
-
`Failed tool call:`,
|
|
102
|
-
` Tool: ${toolName}`,
|
|
103
|
-
` Arguments: ${argsStr}`,
|
|
104
|
-
'',
|
|
105
|
-
`Error output:`,
|
|
106
|
-
` ${errorOutput.slice(0, 500)}`,
|
|
107
|
-
webHints,
|
|
108
|
-
'',
|
|
109
|
-
`Your task: Diagnose WHY this tool call fails and FIX the underlying problem.`,
|
|
110
|
-
` - Read the relevant files to understand the actual state`,
|
|
111
|
-
` - Fix the root cause (wrong path, missing file, syntax error, etc.)`,
|
|
112
|
-
` - If the tool call arguments are wrong, correct them`,
|
|
113
|
-
` - If the file doesn't exist, create it`,
|
|
114
|
-
` - Return a clear summary of what you found and what you fixed`,
|
|
115
|
-
].join('\n');
|
|
116
|
-
}
|
|
117
|
-
/** Паттерны ошибок которые НЕ триггерят specialist (trivial) */
|
|
118
|
-
const TRIVIAL_ERROR_PATTERNS = [
|
|
119
|
-
/no results? found/i,
|
|
120
|
-
/pattern .* matched 0 results/i,
|
|
121
|
-
/0 files? matched/i,
|
|
122
|
-
/command not found.*grep/i,
|
|
123
|
-
/not a directory/i,
|
|
124
|
-
];
|
|
125
|
-
/** Паттерны ошибок которые ВСЕГДА требуют specialist (не trivial) */
|
|
126
|
-
const NON_TRIVIAL_ERROR_PATTERNS = [
|
|
127
|
-
/unexpected token/i,
|
|
128
|
-
/syntaxerror/i,
|
|
129
|
-
/cannot find module/i,
|
|
130
|
-
/module not found/i,
|
|
131
|
-
/permission denied/i,
|
|
132
|
-
/enoent/i,
|
|
133
|
-
/eacces/i,
|
|
134
|
-
];
|
|
135
|
-
/**
|
|
136
|
-
* Проверяет является ли ошибка тривиальной (не требует specialist).
|
|
137
|
-
*/
|
|
138
|
-
export function isTrivialError(toolName, errorOutput) {
|
|
139
|
-
// Синтаксические и критические ошибки — ВСЕГДА не trivial
|
|
140
|
-
if (NON_TRIVIAL_ERROR_PATTERNS.some(p => p.test(errorOutput))) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
// grep_search/glob_search с 0 результатами — нормальное поведение
|
|
144
|
-
if (toolName === 'grep_search' || toolName === 'glob_search') {
|
|
145
|
-
if (/0 results|0 files|no match/i.test(errorOutput) && errorOutput.length < 200) {
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// Любая ошибка < 10 символов — скорее всего trivia
|
|
150
|
-
if (errorOutput.length < 10)
|
|
151
|
-
return true;
|
|
152
|
-
return TRIVIAL_ERROR_PATTERNS.some(p => p.test(errorOutput));
|
|
153
|
-
}
|
|
10
|
+
import { buildSubagentContext } from "../subagent/context.js";
|
|
154
11
|
// ─── Tool call parsing ───────────────────────────────────
|
|
12
|
+
// validateToolCall moved to src/subagent/validation.ts
|
|
13
|
+
import { doChat } from "../subagent/chat.js";
|
|
14
|
+
// ─── Tool execution helper ────────────────────────────────
|
|
155
15
|
/**
|
|
156
|
-
*
|
|
16
|
+
* Выполняет один tool call внутри цикла субагента.
|
|
17
|
+
* Обрабатывает bash и не-bash инструменты, проверяет синтаксис созданных файлов.
|
|
157
18
|
*
|
|
158
|
-
* @param
|
|
159
|
-
* @
|
|
19
|
+
* @param call - распарсенный tool call
|
|
20
|
+
* @param toolExecutor - исполнитель инструментов
|
|
21
|
+
* @param cwd - рабочая директория
|
|
22
|
+
* @param createdFiles - массив для отслеживания созданных файлов
|
|
23
|
+
* @param errors - массив для сбора ошибок
|
|
24
|
+
* @param step - номер шага в цикле
|
|
160
25
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
glob_search: ['pattern'],
|
|
190
|
-
grep_search: ['pattern'],
|
|
191
|
-
tree: [],
|
|
192
|
-
web_search: ['query'],
|
|
193
|
-
web_fetch: ['url'],
|
|
194
|
-
user_profile: [],
|
|
195
|
-
question: ['question'],
|
|
26
|
+
async function executeToolCall(call, toolExecutor, cwd, createdFiles, errors, step) {
|
|
27
|
+
const { parsed, cmd } = call;
|
|
28
|
+
const isBash = parsed.name === "bash";
|
|
29
|
+
const toolId = generateSubagentToolCallId();
|
|
30
|
+
if (parsed.name === "subagent") {
|
|
31
|
+
logWarn("SUBAGENT", `nested subagent blocked at step ${step}`);
|
|
32
|
+
return {
|
|
33
|
+
output: "Subagents cannot spawn subagents (max depth 1)",
|
|
34
|
+
exitCode: 1,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const preview = isBash
|
|
38
|
+
? String(cmd).slice(0, 80)
|
|
39
|
+
: parsed.name === "write_file" || parsed.name === "edit_file"
|
|
40
|
+
? String(parsed.arguments.path ?? "").slice(-60)
|
|
41
|
+
: JSON.stringify(parsed.arguments).slice(0, 80);
|
|
42
|
+
logToolCall(`sub:${parsed.name}`, preview);
|
|
43
|
+
const hookCmd = isBash
|
|
44
|
+
? String(cmd)
|
|
45
|
+
: `${parsed.name} ${JSON.stringify(parsed.arguments)}`;
|
|
46
|
+
const execResult = toolExecutor
|
|
47
|
+
? await toolExecutor.execute({ id: toolId, name: parsed.name, arguments: parsed.arguments }, { cwd, quiet: false }, hookCmd)
|
|
48
|
+
: null;
|
|
49
|
+
const result = execResult?.result ?? {
|
|
50
|
+
stdout: "",
|
|
51
|
+
stderr: "ToolExecutor not available",
|
|
52
|
+
exitCode: 1,
|
|
53
|
+
truncated: false,
|
|
196
54
|
};
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let started = false;
|
|
227
|
-
let reasoningBuf = false;
|
|
228
|
-
let currentReasoning = ''; // накопленный reasoning текст для этого attempt
|
|
229
|
-
let lastFinishReason = null;
|
|
230
|
-
let reasoningChunksCount = 0;
|
|
231
|
-
let contentChunksCount = 0;
|
|
232
|
-
if (signal?.aborted)
|
|
233
|
-
return { response: '', genTimeMs: 0 };
|
|
234
|
-
const lastMsg = messages[messages.length - 1];
|
|
235
|
-
logLLMRequest(provider.model, messages.length, lastMsg?.content || '', caller);
|
|
236
|
-
if (caller === 'main') {
|
|
237
|
-
renderer.setStreamRole?.('assistant');
|
|
238
|
-
}
|
|
239
|
-
renderer.writeSpinner();
|
|
240
|
-
try {
|
|
241
|
-
let buf = '';
|
|
242
|
-
for await (const chunk of provider.chat(messages, { temperature: 0.7, signal, showThinking, enableThinking: true, max_tokens: maxTokens })) {
|
|
243
|
-
if (!chunk.content && !chunk.reasoning && !chunk.finish_reason)
|
|
244
|
-
continue;
|
|
245
|
-
if (chunk.finish_reason) {
|
|
246
|
-
lastFinishReason = chunk.finish_reason;
|
|
247
|
-
}
|
|
248
|
-
if (chunk.content) {
|
|
249
|
-
fullText.push(chunk.content);
|
|
250
|
-
contentChunksCount++;
|
|
251
|
-
}
|
|
252
|
-
if (chunk.reasoning) {
|
|
253
|
-
reasoningChunksCount++;
|
|
254
|
-
currentReasoning += chunk.reasoning;
|
|
255
|
-
}
|
|
256
|
-
if (!chunk.content && !chunk.reasoning)
|
|
257
|
-
continue;
|
|
258
|
-
if (!started) {
|
|
259
|
-
renderer.clearSpinner();
|
|
260
|
-
started = true;
|
|
261
|
-
}
|
|
262
|
-
if (chunk.reasoning) {
|
|
263
|
-
if (showThinking) {
|
|
264
|
-
if (reasoningBuf === false) {
|
|
265
|
-
renderer.writeNewline();
|
|
266
|
-
renderer.writeDim('┈ reasoning ┈');
|
|
267
|
-
reasoningBuf = '';
|
|
268
|
-
}
|
|
269
|
-
reasoningBuf += chunk.reasoning;
|
|
270
|
-
}
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
if (reasoningBuf !== false && typeof reasoningBuf === 'string') {
|
|
274
|
-
renderer.writeNewline();
|
|
275
|
-
renderer.writeDim(reasoningBuf);
|
|
276
|
-
reasoningBuf = false;
|
|
277
|
-
}
|
|
278
|
-
buf += chunk.content;
|
|
279
|
-
while (buf.length > 0) {
|
|
280
|
-
const tcStart = buf.indexOf('<tool_call>');
|
|
281
|
-
const tcEnd = buf.indexOf('</tool_call>');
|
|
282
|
-
if (tcStart === -1 && tcEnd === -1) {
|
|
283
|
-
const clean = buf.replace(/@grounding\[файл:\s*[^\]]*\]\n?/g, '');
|
|
284
|
-
if (clean)
|
|
285
|
-
renderer.writeMarkdownChunk(clean);
|
|
286
|
-
buf = '';
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
if (tcStart !== -1 && (tcEnd === -1 || tcStart < tcEnd)) {
|
|
290
|
-
if (tcStart > 0) {
|
|
291
|
-
const clean = buf.slice(0, tcStart).replace(/@grounding\[файл:\s*[^\]]*\]\n?/g, '');
|
|
292
|
-
if (clean)
|
|
293
|
-
renderer.writeMarkdownChunk(clean);
|
|
294
|
-
}
|
|
295
|
-
buf = buf.slice(tcStart);
|
|
296
|
-
const closeIdx = buf.indexOf('</tool_call>');
|
|
297
|
-
if (closeIdx !== -1) {
|
|
298
|
-
buf = buf.slice(closeIdx + '</tool_call>'.length);
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
if (tcEnd !== -1 && (tcStart === -1 || tcEnd < tcStart)) {
|
|
303
|
-
const clean = buf.slice(0, tcEnd).replace(/@grounding\[файл:\s*[^\]]*\]\n?/g, '');
|
|
304
|
-
if (clean)
|
|
305
|
-
renderer.writeMarkdownChunk(clean);
|
|
306
|
-
buf = buf.slice(tcEnd + '</tool_call>'.length);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
if (reasoningBuf !== false && typeof reasoningBuf === 'string') {
|
|
311
|
-
renderer.writeNewline();
|
|
312
|
-
renderer.writeDim(reasoningBuf);
|
|
313
|
-
}
|
|
314
|
-
// Сохраняем reasoning для fallback
|
|
315
|
-
if (currentReasoning) {
|
|
316
|
-
lastReasoningText = currentReasoning;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch (e) {
|
|
320
|
-
if (e.name === 'AbortError') {
|
|
321
|
-
logLLMResponse(provider.model, 0, performance.now() - startTime, 'Aborted', caller);
|
|
322
|
-
return { response: '', genTimeMs: 0 };
|
|
323
|
-
}
|
|
324
|
-
logLLMResponse(provider.model, 0, performance.now() - startTime, e.message, caller);
|
|
325
|
-
renderer.writeError(`\n[LLM] ${e.message}`);
|
|
326
|
-
// Сетевые ошибки (fetch failed, ECONNREFUSED и т.д.) — retry с задержкой
|
|
327
|
-
const isNetworkError = !e.message?.includes('API ') && !e.message?.includes('status');
|
|
328
|
-
if (isNetworkError && attempt < retries) {
|
|
329
|
-
const delay = Math.min(2000 * (attempt + 1), 10000);
|
|
330
|
-
renderer.writeWarning(`[LLM] Network error, retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${retries})`);
|
|
331
|
-
await new Promise(r => setTimeout(r, delay));
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
const providerHint = provider instanceof LMStudioNative
|
|
335
|
-
? 'Check that LM Studio is running on localhost:1234'
|
|
336
|
-
: provider instanceof OpenAICompat
|
|
337
|
-
? 'Check that your API server is running and the API key is correct'
|
|
338
|
-
: 'Check that your LLM server is running';
|
|
339
|
-
renderer.writeWarning(`[LLM] ${providerHint}`);
|
|
340
|
-
return { response: '', genTimeMs: 0 };
|
|
341
|
-
}
|
|
342
|
-
const genTimeMs = performance.now() - startTime;
|
|
343
|
-
const response = fullText.join('');
|
|
344
|
-
renderer.commitMarkdown();
|
|
345
|
-
if (response.trim()) {
|
|
346
|
-
logLLMResponse(provider.model, response.length, genTimeMs, undefined, caller);
|
|
347
|
-
return { response, genTimeMs };
|
|
348
|
-
}
|
|
349
|
-
// Диагностика пустого ответа
|
|
350
|
-
const reason = lastFinishReason ?? 'unknown';
|
|
351
|
-
logWarn('DOCHAT', `empty response #${attempt + 1}: finish_reason=${reason}, reasoning_chunks=${reasoningChunksCount}, content_chunks=${contentChunksCount}`);
|
|
352
|
-
if (lastFinishReason === 'length') {
|
|
353
|
-
renderer.writeWarning(` [empty response: model hit max_tokens during reasoning (${reasoningChunksCount} reasoning chunks, 0 content chunks)]`);
|
|
354
|
-
}
|
|
355
|
-
else if (reasoningChunksCount > 0 && contentChunksCount === 0) {
|
|
356
|
-
renderer.writeWarning(` [empty response: model produced ${reasoningChunksCount} reasoning chunks but no content]`);
|
|
357
|
-
}
|
|
358
|
-
if (attempt < retries) {
|
|
359
|
-
renderer.writeDim(`[retry ${attempt + 1}/${retries}]`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const llmCfg = getLLMConfig();
|
|
363
|
-
// Phase 2: Retry WITHOUT thinking — модель генерирует content напрямую
|
|
364
|
-
if (llmCfg.retryWithoutThinking && lastReasoningText.trim()) {
|
|
365
|
-
logInfo('DOCHAT', `retrying without thinking (had ${lastReasoningText.length} reasoning chars)`);
|
|
366
|
-
renderer.writeDim(`[retry without thinking]`);
|
|
367
|
-
const retryResult = await doChatOnce(provider, messages, renderer, signal, false, showThinking, caller, maxTokens);
|
|
368
|
-
if (retryResult.response.trim()) {
|
|
369
|
-
logInfo('DOCHAT', `retry without thinking succeeded: ${retryResult.response.length} chars`);
|
|
370
|
-
return retryResult;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
// Phase 2.5: Emergency — принудительно генерируем content с низкой температурой
|
|
374
|
-
// Когда reasoning есть, но content пустой — модель "застряла" в thinking loop
|
|
375
|
-
if (lastReasoningText.trim()) {
|
|
376
|
-
logInfo('DOCHAT', `emergency: forcing content with low temperature`);
|
|
377
|
-
renderer.writeDim(`[emergency: forcing content with temperature=0.1]`);
|
|
378
|
-
const emergencyMessages = [
|
|
379
|
-
...messages,
|
|
380
|
-
{ role: 'user', content: '[System] You MUST produce a response now. Do NOT use thinking tags. Reply with a tool call or a direct answer. Any response is better than empty.' },
|
|
381
|
-
];
|
|
382
|
-
const emergencyResult = await doChatOnce(provider, emergencyMessages, renderer, signal, false, showThinking, caller, maxTokens, 0.1);
|
|
383
|
-
if (emergencyResult.response.trim()) {
|
|
384
|
-
logInfo('DOCHAT', `emergency succeeded: ${emergencyResult.response.length} chars`);
|
|
385
|
-
return emergencyResult;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// Phase 3: Fallback — используем reasoning как content
|
|
389
|
-
if (llmCfg.fallbackToReasoning && lastReasoningText.trim()) {
|
|
390
|
-
logInfo('DOCHAT', `fallback: using reasoning as content (${lastReasoningText.length} chars)`);
|
|
391
|
-
renderer.writeWarning(` [fallback: using reasoning as content]`);
|
|
392
|
-
return { response: lastReasoningText, genTimeMs: 0 };
|
|
393
|
-
}
|
|
394
|
-
return { response: '', genTimeMs: 0 };
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Одиночный вызов LLM без retry логики.
|
|
398
|
-
* Используется для retry с другими параметрами (например, enableThinking: false).
|
|
399
|
-
*/
|
|
400
|
-
async function doChatOnce(provider, messages, renderer, signal, enableThinking = true, showThinking = false, caller = 'main', maxTokens, temperature = 0.7) {
|
|
401
|
-
const startTime = performance.now();
|
|
402
|
-
const fullText = [];
|
|
403
|
-
let started = false;
|
|
404
|
-
if (signal?.aborted)
|
|
405
|
-
return { response: '', genTimeMs: 0 };
|
|
406
|
-
if (caller === 'main') {
|
|
407
|
-
renderer.setStreamRole?.('assistant');
|
|
408
|
-
}
|
|
409
|
-
renderer.writeSpinner();
|
|
410
|
-
try {
|
|
411
|
-
let buf = '';
|
|
412
|
-
for await (const chunk of provider.chat(messages, { temperature, signal, showThinking, enableThinking, max_tokens: maxTokens })) {
|
|
413
|
-
if (!chunk.content && !chunk.finish_reason)
|
|
414
|
-
continue;
|
|
415
|
-
if (chunk.content) {
|
|
416
|
-
fullText.push(chunk.content);
|
|
55
|
+
const output = result.stdout || result.stderr || "(no output)";
|
|
56
|
+
const exitCode = result.exitCode;
|
|
57
|
+
logToolOutput(`sub:${parsed.name}`, output, exitCode);
|
|
58
|
+
if (exitCode !== 0 &&
|
|
59
|
+
isBash &&
|
|
60
|
+
/pwsh|powershell|not recognized|not found/i.test(output)) {
|
|
61
|
+
return {
|
|
62
|
+
output: output +
|
|
63
|
+
"\n\n[HINT] Shell unavailable on this system. Use write_file tool to create files manually. Do NOT retry bash commands.",
|
|
64
|
+
exitCode,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (exitCode === 0 &&
|
|
68
|
+
(parsed.name === "write_file" || parsed.name === "edit_file")) {
|
|
69
|
+
const filePath = String(parsed.arguments.path ?? "");
|
|
70
|
+
if (filePath && !createdFiles.includes(filePath)) {
|
|
71
|
+
createdFiles.push(filePath);
|
|
72
|
+
logInfo("SUBAGENT", `created: ${filePath}`);
|
|
73
|
+
}
|
|
74
|
+
if (filePath && /\.(ts|tsx|js|jsx|json)$/.test(filePath)) {
|
|
75
|
+
const { isAbsolute, relative } = await import("node:path");
|
|
76
|
+
const relPath = isAbsolute(filePath)
|
|
77
|
+
? relative(cwd, filePath).replace(/\\/g, "/")
|
|
78
|
+
: filePath;
|
|
79
|
+
const syntaxResult = checkFileSyntax(relPath, cwd);
|
|
80
|
+
if (syntaxResult !== "OK") {
|
|
81
|
+
errors.push(`${filePath}: ${syntaxResult.split("\n")[0]}`);
|
|
82
|
+
logWarn("SUBAGENT", `syntax error in ${filePath}: ${syntaxResult.split("\n")[0]}`);
|
|
83
|
+
return { output: output + `\nSYNTAX ERROR: ${syntaxResult}`, exitCode };
|
|
417
84
|
}
|
|
418
|
-
if (!chunk.content)
|
|
419
|
-
continue;
|
|
420
|
-
if (!started) {
|
|
421
|
-
renderer.clearSpinner();
|
|
422
|
-
started = true;
|
|
423
|
-
}
|
|
424
|
-
buf += chunk.content;
|
|
425
|
-
while (buf.length > 0) {
|
|
426
|
-
const tcStart = buf.indexOf('<tool_call>');
|
|
427
|
-
const tcEnd = buf.indexOf('</tool_call>');
|
|
428
|
-
if (tcStart === -1 && tcEnd === -1) {
|
|
429
|
-
if (buf)
|
|
430
|
-
renderer.writeMarkdownChunk(buf);
|
|
431
|
-
buf = '';
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
if (tcStart !== -1 && (tcEnd === -1 || tcStart < tcEnd)) {
|
|
435
|
-
if (tcStart > 0) {
|
|
436
|
-
renderer.writeMarkdownChunk(buf.slice(0, tcStart));
|
|
437
|
-
}
|
|
438
|
-
buf = buf.slice(tcStart);
|
|
439
|
-
const closeIdx = buf.indexOf('</tool_call>');
|
|
440
|
-
if (closeIdx !== -1) {
|
|
441
|
-
buf = buf.slice(closeIdx + '</tool_call>'.length);
|
|
442
|
-
}
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
if (tcEnd !== -1 && (tcStart === -1 || tcEnd < tcStart)) {
|
|
446
|
-
renderer.writeMarkdownChunk(buf.slice(0, tcEnd));
|
|
447
|
-
buf = buf.slice(tcEnd + '</tool_call>'.length);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
catch (e) {
|
|
453
|
-
if (e.name === 'AbortError') {
|
|
454
|
-
return { response: '', genTimeMs: 0 };
|
|
455
85
|
}
|
|
456
|
-
renderer.writeError(`\n[LLM] ${e.message}`);
|
|
457
|
-
return { response: '', genTimeMs: 0 };
|
|
458
86
|
}
|
|
459
|
-
|
|
460
|
-
const response = fullText.join('');
|
|
461
|
-
renderer.commitMarkdown();
|
|
462
|
-
logLLMResponse(provider.model, response.length, genTimeMs, undefined, caller);
|
|
463
|
-
return { response, genTimeMs };
|
|
87
|
+
return { output, exitCode };
|
|
464
88
|
}
|
|
465
89
|
// ─── Isolated agent loop ──────────────────────────────────
|
|
466
90
|
/**
|
|
@@ -483,24 +107,24 @@ export async function runIsolatedLoop(provider, registry, task, existingFiles, c
|
|
|
483
107
|
let effectiveProvider = provider;
|
|
484
108
|
if (toolModel && provider instanceof LMStudioNative) {
|
|
485
109
|
effectiveProvider = new LMStudioNative(provider.baseUrl, toolModel, provider.contextWindow);
|
|
486
|
-
logInfo(
|
|
110
|
+
logInfo("SUBAGENT", `using tool model: ${toolModel} (main: ${provider.model})`);
|
|
487
111
|
}
|
|
488
|
-
logInfo(
|
|
112
|
+
logInfo("SUBAGENT", `START | task: "${task.slice(0, 200)}" | context window: ${effectiveProvider.contextWindow}`);
|
|
489
113
|
// Always build auto-context from project, even if model didn't pass existingFiles
|
|
490
114
|
const autoContext = buildSubagentContext(cwd);
|
|
491
115
|
const mergedContext = existingFiles
|
|
492
|
-
? existingFiles +
|
|
116
|
+
? existingFiles + "\n" + autoContext
|
|
493
117
|
: autoContext;
|
|
494
118
|
if (mergedContext) {
|
|
495
|
-
logDebug(
|
|
119
|
+
logDebug("SUBAGENT", `context (${mergedContext.split("\n").length} lines):\n${mergedContext}`);
|
|
496
120
|
}
|
|
497
121
|
const systemPrompt = buildSystemPrompt(undefined, (mode || "base"));
|
|
498
122
|
const contextInfo = mergedContext
|
|
499
123
|
? `\n\nКонтекст проекта:\n${mergedContext}\nПосмотри package.json для списка пакетов. Используй read_file для чтения существующих файлов.`
|
|
500
|
-
:
|
|
124
|
+
: "";
|
|
501
125
|
const messages = [
|
|
502
|
-
{ role:
|
|
503
|
-
{ role:
|
|
126
|
+
{ role: "system", content: systemPrompt },
|
|
127
|
+
{ role: "user", content: task + contextInfo },
|
|
504
128
|
];
|
|
505
129
|
const ctx = new ContextManager(effectiveProvider.contextWindow);
|
|
506
130
|
const maxSteps = maxStepsParam ?? 20;
|
|
@@ -508,127 +132,59 @@ export async function runIsolatedLoop(provider, registry, task, existingFiles, c
|
|
|
508
132
|
for (let step = 0; step < maxSteps; step++) {
|
|
509
133
|
await ctx.updateTokens(messages);
|
|
510
134
|
if (step === 0 || step % 5 === 0) {
|
|
511
|
-
logDebug(
|
|
135
|
+
logDebug("SUBAGENT", `step ${step} | ${messages.length} msgs | ${ctx.totalTokens} tokens (${ctx.usagePercent.toFixed(1)}%)`);
|
|
512
136
|
}
|
|
513
137
|
if (ctx.needsCompact && messages.length > 6) {
|
|
514
|
-
logInfo(
|
|
138
|
+
logInfo("SUBAGENT", `COMPACT at step ${step} | ${ctx.totalTokens} tokens (${ctx.usagePercent.toFixed(1)}%)`);
|
|
515
139
|
messages.splice(0, messages.length, ...ctx.compact(messages));
|
|
516
140
|
await ctx.updateTokens(messages);
|
|
517
|
-
logInfo(
|
|
141
|
+
logInfo("SUBAGENT", `after compact | ${messages.length} msgs | ${ctx.totalTokens} tokens`);
|
|
518
142
|
}
|
|
519
143
|
// После tool call увеличиваем max_tokens чтобы модель успела сгенерировать контент после reasoning
|
|
520
|
-
const hasToolResult = messages.some(m => m.role ===
|
|
144
|
+
const hasToolResult = messages.some((m) => m.role === "tool");
|
|
521
145
|
const llmCfg = getLLMConfig();
|
|
522
146
|
const maxTokens = hasToolResult ? llmCfg.maxTokensAfterTool : undefined;
|
|
523
|
-
const { response } = await doChat(effectiveProvider, messages, renderer, renderer?.abortSignal, 1, false,
|
|
147
|
+
const { response } = await doChat(effectiveProvider, messages, renderer, renderer?.abortSignal, 1, false, "subagent", maxTokens);
|
|
524
148
|
if (!response.trim()) {
|
|
525
|
-
logDebug(
|
|
149
|
+
logDebug("SUBAGENT", `empty response at step ${step}, stopping`);
|
|
526
150
|
break;
|
|
527
151
|
}
|
|
528
|
-
messages.push({ role:
|
|
152
|
+
messages.push({ role: "assistant", content: response });
|
|
529
153
|
const { validCalls } = parseToolCalls(response);
|
|
530
154
|
if (validCalls.length === 0) {
|
|
531
|
-
logDebug(
|
|
155
|
+
logDebug("SUBAGENT", `no valid tool calls at step ${step}, stopping`);
|
|
532
156
|
break;
|
|
533
157
|
}
|
|
534
158
|
for (const call of validCalls) {
|
|
535
|
-
const { parsed, cmd } = call;
|
|
536
|
-
const isBash = parsed.name === 'bash';
|
|
537
|
-
const toolId = generateSubagentToolCallId();
|
|
538
159
|
toolCallCount++;
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
else if (!isBash) {
|
|
547
|
-
const preview = parsed.name === 'write_file'
|
|
548
|
-
? String(parsed.arguments.path ?? '').slice(-60)
|
|
549
|
-
: parsed.name === 'edit_file'
|
|
550
|
-
? String(parsed.arguments.path ?? '').slice(-60)
|
|
551
|
-
: JSON.stringify(parsed.arguments).slice(0, 80);
|
|
552
|
-
logToolCall(`sub:${parsed.name}`, preview);
|
|
553
|
-
const hookCmd = isBash ? String(cmd) : `${parsed.name} ${JSON.stringify(parsed.arguments)}`;
|
|
554
|
-
const execResult = toolExecutor ? await toolExecutor.execute({ id: toolId, name: parsed.name, arguments: parsed.arguments }, { cwd, quiet: false }, hookCmd) : null;
|
|
555
|
-
const result = execResult?.result ?? { stdout: '', stderr: 'ToolExecutor not available', exitCode: 1, truncated: false };
|
|
556
|
-
toolOutput = result.stdout || result.stderr || '(no output)';
|
|
557
|
-
toolExitCode = result.exitCode;
|
|
558
|
-
if (toolExitCode !== 0) {
|
|
559
|
-
logDebug('SUBAGENT', `tool ${parsed.name} failed: exit ${toolExitCode} | ${toolOutput.slice(0, 200)}`);
|
|
560
|
-
}
|
|
561
|
-
if (toolExitCode === 0 && (parsed.name === 'write_file' || parsed.name === 'edit_file')) {
|
|
562
|
-
const filePath = String(parsed.arguments.path ?? '');
|
|
563
|
-
if (filePath && !createdFiles.includes(filePath)) {
|
|
564
|
-
createdFiles.push(filePath);
|
|
565
|
-
logInfo('SUBAGENT', `created: ${filePath}`);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (toolExitCode === 0 && (parsed.name === 'write_file' || parsed.name === 'edit_file')) {
|
|
569
|
-
const filePath = String(parsed.arguments.path ?? '');
|
|
570
|
-
if (filePath && /\.(ts|tsx|js|jsx|json)$/.test(filePath)) {
|
|
571
|
-
const { isAbsolute, relative } = await import('node:path');
|
|
572
|
-
const relPath = isAbsolute(filePath) ? relative(cwd, filePath).replace(/\\/g, '/') : filePath;
|
|
573
|
-
const syntaxResult = checkFileSyntax(relPath, cwd);
|
|
574
|
-
if (syntaxResult !== 'OK') {
|
|
575
|
-
errors.push(`${filePath}: ${syntaxResult.split('\n')[0]}`);
|
|
576
|
-
toolOutput += `\nSYNTAX ERROR: ${syntaxResult}`;
|
|
577
|
-
logWarn('SUBAGENT', `syntax error in ${filePath}: ${syntaxResult.split('\n')[0]}`);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
logToolCall('sub:bash', String(cmd).slice(0, 80));
|
|
584
|
-
const bashExecResult = toolExecutor ? await toolExecutor.execute({ id: toolId, name: 'bash', arguments: { cmd, timeout: parsed.arguments?.timeout ?? 30 } }, { cwd, quiet: false }, String(cmd)) : null;
|
|
585
|
-
const result = bashExecResult?.result ?? { stdout: '', stderr: 'ToolExecutor not available', exitCode: 1, truncated: false };
|
|
586
|
-
toolOutput = result.stdout || result.stderr || '(no output)';
|
|
587
|
-
toolExitCode = result.exitCode;
|
|
588
|
-
if (toolExitCode !== 0) {
|
|
589
|
-
logDebug('SUBAGENT', `bash failed: exit ${toolExitCode} | ${toolOutput.slice(0, 200)}`);
|
|
590
|
-
// Если bash падает из-за отсутствия shell —提示 модель использовать write_file
|
|
591
|
-
if (/pwsh|powershell|not recognized|not found/i.test(toolOutput)) {
|
|
592
|
-
toolOutput += '\n\n[HINT] Shell unavailable on this system. Use write_file tool to create files manually. Do NOT retry bash commands.';
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
messages.push({ role: 'tool', content: toolOutput.slice(0, 8000), tool_call_id: toolId });
|
|
597
|
-
messages.push({ role: 'user', content: 'Continue.' });
|
|
160
|
+
const { output, exitCode } = await executeToolCall(call, toolExecutor, cwd, createdFiles, errors, step);
|
|
161
|
+
messages.push({
|
|
162
|
+
role: "tool",
|
|
163
|
+
content: output.slice(0, 8000),
|
|
164
|
+
tool_call_id: generateSubagentToolCallId(),
|
|
165
|
+
});
|
|
166
|
+
messages.push({ role: "user", content: "Continue." });
|
|
598
167
|
}
|
|
599
168
|
}
|
|
600
|
-
const turns = messages.filter(m => m.role ===
|
|
169
|
+
const turns = messages.filter((m) => m.role === "assistant").length;
|
|
601
170
|
const elapsedMs = performance.now() - startTime;
|
|
602
171
|
await ctx.updateTokens(messages);
|
|
603
172
|
const finalTokens = ctx.totalTokens;
|
|
604
173
|
const finalUsage = ctx.usagePercent;
|
|
605
|
-
logInfo(
|
|
174
|
+
logInfo("SUBAGENT", `DONE | ${turns} turns | ${toolCallCount} tool calls | ${createdFiles.length} files created | ${errors.length} errors | ${finalTokens} tokens (${finalUsage.toFixed(1)}%) | ${(elapsedMs / 1000).toFixed(1)}s`);
|
|
606
175
|
// Собираем список использованных инструментов из истории
|
|
607
|
-
const toolsUsed = [
|
|
608
|
-
|
|
609
|
-
.
|
|
176
|
+
const toolsUsed = [
|
|
177
|
+
...new Set(messages
|
|
178
|
+
.filter((m) => m.role === "assistant")
|
|
179
|
+
.flatMap((m) => {
|
|
610
180
|
const calls = m.content.matchAll(/<tool_call>\{"name":"([^"]+)"/g);
|
|
611
|
-
return [...calls].map(c => c[1]);
|
|
612
|
-
}))
|
|
613
|
-
|
|
181
|
+
return [...calls].map((c) => c[1]);
|
|
182
|
+
})),
|
|
183
|
+
];
|
|
184
|
+
const toolsStr = toolsUsed.length > 0 ? `\nTools used: ${toolsUsed.join(", ")}` : "";
|
|
614
185
|
const summary = createdFiles.length > 0
|
|
615
|
-
? `[Subagent done — ${createdFiles.length} file(s), ${turns} turns]\nFiles: ${createdFiles.join(
|
|
616
|
-
: `[Subagent done — no files, ${turns} turns]${toolsStr}${errors.length > 0 ? `\nErrors: ${errors.join(
|
|
186
|
+
? `[Subagent done — ${createdFiles.length} file(s), ${turns} turns]\nFiles: ${createdFiles.join(", ")}${toolsStr}${errors.length > 0 ? `\nErrors: ${errors.join("; ")}` : ""}`
|
|
187
|
+
: `[Subagent done — no files, ${turns} turns]${toolsStr}${errors.length > 0 ? `\nErrors: ${errors.join("; ")}` : ""}`;
|
|
617
188
|
return { createdFiles, errors, summary, turns };
|
|
618
189
|
}
|
|
619
|
-
export const subagentDefinition = {
|
|
620
|
-
type: 'function',
|
|
621
|
-
function: {
|
|
622
|
-
name: 'subagent',
|
|
623
|
-
description: 'Delegate a task to an isolated agent with a FRESH context (no history of prior errors). Use for: (1) each file when creating 3+ new files, (2) any task where you are stuck or making repeated errors, (3) simple single-step operations (bash, file read/write). The subagent has all tools and starts clean — ideal when accumulated context is causing confusion.',
|
|
624
|
-
parameters: {
|
|
625
|
-
type: 'object',
|
|
626
|
-
properties: {
|
|
627
|
-
task: { type: 'string', description: 'What the subagent should do — be specific and include file paths' },
|
|
628
|
-
existingFiles: { type: 'string', description: 'Comma-separated files the subagent can read for context (optional)' },
|
|
629
|
-
},
|
|
630
|
-
required: ['task'],
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
|
-
};
|
|
634
190
|
//# sourceMappingURL=subagent.js.map
|