@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 +1 -1
- package/src/scripts/onboarding.js +2 -2
- package/src/server/agent.js +10 -5
- package/src/server/config.js +2 -2
- package/src/server/crons.js +11 -33
- package/src/server/tools.js +9 -0
package/package.json
CHANGED
|
@@ -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 =
|
|
380
|
+
settings.maxIterations = 20;
|
|
381
381
|
}
|
|
382
382
|
if (settings.maxHandoffs === undefined) {
|
|
383
|
-
settings.maxHandoffs =
|
|
383
|
+
settings.maxHandoffs = 3;
|
|
384
384
|
}
|
|
385
385
|
if (settings.port === undefined) {
|
|
386
386
|
settings.port = 18008;
|
package/src/server/agent.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
986
|
+
resumeParts += `\n\nKnown facts from previous runs:\n${Object.entries(stateToInject).map(([k, v]) => `- ${k}: ${v}`).join('\n')}`;
|
|
983
987
|
}
|
|
984
|
-
|
|
988
|
+
resumeParts += ']';
|
|
989
|
+
session.messages.push({ role: 'user', content: resumeParts });
|
|
985
990
|
}
|
|
986
991
|
} catch (e) {
|
|
987
992
|
await appendLog(sessionId, {
|
package/src/server/config.js
CHANGED
|
@@ -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 ||
|
|
73
|
-
maxHandoffs: settings.maxHandoffs ||
|
|
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: {
|
package/src/server/crons.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { runAgentLoop
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
resumeParts += `\n\nKnown facts from previous runs:\n${Object.entries(checkpointState).map(([k, v]) => `- ${k}: ${v}`).join('\n')}`;
|
|
116
98
|
}
|
|
117
|
-
|
|
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 {
|
package/src/server/tools.js
CHANGED
|
@@ -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
|
`,
|