@ducci/jarvis 1.0.42 → 1.0.43

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.
@@ -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 plain text string — never an array or object. If you need to present structured data (e.g. a list of items), format it as text within the string value.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@ducci/jarvis",
3
- "version": "1.0.42",
3
+ "version": "1.0.43",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -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
- if (text.length <= MAX_TG) {
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.reply('Sorry, something went wrong sending the response. Please try again.').catch(() => {});
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
- if (text.length <= MAX_TG) {
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.reply('Sorry, something went wrong sending the response. Please try again.').catch(() => {});
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
  }
@@ -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 skillFile = path.join(PATHS.skillsDir, entry.name, 'skill.md');
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);
@@ -472,8 +472,7 @@ const SEED_TOOLS = {
472
472
  const chatId = settings.channels?.telegram?.allowedUserIds?.[0];
473
473
  if (!chatId) return { status: 'error', error: 'No Telegram chat_id configured.' };
474
474
  if (!token) return { status: 'error', error: 'No TELEGRAM_BOT_TOKEN configured.' };
475
- const body = JSON.stringify({ chat_id: chatId, text: args.message });
476
- await new Promise((resolve, reject) => {
475
+ const sendRequest = (body) => new Promise((resolve, reject) => {
477
476
  const req = https.request({
478
477
  hostname: 'api.telegram.org',
479
478
  path: '/bot' + token + '/sendMessage',
@@ -484,7 +483,7 @@ const SEED_TOOLS = {
484
483
  res.on('data', chunk => data += chunk);
485
484
  res.on('end', () => {
486
485
  const parsed = JSON.parse(data);
487
- if (!parsed.ok) reject(new Error(parsed.description));
486
+ if (!parsed.ok) reject(Object.assign(new Error(parsed.description), { description: parsed.description, error_code: parsed.error_code }));
488
487
  else resolve(parsed);
489
488
  });
490
489
  });
@@ -492,6 +491,15 @@ const SEED_TOOLS = {
492
491
  req.write(body);
493
492
  req.end();
494
493
  });
494
+ try {
495
+ await sendRequest(JSON.stringify({ chat_id: chatId, text: args.message, parse_mode: 'HTML' }));
496
+ } catch (e) {
497
+ if (e.error_code === 400) {
498
+ await sendRequest(JSON.stringify({ chat_id: chatId, text: args.message }));
499
+ } else {
500
+ throw e;
501
+ }
502
+ }
495
503
  return { status: 'ok', chatId };
496
504
  `,
497
505
  },
@@ -540,7 +548,10 @@ const SEED_TOOLS = {
540
548
  },
541
549
  },
542
550
  code: `
543
- const skillFile = path.join(process.env.HOME, '.jarvis/data/skills', args.name, 'skill.md');
551
+ const skillDir = path.join(process.env.HOME, '.jarvis/data/skills', args.name);
552
+ const dirFiles = await fs.promises.readdir(skillDir).catch(() => []);
553
+ const skillFileName = dirFiles.find(f => f.toLowerCase() === 'skill.md') || 'skill.md';
554
+ const skillFile = path.join(skillDir, skillFileName);
544
555
  const content = await fs.promises.readFile(skillFile, 'utf8').catch(() => null);
545
556
  if (!content) return { status: 'not_found', name: args.name };
546
557
  return { status: 'ok', name: args.name, content };