@ducci/jarvis 1.0.42 → 1.0.44
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/docs/system-prompt.md +1 -1
- package/package.json +1 -1
- package/src/channels/telegram/index.js +36 -18
- package/src/server/config.js +3 -1
- package/src/server/tools.js +35 -19
package/docs/system-prompt.md
CHANGED
|
@@ -52,7 +52,7 @@ There are two types of responses depending on whether you need to use tools:
|
|
|
52
52
|
"logSummary": "A concise explanation of what you did and why, written for a human reading the logs."
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
The `response` value must be a
|
|
55
|
+
The `response` value must be a string — never an array or object. Use HTML formatting tags for readability: <b>bold</b>, <i>italic</i>, <code>inline code</code>, <pre>code blocks</pre>, <blockquote>quotes</blockquote>. Never use Markdown formatting (no **, __, `, or ```). If you need to present structured data (e.g. a list of items), format it as text within the string value.
|
|
56
56
|
|
|
57
57
|
Never include markdown code fences, preamble, or any text outside this JSON object. If you cannot complete a task, explain why in the `response` field — still as valid JSON.
|
|
58
58
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,41 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
1
3
|
import { Bot } from 'grammy';
|
|
2
4
|
import { run } from '@grammyjs/runner';
|
|
3
5
|
import { handleChat } from '../../server/agent.js';
|
|
4
6
|
import { loadSession } from '../../server/sessions.js';
|
|
7
|
+
import { PATHS } from '../../server/config.js';
|
|
5
8
|
import { load, save } from './sessions.js';
|
|
6
9
|
|
|
10
|
+
async function sendMessage(api, chatId, text, sessionId) {
|
|
11
|
+
const MAX_TG = 4096;
|
|
12
|
+
const chunks = [];
|
|
13
|
+
for (let i = 0; i < text.length; i += MAX_TG) {
|
|
14
|
+
chunks.push(text.slice(i, i + MAX_TG));
|
|
15
|
+
}
|
|
16
|
+
for (const chunk of chunks) {
|
|
17
|
+
try {
|
|
18
|
+
await api.sendMessage(chatId, chunk, { parse_mode: 'HTML' });
|
|
19
|
+
} catch (e) {
|
|
20
|
+
if (e.error_code === 400) {
|
|
21
|
+
console.error(`[telegram] HTML parse error chat_id=${chatId}, falling back to plaintext: ${e.description}`);
|
|
22
|
+
if (sessionId) {
|
|
23
|
+
const logFile = path.join(PATHS.logsDir, `session-${sessionId}.jsonl`);
|
|
24
|
+
fs.promises.appendFile(logFile, JSON.stringify({
|
|
25
|
+
ts: new Date().toISOString(),
|
|
26
|
+
sessionId,
|
|
27
|
+
type: 'telegram_parse_error',
|
|
28
|
+
description: e.description || e.message,
|
|
29
|
+
}) + '\n', 'utf8').catch(() => {});
|
|
30
|
+
}
|
|
31
|
+
await api.sendMessage(chatId, chunk);
|
|
32
|
+
} else {
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
7
39
|
export async function startTelegramChannel(config) {
|
|
8
40
|
const { token, allowedUserIds } = config.telegram;
|
|
9
41
|
|
|
@@ -103,23 +135,16 @@ export async function startTelegramChannel(config) {
|
|
|
103
135
|
}
|
|
104
136
|
|
|
105
137
|
try {
|
|
106
|
-
const MAX_TG = 4096;
|
|
107
138
|
const rawResponse = typeof result.response === 'string'
|
|
108
139
|
? result.response
|
|
109
140
|
: result.response != null ? JSON.stringify(result.response, null, 2) : '';
|
|
110
141
|
const text = rawResponse.trim()
|
|
111
142
|
|| 'The agent encountered an error and could not produce a response. Please try again.';
|
|
112
|
-
|
|
113
|
-
await ctx.reply(text);
|
|
114
|
-
} else {
|
|
115
|
-
for (let i = 0; i < text.length; i += MAX_TG) {
|
|
116
|
-
await ctx.reply(text.slice(i, i + MAX_TG));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
143
|
+
await sendMessage(ctx.api, chatId, text, result.sessionId);
|
|
119
144
|
console.log(`[telegram] response sent chat_id=${chatId} length=${text.length}`);
|
|
120
145
|
} catch (e) {
|
|
121
146
|
console.error(`[telegram] delivery error chat_id=${chatId}: ${e.message}`);
|
|
122
|
-
await ctx.
|
|
147
|
+
await ctx.api.sendMessage(chatId, 'Sorry, something went wrong sending the response. Please try again.').catch(() => {});
|
|
123
148
|
} finally {
|
|
124
149
|
clearInterval(typingInterval);
|
|
125
150
|
}
|
|
@@ -162,24 +187,17 @@ export async function startTelegramChannel(config) {
|
|
|
162
187
|
}
|
|
163
188
|
|
|
164
189
|
try {
|
|
165
|
-
const MAX_TG = 4096;
|
|
166
190
|
// Guard against empty or non-string response (e.g. model returns array instead of string)
|
|
167
191
|
const rawResponse = typeof result.response === 'string'
|
|
168
192
|
? result.response
|
|
169
193
|
: result.response != null ? JSON.stringify(result.response, null, 2) : '';
|
|
170
194
|
const text = rawResponse.trim()
|
|
171
195
|
|| 'The agent encountered an error and could not produce a response. Please try again.';
|
|
172
|
-
|
|
173
|
-
await ctx.reply(text);
|
|
174
|
-
} else {
|
|
175
|
-
for (let i = 0; i < text.length; i += MAX_TG) {
|
|
176
|
-
await ctx.reply(text.slice(i, i + MAX_TG));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
196
|
+
await sendMessage(ctx.api, chatId, text, result.sessionId);
|
|
179
197
|
console.log(`[telegram] response sent chat_id=${chatId} length=${text.length}`);
|
|
180
198
|
} catch (e) {
|
|
181
199
|
console.error(`[telegram] delivery error chat_id=${chatId}: ${e.message}`);
|
|
182
|
-
await ctx.
|
|
200
|
+
await ctx.api.sendMessage(chatId, 'Sorry, something went wrong sending the response. Please try again.').catch(() => {});
|
|
183
201
|
} finally {
|
|
184
202
|
clearInterval(typingInterval);
|
|
185
203
|
}
|
package/src/server/config.js
CHANGED
|
@@ -98,7 +98,9 @@ export function resolveSystemPrompt(promptTemplate, sessionId) {
|
|
|
98
98
|
const skills = [];
|
|
99
99
|
for (const entry of entries) {
|
|
100
100
|
if (!entry.isDirectory()) continue;
|
|
101
|
-
const
|
|
101
|
+
const skillFileName = fs.readdirSync(path.join(PATHS.skillsDir, entry.name))
|
|
102
|
+
.find(f => f.toLowerCase() === 'skill.md') || 'skill.md';
|
|
103
|
+
const skillFile = path.join(PATHS.skillsDir, entry.name, skillFileName);
|
|
102
104
|
try {
|
|
103
105
|
const content = fs.readFileSync(skillFile, 'utf8');
|
|
104
106
|
const meta = parseSkillFrontmatter(content);
|
package/src/server/tools.js
CHANGED
|
@@ -11,35 +11,40 @@ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
|
11
11
|
const TOOL_TIMEOUT_MS = 60_000;
|
|
12
12
|
|
|
13
13
|
const SEED_TOOLS = {
|
|
14
|
-
|
|
14
|
+
read_file: {
|
|
15
15
|
definition: {
|
|
16
16
|
type: 'function',
|
|
17
17
|
function: {
|
|
18
|
-
name: '
|
|
19
|
-
description: '
|
|
18
|
+
name: 'read_file',
|
|
19
|
+
description: 'Read a file from disk. Returns the file content as a string. Use offset and limit to read large files in chunks instead of loading everything at once.',
|
|
20
20
|
parameters: {
|
|
21
21
|
type: 'object',
|
|
22
22
|
properties: {
|
|
23
23
|
path: {
|
|
24
24
|
type: 'string',
|
|
25
|
-
description: '
|
|
25
|
+
description: 'Absolute or relative path to the file to read.',
|
|
26
|
+
},
|
|
27
|
+
offset: {
|
|
28
|
+
type: 'number',
|
|
29
|
+
description: 'Line number to start reading from (1-based). Omit to start from the beginning.',
|
|
30
|
+
},
|
|
31
|
+
limit: {
|
|
32
|
+
type: 'number',
|
|
33
|
+
description: 'Maximum number of lines to return. Omit to read the entire file (or remainder from offset).',
|
|
26
34
|
},
|
|
27
35
|
},
|
|
28
|
-
required: [],
|
|
36
|
+
required: ['path'],
|
|
29
37
|
},
|
|
30
38
|
},
|
|
31
39
|
},
|
|
32
40
|
code: `
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
timeout: 10000,
|
|
41
|
-
});
|
|
42
|
-
return { status: "ok", path: resolved, output };
|
|
41
|
+
const targetPath = path.resolve(args.path);
|
|
42
|
+
const raw = await fs.promises.readFile(targetPath, 'utf8');
|
|
43
|
+
const lines = raw.split('\\n');
|
|
44
|
+
const offset = args.offset ? args.offset - 1 : 0;
|
|
45
|
+
const slice = args.limit ? lines.slice(offset, offset + args.limit) : lines.slice(offset);
|
|
46
|
+
const totalLines = lines.length;
|
|
47
|
+
return { status: 'ok', path: targetPath, content: slice.join('\\n'), totalLines, returnedLines: slice.length, offset: offset + 1 };
|
|
43
48
|
`,
|
|
44
49
|
},
|
|
45
50
|
exec: {
|
|
@@ -472,8 +477,7 @@ const SEED_TOOLS = {
|
|
|
472
477
|
const chatId = settings.channels?.telegram?.allowedUserIds?.[0];
|
|
473
478
|
if (!chatId) return { status: 'error', error: 'No Telegram chat_id configured.' };
|
|
474
479
|
if (!token) return { status: 'error', error: 'No TELEGRAM_BOT_TOKEN configured.' };
|
|
475
|
-
const
|
|
476
|
-
await new Promise((resolve, reject) => {
|
|
480
|
+
const sendRequest = (body) => new Promise((resolve, reject) => {
|
|
477
481
|
const req = https.request({
|
|
478
482
|
hostname: 'api.telegram.org',
|
|
479
483
|
path: '/bot' + token + '/sendMessage',
|
|
@@ -484,7 +488,7 @@ const SEED_TOOLS = {
|
|
|
484
488
|
res.on('data', chunk => data += chunk);
|
|
485
489
|
res.on('end', () => {
|
|
486
490
|
const parsed = JSON.parse(data);
|
|
487
|
-
if (!parsed.ok) reject(new Error(parsed.description));
|
|
491
|
+
if (!parsed.ok) reject(Object.assign(new Error(parsed.description), { description: parsed.description, error_code: parsed.error_code }));
|
|
488
492
|
else resolve(parsed);
|
|
489
493
|
});
|
|
490
494
|
});
|
|
@@ -492,6 +496,15 @@ const SEED_TOOLS = {
|
|
|
492
496
|
req.write(body);
|
|
493
497
|
req.end();
|
|
494
498
|
});
|
|
499
|
+
try {
|
|
500
|
+
await sendRequest(JSON.stringify({ chat_id: chatId, text: args.message, parse_mode: 'HTML' }));
|
|
501
|
+
} catch (e) {
|
|
502
|
+
if (e.error_code === 400) {
|
|
503
|
+
await sendRequest(JSON.stringify({ chat_id: chatId, text: args.message }));
|
|
504
|
+
} else {
|
|
505
|
+
throw e;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
495
508
|
return { status: 'ok', chatId };
|
|
496
509
|
`,
|
|
497
510
|
},
|
|
@@ -540,7 +553,10 @@ const SEED_TOOLS = {
|
|
|
540
553
|
},
|
|
541
554
|
},
|
|
542
555
|
code: `
|
|
543
|
-
const
|
|
556
|
+
const skillDir = path.join(process.env.HOME, '.jarvis/data/skills', args.name);
|
|
557
|
+
const dirFiles = await fs.promises.readdir(skillDir).catch(() => []);
|
|
558
|
+
const skillFileName = dirFiles.find(f => f.toLowerCase() === 'skill.md') || 'skill.md';
|
|
559
|
+
const skillFile = path.join(skillDir, skillFileName);
|
|
544
560
|
const content = await fs.promises.readFile(skillFile, 'utf8').catch(() => null);
|
|
545
561
|
if (!content) return { status: 'not_found', name: args.name };
|
|
546
562
|
return { status: 'ok', name: args.name, content };
|