@ducci/jarvis 1.0.78 → 1.0.79

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ducci/jarvis",
3
- "version": "1.0.78",
3
+ "version": "1.0.79",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { Bot } from 'grammy';
3
+ import { Bot, InlineKeyboard } from 'grammy';
4
4
  import { run } from '@grammyjs/runner';
5
5
  import { handleChat, requestAbort } from '../../server/agent.js';
6
6
  import { loadSession } from '../../server/sessions.js';
@@ -164,7 +164,6 @@ export async function startTelegramChannel(config) {
164
164
  { command: 'usage', description: 'Token usage for the active slot' },
165
165
  { command: 'stop', description: 'Stop the running agent on the active slot' },
166
166
  { command: 'slots', description: 'Show all slots and their status' },
167
- { command: 'slot', description: 'Switch or delete a slot: /slot 2 or /slot del 2' },
168
167
  ]);
169
168
 
170
169
  bot.command('usage', async (ctx) => {
@@ -234,11 +233,7 @@ export async function startTelegramChannel(config) {
234
233
  await ctx.reply(`New session started on slot ${slot}.`);
235
234
  });
236
235
 
237
- bot.command('slots', async (ctx) => {
238
- const userId = ctx.from?.id;
239
- if (!allowedUserIds.includes(userId)) return;
240
-
241
- const chatId = ctx.chat.id;
236
+ function buildSlotsDisplay(chatId) {
242
237
  const d = sessions[chatId];
243
238
  const activeSlot = getActiveSlot(chatId);
244
239
 
@@ -250,7 +245,10 @@ export async function startTelegramChannel(config) {
250
245
  }
251
246
 
252
247
  const slotNums = [...new Set(['1', ...Object.keys(slotsMap)])].sort((a, b) => Number(a) - Number(b));
248
+ const maxSlot = Math.max(...slotNums.map(Number));
249
+ const nextSlot = maxSlot + 1;
253
250
 
251
+ // Status text
254
252
  const lines = ['<b>Slots:</b>'];
255
253
  for (const sn of slotNums) {
256
254
  const n = Number(sn);
@@ -275,64 +273,93 @@ export async function startTelegramChannel(config) {
275
273
  }
276
274
  lines.push(`Slot ${n}: ${statusIcon}${activeMarker}`);
277
275
  }
278
-
279
- // Always show one empty slot beyond the highest existing one
280
- const maxSlot = Math.max(...slotNums.map(Number));
281
- const nextSlot = maxSlot + 1;
282
276
  if (!isRunning.has(slotKey(chatId, nextSlot)) && !slotsMap[String(nextSlot)]) {
283
277
  lines.push(`Slot ${nextSlot}: ➕ leer`);
284
278
  }
285
279
 
286
- await ctx.reply(lines.join('\n'), { parse_mode: 'HTML' });
287
- });
280
+ // Inline keyboard
281
+ const kb = new InlineKeyboard();
282
+ for (const sn of slotNums) {
283
+ const n = Number(sn);
284
+ const sid = slotsMap[sn] ?? null;
285
+ const key = slotKey(chatId, n);
286
+ const running = isRunning.has(key);
287
+ if (n === activeSlot) {
288
+ kb.text(`✓ Slot ${n} (aktiv)`, `slots_noop`);
289
+ } else {
290
+ kb.text(`↩️ Slot ${n}`, `slots_switch_${n}`);
291
+ }
292
+ if (sid && !running) {
293
+ kb.text(`🗑️`, `slots_del_${n}`);
294
+ }
295
+ kb.row();
296
+ }
297
+ // Button for the next empty slot
298
+ kb.text(`➕ Slot ${nextSlot} (neu)`, `slots_switch_${nextSlot}`);
299
+
300
+ return { text: lines.join('\n'), keyboard: kb };
301
+ }
288
302
 
289
- bot.command('slot', async (ctx) => {
303
+ bot.command('slots', async (ctx) => {
290
304
  const userId = ctx.from?.id;
291
305
  if (!allowedUserIds.includes(userId)) return;
292
306
 
293
307
  const chatId = ctx.chat.id;
294
- const args = (ctx.match || '').trim().split(/\s+/).filter(Boolean);
308
+ const { text, keyboard } = buildSlotsDisplay(chatId);
309
+ await ctx.reply(text, { parse_mode: 'HTML', reply_markup: keyboard });
310
+ });
295
311
 
296
- // /slot del N
297
- if (args[0] === 'del') {
298
- const n = parseInt(args[1], 10);
299
- if (!n || n < 1) { await ctx.reply('Usage: /slot del <number>'); return; }
300
- const key = slotKey(chatId, n);
301
- if (isRunning.has(key) || pendingMessages.has(key)) {
302
- await ctx.reply(`Slot ${n} ist gerade aktiv. Erst /stop, dann löschen.`);
303
- return;
304
- }
305
- const oldSid = getSessionId(chatId, n);
306
- if (oldSid) {
307
- await appendTelegramChatLog(chatId, oldSid, 'SYSTEM', `--- /slot del ${n} ---`);
308
- }
309
- setSessionId(chatId, n, null);
310
- pendingMessages.delete(key);
311
- runStartTimes.delete(key);
312
- if (getActiveSlot(chatId) === n) {
313
- setActiveSlot(chatId, 1);
314
- await ctx.reply(`Slot ${n} gelöscht. Zu Slot 1 gewechselt.`);
315
- } else {
316
- await ctx.reply(`Slot ${n} gelöscht.`);
317
- }
318
- return;
319
- }
312
+ bot.callbackQuery(/^slots_switch_(\d+)$/, async (ctx) => {
313
+ const userId = ctx.from?.id;
314
+ if (!allowedUserIds.includes(userId)) { await ctx.answerCallbackQuery(); return; }
320
315
 
321
- // /slot N — switch active slot
322
- const n = parseInt(args[0], 10);
323
- if (!n || n < 1) { await ctx.reply('Usage: /slot <number> oder /slot del <number>'); return; }
316
+ const chatId = ctx.chat.id;
317
+ const n = parseInt(ctx.match[1], 10);
324
318
  setActiveSlot(chatId, n);
325
- const sid = getSessionId(chatId, n);
326
319
  const key = slotKey(chatId, n);
320
+ const sid = getSessionId(chatId, n);
327
321
  let status;
328
- if (isRunning.has(key)) {
329
- status = '🟢 läuft';
330
- } else if (sid) {
331
- status = '💬 bereit (vorhandene Session)';
332
- } else {
333
- status = '➕ leer (neue Session beim nächsten Message)';
322
+ if (isRunning.has(key)) status = '🟢 läuft';
323
+ else if (sid) status = '💬 bereit';
324
+ else status = '➕ leer (neue Session beim nächsten Message)';
325
+
326
+ const { text, keyboard } = buildSlotsDisplay(chatId);
327
+ await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
328
+ await ctx.answerCallbackQuery(`Slot ${n} aktiv — ${status}`);
329
+ });
330
+
331
+ bot.callbackQuery(/^slots_del_(\d+)$/, async (ctx) => {
332
+ const userId = ctx.from?.id;
333
+ if (!allowedUserIds.includes(userId)) { await ctx.answerCallbackQuery(); return; }
334
+
335
+ const chatId = ctx.chat.id;
336
+ const n = parseInt(ctx.match[1], 10);
337
+ const key = slotKey(chatId, n);
338
+
339
+ if (isRunning.has(key) || pendingMessages.has(key)) {
340
+ await ctx.answerCallbackQuery(`Slot ${n} läuft gerade — erst /stop`);
341
+ return;
342
+ }
343
+
344
+ const oldSid = getSessionId(chatId, n);
345
+ if (oldSid) {
346
+ await appendTelegramChatLog(chatId, oldSid, 'SYSTEM', `--- slot del ${n} (via keyboard) ---`);
334
347
  }
335
- await ctx.reply(`Slot ${n} ist jetzt aktiv. Status: ${status}`);
348
+ setSessionId(chatId, n, null);
349
+ pendingMessages.delete(key);
350
+ runStartTimes.delete(key);
351
+
352
+ if (getActiveSlot(chatId) === n) {
353
+ setActiveSlot(chatId, 1);
354
+ }
355
+
356
+ const { text, keyboard } = buildSlotsDisplay(chatId);
357
+ await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
358
+ await ctx.answerCallbackQuery(`Slot ${n} gelöscht`);
359
+ });
360
+
361
+ bot.callbackQuery('slots_noop', async (ctx) => {
362
+ await ctx.answerCallbackQuery();
336
363
  });
337
364
 
338
365
  // Runs one or more batches until the pending queue is drained.