neoagent 3.0.1-beta.13 → 3.0.1-beta.15

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": "3.0.1-beta.13",
3
+ "version": "3.0.1-beta.15",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -78,8 +78,10 @@ function registerStaticRoutes(app) {
78
78
  app.get(/^\/app(\/.*)?$/, serveFlutterApp);
79
79
 
80
80
  // Landing page at /
81
- app.use(express.static(LANDING_DIR));
82
- app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
81
+ if (fs.existsSync(path.join(LANDING_DIR, 'index.html'))) {
82
+ app.use(express.static(LANDING_DIR));
83
+ app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
84
+ }
83
85
  }
84
86
 
85
87
  function serveFlutterApp(req, res) {
@@ -1 +1 @@
1
- 6909cff5608bd6620d57f74f1cca0974
1
+ 8477300cc10bca3cb8a9dcb803dcb79d
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "3978533651" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "462988837" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -136637,7 +136637,7 @@ r===$&&A.b()
136637
136637
  p.push(A.ji(q,A.jm(!1,new A.a_(B.v1,A.cV(new A.cA(B.jF,new A.a8s(r,q),q),q,q),q),!1,B.H,!0),q,q,0,0,0,q))}r=!1
136638
136638
  if(!s.ay)if(!s.ch){r=s.e
136639
136639
  r===$&&A.b()
136640
- r=B.b.v("mql1l1zo-2895cea").length!==0&&r.b}if(r){r=s.d
136640
+ r=B.b.v("mql818rm-601e580").length!==0&&r.b}if(r){r=s.d
136641
136641
  r===$&&A.b()
136642
136642
  r=r.aC&&!r.au?84:0
136643
136643
  s=s.e
@@ -142407,7 +142407,7 @@ $S:0}
142407
142407
  A.a_G.prototype={}
142408
142408
  A.Tp.prototype={
142409
142409
  ng(a){var s=this
142410
- if(B.b.v("mql1l1zo-2895cea").length===0||s.a!=null)return
142410
+ if(B.b.v("mql818rm-601e580").length===0||s.a!=null)return
142411
142411
  s.B2()
142412
142412
  s.a=A.ow(B.Sk,new A.bec(s))},
142413
142413
  B2(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -142425,7 +142425,7 @@ if(!t.f.b(k)){s=1
142425
142425
  break}i=J.a0(k,"buildId")
142426
142426
  h=i==null?null:B.b.v(J.p(i))
142427
142427
  j=h==null?"":h
142428
- if(J.bf(j)===0||J.f(j,"mql1l1zo-2895cea")){s=1
142428
+ if(J.bf(j)===0||J.f(j,"mql818rm-601e580")){s=1
142429
142429
  break}n.b=!0
142430
142430
  n.D()
142431
142431
  p=2
@@ -142442,7 +142442,7 @@ case 2:return A.i(o.at(-1),r)}})
142442
142442
  return A.k($async$B2,r)},
142443
142443
  vK(){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
142444
142444
  var $async$vK=A.h(function(a2,a3){if(a2===1){o.push(a3)
142445
- s=p}for(;;)switch(s){case 0:if(B.b.v("mql1l1zo-2895cea").length===0||n.c){s=1
142445
+ s=p}for(;;)switch(s){case 0:if(B.b.v("mql818rm-601e580").length===0||n.c){s=1
142446
142446
  break}n.c=!0
142447
142447
  n.D()
142448
142448
  p=4
@@ -2121,6 +2121,12 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
2121
2121
  lastContent = 'Widget snapshot updated.';
2122
2122
  }
2123
2123
  const messagingSent = runMeta?.messagingSent || false;
2124
+ const stagedProactiveReply = normalizeOutgoingMessage(
2125
+ runMeta?.stagedProactiveMessage?.content
2126
+ || options?.deliveryState?.stagedProactiveMessage?.content
2127
+ || '',
2128
+ options?.source || null,
2129
+ );
2124
2130
  const lastToolWasMessaging = runMeta?.lastToolName === 'send_message' || runMeta?.lastToolName === 'make_call';
2125
2131
 
2126
2132
  // Hermes _handle_max_iterations: if the run exhausted its step budget without a
@@ -2226,6 +2232,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
2226
2232
  if (
2227
2233
  !normalizeOutgoingMessage(lastContent, options?.source || null)
2228
2234
  && !messagingSent
2235
+ && !stagedProactiveReply
2229
2236
  && runMeta?.widgetSnapshotSaved !== true
2230
2237
  ) {
2231
2238
  const explicitNoResponse = (
@@ -2275,10 +2282,11 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
2275
2282
  const normalizedLastContent = normalizeOutgoingMessage(lastContent, options?.source || null);
2276
2283
  let finalResponseText = messagingSent
2277
2284
  ? (sentMessageText || (normalizedLastContent ? lastContent.trim() : ''))
2278
- : (normalizedLastContent ? lastContent.trim() : sentMessageText);
2285
+ : (normalizedLastContent ? lastContent.trim() : (stagedProactiveReply || sentMessageText));
2279
2286
  const lastFinalDeliveryMessage = normalizeOutgoingMessage(
2280
2287
  runMeta?.lastSentMessage
2281
2288
  || (Array.isArray(runMeta?.sentMessages) ? runMeta.sentMessages[runMeta.sentMessages.length - 1] : '')
2289
+ || runMeta?.stagedProactiveMessage?.content
2282
2290
  || '',
2283
2291
  options?.source || null
2284
2292
  );
@@ -74,6 +74,8 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
74
74
  'Verify whether the draft final reply is adequately supported by the gathered evidence.',
75
75
  'If the evidence is insufficient, revise the reply so it states the uncertainty clearly instead of guessing.',
76
76
  'Cross-check every concrete claim against tool status and output. Remove or rewrite claims that are contradicted by the evidence.',
77
+ 'When the task or prompt names a specific entity to check (for example a city, account, person, date, source, or destination), preserve that target exactly. If the gathered evidence is for a different entity, treat the draft as needing revision or insufficient evidence.',
78
+ 'When the task says to include something only if it is available or only if a condition is met, omit that section when the evidence does not show the condition was satisfied.',
77
79
  'A non-zero execute_command exit code means partial or failed shell evidence. Do not treat later sections of a chained shell command as observed unless they were verified separately.',
78
80
  'A successful send_message or make_call means outbound delivery succeeded in this run unless a later messaging tool failed.',
79
81
  'Any claim that an outbound action already happened (sent/submitted/called/"already done") must be backed by a successful outbound tool execution in this run. If not backed, rewrite the reply to "not sent yet" and provide a draft or next concrete step.',
@@ -276,7 +276,33 @@ function getRunState(engine, runId) {
276
276
 
277
277
  function hasAlreadySentProactiveMessage({ triggerSource, runState, deliveryState, allowMultipleProactiveMessages }) {
278
278
  if (!isProactiveTrigger(triggerSource) || allowMultipleProactiveMessages) return false;
279
- return Boolean(runState?.messagingSent || deliveryState?.messagingSent);
279
+ return Boolean(
280
+ runState?.messagingSent
281
+ || deliveryState?.messagingSent
282
+ || runState?.proactiveMessageStaged
283
+ || deliveryState?.proactiveMessageStaged
284
+ );
285
+ }
286
+
287
+ function markProactiveMessageStaged({ runState, deliveryState, platform, to, content, purpose, mediaPath = null }) {
288
+ const staged = {
289
+ platform: String(platform || '').trim(),
290
+ to: String(to || '').trim(),
291
+ content: String(content || '').trim(),
292
+ purpose: String(purpose || '').trim().toLowerCase(),
293
+ mediaPath: mediaPath || null,
294
+ stagedAt: new Date().toISOString(),
295
+ };
296
+
297
+ if (runState) {
298
+ runState.proactiveMessageStaged = true;
299
+ runState.stagedProactiveMessage = staged;
300
+ }
301
+
302
+ if (deliveryState) {
303
+ deliveryState.proactiveMessageStaged = true;
304
+ deliveryState.stagedProactiveMessage = staged;
305
+ }
280
306
  }
281
307
 
282
308
  function markProactiveMessageSent({ runState, deliveryState, content }) {
@@ -2485,6 +2511,26 @@ async function executeTool(toolName, args, context, engine) {
2485
2511
  };
2486
2512
  }
2487
2513
 
2514
+ if (isProactiveTrigger(triggerSource) && context.stageProactiveMessages === true && !suppressReply) {
2515
+ markProactiveMessageStaged({
2516
+ runState,
2517
+ deliveryState,
2518
+ platform: args.platform,
2519
+ to: args.to,
2520
+ content: normalizedMessage,
2521
+ purpose: args.purpose,
2522
+ mediaPath: args.media_path,
2523
+ });
2524
+ return {
2525
+ success: true,
2526
+ staged: true,
2527
+ purpose: normalizeSendMessagePurpose(args.purpose),
2528
+ platform: args.platform,
2529
+ to: args.to,
2530
+ content: normalizedMessage,
2531
+ };
2532
+ }
2533
+
2488
2534
  const sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, {
2489
2535
  agentId,
2490
2536
  mediaPath: args.media_path,
@@ -7,7 +7,7 @@ const calendarToolDefinitions = [
7
7
  {
8
8
  name: 'google_workspace_calendar_list_events',
9
9
  access: 'read',
10
- description: 'List or search Google Calendar events for the connected account. Results include all-day/multi-day events (birthdays, anniversaries, full-day markers) that overlap the window each event has an allDay flag, and the response reports timedCount and allDayCount. For "what is scheduled / starting soon", rely on timedCount and the allDay flag; do not re-query with different windows to filter out all-day entries.',
10
+ description: 'List or search Google Calendar events for the connected account. Results include all-day/multi-day events (birthdays, anniversaries, full-day markers) that overlap the window. Each event has an allDay flag, timedCount/allDayCount summarize the mix, and timedEvents/nextTimedEvent expose the real timed appointments directly. For "what is scheduled / starting soon", decide from that single result instead of re-querying nearby windows to filter out all-day entries.',
11
11
  parameters: {
12
12
  type: 'object',
13
13
  properties: {
@@ -167,6 +167,25 @@ function summarizeEvent(event) {
167
167
  };
168
168
  }
169
169
 
170
+ function summarizeListedEvents(items) {
171
+ const events = Array.isArray(items) ? items.map(summarizeEvent) : [];
172
+ const timedEvents = events.filter((event) => !event.allDay);
173
+ const allDayEvents = events.filter((event) => event.allDay);
174
+ return {
175
+ count: events.length,
176
+ timedCount: timedEvents.length,
177
+ allDayCount: allDayEvents.length,
178
+ hasTimedEvents: timedEvents.length > 0,
179
+ hasOnlyAllDayEvents: timedEvents.length === 0 && allDayEvents.length > 0,
180
+ nextTimedEvent: timedEvents[0] || null,
181
+ timedEvents,
182
+ allDayEvents,
183
+ // Keep the legacy merged list for existing callers, but put timed items first so
184
+ // the most actionable event is visible without extra filtering.
185
+ events: [...timedEvents, ...allDayEvents],
186
+ };
187
+ }
188
+
170
189
  async function executeCalendarTool(toolName, args, auth) {
171
190
  const calendar = google.calendar({ version: 'v3', auth });
172
191
  const calendarId = String(args.calendar_id || 'primary').trim() || 'primary';
@@ -183,19 +202,7 @@ async function executeCalendarTool(toolName, args, auth) {
183
202
  maxResults: Math.max(1, Math.min(Number(args.max_results) || 10, 50)),
184
203
  });
185
204
  const items = Array.isArray(response.data.items) ? response.data.items : [];
186
- const events = items.map(summarizeEvent);
187
- // Timed events first, then all-day markers, so the caller sees the entries it
188
- // most likely cares about at the top. timedCount/allDayCount let a scheduled
189
- // check conclude "nothing is actually scheduled" from one query instead of
190
- // re-filtering the same flooded result set over and over.
191
- const timed = events.filter((event) => !event.allDay);
192
- const allDay = events.filter((event) => event.allDay);
193
- return {
194
- count: events.length,
195
- timedCount: timed.length,
196
- allDayCount: allDay.length,
197
- events: [...timed, ...allDay],
198
- };
205
+ return summarizeListedEvents(items);
199
206
  }
200
207
 
201
208
  case 'google_workspace_calendar_create_event': {
@@ -300,4 +307,5 @@ module.exports = {
300
307
  calendarToolDefinitions,
301
308
  executeCalendarTool,
302
309
  summarizeEvent,
310
+ summarizeListedEvents,
303
311
  };
@@ -462,6 +462,8 @@ class TaskRuntime {
462
462
  const deliveryState = {
463
463
  messagingSent: false,
464
464
  noResponse: false,
465
+ proactiveMessageStaged: false,
466
+ stagedProactiveMessage: null,
465
467
  lastSentMessage: '',
466
468
  sentMessages: [],
467
469
  };
@@ -489,7 +491,7 @@ class TaskRuntime {
489
491
  if (normalizedConfig.callTo) {
490
492
  notifyHint = `\n\nThis task is configured to notify the user by phone. Use the make_call tool to call "${normalizedConfig.callTo}" with an appropriate greeting based on your findings. The configured greeting hint is: "${normalizedConfig.callGreeting || 'Hello, this is your task reminder.'}"`;
491
493
  } else if (normalizedConfig.notifyPlatform && normalizedConfig.notifyTo) {
492
- notifyHint = `\n\nIf your task result is worth notifying the user about, send it proactively via send_message to platform="${normalizedConfig.notifyPlatform}" to="${normalizedConfig.notifyTo}" and set purpose="final_result" for a concrete useful outcome or purpose="blocker" for a real issue the user should know about. If nothing important or actionable changed, call send_message with purpose="no_response" and content="[NO RESPONSE]".${manualRun ? '' : ' For this automatic scheduled run, plain assistant text is internal only and is NOT delivered. You MUST end the run with exactly one explicit send_message decision (purpose="final_result", "blocker", or "no_response") — if you produce a real result, deliver it with send_message or it is lost.'}`;
494
+ notifyHint = `\n\nIf your task result is worth notifying the user about, send it proactively via send_message to platform="${normalizedConfig.notifyPlatform}" to="${normalizedConfig.notifyTo}" and set purpose="final_result" for a concrete useful outcome or purpose="blocker" for a real issue the user should know about. If nothing important or actionable changed, call send_message with purpose="no_response" and content="[NO RESPONSE]" exactly; never leave content blank for no_response. When a tool result already gives you summary fields or flags that answer the task, decide from that evidence instead of re-running nearby variants of the same lookup.${manualRun ? '' : ' For this automatic scheduled run, plain assistant text is internal only and is NOT delivered. You MUST end the run with exactly one explicit send_message decision (purpose="final_result", "blocker", or "no_response") — if you produce a real result, deliver it with send_message or it is lost.'}`;
493
495
  }
494
496
 
495
497
  const triggerPayloadText = executionMeta.triggerPayload
@@ -521,13 +523,14 @@ class TaskRuntime {
521
523
  taskId,
522
524
  deliveryState,
523
525
  allowMultipleProactiveMessages: normalizedConfig.allowMultipleMessages === true || normalizedConfig.allow_multiple_messages === true,
526
+ stageProactiveMessages: true,
524
527
  skipTaskAnalysis: true,
525
528
  skipDeliverableWorkflow: true,
526
529
  skipGlobalRecall: true,
527
530
  skipConversationHistory: true,
528
531
  skipConversationMaintenance: true,
529
532
  skipRunContextPersistence: true,
530
- skipVerifier: true,
533
+ skipVerifier: false,
531
534
  stream: false,
532
535
  context: executionMeta.triggerPayload || {},
533
536
  };
@@ -841,13 +844,19 @@ class TaskRuntime {
841
844
  if (!targets.length) return null;
842
845
  const resultText = stringifyTaskResult(result).trim();
843
846
  const resultLooksLikeError = Boolean(result?.error);
847
+ const stagedMessage = normalizeOutgoingMessageForPlatform(
848
+ deliveryState?.stagedProactiveMessage?.platform,
849
+ deliveryState?.stagedProactiveMessage?.content || '',
850
+ { stripNoResponseMarker: false },
851
+ );
852
+ const explicitStagedDelivery = deliveryState?.proactiveMessageStaged === true && Boolean(stagedMessage);
844
853
  // A forced terminal wrap-up (read-only/blocked hard-stop) is the model's final
845
854
  // answer produced WITHOUT the ability to call send_message itself. Gating it
846
855
  // would silently drop a stuck scheduled task's result, so deliver it even on an
847
856
  // automatic run. Ordinary mid-run plain text (model had send_message available
848
857
  // and chose not to use it) is still gated below.
849
858
  const forcedTerminal = deliveryState?.terminalWrapup === true && Boolean(resultText);
850
- if (!allowPlainResultFallback && !resultLooksLikeError && !forcedTerminal) {
859
+ if (!allowPlainResultFallback && !resultLooksLikeError && !forcedTerminal && !explicitStagedDelivery) {
851
860
  // Automatic run produced substantive text but never made an explicit
852
861
  // send_message decision (a deliberate no_response would have short-circuited
853
862
  // above). We suppress to avoid recurring-check spam, but surface it so a
@@ -874,10 +883,17 @@ class TaskRuntime {
874
883
  }
875
884
 
876
885
  let lastError = null;
877
- for (const target of targets) {
886
+ const resolvedTargets = explicitStagedDelivery
887
+ ? [{
888
+ platform: deliveryState.stagedProactiveMessage.platform,
889
+ to: deliveryState.stagedProactiveMessage.to,
890
+ mediaPath: deliveryState.stagedProactiveMessage.mediaPath || null,
891
+ }]
892
+ : targets;
893
+ for (const target of resolvedTargets) {
878
894
  const message = normalizeOutgoingMessageForPlatform(
879
895
  target.platform,
880
- resultText,
896
+ resultText || stagedMessage,
881
897
  { stripNoResponseMarker: false },
882
898
  );
883
899
  if (!message || message.toUpperCase() === '[NO RESPONSE]') return null;
@@ -893,10 +909,13 @@ class TaskRuntime {
893
909
  try {
894
910
  const sendResult = await manager.sendMessage(userId, target.platform, target.to, message, {
895
911
  agentId,
912
+ mediaPath: target.mediaPath || null,
896
913
  runId: result?.runId || null,
897
914
  persistConversation: true,
898
915
  });
899
916
  deliveryState.messagingSent = true;
917
+ deliveryState.proactiveMessageStaged = false;
918
+ deliveryState.stagedProactiveMessage = null;
900
919
  deliveryState.lastSentMessage = message;
901
920
  if (!Array.isArray(deliveryState.sentMessages)) {
902
921
  deliveryState.sentMessages = [];