@ducci/jarvis 1.0.78 → 1.0.80
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/channels/telegram/index.js +187 -54
- package/src/server/config.js +1 -0
- package/src/server/crons.js +25 -8
- package/src/server/tools.js +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
const _require = createRequire(import.meta.url);
|
|
8
|
+
const { version: JARVIS_VERSION } = _require('../../../package.json');
|
|
9
|
+
import { Bot, InlineKeyboard } from 'grammy';
|
|
4
10
|
import { run } from '@grammyjs/runner';
|
|
5
11
|
import { handleChat, requestAbort } from '../../server/agent.js';
|
|
6
12
|
import { loadSession } from '../../server/sessions.js';
|
|
@@ -90,6 +96,33 @@ async function sendMessage(api, chatId, text, sessionId) {
|
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
// Known model context windows in tokens. Used by /context command.
|
|
100
|
+
// Partial match: checked with model.includes(key) so short keys like 'gpt-4o' match 'openrouter/gpt-4o'.
|
|
101
|
+
const MODEL_CONTEXT_WINDOWS = {
|
|
102
|
+
'claude': 200000, // all claude models (opus, sonnet, haiku)
|
|
103
|
+
'gpt-4o': 128000,
|
|
104
|
+
'gpt-4-turbo': 128000,
|
|
105
|
+
'gpt-3.5': 16385,
|
|
106
|
+
'gemini-1.5-pro': 1000000,
|
|
107
|
+
'gemini-1.5-flash': 1000000,
|
|
108
|
+
'gemini-2': 1000000,
|
|
109
|
+
'llama-3.3': 128000,
|
|
110
|
+
'llama-3.1': 128000,
|
|
111
|
+
'mistral': 32000,
|
|
112
|
+
'deepseek': 64000,
|
|
113
|
+
'glm-5': 200000,
|
|
114
|
+
'glm-4': 128000,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
function lookupContextWindow(model) {
|
|
118
|
+
if (!model) return null;
|
|
119
|
+
const m = model.toLowerCase();
|
|
120
|
+
for (const [key, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
|
|
121
|
+
if (m.includes(key)) return size;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
93
126
|
export async function startTelegramChannel(config) {
|
|
94
127
|
const { token, allowedUserIds } = config.telegram;
|
|
95
128
|
|
|
@@ -160,11 +193,14 @@ export async function startTelegramChannel(config) {
|
|
|
160
193
|
// --- Commands ---
|
|
161
194
|
|
|
162
195
|
await bot.api.setMyCommands([
|
|
163
|
-
{ command: 'new',
|
|
164
|
-
{ command: 'usage',
|
|
165
|
-
{ command: '
|
|
166
|
-
{ command: '
|
|
167
|
-
{ command: '
|
|
196
|
+
{ command: 'new', description: 'Reset the active slot (fresh session)' },
|
|
197
|
+
{ command: 'usage', description: 'Token usage for the active slot' },
|
|
198
|
+
{ command: 'context', description: 'Estimated context size vs model limit' },
|
|
199
|
+
{ command: 'stop', description: 'Stop the running agent on the active slot' },
|
|
200
|
+
{ command: 'slots', description: 'Show all slots and their status' },
|
|
201
|
+
{ command: 'version', description: 'Show Jarvis version' },
|
|
202
|
+
{ command: 'update', description: 'Update Jarvis to the latest version' },
|
|
203
|
+
{ command: 'restart', description: 'Restart Jarvis' },
|
|
168
204
|
]);
|
|
169
205
|
|
|
170
206
|
bot.command('usage', async (ctx) => {
|
|
@@ -197,6 +233,75 @@ export async function startTelegramChannel(config) {
|
|
|
197
233
|
);
|
|
198
234
|
});
|
|
199
235
|
|
|
236
|
+
bot.command('version', async (ctx) => {
|
|
237
|
+
const userId = ctx.from?.id;
|
|
238
|
+
if (!allowedUserIds.includes(userId)) return;
|
|
239
|
+
await ctx.reply(`Jarvis v${JARVIS_VERSION}`);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
bot.command('update', async (ctx) => {
|
|
243
|
+
const userId = ctx.from?.id;
|
|
244
|
+
if (!allowedUserIds.includes(userId)) return;
|
|
245
|
+
await ctx.reply('Updating Jarvis...');
|
|
246
|
+
try {
|
|
247
|
+
const { stdout, stderr } = await execAsync('npm install -g @ducci/jarvis@latest', { timeout: 120000 });
|
|
248
|
+
const out = (stdout + stderr).trim().slice(-1000) || 'Done.';
|
|
249
|
+
await ctx.api.sendMessage(ctx.chat.id, `Update complete:\n${out}`);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
await ctx.api.sendMessage(ctx.chat.id, `Update failed:\n${e.message.slice(0, 1000)}`);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
bot.command('restart', async (ctx) => {
|
|
256
|
+
const userId = ctx.from?.id;
|
|
257
|
+
if (!allowedUserIds.includes(userId)) return;
|
|
258
|
+
await ctx.reply('Restarting Jarvis...');
|
|
259
|
+
// Fire and forget — process will exit before a response could be sent
|
|
260
|
+
setTimeout(() => execAsync('jarvis restart').catch(() => {}), 500);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
bot.command('context', async (ctx) => {
|
|
264
|
+
const userId = ctx.from?.id;
|
|
265
|
+
if (!allowedUserIds.includes(userId)) return;
|
|
266
|
+
|
|
267
|
+
const chatId = ctx.chat.id;
|
|
268
|
+
const slot = getActiveSlot(chatId);
|
|
269
|
+
const sessionId = getSessionId(chatId, slot);
|
|
270
|
+
if (!sessionId) {
|
|
271
|
+
await ctx.reply('No active session. Send a message to start one.');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const session = await loadSession(sessionId);
|
|
276
|
+
if (!session) {
|
|
277
|
+
await ctx.reply('Could not load session.');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const msgCount = Math.max(0, session.messages.length - 1); // exclude system prompt
|
|
282
|
+
const estimatedTokens = Math.round(JSON.stringify(session.messages).length / 4);
|
|
283
|
+
const model = config.selectedModel || 'unknown';
|
|
284
|
+
const contextWindow = config.modelContextWindow || lookupContextWindow(model);
|
|
285
|
+
|
|
286
|
+
let lines = [
|
|
287
|
+
`<b>Context — Slot ${slot}</b>`,
|
|
288
|
+
`Model: <code>${escapeHtml(model)}</code>`,
|
|
289
|
+
`Messages in history: ${msgCount}`,
|
|
290
|
+
`Estimated tokens: ~${estimatedTokens.toLocaleString()}`,
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
if (contextWindow) {
|
|
294
|
+
const pct = Math.round((estimatedTokens / contextWindow) * 100);
|
|
295
|
+
const bar = pct >= 90 ? '🔴' : pct >= 70 ? '🟡' : '🟢';
|
|
296
|
+
lines.push(`Model context window: ${contextWindow.toLocaleString()}`);
|
|
297
|
+
lines.push(`Usage: ${bar} ~${pct}%`);
|
|
298
|
+
} else {
|
|
299
|
+
lines.push(`Model context window: unknown`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await ctx.reply(lines.join('\n'), { parse_mode: 'HTML' });
|
|
303
|
+
});
|
|
304
|
+
|
|
200
305
|
bot.command('stop', async (ctx) => {
|
|
201
306
|
const userId = ctx.from?.id;
|
|
202
307
|
if (!allowedUserIds.includes(userId)) return;
|
|
@@ -234,11 +339,7 @@ export async function startTelegramChannel(config) {
|
|
|
234
339
|
await ctx.reply(`New session started on slot ${slot}.`);
|
|
235
340
|
});
|
|
236
341
|
|
|
237
|
-
|
|
238
|
-
const userId = ctx.from?.id;
|
|
239
|
-
if (!allowedUserIds.includes(userId)) return;
|
|
240
|
-
|
|
241
|
-
const chatId = ctx.chat.id;
|
|
342
|
+
function buildSlotsDisplay(chatId) {
|
|
242
343
|
const d = sessions[chatId];
|
|
243
344
|
const activeSlot = getActiveSlot(chatId);
|
|
244
345
|
|
|
@@ -250,7 +351,10 @@ export async function startTelegramChannel(config) {
|
|
|
250
351
|
}
|
|
251
352
|
|
|
252
353
|
const slotNums = [...new Set(['1', ...Object.keys(slotsMap)])].sort((a, b) => Number(a) - Number(b));
|
|
354
|
+
const maxSlot = Math.max(...slotNums.map(Number));
|
|
355
|
+
const nextSlot = maxSlot + 1;
|
|
253
356
|
|
|
357
|
+
// Status text
|
|
254
358
|
const lines = ['<b>Slots:</b>'];
|
|
255
359
|
for (const sn of slotNums) {
|
|
256
360
|
const n = Number(sn);
|
|
@@ -275,64 +379,93 @@ export async function startTelegramChannel(config) {
|
|
|
275
379
|
}
|
|
276
380
|
lines.push(`Slot ${n}: ${statusIcon}${activeMarker}`);
|
|
277
381
|
}
|
|
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
382
|
if (!isRunning.has(slotKey(chatId, nextSlot)) && !slotsMap[String(nextSlot)]) {
|
|
283
383
|
lines.push(`Slot ${nextSlot}: ➕ leer`);
|
|
284
384
|
}
|
|
285
385
|
|
|
286
|
-
|
|
287
|
-
|
|
386
|
+
// Inline keyboard
|
|
387
|
+
const kb = new InlineKeyboard();
|
|
388
|
+
for (const sn of slotNums) {
|
|
389
|
+
const n = Number(sn);
|
|
390
|
+
const sid = slotsMap[sn] ?? null;
|
|
391
|
+
const key = slotKey(chatId, n);
|
|
392
|
+
const running = isRunning.has(key);
|
|
393
|
+
if (n === activeSlot) {
|
|
394
|
+
kb.text(`✓ Slot ${n} (aktiv)`, `slots_noop`);
|
|
395
|
+
} else {
|
|
396
|
+
kb.text(`↩️ Slot ${n}`, `slots_switch_${n}`);
|
|
397
|
+
}
|
|
398
|
+
if (sid && !running) {
|
|
399
|
+
kb.text(`🗑️`, `slots_del_${n}`);
|
|
400
|
+
}
|
|
401
|
+
kb.row();
|
|
402
|
+
}
|
|
403
|
+
// Button for the next empty slot
|
|
404
|
+
kb.text(`➕ Slot ${nextSlot} (neu)`, `slots_switch_${nextSlot}`);
|
|
405
|
+
|
|
406
|
+
return { text: lines.join('\n'), keyboard: kb };
|
|
407
|
+
}
|
|
288
408
|
|
|
289
|
-
bot.command('
|
|
409
|
+
bot.command('slots', async (ctx) => {
|
|
290
410
|
const userId = ctx.from?.id;
|
|
291
411
|
if (!allowedUserIds.includes(userId)) return;
|
|
292
412
|
|
|
293
413
|
const chatId = ctx.chat.id;
|
|
294
|
-
const
|
|
414
|
+
const { text, keyboard } = buildSlotsDisplay(chatId);
|
|
415
|
+
await ctx.reply(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
416
|
+
});
|
|
295
417
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
}
|
|
418
|
+
bot.callbackQuery(/^slots_switch_(\d+)$/, async (ctx) => {
|
|
419
|
+
const userId = ctx.from?.id;
|
|
420
|
+
if (!allowedUserIds.includes(userId)) { await ctx.answerCallbackQuery(); return; }
|
|
320
421
|
|
|
321
|
-
|
|
322
|
-
const n = parseInt(
|
|
323
|
-
if (!n || n < 1) { await ctx.reply('Usage: /slot <number> oder /slot del <number>'); return; }
|
|
422
|
+
const chatId = ctx.chat.id;
|
|
423
|
+
const n = parseInt(ctx.match[1], 10);
|
|
324
424
|
setActiveSlot(chatId, n);
|
|
325
|
-
const sid = getSessionId(chatId, n);
|
|
326
425
|
const key = slotKey(chatId, n);
|
|
426
|
+
const sid = getSessionId(chatId, n);
|
|
327
427
|
let status;
|
|
328
|
-
if (isRunning.has(key))
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
428
|
+
if (isRunning.has(key)) status = '🟢 läuft';
|
|
429
|
+
else if (sid) status = '💬 bereit';
|
|
430
|
+
else status = '➕ leer (neue Session beim nächsten Message)';
|
|
431
|
+
|
|
432
|
+
const { text, keyboard } = buildSlotsDisplay(chatId);
|
|
433
|
+
await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
434
|
+
await ctx.answerCallbackQuery(`Slot ${n} aktiv — ${status}`);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
bot.callbackQuery(/^slots_del_(\d+)$/, async (ctx) => {
|
|
438
|
+
const userId = ctx.from?.id;
|
|
439
|
+
if (!allowedUserIds.includes(userId)) { await ctx.answerCallbackQuery(); return; }
|
|
440
|
+
|
|
441
|
+
const chatId = ctx.chat.id;
|
|
442
|
+
const n = parseInt(ctx.match[1], 10);
|
|
443
|
+
const key = slotKey(chatId, n);
|
|
444
|
+
|
|
445
|
+
if (isRunning.has(key) || pendingMessages.has(key)) {
|
|
446
|
+
await ctx.answerCallbackQuery(`Slot ${n} läuft gerade — erst /stop`);
|
|
447
|
+
return;
|
|
334
448
|
}
|
|
335
|
-
|
|
449
|
+
|
|
450
|
+
const oldSid = getSessionId(chatId, n);
|
|
451
|
+
if (oldSid) {
|
|
452
|
+
await appendTelegramChatLog(chatId, oldSid, 'SYSTEM', `--- slot del ${n} (via keyboard) ---`);
|
|
453
|
+
}
|
|
454
|
+
setSessionId(chatId, n, null);
|
|
455
|
+
pendingMessages.delete(key);
|
|
456
|
+
runStartTimes.delete(key);
|
|
457
|
+
|
|
458
|
+
if (getActiveSlot(chatId) === n) {
|
|
459
|
+
setActiveSlot(chatId, 1);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const { text, keyboard } = buildSlotsDisplay(chatId);
|
|
463
|
+
await ctx.editMessageText(text, { parse_mode: 'HTML', reply_markup: keyboard });
|
|
464
|
+
await ctx.answerCallbackQuery(`Slot ${n} gelöscht`);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
bot.callbackQuery('slots_noop', async (ctx) => {
|
|
468
|
+
await ctx.answerCallbackQuery();
|
|
336
469
|
});
|
|
337
470
|
|
|
338
471
|
// Runs one or more batches until the pending queue is drained.
|
package/src/server/config.js
CHANGED
|
@@ -72,6 +72,7 @@ export function loadConfig() {
|
|
|
72
72
|
maxIterations: settings.maxIterations || 20,
|
|
73
73
|
maxHandoffs: settings.maxHandoffs || 3,
|
|
74
74
|
contextWindow: settings.contextWindow || 100,
|
|
75
|
+
modelContextWindow: settings.modelContextWindow || null,
|
|
75
76
|
port: settings.port || 18008,
|
|
76
77
|
telegram: {
|
|
77
78
|
token: process.env.TELEGRAM_BOT_TOKEN || null,
|
package/src/server/crons.js
CHANGED
|
@@ -45,6 +45,15 @@ export async function runCron(entry, config) {
|
|
|
45
45
|
const failedApproaches = [];
|
|
46
46
|
const checkpointState = {};
|
|
47
47
|
|
|
48
|
+
// Compact tool call representation for logs: "tool_name first_arg_value"
|
|
49
|
+
function compactToolCalls(toolCalls) {
|
|
50
|
+
return (toolCalls || []).map(tc => {
|
|
51
|
+
const firstVal = Object.values(tc.args || {})[0];
|
|
52
|
+
const argStr = firstVal !== undefined ? ' ' + String(firstVal).slice(0, 80) : '';
|
|
53
|
+
return `${tc.name}${argStr}`;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
try {
|
|
49
58
|
while (true) {
|
|
50
59
|
const runStartIndex = session.messages.length;
|
|
@@ -53,9 +62,25 @@ export async function runCron(entry, config) {
|
|
|
53
62
|
run = await runAgentLoop(client, config, session, prepareMessages, usageAccum);
|
|
54
63
|
} catch (e) {
|
|
55
64
|
run = { status: 'error', response: e.message, logSummary: e.message, runToolCalls: [] };
|
|
65
|
+
await appendCronLog(entry.id, {
|
|
66
|
+
cronName: entry.name,
|
|
67
|
+
handoff: handoffCount + 1,
|
|
68
|
+
status: run.status,
|
|
69
|
+
logSummary: run.logSummary,
|
|
70
|
+
toolCalls: [],
|
|
71
|
+
}).catch(e => console.error(`[cron] log error: ${e.message}`));
|
|
56
72
|
break;
|
|
57
73
|
}
|
|
58
74
|
|
|
75
|
+
await appendCronLog(entry.id, {
|
|
76
|
+
cronName: entry.name,
|
|
77
|
+
handoff: handoffCount + 1,
|
|
78
|
+
status: run.status,
|
|
79
|
+
logSummary: run.logSummary,
|
|
80
|
+
...(run.status !== 'checkpoint_reached' && { response: run.response }),
|
|
81
|
+
toolCalls: compactToolCalls(run.runToolCalls),
|
|
82
|
+
}).catch(e => console.error(`[cron] log error: ${e.message}`));
|
|
83
|
+
|
|
59
84
|
if (run.status !== 'checkpoint_reached') break;
|
|
60
85
|
|
|
61
86
|
if (run.checkpoint.failedApproaches?.length > 0) {
|
|
@@ -103,14 +128,6 @@ export async function runCron(entry, config) {
|
|
|
103
128
|
run = { status: 'error', response: e.message, logSummary: e.message, runToolCalls: [] };
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
// Log to cron JSONL
|
|
107
|
-
await appendCronLog(entry.id, {
|
|
108
|
-
cronName: entry.name,
|
|
109
|
-
status: run.status,
|
|
110
|
-
response: run.response,
|
|
111
|
-
logSummary: run.logSummary,
|
|
112
|
-
}).catch(e => console.error(`[cron] log error: ${e.message}`));
|
|
113
|
-
|
|
114
131
|
// once: true — delete after firing
|
|
115
132
|
if (entry.once) {
|
|
116
133
|
try {
|
package/src/server/tools.js
CHANGED
|
@@ -571,12 +571,13 @@ const SEED_TOOLS = {
|
|
|
571
571
|
type: 'function',
|
|
572
572
|
function: {
|
|
573
573
|
name: 'read_cron_log',
|
|
574
|
-
description: 'Read cron execution logs. Without id: returns recent runs across all crons (last 8 most recently active cron files, 5 entries each). With id: returns runs for that specific cron.',
|
|
574
|
+
description: 'Read cron execution logs. Without id: returns recent runs across all crons (last 8 most recently active cron files, 5 entries each). With id: returns runs for that specific cron. Each cron execution logs one entry per handoff. Use verbose:true to include tool call details for debugging.',
|
|
575
575
|
parameters: {
|
|
576
576
|
type: 'object',
|
|
577
577
|
properties: {
|
|
578
578
|
id: { type: 'string', description: 'The cron id. Omit to get an overview across all crons.' },
|
|
579
579
|
limit: { type: 'number', description: 'Max entries to return when reading a specific cron. Defaults to 20.' },
|
|
580
|
+
verbose: { type: 'boolean', description: 'Include toolCalls array in each entry. Default false.' },
|
|
580
581
|
},
|
|
581
582
|
required: [],
|
|
582
583
|
},
|
|
@@ -584,6 +585,12 @@ const SEED_TOOLS = {
|
|
|
584
585
|
},
|
|
585
586
|
code: `
|
|
586
587
|
const logsDir = path.join(process.env.HOME, '.jarvis/logs');
|
|
588
|
+
const verbose = !!args.verbose;
|
|
589
|
+
function strip(entry) {
|
|
590
|
+
if (verbose) return entry;
|
|
591
|
+
const { toolCalls, ...rest } = entry;
|
|
592
|
+
return rest;
|
|
593
|
+
}
|
|
587
594
|
if (!args.id) {
|
|
588
595
|
const files = await fs.promises.readdir(logsDir).catch(() => []);
|
|
589
596
|
const cronFiles = files.filter(f => f.startsWith('cron-') && f.endsWith('.jsonl'));
|
|
@@ -596,7 +603,7 @@ const SEED_TOOLS = {
|
|
|
596
603
|
for (const { file } of withMtime.slice(0, 8)) {
|
|
597
604
|
const content = await fs.promises.readFile(path.join(logsDir, file), 'utf8').catch(() => '');
|
|
598
605
|
const lines = content.trim().split('\\n').filter(Boolean);
|
|
599
|
-
allEntries.push(...lines.slice(-5).map(line => JSON.parse(line)));
|
|
606
|
+
allEntries.push(...lines.slice(-5).map(line => strip(JSON.parse(line))));
|
|
600
607
|
}
|
|
601
608
|
allEntries.sort((a, b) => new Date(b.ts) - new Date(a.ts));
|
|
602
609
|
return { status: 'ok', entries: allEntries };
|
|
@@ -604,7 +611,7 @@ const SEED_TOOLS = {
|
|
|
604
611
|
const logFile = path.join(logsDir, 'cron-' + args.id + '.jsonl');
|
|
605
612
|
const content = await fs.promises.readFile(logFile, 'utf8').catch(() => '');
|
|
606
613
|
const lines = content.trim().split('\\n').filter(Boolean);
|
|
607
|
-
const entries = lines.slice(-(args.limit || 20)).map(line => JSON.parse(line));
|
|
614
|
+
const entries = lines.slice(-(args.limit || 20)).map(line => strip(JSON.parse(line)));
|
|
608
615
|
return { status: 'ok', entries };
|
|
609
616
|
`,
|
|
610
617
|
},
|