@ducci/jarvis 1.0.64 → 1.0.66
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 +95 -71
package/docs/system-prompt.md
CHANGED
|
@@ -55,7 +55,7 @@ There are two types of responses depending on whether you need to use tools:
|
|
|
55
55
|
"logSummary": "A concise explanation of what you did and why, written for a human reading the logs."
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
The `response` value must be a string — never an array or object. Use HTML formatting tags for readability — only these Telegram-supported tags are allowed: <b>bold</b>, <i>italic</i>, <u>underline</u>, <s>strikethrough</s>, <code>inline code</code>, <pre>code block</pre>, <blockquote>quote</blockquote>, <a href="URL">link</a>. For line breaks use actual newlines (\n), never <br>. 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.
|
|
58
|
+
The `response` value must be a string — never an array or object. Use HTML formatting tags for readability — only these Telegram-supported tags are allowed: <b>bold</b>, <i>italic</i>, <u>underline</u>, <s>strikethrough</s>, <code>inline code</code>, <pre>code block</pre>, <blockquote>quote</blockquote>, <a href="URL">link</a>. For line breaks use actual newlines (\n), never <br>. Never use Markdown formatting (no **, __, `, or ```). Always escape literal `<`, `>`, and `&` characters as `<`, `>`, and `&` — this applies everywhere including inside `<code>` and `<pre>` blocks (e.g. HTML snippets, shell redirects, comparisons like `x < 5`, generics like `List<String>`). In `<a href="">` URLs, escape `&` in query parameters as `&` (e.g. `?foo=1&bar=2`). Unescaped characters cause Telegram to reject the message entirely. If you need to present structured data (e.g. a list of items), format it as text within the string value.
|
|
59
59
|
|
|
60
60
|
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.
|
|
61
61
|
|
package/package.json
CHANGED
|
@@ -58,6 +58,11 @@ export async function startTelegramChannel(config) {
|
|
|
58
58
|
const bot = new Bot(token);
|
|
59
59
|
const sessions = load();
|
|
60
60
|
|
|
61
|
+
// Tracks chats with an active agent run and buffers messages arriving during that run.
|
|
62
|
+
// When the run finishes all buffered messages are merged into one combined run.
|
|
63
|
+
const isRunning = new Set();
|
|
64
|
+
const pendingMessages = new Map(); // chatId -> [{text, attachments, ts}]
|
|
65
|
+
|
|
61
66
|
await bot.api.setMyCommands([
|
|
62
67
|
{ command: 'new', description: 'Start a fresh session' },
|
|
63
68
|
{ command: 'usage', description: 'Show token usage for the current session' },
|
|
@@ -97,6 +102,7 @@ export async function startTelegramChannel(config) {
|
|
|
97
102
|
if (!allowedUserIds.includes(userId)) return;
|
|
98
103
|
|
|
99
104
|
const chatId = ctx.chat.id;
|
|
105
|
+
pendingMessages.delete(chatId);
|
|
100
106
|
if (sessions[chatId]) {
|
|
101
107
|
await appendTelegramChatLog(chatId, sessions[chatId], 'SYSTEM', '--- /new: session reset ---');
|
|
102
108
|
delete sessions[chatId];
|
|
@@ -107,22 +113,74 @@ export async function startTelegramChannel(config) {
|
|
|
107
113
|
await ctx.reply('New session started.');
|
|
108
114
|
});
|
|
109
115
|
|
|
116
|
+
// Runs one or more batches until the pending queue is drained.
|
|
117
|
+
// Each iteration takes all currently pending messages, merges them into a
|
|
118
|
+
// single user turn, calls handleChat once, and sends one response.
|
|
119
|
+
async function processQueue(api, chatId, firstBatch) {
|
|
120
|
+
let batch = firstBatch;
|
|
121
|
+
while (batch.length > 0) {
|
|
122
|
+
const sessionId = sessions[chatId] || null;
|
|
123
|
+
const combinedText = batch.length === 1
|
|
124
|
+
? batch[0].text
|
|
125
|
+
: batch.map(m => m.text).join('\n\n');
|
|
126
|
+
const allAttachments = batch.flatMap(m => m.attachments);
|
|
127
|
+
|
|
128
|
+
let result;
|
|
129
|
+
try {
|
|
130
|
+
result = await handleChat(config, sessionId, combinedText, allAttachments);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error(`[telegram] agent error chat_id=${chatId}: ${e.message}`);
|
|
133
|
+
const errText = e.message
|
|
134
|
+
? `Sorry, something went wrong: ${e.message}`
|
|
135
|
+
: 'Sorry, something went wrong. Please try again.';
|
|
136
|
+
await api.sendMessage(chatId, errText).catch(() => {});
|
|
137
|
+
batch = pendingMessages.get(chatId) || [];
|
|
138
|
+
pendingMessages.delete(chatId);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!sessions[chatId]) {
|
|
143
|
+
sessions[chatId] = result.sessionId;
|
|
144
|
+
save(sessions);
|
|
145
|
+
console.log(`[telegram] session created sessionId=${result.sessionId.slice(0, 8)}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Log each original message individually with its own timestamp
|
|
149
|
+
for (const m of batch) {
|
|
150
|
+
await appendTelegramChatLog(chatId, result.sessionId, 'USER', m.text || '[photo]', m.ts);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const rawResponse = typeof result.response === 'string'
|
|
155
|
+
? result.response
|
|
156
|
+
: result.response != null ? JSON.stringify(result.response, null, 2) : '';
|
|
157
|
+
const text = rawResponse.trim()
|
|
158
|
+
|| 'The agent encountered an error and could not produce a response. Please try again.';
|
|
159
|
+
await appendTelegramChatLog(chatId, result.sessionId, 'JARVIS', text);
|
|
160
|
+
await sendMessage(api, chatId, text, result.sessionId);
|
|
161
|
+
console.log(`[telegram] response sent chat_id=${chatId} length=${text.length}`);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(`[telegram] delivery error chat_id=${chatId}: ${e.message}`);
|
|
164
|
+
await api.sendMessage(chatId, 'Sorry, something went wrong sending the response. Please try again.').catch(() => {});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Drain any messages that arrived while we were running
|
|
168
|
+
batch = pendingMessages.get(chatId) || [];
|
|
169
|
+
pendingMessages.delete(chatId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
110
173
|
bot.on('message:photo', async (ctx) => {
|
|
111
174
|
const userId = ctx.from?.id;
|
|
112
175
|
if (!allowedUserIds.includes(userId)) return;
|
|
113
176
|
|
|
114
177
|
const chatId = ctx.chat.id;
|
|
115
|
-
const
|
|
178
|
+
const ts = new Date().toISOString();
|
|
116
179
|
|
|
117
180
|
console.log(`[telegram] incoming photo chat_id=${chatId}`);
|
|
118
181
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
ctx.api.sendChatAction(chatId, 'typing').catch(() => {});
|
|
122
|
-
}, 4000);
|
|
123
|
-
|
|
124
|
-
const userTs = new Date().toISOString();
|
|
125
|
-
let result;
|
|
182
|
+
// Download the photo first regardless of whether we buffer or run immediately
|
|
183
|
+
let attachment;
|
|
126
184
|
try {
|
|
127
185
|
const photo = ctx.message.photo.filter(p => p.width <= 800).at(-1)
|
|
128
186
|
?? ctx.message.photo[0];
|
|
@@ -131,42 +189,33 @@ export async function startTelegramChannel(config) {
|
|
|
131
189
|
const imgResponse = await fetch(fileUrl);
|
|
132
190
|
const buffer = await imgResponse.arrayBuffer();
|
|
133
191
|
const base64 = Buffer.from(buffer).toString('base64');
|
|
134
|
-
|
|
135
|
-
const caption = ctx.message.caption || '';
|
|
136
|
-
result = await handleChat(config, sessionId, caption, [{ url: dataUrl }]);
|
|
192
|
+
attachment = { url: `data:image/jpeg;base64,${base64}` };
|
|
137
193
|
} catch (e) {
|
|
138
|
-
console.error(`[telegram]
|
|
139
|
-
|
|
140
|
-
? `Sorry, something went wrong: ${e.message}`
|
|
141
|
-
: 'Sorry, something went wrong. Please try again.';
|
|
142
|
-
await ctx.reply(errText).catch(() => {});
|
|
143
|
-
clearInterval(typingInterval);
|
|
194
|
+
console.error(`[telegram] photo download error chat_id=${chatId}: ${e.message}`);
|
|
195
|
+
await ctx.reply('Sorry, could not process the photo.').catch(() => {});
|
|
144
196
|
return;
|
|
145
197
|
}
|
|
146
198
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
199
|
+
const entry = { text: ctx.message.caption || '', attachments: [attachment], ts };
|
|
200
|
+
|
|
201
|
+
if (isRunning.has(chatId)) {
|
|
202
|
+
if (!pendingMessages.has(chatId)) pendingMessages.set(chatId, []);
|
|
203
|
+
pendingMessages.get(chatId).push(entry);
|
|
204
|
+
console.log(`[telegram] buffered photo chat_id=${chatId} pending=${pendingMessages.get(chatId).length}`);
|
|
205
|
+
return;
|
|
151
206
|
}
|
|
152
207
|
|
|
153
|
-
|
|
154
|
-
await
|
|
208
|
+
isRunning.add(chatId);
|
|
209
|
+
await ctx.api.sendChatAction(chatId, 'typing');
|
|
210
|
+
const typingInterval = setInterval(() => {
|
|
211
|
+
ctx.api.sendChatAction(chatId, 'typing').catch(() => {});
|
|
212
|
+
}, 4000);
|
|
155
213
|
|
|
156
214
|
try {
|
|
157
|
-
|
|
158
|
-
? result.response
|
|
159
|
-
: result.response != null ? JSON.stringify(result.response, null, 2) : '';
|
|
160
|
-
const text = rawResponse.trim()
|
|
161
|
-
|| 'The agent encountered an error and could not produce a response. Please try again.';
|
|
162
|
-
await appendTelegramChatLog(chatId, result.sessionId, 'JARVIS', text);
|
|
163
|
-
await sendMessage(ctx.api, chatId, text, result.sessionId);
|
|
164
|
-
console.log(`[telegram] response sent chat_id=${chatId} length=${text.length}`);
|
|
165
|
-
} catch (e) {
|
|
166
|
-
console.error(`[telegram] delivery error chat_id=${chatId}: ${e.message}`);
|
|
167
|
-
await ctx.api.sendMessage(chatId, 'Sorry, something went wrong sending the response. Please try again.').catch(() => {});
|
|
215
|
+
await processQueue(ctx.api, chatId, [entry]);
|
|
168
216
|
} finally {
|
|
169
217
|
clearInterval(typingInterval);
|
|
218
|
+
isRunning.delete(chatId);
|
|
170
219
|
}
|
|
171
220
|
});
|
|
172
221
|
|
|
@@ -177,53 +226,28 @@ export async function startTelegramChannel(config) {
|
|
|
177
226
|
if (!allowedUserIds.includes(userId)) return;
|
|
178
227
|
|
|
179
228
|
const chatId = ctx.chat.id;
|
|
180
|
-
const
|
|
229
|
+
const ts = new Date().toISOString();
|
|
230
|
+
const entry = { text: ctx.message.text, attachments: [], ts };
|
|
181
231
|
|
|
182
|
-
|
|
232
|
+
if (isRunning.has(chatId)) {
|
|
233
|
+
if (!pendingMessages.has(chatId)) pendingMessages.set(chatId, []);
|
|
234
|
+
pendingMessages.get(chatId).push(entry);
|
|
235
|
+
console.log(`[telegram] buffered message chat_id=${chatId} pending=${pendingMessages.get(chatId).length}`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
183
238
|
|
|
239
|
+
isRunning.add(chatId);
|
|
240
|
+
console.log(`[telegram] incoming chat_id=${chatId}`);
|
|
184
241
|
await ctx.api.sendChatAction(chatId, 'typing');
|
|
185
242
|
const typingInterval = setInterval(() => {
|
|
186
243
|
ctx.api.sendChatAction(chatId, 'typing').catch(() => {});
|
|
187
244
|
}, 4000);
|
|
188
245
|
|
|
189
|
-
const userTs = new Date().toISOString();
|
|
190
|
-
let result;
|
|
191
|
-
try {
|
|
192
|
-
result = await handleChat(config, sessionId, ctx.message.text);
|
|
193
|
-
} catch (e) {
|
|
194
|
-
console.error(`[telegram] agent error chat_id=${chatId}: ${e.message}`);
|
|
195
|
-
const errText = e.message
|
|
196
|
-
? `Sorry, something went wrong: ${e.message}`
|
|
197
|
-
: 'Sorry, something went wrong. Please try again.';
|
|
198
|
-
await ctx.reply(errText).catch(() => {});
|
|
199
|
-
clearInterval(typingInterval);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Persist new session mapping on first message
|
|
204
|
-
if (!sessions[chatId]) {
|
|
205
|
-
sessions[chatId] = result.sessionId;
|
|
206
|
-
save(sessions);
|
|
207
|
-
console.log(`[telegram] session created sessionId=${result.sessionId.slice(0, 8)}`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
await appendTelegramChatLog(chatId, result.sessionId, 'USER', ctx.message.text, userTs);
|
|
211
|
-
|
|
212
246
|
try {
|
|
213
|
-
|
|
214
|
-
const rawResponse = typeof result.response === 'string'
|
|
215
|
-
? result.response
|
|
216
|
-
: result.response != null ? JSON.stringify(result.response, null, 2) : '';
|
|
217
|
-
const text = rawResponse.trim()
|
|
218
|
-
|| 'The agent encountered an error and could not produce a response. Please try again.';
|
|
219
|
-
await appendTelegramChatLog(chatId, result.sessionId, 'JARVIS', text);
|
|
220
|
-
await sendMessage(ctx.api, chatId, text, result.sessionId);
|
|
221
|
-
console.log(`[telegram] response sent chat_id=${chatId} length=${text.length}`);
|
|
222
|
-
} catch (e) {
|
|
223
|
-
console.error(`[telegram] delivery error chat_id=${chatId}: ${e.message}`);
|
|
224
|
-
await ctx.api.sendMessage(chatId, 'Sorry, something went wrong sending the response. Please try again.').catch(() => {});
|
|
247
|
+
await processQueue(ctx.api, chatId, [entry]);
|
|
225
248
|
} finally {
|
|
226
249
|
clearInterval(typingInterval);
|
|
250
|
+
isRunning.delete(chatId);
|
|
227
251
|
}
|
|
228
252
|
});
|
|
229
253
|
|