@ducci/jarvis 1.0.43 → 1.0.45

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server/tools.js +43 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ducci/jarvis",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -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
- list_dir: {
14
+ read_file: {
15
15
  definition: {
16
16
  type: 'function',
17
17
  function: {
18
- name: 'list_dir',
19
- description: 'List directory contents (similar to ls -la). Use this to explore the filesystem and see what files and directories exist at a given path.',
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: 'Directory path to list. Defaults to the current working directory if omitted.',
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 { execFile } = require("child_process");
34
- const { promisify } = require("util");
35
- const execFileAsync = promisify(execFile);
36
- const targetPath = args.path || process.cwd();
37
- const resolved = path.resolve(targetPath);
38
- const { stdout: output } = await execFileAsync("ls", ["-la", resolved], {
39
- encoding: "utf8",
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: {
@@ -384,7 +389,7 @@ const SEED_TOOLS = {
384
389
  properties: {
385
390
  name: { type: 'string', description: 'Short identifier for this cron, e.g. "backup-nightly".' },
386
391
  schedule: { type: 'string', description: 'Cron expression, e.g. "0 3 * * *" for 3am daily. For a one-time task, compute the exact time from get_current_time and express it as a cron expression.' },
387
- prompt: { type: 'string', description: 'The task prompt the agent will receive when this cron fires. Must be self-contained. Include "use send_telegram_message to notify the user with the result" if notification is desired.' },
392
+ prompt: { type: 'string', description: 'The task prompt the agent will receive when this cron fires. Must be self-contained. If notification is desired, include: "use send_telegram_message to notify the user with the result. Prefix the message with [Cron: \"<name>\" | <timestamp>] where <name> is the cron name and <timestamp> is the current date and time."' },
388
393
  once: { type: 'boolean', description: 'If true, the cron deletes itself after firing once. Use for one-time reminders or tasks.' },
389
394
  },
390
395
  required: ['name', 'schedule', 'prompt'],
@@ -508,24 +513,40 @@ const SEED_TOOLS = {
508
513
  type: 'function',
509
514
  function: {
510
515
  name: 'read_cron_log',
511
- description: 'Read the execution log for a cron job. Returns the most recent runs with status, response, and logSummary.',
516
+ 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.',
512
517
  parameters: {
513
518
  type: 'object',
514
519
  properties: {
515
- id: { type: 'string', description: 'The cron id.' },
516
- limit: { type: 'number', description: 'Max entries to return. Defaults to 20.' },
520
+ id: { type: 'string', description: 'The cron id. Omit to get an overview across all crons.' },
521
+ limit: { type: 'number', description: 'Max entries to return when reading a specific cron. Defaults to 20.' },
517
522
  },
518
- required: ['id'],
523
+ required: [],
519
524
  },
520
525
  },
521
526
  },
522
527
  code: `
523
528
  const logsDir = path.join(process.env.HOME, '.jarvis/logs');
529
+ if (!args.id) {
530
+ const files = await fs.promises.readdir(logsDir).catch(() => []);
531
+ const cronFiles = files.filter(f => f.startsWith('cron-') && f.endsWith('.jsonl'));
532
+ const withMtime = await Promise.all(cronFiles.map(async f => {
533
+ const stat = await fs.promises.stat(path.join(logsDir, f));
534
+ return { file: f, mtime: stat.mtimeMs };
535
+ }));
536
+ withMtime.sort((a, b) => b.mtime - a.mtime);
537
+ const allEntries = [];
538
+ for (const { file } of withMtime.slice(0, 8)) {
539
+ const content = await fs.promises.readFile(path.join(logsDir, file), 'utf8').catch(() => '');
540
+ const lines = content.trim().split('\\n').filter(Boolean);
541
+ allEntries.push(...lines.slice(-5).map(line => JSON.parse(line)));
542
+ }
543
+ allEntries.sort((a, b) => new Date(b.ts) - new Date(a.ts));
544
+ return { status: 'ok', entries: allEntries };
545
+ }
524
546
  const logFile = path.join(logsDir, 'cron-' + args.id + '.jsonl');
525
547
  const content = await fs.promises.readFile(logFile, 'utf8').catch(() => '');
526
548
  const lines = content.trim().split('\\n').filter(Boolean);
527
- const limit = args.limit || 20;
528
- const entries = lines.slice(-limit).map(line => JSON.parse(line));
549
+ const entries = lines.slice(-(args.limit || 20)).map(line => JSON.parse(line));
529
550
  return { status: 'ok', entries };
530
551
  `,
531
552
  },
@@ -557,50 +578,6 @@ const SEED_TOOLS = {
557
578
  return { status: 'ok', name: args.name, content };
558
579
  `,
559
580
  },
560
- get_recent_sessions: {
561
- definition: {
562
- type: 'function',
563
- function: {
564
- name: 'get_recent_sessions',
565
- description: 'Returns the most recent sessions with their titles and timestamps. Use this to find previous conversations.',
566
- parameters: {
567
- type: 'object',
568
- properties: {
569
- limit: {
570
- type: 'number',
571
- description: 'Number of recent sessions to return. Defaults to 2.',
572
- },
573
- },
574
- required: [],
575
- },
576
- },
577
- },
578
- code: `const logsDir = path.join(process.env.HOME, '.jarvis/logs'); const limit = args.limit || 2; const files = await fs.promises.readdir(logsDir).catch(() => []); const sessionFiles = files.filter(f => f.startsWith('session-') && f.endsWith('.jsonl')); const sessions = []; for (const file of sessionFiles) { const sessionId = file.replace('session-', '').replace('.jsonl', ''); const content = await fs.promises.readFile(path.join(logsDir, file), 'utf8'); const lines = content.trim().split('\\n').filter(Boolean); if (lines.length === 0) continue; const firstEntry = JSON.parse(lines[0]); const lastEntry = JSON.parse(lines[lines.length - 1]); sessions.push({ sessionId, title: (firstEntry.logSummary || '').substring(0, 80), lastTs: lastEntry.ts }); } sessions.sort((a, b) => new Date(b.lastTs) - new Date(a.lastTs)); return { status: 'ok', sessions: sessions.slice(0, limit) };`,
579
- },
580
- read_session_log: {
581
- definition: {
582
- type: 'function',
583
- function: {
584
- name: 'read_session_log',
585
- description: 'Read JSONL log entries for a given session. Use this to inspect what happened in a previous run, including tool calls, errors, and summaries.',
586
- parameters: {
587
- type: 'object',
588
- properties: {
589
- sessionId: {
590
- type: 'string',
591
- description: 'The session ID to read logs for.',
592
- },
593
- limit: {
594
- type: 'number',
595
- description: 'Maximum number of entries to return (from the end). Defaults to 20.',
596
- },
597
- },
598
- required: ['sessionId'],
599
- },
600
- },
601
- },
602
- code: `const logsDir = path.join(process.env.HOME, '.jarvis/logs'); const logFile = path.join(logsDir, 'session-' + args.sessionId + '.jsonl'); const content = await fs.promises.readFile(logFile, 'utf8').catch(() => ''); const lines = content.trim().split('\\n').filter(Boolean); const limit = args.limit || 20; const entries = lines.slice(-limit).map(line => JSON.parse(line)); return { status: 'ok', entries };`,
603
- },
604
581
  };
605
582
 
606
583
  export function seedTools() {