neoagent 2.1.17-beta.2 → 2.1.17-beta.3

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.1.17-beta.2",
3
+ "version": "2.1.17-beta.3",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -25,25 +25,21 @@ function registerApiRoutes(app) {
25
25
  const handler = require(route.modulePath);
26
26
  if (route.basePath) {
27
27
  app.use(route.basePath, handler);
28
- console.log(`[HTTP] Registered API route ${route.basePath} -> ${route.modulePath}`);
29
28
  } else {
30
29
  app.use(handler);
31
- console.log(`[HTTP] Registered API route ${route.modulePath}`);
32
30
  }
33
31
  }
34
32
 
35
33
  setupTelnyxWebhook(app);
36
- console.log('[HTTP] Registered Telnyx webhook');
37
34
 
38
35
  app.get('/api/health', requireAuth, (req, res) => {
39
36
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
40
37
  });
41
- console.log('[HTTP] Registered API route /api/health');
42
38
 
43
39
  app.get('/api/version', requireAuth, (req, res) => {
44
40
  res.json(getVersionInfo());
45
41
  });
46
- console.log('[HTTP] Registered API route /api/version');
42
+ console.log(`[HTTP] Registered ${routeRegistry.length + 3} routes`);
47
43
  }
48
44
 
49
45
  module.exports = {
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"052f31d115eceda8cbff1b3481fcde4330c4ae
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1789916093" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "3731763910" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -154,6 +154,24 @@ function buildForcedFinalReplyPrompt(triggerSource) {
154
154
  return 'Tool work is finished. Write the final user-facing reply now. Do not call tools.';
155
155
  }
156
156
 
157
+ function buildBlankMessagingReplyPrompt(attempt) {
158
+ if (attempt <= 1) {
159
+ return 'You must produce one non-empty plain-text reply for the external messaging user right now. Do not call tools. Do not use markdown. Briefly summarize what you did or what blocked you.';
160
+ }
161
+
162
+ return 'Your previous reply was empty. Return one non-empty plain-text message now. Do not call tools. Do not use markdown. If needed, apologize briefly and explain the blocker in one or two sentences.';
163
+ }
164
+
165
+ function buildDeterministicMessagingFallback({ failedStepCount, stepIndex }) {
166
+ if (failedStepCount > 0) {
167
+ return 'I ran into an issue while working on that, so I do not have a clean final result yet. Ask me for a summary and I will explain what I found.';
168
+ }
169
+ if (stepIndex > 0) {
170
+ return 'I worked on that, but I could not produce a clean summary before finishing. Ask me for a summary and I will condense what I found into one message.';
171
+ }
172
+ return 'I could not generate a proper reply just now, but I am still here. Please send the request again and I will try once more.';
173
+ }
174
+
157
175
  function clampRunContext(text, maxChars) {
158
176
  const value = normalizeOutgoingMessage(text);
159
177
  if (!value) return '';
@@ -208,6 +226,56 @@ class AgentEngine {
208
226
  return buildSystemPrompt(userId, context, memoryManager);
209
227
  }
210
228
 
229
+ async recoverBlankMessagingReply({
230
+ userId,
231
+ runId,
232
+ messages,
233
+ provider,
234
+ model,
235
+ providerName,
236
+ options,
237
+ stepIndex,
238
+ failedStepCount
239
+ }) {
240
+ const attempts = 2;
241
+ let recoveredContent = '';
242
+ let totalTokens = 0;
243
+
244
+ for (let attempt = 1; attempt <= attempts; attempt++) {
245
+ console.warn(
246
+ `[Run ${shortenRunId(runId)}] blank_reply_recovery attempt=${attempt} model=${model}`
247
+ );
248
+ const response = await provider.chat(
249
+ sanitizeConversationMessages([
250
+ ...messages,
251
+ {
252
+ role: 'system',
253
+ content: buildBlankMessagingReplyPrompt(attempt)
254
+ }
255
+ ]),
256
+ [],
257
+ {
258
+ model,
259
+ reasoningEffort: this.getReasoningEffort(providerName, options)
260
+ }
261
+ );
262
+ totalTokens += response.usage?.totalTokens || 0;
263
+ recoveredContent = sanitizeModelOutput(response.content || '', { model });
264
+ if (normalizeOutgoingMessage(recoveredContent)) {
265
+ console.info(
266
+ `[Run ${shortenRunId(runId)}] blank_reply_recovery succeeded attempt=${attempt}`
267
+ );
268
+ return { content: recoveredContent, tokens: totalTokens, recovered: true };
269
+ }
270
+ }
271
+
272
+ const fallback = buildDeterministicMessagingFallback({ failedStepCount, stepIndex });
273
+ console.warn(
274
+ `[Run ${shortenRunId(runId)}] blank_reply_recovery fallback=deterministic`
275
+ );
276
+ return { content: fallback, tokens: totalTokens, recovered: true };
277
+ }
278
+
211
279
  getAvailableTools(app, options = {}) {
212
280
  const { getAvailableTools } = require('./tools');
213
281
  return getAvailableTools(app, options);
@@ -551,6 +619,7 @@ class AgentEngine {
551
619
  let totalTokens = 0;
552
620
  let lastContent = '';
553
621
  let stepIndex = 0;
622
+ let failedStepCount = 0;
554
623
  let promptMetrics = {};
555
624
 
556
625
  try {
@@ -767,6 +836,7 @@ class AgentEngine {
767
836
  );
768
837
  } catch (err) {
769
838
  toolResult = { error: err.message };
839
+ failedStepCount++;
770
840
  this.detachProcessFromRun(runId, toolResult?.pid);
771
841
  db.prepare('UPDATE agent_steps SET status = ?, error = ?, completed_at = datetime(\'now\') WHERE id = ?')
772
842
  .run('failed', err.message, stepId);
@@ -849,6 +919,30 @@ class AgentEngine {
849
919
 
850
920
  const runMeta = this.activeRuns.get(runId);
851
921
  const messagingSent = runMeta?.messagingSent || false;
922
+
923
+ if (triggerSource === 'messaging' && !normalizeOutgoingMessage(lastContent) && !messagingSent) {
924
+ const recovered = await this.recoverBlankMessagingReply({
925
+ userId,
926
+ runId,
927
+ messages,
928
+ provider,
929
+ model,
930
+ providerName,
931
+ options,
932
+ stepIndex,
933
+ failedStepCount
934
+ });
935
+ lastContent = recovered.content;
936
+ totalTokens += recovered.tokens || 0;
937
+ if (normalizeOutgoingMessage(lastContent)) {
938
+ messages.push({ role: 'assistant', content: lastContent });
939
+ if (conversationId) {
940
+ db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tokens) VALUES (?, ?, ?, ?)')
941
+ .run(conversationId, 'assistant', lastContent, recovered.tokens || 0);
942
+ }
943
+ }
944
+ }
945
+
852
946
  const sentMessageText = joinSentMessages(runMeta?.sentMessages);
853
947
  const finalResponseText = lastContent.trim() ? lastContent : sentMessageText;
854
948
  const lastSentMessage = normalizeOutgoingMessage(