@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server/agent.js +41 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ducci/jarvis",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -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
- const wrapUpContent = wrapUpResult.choices[0].message.content || '';
249
- // Store the wrap-up response (but NOT the temporary system note)
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
- const parsed = JSON.parse(wrapUpContent);
254
- response = parsed.response || '';
255
- logSummary = parsed.logSummary || '';
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
- if (parsed.checkpoint) {
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: parsed.checkpoint,
292
+ checkpoint: parsedWrapUp.checkpoint,
265
293
  };
266
294
  }
267
- } catch {
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 };