neoagent 2.1.17-beta.17 → 2.1.17-beta.18

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.17",
3
+ "version": "2.1.17-beta.18",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"425cfb54d01a9472b3e81d9e76fd63a4a44cfb
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "2002157766" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2696186693" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const crypto = require('crypto');
3
4
  const db = require('../../db/database');
4
5
  const { DATA_DIR } = require('../../../runtime/paths');
5
6
 
@@ -71,6 +72,17 @@ function compactToolDefinition(tool, options = {}) {
71
72
  return compact;
72
73
  }
73
74
 
75
+ function normalizeNotifyMessage(content) {
76
+ return String(content || '')
77
+ .replace(/\s+/g, ' ')
78
+ .trim()
79
+ .toLowerCase();
80
+ }
81
+
82
+ function hashNotifyMessage(content) {
83
+ return crypto.createHash('sha256').update(String(content || '')).digest('hex');
84
+ }
85
+
74
86
  /**
75
87
  * Returns the list of available tools for the agent.
76
88
  * @param {object} app - Express app instance.
@@ -1564,6 +1576,15 @@ async function executeTool(toolName, args, context, engine) {
1564
1576
  throw new Error('Messaging manager not available');
1565
1577
  }
1566
1578
 
1579
+ const runState = runId ? engine.activeRuns.get(runId) : null;
1580
+ if (runState?.messagingSent) {
1581
+ return {
1582
+ sent: false,
1583
+ skipped: true,
1584
+ reason: 'A notification was already sent in this run; duplicate scheduler/heartbeat message was suppressed.'
1585
+ };
1586
+ }
1587
+
1567
1588
  const loadDefaultTarget = () => ({
1568
1589
  platform: db.prepare('SELECT value FROM user_settings WHERE user_id = ? AND key = ?')
1569
1590
  .get(userId, 'last_platform')?.value || null,
@@ -1587,6 +1608,22 @@ async function executeTool(toolName, args, context, engine) {
1587
1608
  }
1588
1609
  }
1589
1610
 
1611
+ if (triggerSource === 'scheduler' && taskId && taskConfig) {
1612
+ const normalized = normalizeNotifyMessage(message);
1613
+ const newHash = hashNotifyMessage(normalized);
1614
+ const previousHash = typeof taskConfig.lastNotifyHash === 'string' ? taskConfig.lastNotifyHash : null;
1615
+ const previousAt = Number(taskConfig.lastNotifyAtMs || 0);
1616
+ const dedupeWindowMs = 2 * 60 * 60 * 1000;
1617
+ if (normalized && previousHash && previousHash === newHash && previousAt > 0 && (Date.now() - previousAt) < dedupeWindowMs) {
1618
+ return {
1619
+ sent: false,
1620
+ skipped: true,
1621
+ deduped: true,
1622
+ reason: 'Duplicate scheduler notification suppressed (same content within 2 hours).'
1623
+ };
1624
+ }
1625
+ }
1626
+
1590
1627
  const fallbackTarget = loadDefaultTarget();
1591
1628
  const candidateTargets = [];
1592
1629
  const seenTargets = new Set();
@@ -1626,7 +1663,16 @@ async function executeTool(toolName, args, context, engine) {
1626
1663
  .run(JSON.stringify(taskConfig), taskId, userId);
1627
1664
  }
1628
1665
 
1629
- const runState = runId ? engine.activeRuns.get(runId) : null;
1666
+ if (taskId && taskConfig && triggerSource === 'scheduler') {
1667
+ const normalized = normalizeNotifyMessage(message);
1668
+ if (normalized) {
1669
+ taskConfig.lastNotifyHash = hashNotifyMessage(normalized);
1670
+ taskConfig.lastNotifyAtMs = Date.now();
1671
+ db.prepare('UPDATE scheduled_tasks SET task_config = ? WHERE id = ? AND user_id = ?')
1672
+ .run(JSON.stringify(taskConfig), taskId, userId);
1673
+ }
1674
+ }
1675
+
1630
1676
  if (runState) {
1631
1677
  runState.messagingSent = true;
1632
1678
  runState.lastSentMessage = message;
@@ -291,6 +291,15 @@ class Scheduler {
291
291
  : '';
292
292
  }
293
293
 
294
+ notifyHint += [
295
+ '',
296
+ 'Critical reliability rules for scheduled notifications:',
297
+ '- For time-sensitive external-world claims (for example: mission status, launch timeline, incidents, markets, weather, sports, or wording like "today/now/yesterday"), do not rely on memory alone.',
298
+ '- Verify with at least one fresh source tool in this run before notifying (for example browser_navigate, http_request, search_files where applicable).',
299
+ '- If you cannot verify current status, send a short uncertainty notice and ask whether to retry later. Do not invent timeline details.',
300
+ '- Send at most one proactive user notification per run unless the task explicitly requires multi-part output.'
301
+ ].join('\n');
302
+
294
303
  const taskContext = `[SYSTEM: Executing Scheduled Task]\nTask Name: ${taskName}\nSchedule: ${scheduleInfo}\n\n`;
295
304
  const userPrompt = config.prompt || `You have been triggered by the scheduler to run the background task "${taskName}". Please execute any necessary checks or actions associated with this task.`;
296
305
  const basePrompt = taskContext + userPrompt + notifyHint;