@ducci/jarvis 1.0.11 → 1.0.12
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/server/agent.js +41 -12
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 };
|