@ducci/jarvis 1.0.11 → 1.0.13
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/system-prompt.md +5 -1
- package/package.json +1 -1
- package/src/server/agent.js +42 -13
- package/src/server/config.js +4 -2
package/docs/system-prompt.md
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
This is the authoritative system prompt sent to the model at the start of every session. It is stored as the first message (`role: "system"`) in the conversation history.
|
|
4
4
|
|
|
5
|
-
Before sending to the model, the server replaces the `{{user_info}}`
|
|
5
|
+
Before sending to the model, the server replaces the `{{user_info}}` and `{{session_id}}` placeholders at runtime on every request — these are never stored in the conversation history.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
You are Jarvis, a fully autonomous agent running on a local server. You have access to tools and can execute shell commands on the machine you run on.
|
|
11
11
|
|
|
12
|
+
## Session
|
|
13
|
+
|
|
14
|
+
Current session ID: {{session_id}}
|
|
15
|
+
|
|
12
16
|
## Known User Context
|
|
13
17
|
|
|
14
18
|
{{user_info}}
|
package/package.json
CHANGED
package/src/server/agent.js
CHANGED
|
@@ -7,6 +7,7 @@ import { appendLog } from './logging.js';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
|
|
9
9
|
const FORMAT_NUDGE = 'Your previous response was not valid JSON. Respond only with the required JSON object: {"response": "...", "logSummary": "..."}';
|
|
10
|
+
const LOOP_DETECTION_THRESHOLD = 3;
|
|
10
11
|
|
|
11
12
|
const WRAP_UP_NOTE = `[System: You have reached the iteration limit. This is your final response for this run.
|
|
12
13
|
Respond with your normal JSON, but add a checkpoint field:
|
|
@@ -69,6 +70,7 @@ async function runAgentLoop(client, config, session, prepareMessages) {
|
|
|
69
70
|
let toolDefs = getToolDefinitions(tools);
|
|
70
71
|
let iteration = 0;
|
|
71
72
|
const runToolCalls = [];
|
|
73
|
+
const loopTracker = new Map();
|
|
72
74
|
let done = false;
|
|
73
75
|
let response = '';
|
|
74
76
|
let logSummary = '';
|
|
@@ -154,6 +156,17 @@ async function runAgentLoop(client, config, session, prepareMessages) {
|
|
|
154
156
|
tool_call_id: toolCall.id,
|
|
155
157
|
content: resultStr,
|
|
156
158
|
});
|
|
159
|
+
|
|
160
|
+
const callKey = `${toolName}|${JSON.stringify(toolArgs)}|${resultStr}`;
|
|
161
|
+
loopTracker.set(callKey, (loopTracker.get(callKey) || 0) + 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const loopDetected = [...loopTracker.values()].some(count => count >= LOOP_DETECTION_THRESHOLD);
|
|
165
|
+
if (loopDetected) {
|
|
166
|
+
session.messages.push({
|
|
167
|
+
role: 'user',
|
|
168
|
+
content: '[System: Loop detected. You are repeatedly calling the same tools with identical arguments and getting identical results. Stop calling tools and provide your final answer now based on what you already know.]',
|
|
169
|
+
});
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
// Reload tools if any were created/updated this iteration
|
|
@@ -245,31 +258,47 @@ async function runAgentLoop(client, config, session, prepareMessages) {
|
|
|
245
258
|
};
|
|
246
259
|
}
|
|
247
260
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
session.messages.push({ role: 'assistant', content: wrapUpContent });
|
|
261
|
+
let wrapUpContent = wrapUpResult.choices[0].message.content || '';
|
|
262
|
+
let parsedWrapUp = null;
|
|
251
263
|
|
|
264
|
+
// Try JSON parse; if it fails, nudge retry (Layer 2)
|
|
252
265
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
266
|
+
parsedWrapUp = JSON.parse(wrapUpContent);
|
|
267
|
+
} catch {
|
|
268
|
+
try {
|
|
269
|
+
const nudgeMessages = [...wrapUpMessages, { role: 'user', content: FORMAT_NUDGE }];
|
|
270
|
+
const nudgeResult = await callModelWithFallback(client, config, nudgeMessages, []);
|
|
271
|
+
const nudgeContent = nudgeResult.choices[0]?.message?.content || '';
|
|
272
|
+
parsedWrapUp = JSON.parse(nudgeContent);
|
|
273
|
+
wrapUpContent = nudgeContent;
|
|
274
|
+
} catch {
|
|
275
|
+
// Layer 3: use raw text as best-effort response below
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Store the wrap-up response (but NOT the temporary system note)
|
|
280
|
+
session.messages.push({ role: 'assistant', content: wrapUpContent });
|
|
256
281
|
|
|
257
|
-
|
|
282
|
+
if (parsedWrapUp) {
|
|
283
|
+
response = parsedWrapUp.response || '';
|
|
284
|
+
logSummary = parsedWrapUp.logSummary || '';
|
|
285
|
+
if (parsedWrapUp.checkpoint) {
|
|
258
286
|
return {
|
|
259
287
|
iteration,
|
|
260
288
|
response,
|
|
261
289
|
logSummary,
|
|
262
290
|
status: 'checkpoint_reached',
|
|
263
291
|
runToolCalls,
|
|
264
|
-
checkpoint:
|
|
292
|
+
checkpoint: parsedWrapUp.checkpoint,
|
|
265
293
|
};
|
|
266
294
|
}
|
|
267
|
-
|
|
295
|
+
status = 'ok';
|
|
296
|
+
} else {
|
|
297
|
+
// Layer 3: use raw text — user gets a real response instead of an error
|
|
268
298
|
response = wrapUpContent;
|
|
269
|
-
logSummary = 'Wrap-up response was not valid JSON.';
|
|
299
|
+
logSummary = 'Wrap-up response was not valid JSON after retry.';
|
|
300
|
+
status = 'ok';
|
|
270
301
|
}
|
|
271
|
-
|
|
272
|
-
status = 'checkpoint_reached';
|
|
273
302
|
}
|
|
274
303
|
|
|
275
304
|
return { iteration, response, logSummary, status, runToolCalls, checkpoint: null };
|
|
@@ -301,7 +330,7 @@ export async function handleChat(config, requestSessionId, userMessage) {
|
|
|
301
330
|
function prepareMessages(messages) {
|
|
302
331
|
return messages.map((msg, i) => {
|
|
303
332
|
if (i === 0 && msg.role === 'system') {
|
|
304
|
-
return { ...msg, content: resolveSystemPrompt(msg.content) };
|
|
333
|
+
return { ...msg, content: resolveSystemPrompt(msg.content, sessionId) };
|
|
305
334
|
}
|
|
306
335
|
return msg;
|
|
307
336
|
});
|
package/src/server/config.js
CHANGED
|
@@ -63,7 +63,7 @@ export function loadSystemPrompt() {
|
|
|
63
63
|
return match[1].trim();
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export function resolveSystemPrompt(promptTemplate) {
|
|
66
|
+
export function resolveSystemPrompt(promptTemplate, sessionId) {
|
|
67
67
|
let userInfo = '(none yet)';
|
|
68
68
|
try {
|
|
69
69
|
const raw = fs.readFileSync(PATHS.userInfoFile, 'utf8');
|
|
@@ -74,5 +74,7 @@ export function resolveSystemPrompt(promptTemplate) {
|
|
|
74
74
|
} catch {
|
|
75
75
|
// File doesn't exist yet
|
|
76
76
|
}
|
|
77
|
-
return promptTemplate
|
|
77
|
+
return promptTemplate
|
|
78
|
+
.replace('{{session_id}}', sessionId || 'unknown')
|
|
79
|
+
.replace('{{user_info}}', userInfo);
|
|
78
80
|
}
|