neoagent 2.5.2-beta.6 → 2.5.2-beta.8

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": "neoagent",
3
- "version": "2.5.2-beta.6",
3
+ "version": "2.5.2-beta.8",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- 1050e01e6b4a9c529922c7db724d0188
1
+ bb621393d8fc51c33384ab5b836db272
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1247404091" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2920927188" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -134794,7 +134794,7 @@ r===$&&A.b()
134794
134794
  p.push(A.jP(q,A.j9(!1,new A.a_(B.uG,A.d8(new A.cA(B.jt,new A.a7N(r,q),q),q,q),q),!1,B.H,!0),q,q,0,0,0,q))}r=!1
134795
134795
  if(!s.ay)if(!s.ch){r=s.e
134796
134796
  r===$&&A.b()
134797
- r=B.b.u("mqfmnj9i-24c1816").length!==0&&r.b}if(r){r=s.d
134797
+ r=B.b.u("mqfpjvdl-88fa84f").length!==0&&r.b}if(r){r=s.d
134798
134798
  r===$&&A.b()
134799
134799
  r=r.aP&&!r.ai?84:0
134800
134800
  s=s.e
@@ -140506,7 +140506,7 @@ $S:0}
140506
140506
  A.a_6.prototype={}
140507
140507
  A.SQ.prototype={
140508
140508
  nb(a){var s=this
140509
- if(B.b.u("mqfmnj9i-24c1816").length===0||s.a!=null)return
140509
+ if(B.b.u("mqfpjvdl-88fa84f").length===0||s.a!=null)return
140510
140510
  s.AU()
140511
140511
  s.a=A.on(B.RH,new A.bc8(s))},
140512
140512
  AU(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -140524,7 +140524,7 @@ if(!t.f.b(k)){s=1
140524
140524
  break}i=J.a3(k,"buildId")
140525
140525
  h=i==null?null:B.b.u(J.p(i))
140526
140526
  j=h==null?"":h
140527
- if(J.bi(j)===0||J.d(j,"mqfmnj9i-24c1816")){s=1
140527
+ if(J.bi(j)===0||J.d(j,"mqfpjvdl-88fa84f")){s=1
140528
140528
  break}n.b=!0
140529
140529
  n.F()
140530
140530
  p=2
@@ -140541,7 +140541,7 @@ case 2:return A.i(o.at(-1),r)}})
140541
140541
  return A.k($async$AU,r)},
140542
140542
  vE(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
140543
140543
  var $async$vE=A.h(function(a2,a3){if(a2===1){o.push(a3)
140544
- s=p}for(;;)switch(s){case 0:if(B.b.u("mqfmnj9i-24c1816").length===0||n.c){s=1
140544
+ s=p}for(;;)switch(s){case 0:if(B.b.u("mqfpjvdl-88fa84f").length===0||n.c){s=1
140545
140545
  break}n.c=!0
140546
140546
  n.F()
140547
140547
  p=4
@@ -144,6 +144,7 @@ function normalizeErrorKey(errorMsg) {
144
144
  if (/enoent|no such file/i.test(msg)) return 'enoent';
145
145
  if (/can.?t cd to|no such directory/i.test(msg)) return 'bad_cwd';
146
146
  if (/not found/i.test(msg)) return 'not_found';
147
+ if (/owner_repo.*format|must be.*owner.*repo|owner.*repo.*string|owner.*repo.*combined/i.test(msg)) return 'owner_repo_format';
147
148
  return msg.slice(0, 60);
148
149
  }
149
150
 
@@ -155,10 +156,20 @@ function trackErrorPattern(errorMsg, runMeta) {
155
156
  }
156
157
 
157
158
  function buildErrorPatternGuidance(key, count) {
159
+ // Immediate guidance on first occurrence for high-signal patterns that waste
160
+ // multiple iterations before self-correcting.
161
+ const immediateGuides = {
162
+ eisdir: 'That path is a directory (or a VM-only path like /tmp that read_file cannot reach). Use execute_command with `cat <path>` to read files inside VMs, or list_directory to inspect a directory.',
163
+ owner_repo_format: 'The parameter "owner_repo" expects a single combined string like "NeoLabs-Systems/NeoAgent" — not separate owner/repo fields. Pass the full "owner/repo" as one value.',
164
+ };
165
+ if (immediateGuides[key]) {
166
+ const prefix = count > 1 ? `REPEATED ERROR (${count}×): ` : 'ERROR GUIDANCE: ';
167
+ return `${prefix}${immediateGuides[key]}`;
168
+ }
169
+
158
170
  if (count < 3) return null;
159
171
  const guides = {
160
172
  outside_workspace: 'read_file cannot access /tmp paths. Use execute_command with `cat <path>` instead.',
161
- eisdir: 'That path is a directory, not a file. Use list_directory or execute_command with `ls` to inspect it.',
162
173
  enoent: 'That path does not exist. Use execute_command with `find . -name "..."` to locate the correct path first.',
163
174
  bad_cwd: 'The VM home directory is not ~/. Use absolute paths starting from /tmp or discover the workspace root first.',
164
175
  not_found: 'This path or resource was not found. Try listing the parent directory or checking with a broader search first.',
@@ -168,6 +179,31 @@ function buildErrorPatternGuidance(key, count) {
168
179
  return `REPEATED ERROR (${count}×): ${guide}`;
169
180
  }
170
181
 
182
+ const OUTPUT_FINGERPRINT_TOOLS = /^(list_|search_|read_|get_|find_|github_list|github_get|github_search)/;
183
+
184
+ function fingerprintOutput(toolName, result) {
185
+ if (!toolName || !OUTPUT_FINGERPRINT_TOOLS.test(toolName)) return null;
186
+ const raw = typeof result === 'string' ? result : JSON.stringify(result ?? '');
187
+ if (raw.length < 200) return null;
188
+ // djb2 hash over first 3000 chars — fast, collision-unlikely for our sizes
189
+ let h = 5381;
190
+ const limit = Math.min(raw.length, 3000);
191
+ for (let i = 0; i < limit; i++) h = ((h << 5) + h) ^ raw.charCodeAt(i);
192
+ return h >>> 0;
193
+ }
194
+
195
+ // Tools that represent concrete forward progress (write, create, send, update, run).
196
+ // Anything NOT in this set is considered read-only for the analysis-paralysis gate.
197
+ // execute_command counts as progress — it can do anything, including modify state.
198
+ function isProgressTool(toolName) {
199
+ if (!toolName) return false;
200
+ // Neutral / bookkeeping — don't count either way
201
+ if (toolName === 'activate_tools' || toolName === 'save_widget_snapshot') return false;
202
+ // Explicitly read-only patterns
203
+ if (/^(list_|search_|read_file|get_file|find_files?|github_list|github_get|github_search|browser_get|browser_read)/.test(toolName)) return false;
204
+ return true;
205
+ }
206
+
171
207
  function resolveModelCallTimeoutMs(options = {}) {
172
208
  const requested = Number(options?.modelCallTimeoutMs);
173
209
  if (Number.isFinite(requested) && requested > 0) {
@@ -2623,6 +2659,8 @@ class AgentEngine {
2623
2659
  systemSteeringQueue: [],
2624
2660
  toolPids: new Set(),
2625
2661
  repetitionGuard: new ToolRepetitionGuard(),
2662
+ seenOutputHashes: new Map(),
2663
+ consecutiveReadOnlyIterations: 0,
2626
2664
  messagingContext: triggerSource === 'messaging'
2627
2665
  ? {
2628
2666
  platform: options.source || null,
@@ -3042,6 +3080,21 @@ class AgentEngine {
3042
3080
  });
3043
3081
  messages = steeringAtLoopStart.messages;
3044
3082
  messages = sanitizeConversationMessages(messages);
3083
+
3084
+ // Analysis-paralysis gate: fire at the start of every iteration where
3085
+ // the agent has spent N turns only reading/listing/searching without
3086
+ // taking any concrete action. Escalates in urgency each turn.
3087
+ if (analysis.mode === 'execute' || analysis.mode === 'plan_execute') {
3088
+ const readOnlyCount = this.getRunMeta(runId)?.consecutiveReadOnlyIterations || 0;
3089
+ if (readOnlyCount >= 3) {
3090
+ const urgency = readOnlyCount >= 6 ? 'CRITICAL' : 'ACTION REQUIRED';
3091
+ messages.push({
3092
+ role: 'system',
3093
+ content: `${urgency} — ${readOnlyCount} consecutive read-only turns: You have been gathering information for ${readOnlyCount} turns without writing, creating, sending, or running anything. You must take ONE concrete action this turn (create a file, open a PR, run a command that modifies state, send a message) or call task_complete to report what you found and why you cannot proceed. Do not read or list anything further.`,
3094
+ });
3095
+ }
3096
+ }
3097
+
3045
3098
  this.updateRunProgress(runId, {
3046
3099
  currentPhase: 'model',
3047
3100
  currentStep: `model:${iteration}`,
@@ -3595,6 +3648,40 @@ class AgentEngine {
3595
3648
  }
3596
3649
  } else {
3597
3650
  consecutiveToolFailures = 0;
3651
+ // Output fingerprint guard: steer away from re-fetching data already seen.
3652
+ if (!toolErrorMessage) {
3653
+ const currentRunMeta = this.getRunMeta(runId);
3654
+ const fp = fingerprintOutput(toolName, toolResult);
3655
+ if (fp !== null && currentRunMeta?.seenOutputHashes) {
3656
+ const prior = currentRunMeta.seenOutputHashes.get(fp);
3657
+ if (prior) {
3658
+ messages.push({
3659
+ role: 'system',
3660
+ content: `DUPLICATE DATA: This response is identical to what "${prior.toolName}" returned in iteration ${prior.iteration}. You already have this information. Stop fetching and use what you have — proceed to the next concrete action.`,
3661
+ });
3662
+ } else {
3663
+ currentRunMeta.seenOutputHashes.set(fp, { toolName, iteration });
3664
+ // External state: persist large read results to disk so the
3665
+ // model can reference them after context compaction without
3666
+ // re-fetching. Only for significant payloads.
3667
+ const persistRaw = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult ?? '');
3668
+ if (persistRaw.length >= 1000 && runId) {
3669
+ const persistPath = `/tmp/run-${runId.slice(0, 8)}-${toolName}.json`;
3670
+ try {
3671
+ require('fs').writeFileSync(persistPath, persistRaw.slice(0, 40000));
3672
+ if (!currentRunMeta.persistedDataPaths) currentRunMeta.persistedDataPaths = [];
3673
+ if (!currentRunMeta.persistedDataPaths.includes(persistPath)) {
3674
+ currentRunMeta.persistedDataPaths.push(persistPath);
3675
+ messages.push({
3676
+ role: 'system',
3677
+ content: `Data from "${toolName}" (iteration ${iteration}) persisted to ${persistPath}. If context compacts and you need this data again, use execute_command with \`cat ${persistPath}\` instead of re-fetching.`,
3678
+ });
3679
+ }
3680
+ } catch { /* non-fatal — disk full or permissions */ }
3681
+ }
3682
+ }
3683
+ }
3684
+ }
3598
3685
  }
3599
3686
 
3600
3687
  if (toolName === 'send_interim_update') {
@@ -3656,6 +3743,19 @@ class AgentEngine {
3656
3743
  }
3657
3744
  }
3658
3745
 
3746
+ // Update analysis-paralysis counter after each iteration's tool calls.
3747
+ // Resets to 0 when any progress tool was called; otherwise increments.
3748
+ if (!directAnswerEligible && response?.toolCalls?.length > 0
3749
+ && (analysis.mode === 'execute' || analysis.mode === 'plan_execute')) {
3750
+ const iterMeta = this.getRunMeta(runId);
3751
+ if (iterMeta) {
3752
+ const calledProgress = response.toolCalls.some((tc) => isProgressTool(tc.function?.name || ''));
3753
+ iterMeta.consecutiveReadOnlyIterations = calledProgress
3754
+ ? 0
3755
+ : (iterMeta.consecutiveReadOnlyIterations || 0) + 1;
3756
+ }
3757
+ }
3758
+
3659
3759
  if (this.isRunStopped(runId)) break;
3660
3760
  if (this.getRunMeta(runId)?.terminalInterim) break;
3661
3761
  if (this.getRunMeta(runId)?.widgetSnapshotSaved) break;