kernelbot 1.0.32 โ 1.0.34
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/README.md +281 -276
- package/bin/kernel.js +51 -12
- package/package.json +2 -1
- package/src/agent.js +200 -33
- package/src/bot.js +228 -105
- package/src/conversation.js +121 -8
- package/src/prompts/orchestrator.js +44 -2
- package/src/prompts/persona.md +34 -0
- package/src/providers/base.js +16 -5
- package/src/providers/google-genai.js +198 -0
- package/src/providers/index.js +6 -1
- package/src/providers/models.js +6 -2
- package/src/providers/openai-compat.js +25 -11
- package/src/swarm/job.js +11 -0
- package/src/tools/docker.js +6 -13
- package/src/tools/monitor.js +5 -14
- package/src/tools/network.js +10 -17
- package/src/tools/orchestrator-tools.js +42 -0
- package/src/tools/os.js +37 -2
- package/src/tools/process.js +7 -14
- package/src/utils/config.js +59 -0
- package/src/utils/shell.js +31 -0
- package/src/utils/truncate.js +42 -0
- package/src/worker.js +18 -17
|
@@ -154,6 +154,28 @@ export const orchestratorToolDefinitions = [
|
|
|
154
154
|
required: ['automation_id'],
|
|
155
155
|
},
|
|
156
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: 'send_reaction',
|
|
159
|
+
description: 'Send an emoji reaction on a Telegram message. Use this to react to the user\'s message with an emoji (e.g. โค, ๐, ๐ฅ, ๐, ๐, ๐, ๐, ๐ค, ๐ฑ, ๐). Only standard Telegram reaction emojis are supported. If no message_id is provided, reacts to the latest user message.',
|
|
160
|
+
input_schema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
emoji: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
description: 'The emoji to react with. Must be a standard Telegram reaction emoji: ๐ ๐ โค ๐ฅ ๐ฅฐ ๐ ๐ ๐ค ๐คฏ ๐ฑ ๐คฌ ๐ข ๐ ๐คฉ ๐คฎ ๐ฉ ๐ ๐ ๐ ๐คก ๐ฅฑ ๐ฅด ๐ ๐ณ โค๏ธโ๐ฅ ๐ ๐ญ ๐ฏ ๐คฃ โก ๐ ๐ ๐ ๐คจ ๐ ๐ ๐พ ๐ ๐ ๐ ๐ด ๐ญ ๐ค ๐ป ๐จโ๐ป ๐ ๐ ๐ ๐ ๐จ ๐ค โ ๐ค ๐ซก ๐
๐ โ ๐
๐คช ๐ฟ ๐ ๐ ๐ ๐ฆ ๐ ๐ ๐ ๐ ๐พ ๐คทโโ ๐คท ๐คทโโ ๐ก',
|
|
166
|
+
},
|
|
167
|
+
message_id: {
|
|
168
|
+
type: 'integer',
|
|
169
|
+
description: 'The message ID to react to. If omitted, reacts to the latest user message.',
|
|
170
|
+
},
|
|
171
|
+
is_big: {
|
|
172
|
+
type: 'boolean',
|
|
173
|
+
description: 'Whether to show the reaction with a big animation. Default: false.',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
required: ['emoji'],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
157
179
|
];
|
|
158
180
|
|
|
159
181
|
/**
|
|
@@ -437,6 +459,26 @@ export async function executeOrchestratorTool(name, input, context) {
|
|
|
437
459
|
return { automation_id, status: 'deleted', message: `Automation deleted.` };
|
|
438
460
|
}
|
|
439
461
|
|
|
462
|
+
case 'send_reaction': {
|
|
463
|
+
const { emoji, message_id, is_big } = input;
|
|
464
|
+
const { sendReaction, lastUserMessageId } = context;
|
|
465
|
+
|
|
466
|
+
if (!sendReaction) return { error: 'Reaction sending is not available in this context.' };
|
|
467
|
+
|
|
468
|
+
const targetMessageId = message_id || lastUserMessageId;
|
|
469
|
+
if (!targetMessageId) return { error: 'No message_id provided and no recent user message to react to.' };
|
|
470
|
+
|
|
471
|
+
logger.info(`[send_reaction] Sending ${emoji} to message ${targetMessageId} in chat ${chatId}`);
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
await sendReaction(chatId, targetMessageId, emoji, is_big || false);
|
|
475
|
+
return { success: true, emoji, message_id: targetMessageId };
|
|
476
|
+
} catch (err) {
|
|
477
|
+
logger.error(`[send_reaction] Failed: ${err.message}`);
|
|
478
|
+
return { error: `Failed to send reaction: ${err.message}` };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
440
482
|
default:
|
|
441
483
|
return { error: `Unknown orchestrator tool: ${name}` };
|
|
442
484
|
}
|
package/src/tools/os.js
CHANGED
|
@@ -2,12 +2,24 @@ import { exec } from 'child_process';
|
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
3
3
|
import { dirname, resolve, join } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
+
import { getLogger } from '../utils/logger.js';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Expand a file path, resolving `~` to the user's home directory.
|
|
9
|
+
* @param {string} p - The path to expand.
|
|
10
|
+
* @returns {string} The resolved absolute path.
|
|
11
|
+
*/
|
|
6
12
|
function expandPath(p) {
|
|
7
13
|
if (p.startsWith('~')) return join(homedir(), p.slice(1));
|
|
8
14
|
return resolve(p);
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Check whether a file path falls within any blocked path defined in config.
|
|
19
|
+
* @param {string} filePath - The path to check.
|
|
20
|
+
* @param {object} config - The bot configuration object.
|
|
21
|
+
* @returns {boolean} True if the path is blocked.
|
|
22
|
+
*/
|
|
11
23
|
function isBlocked(filePath, config) {
|
|
12
24
|
const expanded = expandPath(filePath);
|
|
13
25
|
const blockedPaths = config.security?.blocked_paths || [];
|
|
@@ -94,14 +106,25 @@ export const definitions = [
|
|
|
94
106
|
},
|
|
95
107
|
];
|
|
96
108
|
|
|
97
|
-
|
|
109
|
+
/** Maximum recursion depth for directory listing to prevent stack overflow. */
|
|
110
|
+
const MAX_RECURSIVE_DEPTH = 10;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Recursively list all files and directories under a given path.
|
|
114
|
+
* @param {string} dirPath - Absolute path to the directory to list.
|
|
115
|
+
* @param {string} [base=''] - Relative path prefix for nested entries.
|
|
116
|
+
* @param {number} [depth=0] - Current recursion depth (internal).
|
|
117
|
+
* @returns {Array<{name: string, type: string}>} Flat array of entries.
|
|
118
|
+
*/
|
|
119
|
+
function listRecursive(dirPath, base = '', depth = 0) {
|
|
120
|
+
if (depth >= MAX_RECURSIVE_DEPTH) return [];
|
|
98
121
|
const entries = [];
|
|
99
122
|
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
100
123
|
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
101
124
|
const type = entry.isDirectory() ? 'directory' : 'file';
|
|
102
125
|
entries.push({ name: rel, type });
|
|
103
126
|
if (entry.isDirectory()) {
|
|
104
|
-
entries.push(...listRecursive(join(dirPath, entry.name), rel));
|
|
127
|
+
entries.push(...listRecursive(join(dirPath, entry.name), rel, depth + 1));
|
|
105
128
|
}
|
|
106
129
|
}
|
|
107
130
|
return entries;
|
|
@@ -109,6 +132,7 @@ function listRecursive(dirPath, base = '') {
|
|
|
109
132
|
|
|
110
133
|
export const handlers = {
|
|
111
134
|
execute_command: async (params, context) => {
|
|
135
|
+
const logger = getLogger();
|
|
112
136
|
const { command, timeout_seconds = 30 } = params;
|
|
113
137
|
const { config } = context;
|
|
114
138
|
const blockedPaths = config.security?.blocked_paths || [];
|
|
@@ -117,10 +141,13 @@ export const handlers = {
|
|
|
117
141
|
for (const bp of blockedPaths) {
|
|
118
142
|
const expanded = expandPath(bp);
|
|
119
143
|
if (command.includes(expanded)) {
|
|
144
|
+
logger.warn(`execute_command blocked: command references restricted path ${bp}`);
|
|
120
145
|
return { error: `Blocked: command references restricted path ${bp}` };
|
|
121
146
|
}
|
|
122
147
|
}
|
|
123
148
|
|
|
149
|
+
logger.debug(`execute_command: ${command.slice(0, 120)}`);
|
|
150
|
+
|
|
124
151
|
return new Promise((res) => {
|
|
125
152
|
let abortHandler = null;
|
|
126
153
|
|
|
@@ -164,8 +191,10 @@ export const handlers = {
|
|
|
164
191
|
},
|
|
165
192
|
|
|
166
193
|
read_file: async (params, context) => {
|
|
194
|
+
const logger = getLogger();
|
|
167
195
|
const { path: filePath, max_lines } = params;
|
|
168
196
|
if (isBlocked(filePath, context.config)) {
|
|
197
|
+
logger.warn(`read_file blocked: access to ${filePath} is restricted`);
|
|
169
198
|
return { error: `Blocked: access to ${filePath} is restricted` };
|
|
170
199
|
}
|
|
171
200
|
|
|
@@ -184,8 +213,10 @@ export const handlers = {
|
|
|
184
213
|
},
|
|
185
214
|
|
|
186
215
|
write_file: async (params, context) => {
|
|
216
|
+
const logger = getLogger();
|
|
187
217
|
const { path: filePath, content } = params;
|
|
188
218
|
if (isBlocked(filePath, context.config)) {
|
|
219
|
+
logger.warn(`write_file blocked: access to ${filePath} is restricted`);
|
|
189
220
|
return { error: `Blocked: access to ${filePath} is restricted` };
|
|
190
221
|
}
|
|
191
222
|
|
|
@@ -193,15 +224,19 @@ export const handlers = {
|
|
|
193
224
|
const expanded = expandPath(filePath);
|
|
194
225
|
mkdirSync(dirname(expanded), { recursive: true });
|
|
195
226
|
writeFileSync(expanded, content, 'utf-8');
|
|
227
|
+
logger.debug(`write_file: wrote ${content.length} bytes to ${expanded}`);
|
|
196
228
|
return { success: true, path: expanded };
|
|
197
229
|
} catch (err) {
|
|
230
|
+
logger.error(`write_file error for ${filePath}: ${err.message}`);
|
|
198
231
|
return { error: err.message };
|
|
199
232
|
}
|
|
200
233
|
},
|
|
201
234
|
|
|
202
235
|
list_directory: async (params, context) => {
|
|
236
|
+
const logger = getLogger();
|
|
203
237
|
const { path: dirPath, recursive = false } = params;
|
|
204
238
|
if (isBlocked(dirPath, context.config)) {
|
|
239
|
+
logger.warn(`list_directory blocked: access to ${dirPath} is restricted`);
|
|
205
240
|
return { error: `Blocked: access to ${dirPath} is restricted` };
|
|
206
241
|
}
|
|
207
242
|
|
package/src/tools/process.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
function run(cmd, timeout = 10000) {
|
|
4
|
-
return new Promise((resolve) => {
|
|
5
|
-
exec(cmd, { timeout }, (error, stdout, stderr) => {
|
|
6
|
-
if (error) return resolve({ error: stderr || error.message });
|
|
7
|
-
resolve({ output: stdout.trim() });
|
|
8
|
-
});
|
|
9
|
-
});
|
|
10
|
-
}
|
|
1
|
+
import { shellRun as run, shellEscape } from '../utils/shell.js';
|
|
11
2
|
|
|
12
3
|
export const definitions = [
|
|
13
4
|
{
|
|
@@ -52,22 +43,24 @@ export const definitions = [
|
|
|
52
43
|
export const handlers = {
|
|
53
44
|
process_list: async (params) => {
|
|
54
45
|
const filter = params.filter;
|
|
55
|
-
const cmd = filter ? `ps aux | head -1 && ps aux | grep -i
|
|
46
|
+
const cmd = filter ? `ps aux | head -1 && ps aux | grep -i ${shellEscape(filter)} | grep -v grep` : 'ps aux';
|
|
56
47
|
return await run(cmd);
|
|
57
48
|
},
|
|
58
49
|
|
|
59
50
|
kill_process: async (params) => {
|
|
60
51
|
if (params.pid) {
|
|
61
|
-
|
|
52
|
+
const pid = parseInt(params.pid, 10);
|
|
53
|
+
if (!Number.isFinite(pid) || pid <= 0) return { error: 'Invalid PID' };
|
|
54
|
+
return await run(`kill ${pid}`);
|
|
62
55
|
}
|
|
63
56
|
if (params.name) {
|
|
64
|
-
return await run(`pkill -f
|
|
57
|
+
return await run(`pkill -f ${shellEscape(params.name)}`);
|
|
65
58
|
}
|
|
66
59
|
return { error: 'Provide either pid or name' };
|
|
67
60
|
},
|
|
68
61
|
|
|
69
62
|
service_control: async (params) => {
|
|
70
63
|
const { service, action } = params;
|
|
71
|
-
return await run(`systemctl ${action} ${service}`);
|
|
64
|
+
return await run(`systemctl ${shellEscape(action)} ${shellEscape(service)}`);
|
|
72
65
|
},
|
|
73
66
|
};
|
package/src/utils/config.js
CHANGED
|
@@ -270,6 +270,65 @@ export function saveClaudeCodeAuth(config, mode, value) {
|
|
|
270
270
|
// mode === 'system' โ no credentials to save
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Full interactive flow: change orchestrator model + optionally enter API key.
|
|
275
|
+
*/
|
|
276
|
+
export async function changeOrchestratorModel(config, rl) {
|
|
277
|
+
const { createProvider } = await import('../providers/index.js');
|
|
278
|
+
const { providerKey, modelId } = await promptProviderSelection(rl);
|
|
279
|
+
|
|
280
|
+
const providerDef = PROVIDERS[providerKey];
|
|
281
|
+
|
|
282
|
+
// Resolve API key
|
|
283
|
+
const envKey = providerDef.envKey;
|
|
284
|
+
let apiKey = process.env[envKey];
|
|
285
|
+
if (!apiKey) {
|
|
286
|
+
const key = await ask(rl, chalk.cyan(`\n ${providerDef.name} API key (${envKey}): `));
|
|
287
|
+
if (!key.trim()) {
|
|
288
|
+
console.log(chalk.yellow('\n No API key provided. Orchestrator not changed.\n'));
|
|
289
|
+
return config;
|
|
290
|
+
}
|
|
291
|
+
apiKey = key.trim();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Validate the new provider before saving anything
|
|
295
|
+
console.log(chalk.dim(`\n Verifying ${providerDef.name} / ${modelId}...`));
|
|
296
|
+
const testConfig = {
|
|
297
|
+
brain: {
|
|
298
|
+
provider: providerKey,
|
|
299
|
+
model: modelId,
|
|
300
|
+
max_tokens: config.orchestrator.max_tokens,
|
|
301
|
+
temperature: config.orchestrator.temperature,
|
|
302
|
+
api_key: apiKey,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
try {
|
|
306
|
+
const testProvider = createProvider(testConfig);
|
|
307
|
+
await testProvider.ping();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.log(chalk.red(`\n โ Verification failed: ${err.message}`));
|
|
310
|
+
console.log(chalk.yellow(` Orchestrator not changed. Keeping current model.\n`));
|
|
311
|
+
return config;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Validation passed โ save everything
|
|
315
|
+
const savedPath = saveOrchestratorToYaml(providerKey, modelId);
|
|
316
|
+
console.log(chalk.dim(` Saved to ${savedPath}`));
|
|
317
|
+
|
|
318
|
+
config.orchestrator.provider = providerKey;
|
|
319
|
+
config.orchestrator.model = modelId;
|
|
320
|
+
config.orchestrator.api_key = apiKey;
|
|
321
|
+
|
|
322
|
+
// Save the key if it was newly entered
|
|
323
|
+
if (!process.env[envKey]) {
|
|
324
|
+
saveCredential(config, envKey, apiKey);
|
|
325
|
+
console.log(chalk.dim(' API key saved.\n'));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
console.log(chalk.green(` โ Orchestrator switched to ${providerDef.name} / ${modelId}\n`));
|
|
329
|
+
return config;
|
|
330
|
+
}
|
|
331
|
+
|
|
273
332
|
/**
|
|
274
333
|
* Full interactive flow: change brain model + optionally enter API key.
|
|
275
334
|
*/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Escape a string for safe use as a shell argument.
|
|
5
|
+
* Wraps the value in single quotes and escapes any embedded single quotes.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} arg - The value to escape
|
|
8
|
+
* @returns {string} Shell-safe quoted string
|
|
9
|
+
*/
|
|
10
|
+
export function shellEscape(arg) {
|
|
11
|
+
if (arg === undefined || arg === null) return "''";
|
|
12
|
+
return "'" + String(arg).replace(/'/g, "'\\''") + "'";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run a shell command and return { output } on success or { error } on failure.
|
|
17
|
+
* Resolves (never rejects) so callers can handle errors via the result object.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} cmd - The shell command to execute
|
|
20
|
+
* @param {number} [timeout=10000] - Max execution time in milliseconds
|
|
21
|
+
* @param {{ maxBuffer?: number }} [opts] - Optional exec options
|
|
22
|
+
* @returns {Promise<{ output: string } | { error: string }>}
|
|
23
|
+
*/
|
|
24
|
+
export function shellRun(cmd, timeout = 10000, opts = {}) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
exec(cmd, { timeout, maxBuffer: opts.maxBuffer, ...opts }, (error, stdout, stderr) => {
|
|
27
|
+
if (error) return resolve({ error: stderr || error.message });
|
|
28
|
+
resolve({ output: stdout.trim() });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tool-result truncation logic.
|
|
3
|
+
* Used by both OrchestratorAgent and WorkerAgent to cap tool outputs
|
|
4
|
+
* before feeding them back into the LLM context window.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const MAX_RESULT_LENGTH = 3000;
|
|
8
|
+
|
|
9
|
+
const LARGE_FIELDS = [
|
|
10
|
+
'stdout', 'stderr', 'content', 'diff', 'output',
|
|
11
|
+
'body', 'html', 'text', 'log', 'logs',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Truncate a serialized tool result to fit within the context budget.
|
|
16
|
+
*
|
|
17
|
+
* Strategy:
|
|
18
|
+
* 1. If JSON.stringify(result) fits, return it as-is.
|
|
19
|
+
* 2. Otherwise, trim known large string fields to 500 chars each and retry.
|
|
20
|
+
* 3. If still too large, hard-slice the serialized string.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} _name - Tool name (reserved for future per-tool limits)
|
|
23
|
+
* @param {any} result - Raw tool result object
|
|
24
|
+
* @returns {string} JSON string, guaranteed โค MAX_RESULT_LENGTH (+tail note)
|
|
25
|
+
*/
|
|
26
|
+
export function truncateToolResult(_name, result) {
|
|
27
|
+
let str = JSON.stringify(result);
|
|
28
|
+
if (str.length <= MAX_RESULT_LENGTH) return str;
|
|
29
|
+
|
|
30
|
+
if (result && typeof result === 'object') {
|
|
31
|
+
const truncated = { ...result };
|
|
32
|
+
for (const field of LARGE_FIELDS) {
|
|
33
|
+
if (typeof truncated[field] === 'string' && truncated[field].length > 500) {
|
|
34
|
+
truncated[field] = truncated[field].slice(0, 500) + `\n... [truncated ${truncated[field].length - 500} chars]`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
str = JSON.stringify(truncated);
|
|
38
|
+
if (str.length <= MAX_RESULT_LENGTH) return str;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return str.slice(0, MAX_RESULT_LENGTH) + `\n... [truncated, total ${str.length} chars]`;
|
|
42
|
+
}
|
package/src/worker.js
CHANGED
|
@@ -5,9 +5,7 @@ import { getMissingCredential } from './utils/config.js';
|
|
|
5
5
|
import { getWorkerPrompt } from './prompts/workers.js';
|
|
6
6
|
import { getUnifiedSkillById } from './skills/custom.js';
|
|
7
7
|
import { getLogger } from './utils/logger.js';
|
|
8
|
-
|
|
9
|
-
const MAX_RESULT_LENGTH = 3000;
|
|
10
|
-
const LARGE_FIELDS = ['stdout', 'stderr', 'content', 'diff', 'output', 'body', 'html', 'text', 'log', 'logs'];
|
|
8
|
+
import { truncateToolResult } from './utils/truncate.js';
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
11
|
* WorkerAgent โ runs a scoped agent loop in the background.
|
|
@@ -41,6 +39,7 @@ export class WorkerAgent {
|
|
|
41
39
|
this.abortController = abortController || new AbortController();
|
|
42
40
|
this._cancelled = false;
|
|
43
41
|
this._toolCallCount = 0;
|
|
42
|
+
this._llmCallCount = 0;
|
|
44
43
|
this._errors = [];
|
|
45
44
|
|
|
46
45
|
// Create provider from worker brain config
|
|
@@ -121,8 +120,12 @@ export class WorkerAgent {
|
|
|
121
120
|
signal: this.abortController.signal,
|
|
122
121
|
});
|
|
123
122
|
|
|
123
|
+
this._llmCallCount++;
|
|
124
124
|
logger.info(`[Worker ${this.jobId}] LLM response: stopReason=${response.stopReason}, text=${(response.text || '').length} chars, toolCalls=${(response.toolCalls || []).length}`);
|
|
125
125
|
|
|
126
|
+
// Report stats to the job after each LLM call
|
|
127
|
+
this._reportStats(response.text || null);
|
|
128
|
+
|
|
126
129
|
if (this._cancelled) {
|
|
127
130
|
logger.info(`[Worker ${this.jobId}] Cancelled after LLM response`);
|
|
128
131
|
throw new Error('Worker cancelled');
|
|
@@ -353,22 +356,20 @@ export class WorkerAgent {
|
|
|
353
356
|
}
|
|
354
357
|
}
|
|
355
358
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
str = JSON.stringify(truncated);
|
|
368
|
-
if (str.length <= MAX_RESULT_LENGTH) return str;
|
|
359
|
+
_reportStats(thinking) {
|
|
360
|
+
if (this.callbacks.onStats) {
|
|
361
|
+
try {
|
|
362
|
+
this.callbacks.onStats({
|
|
363
|
+
llmCalls: this._llmCallCount,
|
|
364
|
+
toolCalls: this._toolCallCount,
|
|
365
|
+
lastThinking: thinking || null,
|
|
366
|
+
});
|
|
367
|
+
} catch {}
|
|
369
368
|
}
|
|
369
|
+
}
|
|
370
370
|
|
|
371
|
-
|
|
371
|
+
_truncateResult(name, result) {
|
|
372
|
+
return truncateToolResult(name, result);
|
|
372
373
|
}
|
|
373
374
|
|
|
374
375
|
_formatToolSummary(name, input) {
|