@ducci/jarvis 1.0.73 → 1.0.75

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/cli.md CHANGED
@@ -33,6 +33,10 @@ Lifecycle management is handled by the CLI using the **programmatic PM2 API** fo
33
33
  - The process is named `jarvis-server`.
34
34
  - Enables `autorestart` on crash.
35
35
  - Merges logs into a single file in the user's data directory.
36
+ - Calls `pm2 save` (`pm2.dump()`) after start to persist the process list for reboot recovery.
37
+ - Prints a tip to run `pm2 startup` once to register the PM2 boot hook with the OS init system.
38
+
39
+ > **Reboot persistence**: `pm2 save` persists the process list; `pm2 startup` (run once, may need sudo) installs the OS-level boot hook so PM2 — and therefore Jarvis — starts automatically after a system reboot.
36
40
 
37
41
  ### `jarvis stop`
38
42
  - Stops the background process named `jarvis-server` using PM2.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ducci/jarvis",
3
- "version": "1.0.73",
3
+ "version": "1.0.75",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -24,6 +24,12 @@ function escapeHtml(str) {
24
24
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
25
25
  }
26
26
 
27
+ function stripHtml(text) {
28
+ return text
29
+ .replace(/<[^>]+>/g, '')
30
+ .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
31
+ }
32
+
27
33
  function markdownToHtml(text) {
28
34
  // 0. Sanitize unsupported Telegram HTML tags
29
35
  // Headings → <b>
@@ -76,7 +82,7 @@ async function sendMessage(api, chatId, text, sessionId) {
76
82
  description: e.description || e.message,
77
83
  }) + '\n', 'utf8').catch(() => {});
78
84
  }
79
- await api.sendMessage(chatId, chunk);
85
+ await api.sendMessage(chatId, stripHtml(chunk));
80
86
  } else {
81
87
  throw e;
82
88
  }
package/src/index.js CHANGED
@@ -95,6 +95,15 @@ function pm2Restart() {
95
95
  });
96
96
  }
97
97
 
98
+ function pm2Save() {
99
+ return new Promise((resolve, reject) => {
100
+ pm2.dump((err) => {
101
+ if (err) reject(err);
102
+ else resolve();
103
+ });
104
+ });
105
+ }
106
+
98
107
  const program = new Command();
99
108
 
100
109
  program
@@ -154,7 +163,9 @@ program
154
163
  return;
155
164
  }
156
165
  await pm2Start();
166
+ await pm2Save();
157
167
  console.log('Jarvis server started.');
168
+ console.log('Tip: run `pm2 startup` once to make Jarvis survive system reboots.');
158
169
  pm2.disconnect();
159
170
  } catch (e) {
160
171
  console.error('Failed to start Jarvis server:', e.message);
@@ -191,10 +202,13 @@ program
191
202
  const isRunning = desc.length > 0 && desc[0].pm2_env?.status === 'online';
192
203
  if (isRunning) {
193
204
  await pm2Restart();
205
+ await pm2Save();
194
206
  console.log('Jarvis server restarted.');
195
207
  } else {
196
208
  await pm2Start();
209
+ await pm2Save();
197
210
  console.log('Jarvis server started.');
211
+ console.log('Tip: run `pm2 startup` once to make Jarvis survive system reboots.');
198
212
  }
199
213
  pm2.disconnect();
200
214
  } catch (e) {
@@ -710,6 +710,10 @@ async function run() {
710
710
  }
711
711
 
712
712
  console.log(chalk.green.bold('\nSetup complete!'));
713
+ console.log(chalk.cyan('\nNext steps:'));
714
+ console.log(' 1. Run ' + chalk.bold('jarvis start') + ' to launch the server.');
715
+ console.log(' 2. Run ' + chalk.bold('pm2 startup') + ' once to make Jarvis survive system reboots.');
716
+ console.log(' (Follow the command it prints — usually needs sudo on Linux.)');
713
717
  }
714
718
 
715
719
  run().catch(error => {
@@ -1,12 +1,10 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { runAgentLoop, withSessionLock } from './agent.js';
3
+ import { runAgentLoop } from './agent.js';
4
4
  import { createClient } from './provider.js';
5
5
  import { loadSystemPrompt, resolveSystemPrompt, PATHS } from './config.js';
6
- import { createSession, loadSession, saveSession } from './sessions.js';
6
+ import { createSession } from './sessions.js';
7
7
  import * as cronScheduler from './cron-scheduler.js';
8
- import { load as loadTelegramSessions } from '../channels/telegram/sessions.js';
9
-
10
8
  function loadCrons() {
11
9
  try {
12
10
  return JSON.parse(fs.readFileSync(PATHS.cronsFile, 'utf8'));
@@ -21,25 +19,6 @@ async function appendCronLog(cronId, entry) {
21
19
  await fs.promises.appendFile(logFile, line, 'utf8');
22
20
  }
23
21
 
24
- async function writeSyntheticMessageToTelegramSession(entry, response, config) {
25
- const chatId = config.telegram?.allowedUserIds?.[0];
26
- if (!chatId) return;
27
-
28
- const sessions = loadTelegramSessions();
29
- const sessionId = sessions[chatId];
30
- if (!sessionId) return;
31
-
32
- const tz = config.timezone || 'Europe/Berlin';
33
- const ts = new Date().toLocaleString('sv', { timeZone: tz }).slice(0, 16);
34
- const syntheticMessage = `[Cron "${entry.name}" | ${ts}] ${response}`;
35
-
36
- await withSessionLock(sessionId, async () => {
37
- const session = await loadSession(sessionId);
38
- if (!session) return;
39
- session.messages.push({ role: 'assistant', content: syntheticMessage });
40
- await saveSession(sessionId, session);
41
- });
42
- }
43
22
 
44
23
  export async function runCron(entry, config) {
45
24
  console.log(`[cron] running "${entry.name}"`);
@@ -128,11 +107,6 @@ export async function runCron(entry, config) {
128
107
  logSummary: run.logSummary,
129
108
  }).catch(e => console.error(`[cron] log error: ${e.message}`));
130
109
 
131
- // Write synthetic message to user's Telegram session
132
- await writeSyntheticMessageToTelegramSession(entry, run.response, config).catch(e => {
133
- console.error(`[cron] telegram session write error: ${e.message}`);
134
- });
135
-
136
110
  // once: true — delete after firing
137
111
  if (entry.once) {
138
112
  try {
@@ -550,6 +550,15 @@ const SEED_TOOLS = {
550
550
  const ts = new Date().toISOString();
551
551
  await fs.promises.mkdir(logDir, { recursive: true });
552
552
  await fs.promises.appendFile(logFile, ts + ' [CRON] ' + String(args.message).replace(/\\n/g, ' ') + '\\n', 'utf8');
553
+ if (sessionId) {
554
+ const convFile = path.join(process.env.HOME, '.jarvis/data/conversations/' + sessionId + '.json');
555
+ try {
556
+ const conv = JSON.parse(await fs.promises.readFile(convFile, 'utf8'));
557
+ conv.messages.push({ role: 'assistant', content: String(args.message) });
558
+ conv.metadata.updatedAt = ts;
559
+ await fs.promises.writeFile(convFile, JSON.stringify(conv, null, 2), 'utf8');
560
+ } catch {}
561
+ }
553
562
  } catch {}
554
563
  return { status: 'ok', chatId };
555
564
  `,