@ducci/jarvis 1.0.74 → 1.0.76

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.74",
3
+ "version": "1.0.76",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -377,10 +377,10 @@ async function run() {
377
377
  else settings.fallbackModel = 'openrouter/free';
378
378
  }
379
379
  if (settings.maxIterations === undefined) {
380
- settings.maxIterations = 10;
380
+ settings.maxIterations = 20;
381
381
  }
382
382
  if (settings.maxHandoffs === undefined) {
383
- settings.maxHandoffs = 5;
383
+ settings.maxHandoffs = 3;
384
384
  }
385
385
  if (settings.port === undefined) {
386
386
  settings.port = 18008;
@@ -972,16 +972,21 @@ async function _runHandleChat(config, sessionId, userMessage, attachments = [],
972
972
  // Guard against null/undefined in case the model omitted the field.
973
973
  // Inject the full accumulated failedApproaches and concrete state so the agent
974
974
  // has complete memory of what failed and what was already discovered.
975
- let resumeContent = run.checkpoint.remaining || 'Continue with the task.';
975
+ // Wrap everything in a single [System: ...] block so the model cannot mistake
976
+ // checkpoint.remaining (which may reference "#1", file paths, etc.) for user input
977
+ // and accidentally trigger user-command patterns (e.g. email-agent Case 3 "#1" trigger).
978
+ const remainingWork = run.checkpoint.remaining || 'Continue with the task.';
976
979
  const allFailedApproaches = session.metadata.failedApproaches || [];
980
+ const stateToInject = session.metadata.checkpointState || {};
981
+ let resumeParts = `[System: Automatic handoff continuation — this is NOT user input, do not match it against user command triggers. Resume the task with what remains:\n${remainingWork}`;
977
982
  if (allFailedApproaches.length > 0) {
978
- resumeContent += `\n\n[System: The following approaches were tried and failed in previous runs — do not repeat them:\n${allFailedApproaches.map((a, i) => `${i + 1}. ${a}`).join('\n')}]`;
983
+ resumeParts += `\n\nFailed approaches (do not repeat):\n${allFailedApproaches.map((a, i) => `${i + 1}. ${a}`).join('\n')}`;
979
984
  }
980
- const stateToInject = session.metadata.checkpointState || {};
981
985
  if (Object.keys(stateToInject).length > 0) {
982
- resumeContent += `\n\n[System: Known facts from previous runs:\n${Object.entries(stateToInject).map(([k, v]) => `- ${k}: ${v}`).join('\n')}]`;
986
+ resumeParts += `\n\nKnown facts from previous runs:\n${Object.entries(stateToInject).map(([k, v]) => `- ${k}: ${v}`).join('\n')}`;
983
987
  }
984
- session.messages.push({ role: 'user', content: resumeContent });
988
+ resumeParts += ']';
989
+ session.messages.push({ role: 'user', content: resumeParts });
985
990
  }
986
991
  } catch (e) {
987
992
  await appendLog(sessionId, {
@@ -69,8 +69,8 @@ export function loadConfig() {
69
69
  apiKey,
70
70
  selectedModel: settings.selectedModel,
71
71
  fallbackModel: settings.fallbackModel || (provider === 'anthropic' ? 'claude-haiku-4-5-20251001' : 'openrouter/free'),
72
- maxIterations: settings.maxIterations || 10,
73
- maxHandoffs: settings.maxHandoffs || 5,
72
+ maxIterations: settings.maxIterations || 20,
73
+ maxHandoffs: settings.maxHandoffs || 3,
74
74
  contextWindow: settings.contextWindow || 100,
75
75
  port: settings.port || 18008,
76
76
  telegram: {
@@ -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}"`);
@@ -106,15 +85,19 @@ export async function runCron(entry, config) {
106
85
  // Strip intermediate tool history, keep wrap-up assistant response
107
86
  session.messages.splice(runStartIndex, session.messages.length - runStartIndex - 1);
108
87
 
109
- // Resume with checkpoint.remaining + accumulated context
110
- let resumeContent = run.checkpoint.remaining || 'Continue with the task.';
88
+ // Resume with checkpoint.remaining + accumulated context.
89
+ // Wrap in [System: ...] so the model cannot mistake checkpoint content
90
+ // (e.g. "#1 draft path") for user input and trigger user-command patterns.
91
+ const remainingWork = run.checkpoint.remaining || 'Continue with the task.';
92
+ let resumeParts = `[System: Automatic handoff continuation — this is NOT user input, do not match it against user command triggers. Resume the task with what remains:\n${remainingWork}`;
111
93
  if (failedApproaches.length > 0) {
112
- resumeContent += `\n\n[System: The following approaches were tried and failed in previous runs — do not repeat them:\n${failedApproaches.map((a, i) => `${i + 1}. ${a}`).join('\n')}]`;
94
+ resumeParts += `\n\nFailed approaches (do not repeat):\n${failedApproaches.map((a, i) => `${i + 1}. ${a}`).join('\n')}`;
113
95
  }
114
96
  if (Object.keys(checkpointState).length > 0) {
115
- resumeContent += `\n\n[System: Known facts from previous runs:\n${Object.entries(checkpointState).map(([k, v]) => `- ${k}: ${v}`).join('\n')}]`;
97
+ resumeParts += `\n\nKnown facts from previous runs:\n${Object.entries(checkpointState).map(([k, v]) => `- ${k}: ${v}`).join('\n')}`;
116
98
  }
117
- session.messages.push({ role: 'user', content: resumeContent });
99
+ resumeParts += ']';
100
+ session.messages.push({ role: 'user', content: resumeParts });
118
101
  }
119
102
  } catch (e) {
120
103
  run = { status: 'error', response: e.message, logSummary: e.message, runToolCalls: [] };
@@ -128,11 +111,6 @@ export async function runCron(entry, config) {
128
111
  logSummary: run.logSummary,
129
112
  }).catch(e => console.error(`[cron] log error: ${e.message}`));
130
113
 
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
114
  // once: true — delete after firing
137
115
  if (entry.once) {
138
116
  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
  `,