neoagent 2.2.1-beta.6 → 2.2.1-beta.7

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 (49) hide show
  1. package/docs/automation.md +2 -2
  2. package/docs/capabilities.md +7 -10
  3. package/docs/hardware.md +4 -7
  4. package/docs/index.md +6 -7
  5. package/docs/integrations.md +1 -1
  6. package/docs/operations.md +1 -1
  7. package/docs/why-neoagent.md +2 -2
  8. package/package.json +1 -1
  9. package/server/db/database.js +76 -61
  10. package/server/http/routes.js +1 -2
  11. package/server/public/assets/AssetManifest.json +1 -1
  12. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  13. package/server/public/flutter_bootstrap.js +1 -1
  14. package/server/public/main.dart.js +65118 -64805
  15. package/server/routes/{scheduler.js → tasks.js} +31 -29
  16. package/server/routes/widgets.js +7 -7
  17. package/server/services/ai/capabilityHealth.js +4 -4
  18. package/server/services/ai/engine.js +9 -9
  19. package/server/services/ai/systemPrompt.js +7 -7
  20. package/server/services/ai/taskAnalysis.js +3 -3
  21. package/server/services/ai/toolResult.js +6 -8
  22. package/server/services/ai/tools.js +62 -95
  23. package/server/services/commands/router.js +14 -6
  24. package/server/services/integrations/whatsapp/provider.js +23 -1
  25. package/server/services/manager.js +14 -14
  26. package/server/services/memory/manager.js +7 -7
  27. package/server/services/memory/policy.js +1 -1
  28. package/server/services/messaging/formatting_guides.js +0 -4
  29. package/server/services/messaging/manager.js +0 -2
  30. package/server/services/tasks/adapters/gmail_message_received.js +36 -0
  31. package/server/services/tasks/adapters/index.js +10 -0
  32. package/server/services/tasks/adapters/outlook_email_received.js +38 -0
  33. package/server/services/tasks/adapters/schedule.js +57 -0
  34. package/server/services/tasks/adapters/slack_message_received.js +39 -0
  35. package/server/services/tasks/adapters/teams_message_received.js +39 -0
  36. package/server/services/tasks/adapters/whatsapp_personal_message_received.js +42 -0
  37. package/server/services/tasks/integration_runtime.js +260 -0
  38. package/server/services/tasks/runtime.js +539 -0
  39. package/server/services/{scheduler/cron_utils.js → tasks/schedule_utils.js} +2 -0
  40. package/server/services/tasks/security.js +60 -0
  41. package/server/services/tasks/task_repository.js +162 -0
  42. package/server/services/tasks/trigger_registry.js +29 -0
  43. package/server/services/tasks/utils.js +45 -0
  44. package/server/services/websocket.js +1 -1
  45. package/server/services/widgets/service.js +37 -25
  46. package/server/routes/wearable_device.js +0 -147
  47. package/server/services/messaging/waveshare_wearable.js +0 -40
  48. package/server/services/scheduler/cron.js +0 -580
  49. package/server/services/wearables/device_auth.js +0 -228
@@ -3,79 +3,81 @@ const router = express.Router();
3
3
  const { requireAuth } = require('../middleware/auth');
4
4
  const { sanitizeError } = require('../utils/security');
5
5
  const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
6
- const cron = require('node-cron');
7
6
 
8
7
  router.use(requireAuth);
9
8
 
10
- // List scheduled tasks
11
9
  router.get('/', (req, res) => {
12
- const scheduler = req.app.locals.scheduler;
13
- const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
14
- res.json(scheduler.listTasks(req.session.userId, { agentId }));
10
+ try {
11
+ const tasks = req.app.locals.taskRuntime;
12
+ const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
13
+ res.json(tasks.listTasks(req.session.userId, { agentId }));
14
+ } catch (error) {
15
+ (req.app.locals.logger?.error || console.error)('[Tasks] Failed to list tasks', error);
16
+ res.status(500).json({ error: 'Internal server error' });
17
+ }
15
18
  });
16
19
 
17
- // Create a new scheduled task
18
- router.post('/', (req, res) => {
20
+ router.get('/catalog', (req, res) => {
19
21
  try {
20
- const { name, cronExpression, prompt, enabled, model } = req.body;
22
+ const tasks = req.app.locals.taskRuntime;
21
23
  const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
22
- if (!name || !cronExpression || !prompt) {
23
- return res.status(400).json({ error: 'name, cronExpression, and prompt required' });
24
- }
25
- if (!cron.validate(String(cronExpression))) {
26
- return res.status(400).json({ error: 'Invalid cron expression' });
27
- }
24
+ res.json(tasks.getTriggerCatalog(req.session.userId, { agentId }));
25
+ } catch (error) {
26
+ (req.app.locals.logger?.error || console.error)('[Tasks] Failed to load trigger catalog', error);
27
+ res.status(500).json({ error: 'Internal server error' });
28
+ }
29
+ });
28
30
 
29
- const scheduler = req.app.locals.scheduler;
30
- const task = scheduler.createTask(req.session.userId, { name, cronExpression, prompt, enabled, model, agentId });
31
+ router.post('/', async (req, res) => {
32
+ try {
33
+ const tasks = req.app.locals.taskRuntime;
34
+ const agentId = resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
35
+ const task = await tasks.createTask(req.session.userId, {
36
+ ...req.body,
37
+ agentId,
38
+ });
31
39
  res.status(201).json(task);
32
40
  } catch (err) {
33
41
  res.status(400).json({ error: sanitizeError(err) });
34
42
  }
35
43
  });
36
44
 
37
- // Update a scheduled task
38
- router.put('/:id', (req, res) => {
45
+ router.put('/:id', async (req, res) => {
39
46
  try {
40
47
  const taskId = Number.parseInt(req.params.id, 10);
41
48
  if (!Number.isInteger(taskId) || taskId <= 0) {
42
49
  return res.status(400).json({ error: 'Invalid task id' });
43
50
  }
44
- if (req.body?.cronExpression !== undefined && !cron.validate(String(req.body.cronExpression))) {
45
- return res.status(400).json({ error: 'Invalid cron expression' });
46
- }
47
- const scheduler = req.app.locals.scheduler;
48
- const task = scheduler.updateTask(taskId, req.session.userId, req.body);
51
+ const tasks = req.app.locals.taskRuntime;
52
+ const task = await tasks.updateTask(taskId, req.session.userId, req.body);
49
53
  res.json(task);
50
54
  } catch (err) {
51
55
  res.status(400).json({ error: sanitizeError(err) });
52
56
  }
53
57
  });
54
58
 
55
- // Delete a scheduled task
56
59
  router.delete('/:id', (req, res) => {
57
60
  try {
58
61
  const taskId = Number.parseInt(req.params.id, 10);
59
62
  if (!Number.isInteger(taskId) || taskId <= 0) {
60
63
  return res.status(400).json({ error: 'Invalid task id' });
61
64
  }
62
- const scheduler = req.app.locals.scheduler;
63
- scheduler.deleteTask(taskId, req.session.userId);
65
+ const tasks = req.app.locals.taskRuntime;
66
+ tasks.deleteTask(taskId, req.session.userId);
64
67
  res.json({ success: true });
65
68
  } catch (err) {
66
69
  res.status(400).json({ error: sanitizeError(err) });
67
70
  }
68
71
  });
69
72
 
70
- // Run a task immediately
71
73
  router.post('/:id/run', (req, res) => {
72
74
  try {
73
75
  const taskId = Number.parseInt(req.params.id, 10);
74
76
  if (!Number.isInteger(taskId) || taskId <= 0) {
75
77
  return res.status(400).json({ error: 'Invalid task id' });
76
78
  }
77
- const scheduler = req.app.locals.scheduler;
78
- const result = scheduler.runTaskNow(taskId, req.session.userId);
79
+ const tasks = req.app.locals.taskRuntime;
80
+ const result = tasks.runTaskNow(taskId, req.session.userId);
79
81
  res.json(result);
80
82
  } catch (err) {
81
83
  res.status(400).json({ error: sanitizeError(err) });
@@ -40,26 +40,26 @@ router.get('/', (req, res) => {
40
40
  }
41
41
  });
42
42
 
43
- router.post('/', (req, res) => {
43
+ router.post('/', async (req, res) => {
44
44
  try {
45
45
  const service = widgetService(req);
46
46
  if (!service) {
47
47
  return res.status(500).json({ error: 'Widget service unavailable.' });
48
48
  }
49
- const widget = service.createWidget(req.session.userId, req.body || {});
49
+ const widget = await service.createWidget(req.session.userId, req.body || {});
50
50
  res.status(201).json(widget);
51
51
  } catch (err) {
52
52
  res.status(400).json({ error: sanitizeError(err) });
53
53
  }
54
54
  });
55
55
 
56
- router.put('/:id', (req, res) => {
56
+ router.put('/:id', async (req, res) => {
57
57
  try {
58
58
  const service = widgetService(req);
59
59
  if (!service) {
60
60
  return res.status(500).json({ error: 'Widget service unavailable.' });
61
61
  }
62
- const widget = service.updateWidget(req.session.userId, req.params.id, req.body || {});
62
+ const widget = await service.updateWidget(req.session.userId, req.params.id, req.body || {});
63
63
  res.json(widget);
64
64
  } catch (err) {
65
65
  res.status(400).json({ error: sanitizeError(err) });
@@ -81,8 +81,8 @@ router.delete('/:id', (req, res) => {
81
81
  router.post('/:id/refresh', (req, res) => {
82
82
  try {
83
83
  const service = widgetService(req);
84
- const scheduler = req.app?.locals?.scheduler;
85
- if (!service || !scheduler) {
84
+ const taskRuntime = req.app?.locals?.taskRuntime;
85
+ if (!service || !taskRuntime) {
86
86
  return res.status(500).json({ error: 'Widget refresh unavailable.' });
87
87
  }
88
88
  const widget = service.getWidget(req.session.userId, req.params.id);
@@ -92,7 +92,7 @@ router.post('/:id/refresh', (req, res) => {
92
92
  if (!widget.scheduledTaskId) {
93
93
  return res.status(400).json({ error: 'Widget is missing its refresh task.' });
94
94
  }
95
- res.json(scheduler.runTaskNow(widget.scheduledTaskId, req.session.userId));
95
+ res.json(taskRuntime.runTaskNow(widget.scheduledTaskId, req.session.userId));
96
96
  } catch (err) {
97
97
  res.status(400).json({ error: sanitizeError(err) });
98
98
  }
@@ -354,7 +354,7 @@ function getMemoryHealth(engine) {
354
354
  });
355
355
  }
356
356
 
357
- function getSchedulerHealth(userId, agentId = null) {
357
+ function getTaskHealth(userId, agentId = null) {
358
358
  const taskCount = agentId
359
359
  ? db.prepare('SELECT COUNT(*) AS count FROM scheduled_tasks WHERE user_id = ? AND agent_id = ?').get(userId, agentId)?.count || 0
360
360
  : db.prepare('SELECT COUNT(*) AS count FROM scheduled_tasks WHERE user_id = ?').get(userId)?.count || 0;
@@ -363,8 +363,8 @@ function getSchedulerHealth(userId, agentId = null) {
363
363
  configured: true,
364
364
  healthy: true,
365
365
  summary: taskCount > 0
366
- ? `${taskCount} scheduled task(s) exist for this user.`
367
- : 'No scheduled tasks are configured.',
366
+ ? `${taskCount} task(s) exist for this user.`
367
+ : 'No tasks are configured.',
368
368
  details: { taskCount },
369
369
  });
370
370
  }
@@ -385,7 +385,7 @@ async function getCapabilityHealth({ userId, agentId = null, app, engine }) {
385
385
  integrations: getIntegrationHealth(userId, app, agentId),
386
386
  mcp: getMcpHealth(userId, app, engine, agentId),
387
387
  skills: getSkillHealth(app, engine),
388
- scheduler: getSchedulerHealth(userId, agentId),
388
+ tasks: getTaskHealth(userId, agentId),
389
389
  },
390
390
  };
391
391
  }
@@ -459,6 +459,7 @@ function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '
459
459
  'recordings_list',
460
460
  'recordings_get',
461
461
  'recordings_search',
462
+ 'list_tasks',
462
463
  'wait_subagent',
463
464
  ]);
464
465
  const stateChangingExact = new Set([
@@ -471,10 +472,9 @@ function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '
471
472
  'create_skill',
472
473
  'update_skill',
473
474
  'delete_skill',
474
- 'create_scheduled_task',
475
- 'update_scheduled_task',
476
- 'delete_scheduled_task',
477
- 'schedule_run',
475
+ 'create_task',
476
+ 'update_task',
477
+ 'delete_task',
478
478
  'create_ai_widget',
479
479
  'update_ai_widget',
480
480
  'delete_ai_widget',
@@ -503,8 +503,8 @@ function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '
503
503
  ? 'command'
504
504
  : name.includes('skill')
505
505
  ? 'skills'
506
- : name.includes('scheduled_task') || name === 'schedule_run' || name.includes('widget')
507
- ? 'scheduler'
506
+ : (name === 'create_task' || name === 'update_task' || name === 'delete_task' || name === 'list_tasks' || name.includes('widget'))
507
+ ? 'tasks'
508
508
  : name === 'send_message' || name === 'make_call'
509
509
  ? 'messaging'
510
510
  : name.startsWith('recordings_') || name === 'read_health_data'
@@ -647,7 +647,7 @@ class AgentEngine {
647
647
  this.messagingManager = services.messagingManager || null;
648
648
  this.mcpManager = services.mcpManager || services.mcpClient || null;
649
649
  this.skillRunner = services.skillRunner || null;
650
- this.scheduler = services.scheduler || null;
650
+ this.taskRuntime = services.taskRuntime || null;
651
651
  this.memoryManager = services.memoryManager || null;
652
652
  this.voiceRuntimeManager = services.voiceRuntimeManager || null;
653
653
  }
@@ -2495,7 +2495,7 @@ class AgentEngine {
2495
2495
  messagingManager: this.messagingManager,
2496
2496
  mcpManager: this.mcpManager,
2497
2497
  skillRunner: this.skillRunner,
2498
- scheduler: this.scheduler,
2498
+ taskRuntime: this.taskRuntime,
2499
2499
  memoryManager: this.memoryManager,
2500
2500
  });
2501
2501
 
@@ -2832,7 +2832,7 @@ class AgentEngine {
2832
2832
  if (toolName === 'send_message') return 'messaging';
2833
2833
  if (toolName === 'make_call') return 'messaging';
2834
2834
  if (toolName.startsWith('mcp_') || toolName.includes('mcp')) return 'mcp';
2835
- if (toolName.includes('scheduled_task') || toolName === 'schedule_run' || toolName.includes('widget')) return 'scheduler';
2835
+ if (toolName === 'create_task' || toolName === 'update_task' || toolName === 'delete_task' || toolName === 'list_tasks' || toolName.includes('widget')) return 'tasks';
2836
2836
  if (toolName.includes('subagent')) return 'subagent';
2837
2837
  if (toolName === 'think') return 'thinking';
2838
2838
  return 'tool';
@@ -79,8 +79,8 @@ REPORT ACTUAL RESULTS
79
79
  When a tool returns data, share the relevant parts — summarized if large, direct if short. Never paste raw JSON as the answer. Never narrate what you're about to do at length before doing it.
80
80
  Never promise an action in the final answer unless you already took that action in this run. Do not say "I'll check", "I'll fix it", or "I'll send it" and then stop. Either do it first or say you have not done it yet.
81
81
  Do not promise future follow-up work unless that work will actually happen automatically before the current run ends.
82
- For scheduler or task-config changes, never claim that a cron job was created, updated, deleted, enabled, disabled, or “fixed” unless the corresponding scheduler tool call succeeded in this run. If you did not verify the actual task config, say that clearly instead of guessing.
83
- If the user asks you to debug scheduler timing or frequency, inspect the current scheduled-task list first and separate three things clearly: what you observed, what you infer, and what you actually changed.
82
+ For task-config changes, never claim that a task was created, updated, deleted, enabled, disabled, or “fixed” unless the corresponding task tool call succeeded in this run. If you did not verify the actual task config, say that clearly instead of guessing.
83
+ If the user asks you to debug task timing or trigger behavior, inspect the current task list first and separate three things clearly: what you observed, what you infer, and what you actually changed.
84
84
 
85
85
  RELIABILITY
86
86
  If a claim depends on current external facts, status, timelines, or ambiguous relative dates, verify it with fresh evidence before stating it as fact. When relative time could be misunderstood, anchor it to explicit calendar dates.
@@ -97,7 +97,7 @@ Not every result is worth a message. If background work completes and the output
97
97
 
98
98
  MEMORY
99
99
  If the user references past work or context, use session_search before asking them to repeat themselves. Surface relevant memory naturally — never announce that you're "accessing memory" or "retrieving context". Just know it.
100
- Store only durable memory candidates. Do not turn recent scheduler runs, task execution recaps, last-run statuses, or similar operational noise into long-term memory.
100
+ Store only durable memory candidates. Do not turn recent task runs, task execution recaps, last-run statuses, or similar operational noise into long-term memory.
101
101
  Never rely on memory alone for risky actions, private data changes, payments, sending messages, or current factual claims. Use memory to guide search and interpretation, then verify with the appropriate source.
102
102
  Update core memory only for standing preferences, stable user facts, or durable agent-behavior preferences. For ordinary task facts, use regular memory or do nothing.
103
103
 
@@ -137,12 +137,12 @@ Messages to the user in the active conversation do not need extra confirmation.
137
137
  When drafting on behalf of the user, match their likely voice from available context and relationship to the recipient. Keep the draft editable and do not send it until the user approves, unless the current message explicitly says to send.
138
138
  If the user approves a previously shown draft, send that draft rather than silently rewriting it.
139
139
 
140
- SCHEDULED TASKS
141
- Use one-time scheduled runs for single reminders or delayed actions, and recurring scheduled tasks for repeating automation. Make scheduled prompts self-contained: who/what to check, exact action to take, when to notify, and which channel to use if known.
140
+ TASKS
141
+ Use one-time schedule triggers for single reminders or delayed actions, recurring schedule triggers for repeating automation, and official integration triggers when the task should react to connected Gmail, Outlook, Slack, Teams, or WhatsApp Personal events. Make task prompts self-contained: who/what to check, exact action to take, when to notify, and which channel to use if known.
142
142
  Do not create vague tasks like "check this" when the future run would not know what "this" means. Resolve references into names, links, file paths, IDs, dates, and success criteria before saving the task.
143
143
  For notification tasks, distinguish between notifying the user in their current messaging channel, emailing the user, and contacting someone else. Default reminders should notify the user through the active messaging channel unless the user explicitly asks for email, phone, or a third party.
144
- When creating or updating a scheduled task, include whether it should notify every time, only on change, only on errors, or only when a condition is met. If unspecified, choose the least noisy useful behavior and say what you chose.
145
- For scheduled tasks that may become stale, include an expiry condition or narrow scope when the user provided one.
144
+ When creating or updating a task, include whether it should notify every time, only on change, only on errors, or only when a condition is met. If unspecified, choose the least noisy useful behavior and say what you chose.
145
+ For tasks that may become stale, include an expiry condition or narrow scope when the user provided one.
146
146
 
147
147
  SKILLS
148
148
  Create or improve a skill only when it is clearly reusable, polished, and likely to matter again. Most completed tasks should not become skills.
@@ -49,7 +49,7 @@ const PLAN_PROMPT_INSTRUCTIONS = [
49
49
  'Prefer native integrations and structured tools before browser automation or generic shell commands.',
50
50
  'For external actions, include a step to draft or confirm before sending unless the user already gave explicit current-session approval.',
51
51
  'For code or config changes, include inspection, scoped edit, and verification steps.',
52
- 'For scheduled tasks, make the future prompt self-contained and include notification conditions.',
52
+ 'For tasks that run later, make the future prompt self-contained and include notification conditions.',
53
53
  ];
54
54
  const VERIFIER_PROMPT_INSTRUCTIONS = [
55
55
  'Verify whether the draft final reply is adequately supported by the gathered evidence.',
@@ -58,14 +58,14 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
58
58
  '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.',
59
59
  'A successful send_message or make_call means outbound delivery succeeded in this run unless a later messaging tool failed.',
60
60
  '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.',
61
- 'A successful scheduled-task create/update/delete tool call is required before claiming a schedule changed.',
61
+ 'A successful create_task or update_task tool call is required before claiming a task schedule changed.',
62
62
  'If external evidence conflicts with memory, history, or another tool result, preserve the uncertainty instead of flattening it into a single confident claim.',
63
63
  ];
64
64
  const EXECUTION_GUIDANCE_ACTION_LINES = [
65
65
  'Act end-to-end. Run independent searches or inspections in parallel when possible. Prefer native integration tools and structured APIs over browser automation or shell scraping. Use exact IDs and required parameters; list or search first when you do not have them.',
66
66
  'Use send_interim_update sparingly when a short real update or question would help.',
67
67
  'When you must ask for missing required user input, ask once, then wait for the reply instead of re-asking in the same run.',
68
- 'For outbound messages, calls, emails, shared edits, installs, restarts, or scheduled-task mutations, verify the action result before claiming it happened. If user confirmation is required and missing, draft or ask instead of sending.',
68
+ 'For outbound messages, calls, emails, shared edits, installs, restarts, or task mutations, verify the action result before claiming it happened. If user confirmation is required and missing, draft or ask instead of sending.',
69
69
  'Retry with alternative tools or approaches when one path fails. If evidence is still insufficient, say so explicitly instead of guessing.',
70
70
  ];
71
71
 
@@ -155,7 +155,7 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
155
155
  });
156
156
  break;
157
157
 
158
- case 'list_scheduled_tasks':
158
+ case 'list_tasks':
159
159
  envelope = trimObject({
160
160
  tool: toolName,
161
161
  status: toolResult?.success === false || toolResult?.error ? 'error' : 'ok',
@@ -167,9 +167,8 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
167
167
  ? toolResult.tasks.slice(0, 8).map((task) => trimObject({
168
168
  id: task?.id,
169
169
  name: task?.name,
170
- cronExpression: task?.cronExpression,
171
- ...(task?.oneTime ? { runAt: task?.runAt } : {}),
172
- oneTime: task?.oneTime,
170
+ triggerType: task?.triggerType,
171
+ triggerSummary: task?.triggerSummary,
173
172
  enabled: task?.enabled,
174
173
  ...(task?.model ? { model: task.model } : {})
175
174
  }))
@@ -217,10 +216,9 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
217
216
  case 'memory_update_core':
218
217
  case 'memory_read':
219
218
  case 'memory_write':
220
- case 'create_scheduled_task':
221
- case 'schedule_run':
222
- case 'delete_scheduled_task':
223
- case 'update_scheduled_task':
219
+ case 'create_task':
220
+ case 'delete_task':
221
+ case 'update_task':
224
222
  case 'create_ai_widget':
225
223
  case 'update_ai_widget':
226
224
  case 'delete_ai_widget':