kernelbot 1.0.21 → 1.0.23
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/package.json +1 -1
- package/src/agent.js +11 -3
- package/src/bot.js +19 -1
- package/src/coder.js +12 -14
- package/src/prompts/system.js +2 -1
- package/src/tools/browser.js +59 -3
- package/src/utils/display.js +8 -10
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -13,10 +13,11 @@ export class Agent {
|
|
|
13
13
|
this._pending = new Map(); // chatId -> pending state
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
async processMessage(chatId, userMessage, user, onUpdate) {
|
|
16
|
+
async processMessage(chatId, userMessage, user, onUpdate, sendPhoto) {
|
|
17
17
|
const logger = getLogger();
|
|
18
18
|
|
|
19
19
|
this._onUpdate = onUpdate || null;
|
|
20
|
+
this._sendPhoto = sendPhoto || null;
|
|
20
21
|
|
|
21
22
|
// Handle pending responses (confirmation or credential)
|
|
22
23
|
const pending = this._pending.get(chatId);
|
|
@@ -66,6 +67,11 @@ export class Agent {
|
|
|
66
67
|
docker_compose: 'action',
|
|
67
68
|
curl_url: 'url',
|
|
68
69
|
check_port: 'port',
|
|
70
|
+
screenshot_website: 'url',
|
|
71
|
+
send_image: 'file_path',
|
|
72
|
+
browse_website: 'url',
|
|
73
|
+
extract_content: 'url',
|
|
74
|
+
interact_with_page: 'url',
|
|
69
75
|
}[name];
|
|
70
76
|
const val = key && input[key] ? String(input[key]).slice(0, 120) : JSON.stringify(input).slice(0, 120);
|
|
71
77
|
return `${name}: ${val}`;
|
|
@@ -100,6 +106,7 @@ export class Agent {
|
|
|
100
106
|
config: this.config,
|
|
101
107
|
user,
|
|
102
108
|
onUpdate: this._onUpdate,
|
|
109
|
+
sendPhoto: this._sendPhoto,
|
|
103
110
|
});
|
|
104
111
|
|
|
105
112
|
pending.toolResults.push({
|
|
@@ -117,7 +124,7 @@ export class Agent {
|
|
|
117
124
|
|
|
118
125
|
if (lower === 'yes' || lower === 'y' || lower === 'confirm') {
|
|
119
126
|
logger.info(`User confirmed dangerous tool: ${pending.block.name}`);
|
|
120
|
-
const result = await executeTool(pending.block.name, pending.block.input, { ...pending.context, onUpdate: this._onUpdate });
|
|
127
|
+
const result = await executeTool(pending.block.name, pending.block.input, { ...pending.context, onUpdate: this._onUpdate, sendPhoto: this._sendPhoto });
|
|
121
128
|
|
|
122
129
|
pending.toolResults.push({
|
|
123
130
|
type: 'tool_result',
|
|
@@ -144,7 +151,7 @@ export class Agent {
|
|
|
144
151
|
const pauseMsg = await this._checkPause(chatId, block, user, pending.toolResults, pending.remainingBlocks.filter((b) => b !== block), pending.messages);
|
|
145
152
|
if (pauseMsg) return pauseMsg;
|
|
146
153
|
|
|
147
|
-
const r = await executeTool(block.name, block.input, { config: this.config, user, onUpdate: this._onUpdate });
|
|
154
|
+
const r = await executeTool(block.name, block.input, { config: this.config, user, onUpdate: this._onUpdate, sendPhoto: this._sendPhoto });
|
|
148
155
|
pending.toolResults.push({
|
|
149
156
|
type: 'tool_result',
|
|
150
157
|
tool_use_id: block.id,
|
|
@@ -256,6 +263,7 @@ export class Agent {
|
|
|
256
263
|
config: this.config,
|
|
257
264
|
user,
|
|
258
265
|
onUpdate: this._onUpdate,
|
|
266
|
+
sendPhoto: this._sendPhoto,
|
|
259
267
|
});
|
|
260
268
|
|
|
261
269
|
toolResults.push({
|
package/src/bot.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import TelegramBot from 'node-telegram-bot-api';
|
|
2
|
+
import { createReadStream } from 'fs';
|
|
2
3
|
import { isAllowedUser, getUnauthorizedMessage } from './security/auth.js';
|
|
3
4
|
import { getLogger } from './utils/logger.js';
|
|
4
5
|
|
|
@@ -152,10 +153,27 @@ export function startBot(config, agent, conversationManager) {
|
|
|
152
153
|
return lastMsgId;
|
|
153
154
|
};
|
|
154
155
|
|
|
156
|
+
const sendPhoto = async (filePath, caption) => {
|
|
157
|
+
try {
|
|
158
|
+
await bot.sendPhoto(chatId, createReadStream(filePath), {
|
|
159
|
+
caption: caption || '',
|
|
160
|
+
parse_mode: 'Markdown',
|
|
161
|
+
});
|
|
162
|
+
} catch {
|
|
163
|
+
try {
|
|
164
|
+
await bot.sendPhoto(chatId, createReadStream(filePath), {
|
|
165
|
+
caption: caption || '',
|
|
166
|
+
});
|
|
167
|
+
} catch (err) {
|
|
168
|
+
logger.error(`Failed to send photo: ${err.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
155
173
|
const reply = await agent.processMessage(chatId, text, {
|
|
156
174
|
id: userId,
|
|
157
175
|
username,
|
|
158
|
-
}, onUpdate);
|
|
176
|
+
}, onUpdate, sendPhoto);
|
|
159
177
|
|
|
160
178
|
clearInterval(typingInterval);
|
|
161
179
|
|
package/src/coder.js
CHANGED
|
@@ -84,7 +84,7 @@ function processEvent(line, onOutput, logger) {
|
|
|
84
84
|
// Not JSON — send raw text if it looks meaningful
|
|
85
85
|
if (line.trim() && line.length > 3 && onOutput) {
|
|
86
86
|
logger.info(`Claude Code (raw): ${line.slice(0, 200)}`);
|
|
87
|
-
onOutput(
|
|
87
|
+
onOutput(`▹ ${line.trim()}`).catch(() => {});
|
|
88
88
|
}
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
@@ -103,7 +103,7 @@ function processEvent(line, onOutput, logger) {
|
|
|
103
103
|
const tool = extractToolUse(event);
|
|
104
104
|
if (tool) {
|
|
105
105
|
logger.info(`Claude Code tool: ${tool.name}: ${tool.summary}`);
|
|
106
|
-
if (onOutput) onOutput(
|
|
106
|
+
if (onOutput) onOutput(`▸ ${tool.name}: ${tool.summary}`).catch(() => {});
|
|
107
107
|
}
|
|
108
108
|
return event;
|
|
109
109
|
}
|
|
@@ -113,7 +113,7 @@ function processEvent(line, onOutput, logger) {
|
|
|
113
113
|
const tool = extractToolUse(event);
|
|
114
114
|
if (tool) {
|
|
115
115
|
logger.info(`Claude Code tool: ${tool.name}: ${tool.summary}`);
|
|
116
|
-
if (onOutput) onOutput(
|
|
116
|
+
if (onOutput) onOutput(`▸ ${tool.name}: ${tool.summary}`).catch(() => {});
|
|
117
117
|
}
|
|
118
118
|
return event;
|
|
119
119
|
}
|
|
@@ -124,7 +124,7 @@ function processEvent(line, onOutput, logger) {
|
|
|
124
124
|
const duration = event.duration_ms ? ` in ${(event.duration_ms / 1000).toFixed(1)}s` : '';
|
|
125
125
|
const cost = event.cost_usd ? ` ($${event.cost_usd.toFixed(3)})` : '';
|
|
126
126
|
logger.info(`Claude Code finished: ${status}${duration}${cost}`);
|
|
127
|
-
if (onOutput) onOutput(
|
|
127
|
+
if (onOutput) onOutput(`▪ done (${status}${duration}${cost})`).catch(() => {});
|
|
128
128
|
return event;
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -173,12 +173,12 @@ export class ClaudeCodeSpawner {
|
|
|
173
173
|
? `\n_... ${activityLines.length} operations total_\n`
|
|
174
174
|
: '';
|
|
175
175
|
if (finalState === 'done') {
|
|
176
|
-
return
|
|
176
|
+
return `░▒▓ *Claude Code Done* — ${activityLines.length} ops\n${countInfo}\n${visible.join('\n')}`;
|
|
177
177
|
}
|
|
178
178
|
if (finalState === 'error') {
|
|
179
|
-
return
|
|
179
|
+
return `░▒▓ *Claude Code Failed* — ${activityLines.length} ops\n${countInfo}\n${visible.join('\n')}`;
|
|
180
180
|
}
|
|
181
|
-
return
|
|
181
|
+
return `░▒▓ *Claude Code Working...*\n${countInfo}\n${visible.join('\n')}`;
|
|
182
182
|
};
|
|
183
183
|
|
|
184
184
|
const flushStatus = async () => {
|
|
@@ -206,17 +206,15 @@ export class ClaudeCodeSpawner {
|
|
|
206
206
|
|
|
207
207
|
const smartOutput = onOutput ? async (text) => {
|
|
208
208
|
// Tool calls, raw output, warnings, starting → accumulate in status message
|
|
209
|
-
if (text.startsWith('
|
|
209
|
+
if (text.startsWith('▸') || text.startsWith('▹') || text.startsWith('▪')) {
|
|
210
210
|
addActivity(text);
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
|
-
//
|
|
214
|
-
if (text.startsWith('✅')) return;
|
|
215
|
-
// Everything else (💬 text, ❌ error, ⏰ timeout) → new message
|
|
213
|
+
// Everything else (💬 text, errors, timeout) → new message
|
|
216
214
|
await onOutput(text);
|
|
217
215
|
} : null;
|
|
218
216
|
|
|
219
|
-
if (smartOutput) smartOutput(
|
|
217
|
+
if (smartOutput) smartOutput(`▸ Starting Claude Code...`).catch(() => {});
|
|
220
218
|
|
|
221
219
|
return new Promise((resolve, reject) => {
|
|
222
220
|
const child = spawn('claude', args, {
|
|
@@ -258,13 +256,13 @@ export class ClaudeCodeSpawner {
|
|
|
258
256
|
stderr += chunk + '\n';
|
|
259
257
|
logger.warn(`Claude Code stderr: ${chunk.slice(0, 300)}`);
|
|
260
258
|
if (smartOutput && chunk) {
|
|
261
|
-
smartOutput(
|
|
259
|
+
smartOutput(`▹ ${chunk.slice(0, 300)}`).catch(() => {});
|
|
262
260
|
}
|
|
263
261
|
});
|
|
264
262
|
|
|
265
263
|
const timer = setTimeout(() => {
|
|
266
264
|
child.kill('SIGTERM');
|
|
267
|
-
if (smartOutput) smartOutput(
|
|
265
|
+
if (smartOutput) smartOutput(`▸ Claude Code timed out after ${this.timeout / 1000}s`).catch(() => {});
|
|
268
266
|
reject(new Error(`Claude Code timed out after ${this.timeout / 1000}s`));
|
|
269
267
|
}, this.timeout);
|
|
270
268
|
|
package/src/prompts/system.js
CHANGED
|
@@ -19,9 +19,10 @@ IMPORTANT: You MUST NOT write code yourself using read_file/write_file. ALWAYS d
|
|
|
19
19
|
|
|
20
20
|
## Web Browsing Tasks (researching, scraping, reading documentation, taking screenshots)
|
|
21
21
|
- Use browse_website to read and summarize web pages
|
|
22
|
-
- Use screenshot_website to capture visual snapshots of pages
|
|
22
|
+
- Use screenshot_website to capture visual snapshots of pages — the screenshot is automatically sent to the chat
|
|
23
23
|
- Use extract_content to pull specific data from pages using CSS selectors
|
|
24
24
|
- Use interact_with_page for pages that need clicking, typing, or scrolling to reveal content
|
|
25
|
+
- Use send_image to send any image file directly to the Telegram chat (screenshots, generated images, etc.)
|
|
25
26
|
- When a user sends /browse <url>, use browse_website on that URL
|
|
26
27
|
- When a user sends /screenshot <url>, use screenshot_website on that URL
|
|
27
28
|
- When a user sends /extract <url> <selector>, use extract_content with that URL and selector
|
package/src/tools/browser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import puppeteer from 'puppeteer';
|
|
2
|
-
import { writeFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { writeFile, mkdir, access } from 'fs/promises';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
|
|
@@ -186,6 +186,25 @@ export const definitions = [
|
|
|
186
186
|
required: ['url', 'selector'],
|
|
187
187
|
},
|
|
188
188
|
},
|
|
189
|
+
{
|
|
190
|
+
name: 'send_image',
|
|
191
|
+
description:
|
|
192
|
+
'Send an image or screenshot file directly to the Telegram chat. Use this to share screenshots, generated images, or any image file with the user.',
|
|
193
|
+
input_schema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
file_path: {
|
|
197
|
+
type: 'string',
|
|
198
|
+
description: 'Absolute path to the image file to send (e.g., "/home/user/.kernelbot/screenshots/example.png")',
|
|
199
|
+
},
|
|
200
|
+
caption: {
|
|
201
|
+
type: 'string',
|
|
202
|
+
description: 'Optional caption to include with the image',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
required: ['file_path'],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
189
208
|
{
|
|
190
209
|
name: 'interact_with_page',
|
|
191
210
|
description:
|
|
@@ -350,7 +369,7 @@ async function handleBrowse(params) {
|
|
|
350
369
|
});
|
|
351
370
|
}
|
|
352
371
|
|
|
353
|
-
async function handleScreenshot(params) {
|
|
372
|
+
async function handleScreenshot(params, context) {
|
|
354
373
|
const validation = validateUrl(params.url);
|
|
355
374
|
if (!validation.valid) return { error: validation.error };
|
|
356
375
|
|
|
@@ -397,12 +416,24 @@ async function handleScreenshot(params) {
|
|
|
397
416
|
await page.screenshot(screenshotOptions);
|
|
398
417
|
}
|
|
399
418
|
|
|
419
|
+
const title = await page.title();
|
|
420
|
+
|
|
421
|
+
// Send the screenshot directly to Telegram chat
|
|
422
|
+
if (context?.sendPhoto) {
|
|
423
|
+
try {
|
|
424
|
+
await context.sendPhoto(filepath, `📸 ${title || url}`);
|
|
425
|
+
} catch {
|
|
426
|
+
// Photo sending is best-effort; don't fail the tool
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
400
430
|
return {
|
|
401
431
|
success: true,
|
|
402
432
|
url: page.url(),
|
|
403
|
-
title
|
|
433
|
+
title,
|
|
404
434
|
screenshot_path: filepath,
|
|
405
435
|
filename,
|
|
436
|
+
sent_to_chat: !!context?.sendPhoto,
|
|
406
437
|
};
|
|
407
438
|
});
|
|
408
439
|
}
|
|
@@ -614,6 +645,30 @@ async function handleInteract(params) {
|
|
|
614
645
|
});
|
|
615
646
|
}
|
|
616
647
|
|
|
648
|
+
async function handleSendImage(params, context) {
|
|
649
|
+
if (!params.file_path) {
|
|
650
|
+
return { error: 'file_path is required' };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Verify the file exists
|
|
654
|
+
try {
|
|
655
|
+
await access(params.file_path);
|
|
656
|
+
} catch {
|
|
657
|
+
return { error: `File not found: ${params.file_path}` };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (!context?.sendPhoto) {
|
|
661
|
+
return { error: 'Image sending is not available in this context (no active Telegram chat)' };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
await context.sendPhoto(params.file_path, params.caption || '');
|
|
666
|
+
return { success: true, file_path: params.file_path, sent: true };
|
|
667
|
+
} catch (err) {
|
|
668
|
+
return { error: `Failed to send image: ${err.message}` };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
617
672
|
// ── Export ────────────────────────────────────────────────────────────────────
|
|
618
673
|
|
|
619
674
|
export const handlers = {
|
|
@@ -621,4 +676,5 @@ export const handlers = {
|
|
|
621
676
|
screenshot_website: handleScreenshot,
|
|
622
677
|
extract_content: handleExtract,
|
|
623
678
|
interact_with_page: handleInteract,
|
|
679
|
+
send_image: handleSendImage,
|
|
624
680
|
};
|
package/src/utils/display.js
CHANGED
|
@@ -26,19 +26,17 @@ const LOGO = `
|
|
|
26
26
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚═════╝ ╚═════╝ ╚═╝
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
'#
|
|
32
|
-
'#
|
|
33
|
-
'#
|
|
34
|
-
'#
|
|
35
|
-
'#
|
|
36
|
-
'#1E90FF', // Dodger Blue
|
|
37
|
-
'#9370DB' // Medium Purple
|
|
29
|
+
// White to ~70% black gradient
|
|
30
|
+
const monoGradient = gradient([
|
|
31
|
+
'#FFFFFF',
|
|
32
|
+
'#D0D0D0',
|
|
33
|
+
'#A0A0A0',
|
|
34
|
+
'#707070',
|
|
35
|
+
'#4D4D4D',
|
|
38
36
|
]);
|
|
39
37
|
|
|
40
38
|
export function showLogo() {
|
|
41
|
-
console.log(
|
|
39
|
+
console.log(monoGradient.multiline(LOGO));
|
|
42
40
|
console.log(chalk.dim(` AI Engineering Agent — v${getVersion()}\n`));
|
|
43
41
|
console.log(
|
|
44
42
|
boxen(
|