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
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ const db = require('../../db/database');
4
+
5
+ class TaskRepository {
6
+ createTask(userId, normalizedTask) {
7
+ const result = db.prepare(
8
+ `INSERT INTO scheduled_tasks (
9
+ user_id, agent_id, name, trigger_type, trigger_config, cron_expression, run_at, one_time,
10
+ execution_mode, task_type, task_config, enabled
11
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
12
+ ).run(
13
+ userId,
14
+ normalizedTask.agentId,
15
+ normalizedTask.name,
16
+ normalizedTask.triggerType,
17
+ JSON.stringify(normalizedTask.triggerConfig),
18
+ normalizedTask.legacyCronExpression,
19
+ normalizedTask.legacyRunAt,
20
+ normalizedTask.legacyOneTime ? 1 : 0,
21
+ normalizedTask.executionMode,
22
+ normalizedTask.taskType,
23
+ JSON.stringify(normalizedTask.taskConfig),
24
+ normalizedTask.enabled ? 1 : 0,
25
+ );
26
+ return result.lastInsertRowid;
27
+ }
28
+
29
+ updateTask(taskId, userId, normalizedTask) {
30
+ db.prepare(
31
+ `UPDATE scheduled_tasks
32
+ SET agent_id = ?, name = ?, trigger_type = ?, trigger_config = ?, cron_expression = ?, run_at = ?,
33
+ one_time = ?, execution_mode = ?, task_type = ?, task_config = ?, enabled = ?
34
+ WHERE id = ? AND user_id = ?`
35
+ ).run(
36
+ normalizedTask.agentId,
37
+ normalizedTask.name,
38
+ normalizedTask.triggerType,
39
+ JSON.stringify(normalizedTask.triggerConfig),
40
+ normalizedTask.legacyCronExpression,
41
+ normalizedTask.legacyRunAt,
42
+ normalizedTask.legacyOneTime ? 1 : 0,
43
+ normalizedTask.executionMode,
44
+ normalizedTask.taskType,
45
+ JSON.stringify(normalizedTask.taskConfig),
46
+ normalizedTask.enabled ? 1 : 0,
47
+ taskId,
48
+ userId,
49
+ );
50
+ }
51
+
52
+ deleteTask(taskId, userId) {
53
+ db.prepare('DELETE FROM scheduled_tasks WHERE id = ? AND user_id = ?').run(taskId, userId);
54
+ }
55
+
56
+ deleteById(taskId, userId) {
57
+ db.prepare('DELETE FROM scheduled_tasks WHERE id = ? AND user_id = ?').run(taskId, userId);
58
+ }
59
+
60
+ getTaskById(taskId, userId) {
61
+ return db.prepare('SELECT * FROM scheduled_tasks WHERE id = ? AND user_id = ?').get(taskId, userId);
62
+ }
63
+
64
+ listTasksForAgent(userId, agentId, includeLegacyMainTasks) {
65
+ return includeLegacyMainTasks
66
+ ? db.prepare(
67
+ `SELECT * FROM scheduled_tasks
68
+ WHERE user_id = ? AND (agent_id = ? OR agent_id IS NULL)
69
+ ORDER BY created_at DESC`
70
+ ).all(userId, agentId)
71
+ : db.prepare(
72
+ `SELECT * FROM scheduled_tasks
73
+ WHERE user_id = ? AND agent_id = ?
74
+ ORDER BY created_at DESC`
75
+ ).all(userId, agentId);
76
+ }
77
+
78
+ listEnabledTasks() {
79
+ return db.prepare('SELECT * FROM scheduled_tasks WHERE enabled = 1').all();
80
+ }
81
+
82
+ listDueOneTimeTasks() {
83
+ return db.prepare(
84
+ `SELECT * FROM scheduled_tasks
85
+ WHERE trigger_type = 'schedule'
86
+ AND one_time = 1
87
+ AND enabled = 1
88
+ AND run_at IS NOT NULL
89
+ AND run_at <= datetime('now')`
90
+ ).all();
91
+ }
92
+
93
+ listEnabledByTriggerTypes(triggerTypes) {
94
+ if (!Array.isArray(triggerTypes) || triggerTypes.length === 0) {
95
+ return [];
96
+ }
97
+ const placeholders = triggerTypes.map(() => '?').join(', ');
98
+ return db.prepare(
99
+ `SELECT * FROM scheduled_tasks
100
+ WHERE enabled = 1
101
+ AND trigger_type IN (${placeholders})`
102
+ ).all(...triggerTypes);
103
+ }
104
+
105
+ listEnabledWhatsappEventTasks(userId, agentId) {
106
+ return db.prepare(
107
+ `SELECT * FROM scheduled_tasks
108
+ WHERE enabled = 1 AND user_id = ? AND agent_id = ? AND trigger_type = 'whatsapp_personal_message_received'`
109
+ ).all(userId, agentId);
110
+ }
111
+
112
+ markTaskTriggered(taskId, userId, fingerprint) {
113
+ db.prepare(
114
+ `UPDATE scheduled_tasks
115
+ SET last_triggered_at = datetime('now'), last_trigger_fingerprint = ?
116
+ WHERE id = ? AND user_id = ?`
117
+ ).run(fingerprint, taskId, userId);
118
+ }
119
+
120
+ markTaskTriggerCheckpoint(taskId, fingerprint) {
121
+ db.prepare(
122
+ `UPDATE scheduled_tasks
123
+ SET last_triggered_at = datetime('now'), last_trigger_fingerprint = ?
124
+ WHERE id = ?`
125
+ ).run(fingerprint, taskId);
126
+ }
127
+
128
+ markTaskRun(taskId, userId) {
129
+ db.prepare('UPDATE scheduled_tasks SET last_run = datetime(\'now\') WHERE id = ? AND user_id = ?').run(taskId, userId);
130
+ }
131
+
132
+ updateTaskConfig(taskId, userId, taskConfig) {
133
+ db.prepare('UPDATE scheduled_tasks SET task_config = ? WHERE id = ? AND user_id = ?')
134
+ .run(JSON.stringify(taskConfig), taskId, userId);
135
+ }
136
+
137
+ getAgentSetting(userId, agentId, key) {
138
+ return db.prepare('SELECT value FROM agent_settings WHERE user_id = ? AND agent_id = ? AND key = ?')
139
+ .get(userId, agentId, key);
140
+ }
141
+
142
+ getUserSetting(userId, key) {
143
+ return db.prepare('SELECT value FROM user_settings WHERE user_id = ? AND key = ?')
144
+ .get(userId, key);
145
+ }
146
+
147
+ getTaskConversation(userId, agentId, platform, platformChatId) {
148
+ return db.prepare(
149
+ 'SELECT id FROM conversations WHERE user_id = ? AND agent_id = ? AND platform = ? AND platform_chat_id = ?'
150
+ ).get(userId, agentId, platform, platformChatId);
151
+ }
152
+
153
+ createTaskConversation({ id, userId, agentId, platform, platformChatId, title }) {
154
+ db.prepare(
155
+ 'INSERT INTO conversations (id, user_id, agent_id, platform, platform_chat_id, title) VALUES (?, ?, ?, ?, ?, ?)'
156
+ ).run(id, userId, agentId, platform, platformChatId, title);
157
+ }
158
+ }
159
+
160
+ module.exports = {
161
+ TaskRepository,
162
+ };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ class TriggerRegistry {
4
+ constructor(adapters = []) {
5
+ this.adapters = new Map();
6
+ for (const adapter of adapters) {
7
+ this.register(adapter);
8
+ }
9
+ }
10
+
11
+ register(adapter) {
12
+ if (!adapter?.type) {
13
+ throw new Error('Trigger adapters must define a type.');
14
+ }
15
+ this.adapters.set(adapter.type, adapter);
16
+ }
17
+
18
+ get(type) {
19
+ return this.adapters.get(String(type || '').trim()) || null;
20
+ }
21
+
22
+ list() {
23
+ return Array.from(this.adapters.values());
24
+ }
25
+ }
26
+
27
+ module.exports = {
28
+ TriggerRegistry,
29
+ };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ function normalizeJsonObject(value, fallback = {}) {
4
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
5
+ return { ...fallback, ...value };
6
+ }
7
+ try {
8
+ const parsed = JSON.parse(String(value || '{}'));
9
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
10
+ ? { ...fallback, ...parsed }
11
+ : { ...fallback };
12
+ } catch {
13
+ return { ...fallback };
14
+ }
15
+ }
16
+
17
+ function stringifyJson(value) {
18
+ const normalized = normalizeJsonObject(value);
19
+ try {
20
+ return JSON.stringify(normalized);
21
+ } catch {
22
+ try {
23
+ const seen = new WeakSet();
24
+ return JSON.stringify(normalized, (_key, currentValue) => {
25
+ if (typeof currentValue === 'bigint') {
26
+ return currentValue.toString();
27
+ }
28
+ if (currentValue && typeof currentValue === 'object') {
29
+ if (seen.has(currentValue)) {
30
+ return '[Circular]';
31
+ }
32
+ seen.add(currentValue);
33
+ }
34
+ return currentValue;
35
+ });
36
+ } catch {
37
+ return JSON.stringify(String(value));
38
+ }
39
+ }
40
+ }
41
+
42
+ module.exports = {
43
+ normalizeJsonObject,
44
+ stringifyJson,
45
+ };
@@ -160,7 +160,7 @@ function recordRateLimitHit(observer, userId, socketId, eventName, retryAfterMs)
160
160
  }
161
161
 
162
162
  function setupWebSocket(io, services) {
163
- const { agentEngine, messagingManager, mcpClient, scheduler, memoryManager, voiceRuntimeManager } = services;
163
+ const { agentEngine, messagingManager, mcpClient, taskRuntime, memoryManager, voiceRuntimeManager } = services;
164
164
  const rateLimitObserver = createRateLimitObserver();
165
165
  const integrationManager =
166
166
  services.integrationManager || services.app?.locals?.integrationManager || null;
@@ -1,7 +1,7 @@
1
1
  const crypto = require('crypto');
2
2
  const db = require('../../db/database');
3
3
  const { resolveAgentId } = require('../agents/manager');
4
- const { getMinimumIntervalMinutes } = require('../scheduler/cron_utils');
4
+ const { findNextRun, getMinimumIntervalMinutes } = require('../tasks/schedule_utils');
5
5
 
6
6
  const MIN_WIDGET_REFRESH_MINUTES = 60;
7
7
 
@@ -228,8 +228,8 @@ class WidgetService {
228
228
  this.app = app;
229
229
  }
230
230
 
231
- get scheduler() {
232
- return this.app?.locals?.scheduler || null;
231
+ get taskRuntime() {
232
+ return this.app?.locals?.taskRuntime || null;
233
233
  }
234
234
 
235
235
  get agentEngine() {
@@ -271,11 +271,11 @@ class WidgetService {
271
271
  .filter(Boolean);
272
272
  }
273
273
 
274
- createWidget(userId, input = {}) {
274
+ async createWidget(userId, input = {}) {
275
275
  const normalized = normalizeWidgetInput(input, userId);
276
- const scheduler = this.scheduler;
277
- if (!scheduler) {
278
- throw new Error('Scheduler not available.');
276
+ const taskRuntime = this.taskRuntime;
277
+ if (!taskRuntime) {
278
+ throw new Error('Task runtime not available.');
279
279
  }
280
280
  const widgetId = crypto.randomUUID();
281
281
 
@@ -302,9 +302,13 @@ class WidgetService {
302
302
 
303
303
  let task;
304
304
  try {
305
- task = scheduler.createTask(userId, {
305
+ task = await taskRuntime.createTask(userId, {
306
306
  name: buildWidgetRefreshTaskName(normalized.name),
307
- cronExpression: normalized.refreshCron,
307
+ triggerType: 'schedule',
308
+ triggerConfig: {
309
+ mode: 'recurring',
310
+ cronExpression: normalized.refreshCron,
311
+ },
308
312
  enabled: normalized.enabled,
309
313
  agentId: normalized.agentId,
310
314
  taskType: 'widget_refresh',
@@ -323,7 +327,7 @@ class WidgetService {
323
327
  ).run(task.id, widgetId, userId);
324
328
  } catch (error) {
325
329
  try {
326
- scheduler.deleteTask(task.id, userId, { allowManaged: true });
330
+ taskRuntime.deleteTask(task.id, userId, { allowManaged: true });
327
331
  } catch {
328
332
  // Ignore cleanup failures and rethrow the original DB error.
329
333
  }
@@ -333,7 +337,7 @@ class WidgetService {
333
337
  return this.getWidget(userId, widgetId);
334
338
  }
335
339
 
336
- updateWidget(userId, widgetId, input = {}) {
340
+ async updateWidget(userId, widgetId, input = {}) {
337
341
  const existingRow = db.prepare('SELECT * FROM ai_widgets WHERE id = ? AND user_id = ?').get(widgetId, userId);
338
342
  if (!existingRow) {
339
343
  throw new Error('Widget not found.');
@@ -353,9 +357,9 @@ class WidgetService {
353
357
  description: input.description,
354
358
  }, userId);
355
359
 
356
- const scheduler = this.scheduler;
357
- if (!scheduler) {
358
- throw new Error('Scheduler not available.');
360
+ const taskRuntime = this.taskRuntime;
361
+ if (!taskRuntime) {
362
+ throw new Error('Task runtime not available.');
359
363
  }
360
364
 
361
365
  db.prepare('BEGIN').run();
@@ -378,12 +382,16 @@ class WidgetService {
378
382
  );
379
383
 
380
384
  if (existingRow.scheduled_task_id) {
381
- scheduler.updateTask(
385
+ await taskRuntime.updateTask(
382
386
  existingRow.scheduled_task_id,
383
387
  userId,
384
388
  {
385
389
  name: buildWidgetRefreshTaskName(normalized.name),
386
- cronExpression: normalized.refreshCron,
390
+ triggerType: 'schedule',
391
+ triggerConfig: {
392
+ mode: 'recurring',
393
+ cronExpression: normalized.refreshCron,
394
+ },
387
395
  enabled: normalized.enabled,
388
396
  agentId: normalized.agentId,
389
397
  taskConfig: { widgetId },
@@ -391,9 +399,13 @@ class WidgetService {
391
399
  { allowManaged: true },
392
400
  );
393
401
  } else {
394
- const task = scheduler.createTask(userId, {
402
+ const task = await taskRuntime.createTask(userId, {
395
403
  name: buildWidgetRefreshTaskName(normalized.name),
396
- cronExpression: normalized.refreshCron,
404
+ triggerType: 'schedule',
405
+ triggerConfig: {
406
+ mode: 'recurring',
407
+ cronExpression: normalized.refreshCron,
408
+ },
397
409
  enabled: normalized.enabled,
398
410
  agentId: normalized.agentId,
399
411
  taskType: 'widget_refresh',
@@ -411,7 +423,7 @@ class WidgetService {
411
423
  try {
412
424
  db.prepare('ROLLBACK').run();
413
425
  } catch {
414
- // Ignore rollback failures and rethrow the original scheduler/DB error.
426
+ // Ignore rollback failures and rethrow the original task runtime/DB error.
415
427
  }
416
428
  throw error;
417
429
  }
@@ -425,10 +437,10 @@ class WidgetService {
425
437
  throw new Error('Widget not found.');
426
438
  }
427
439
 
428
- const scheduler = this.scheduler;
440
+ const taskRuntime = this.taskRuntime;
429
441
  const tx = db.transaction(() => {
430
- if (existingRow.scheduled_task_id && scheduler) {
431
- scheduler.deleteTask(existingRow.scheduled_task_id, userId, { allowManaged: true });
442
+ if (existingRow.scheduled_task_id && taskRuntime) {
443
+ taskRuntime.deleteTask(existingRow.scheduled_task_id, userId, { allowManaged: true });
432
444
  }
433
445
  db.prepare('DELETE FROM ai_widget_snapshots WHERE widget_id = ?').run(widgetId);
434
446
  db.prepare('DELETE FROM ai_widgets WHERE id = ? AND user_id = ?').run(widgetId, userId);
@@ -484,8 +496,8 @@ class WidgetService {
484
496
  try {
485
497
  const prompt = this._buildRefreshPrompt(widget);
486
498
  const result = await engine.run(userId, prompt, {
487
- triggerType: 'scheduler',
488
- triggerSource: 'scheduler',
499
+ triggerType: 'schedule',
500
+ triggerSource: 'tasks',
489
501
  agentId: widget.agentId,
490
502
  app: this.app,
491
503
  taskId: options.taskId || widget.scheduledTaskId || null,
@@ -586,7 +598,7 @@ class WidgetService {
586
598
  lastError: row.last_error || null,
587
599
  createdAt: row.created_at || null,
588
600
  updatedAt: row.updated_at || null,
589
- nextRefresh: row.refresh_cron ? this.scheduler?._getNextRun?.(row.refresh_cron) || null : null,
601
+ nextRefresh: row.refresh_cron ? findNextRun(row.refresh_cron)?.toISOString() || null : null,
590
602
  latestSnapshot,
591
603
  };
592
604
  }
@@ -1,147 +0,0 @@
1
- 'use strict';
2
-
3
- const express = require('express');
4
- const { sanitizeError } = require('../utils/security');
5
- const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
6
- const { wearableDeviceAuth } = require('../services/wearables/device_auth');
7
- const { createVoiceMessage } = require('../services/voice/message');
8
-
9
- const router = express.Router();
10
-
11
- router.post('/pairing/code', (req, res) => {
12
- try {
13
- const sessionUserId = req.session?.userId;
14
- if (!sessionUserId) {
15
- return res.status(401).json({ error: 'Authentication required' });
16
- }
17
- const agentId = resolveAgentId(sessionUserId, getAgentIdFromRequest(req));
18
- const pairing = wearableDeviceAuth.createPairingCode(req.session.userId, {
19
- agentId,
20
- ttlMinutes: req.body?.ttlMinutes,
21
- source: 'messaging_tab',
22
- deviceHint: req.body?.deviceHint,
23
- });
24
- res.status(201).json(pairing);
25
- } catch (err) {
26
- res.status(500).json({ error: sanitizeError(err) });
27
- }
28
- });
29
-
30
- function extractBearerToken(req) {
31
- const auth = String(req.get('authorization') || '');
32
- if (!auth.toLowerCase().startsWith('bearer ')) return null;
33
- return auth.slice(7).trim();
34
- }
35
-
36
- function requireWearableToken(req, res, next) {
37
- const token = extractBearerToken(req);
38
- if (!token) return res.status(401).json({ error: 'Missing bearer token' });
39
- const tokenRow = wearableDeviceAuth.validateBearerToken(token);
40
- if (!tokenRow) return res.status(401).json({ error: 'Invalid wearable token' });
41
- wearableDeviceAuth.touchToken(tokenRow.id);
42
- req.wearableToken = tokenRow;
43
- next();
44
- }
45
-
46
- router.post('/pair/claim', (req, res) => {
47
- try {
48
- const code = String(req.body?.code || '').trim();
49
- if (!code) return res.status(400).json({ error: 'code is required' });
50
-
51
- const claimed = wearableDeviceAuth.claimPairingCode(code, {
52
- deviceId: req.body?.deviceId,
53
- deviceName: req.body?.deviceName,
54
- macAddress: req.body?.macAddress,
55
- protocol: req.body?.protocol,
56
- firmwareVersion: req.body?.firmwareVersion,
57
- });
58
-
59
- res.status(201).json({
60
- token: claimed.token,
61
- tokenId: claimed.tokenId,
62
- agentId: claimed.agentId,
63
- deviceId: claimed.deviceId,
64
- deviceName: claimed.deviceName,
65
- protocol: claimed.protocol,
66
- });
67
- } catch (err) {
68
- res.status(err.status || 500).json({ error: sanitizeError(err) });
69
- }
70
- });
71
-
72
- router.post('/utterance', requireWearableToken, async (req, res) => {
73
- try {
74
- const token = req.wearableToken;
75
- const text = String(req.body?.text || '').trim();
76
- if (!text) return res.status(400).json({ error: 'text is required' });
77
-
78
- const messagingManager = req.app.locals.messagingManager;
79
- if (!messagingManager) {
80
- return res.status(503).json({ error: 'Agent services unavailable' });
81
- }
82
-
83
- const chatId = token.device_id || token.id;
84
- const message = createVoiceMessage({
85
- platform: 'waveshare_wearable',
86
- agentId: token.agent_id || null,
87
- chatId,
88
- sender: chatId,
89
- senderName: token.device_name || 'NeoOS Wearable',
90
- content: text,
91
- mediaType: 'audio',
92
- metadata: {
93
- source: 'wearable_device_token',
94
- },
95
- });
96
-
97
- await messagingManager.ingestMessage(
98
- token.user_id,
99
- 'waveshare_wearable',
100
- message,
101
- { agentId: token.agent_id || null },
102
- );
103
-
104
- res.status(202).json({ success: true, accepted: true });
105
- } catch (err) {
106
- res.status(500).json({ error: sanitizeError(err) });
107
- }
108
- });
109
-
110
- router.get('/me', requireWearableToken, (req, res) => {
111
- const token = req.wearableToken;
112
- res.json({
113
- deviceId: token.device_id,
114
- name: token.device_name,
115
- macAddress: token.mac_address,
116
- protocol: token.protocol,
117
- firmwareVersion: token.firmware_version,
118
- lastSeenAt: token.last_seen_at,
119
- });
120
- });
121
-
122
- router.get('/responses/next', requireWearableToken, (req, res) => {
123
- try {
124
- const token = req.wearableToken;
125
- const limit = Number(req.query.limit || 5);
126
- const responses = wearableDeviceAuth.getPendingResponses(token, limit);
127
- res.json({ responses });
128
- } catch (err) {
129
- res.status(500).json({ error: sanitizeError(err) });
130
- }
131
- });
132
-
133
- router.post('/responses/ack', requireWearableToken, (req, res) => {
134
- try {
135
- const token = req.wearableToken;
136
- const lastMessageId = Number(req.body?.lastMessageId || 0);
137
- if (!Number.isFinite(lastMessageId) || lastMessageId <= 0) {
138
- return res.status(400).json({ error: 'lastMessageId must be a positive number' });
139
- }
140
- wearableDeviceAuth.setLastCursor(token.id, lastMessageId);
141
- res.json({ success: true, lastMessageId });
142
- } catch (err) {
143
- res.status(500).json({ error: sanitizeError(err) });
144
- }
145
- });
146
-
147
- module.exports = router;
@@ -1,40 +0,0 @@
1
- const db = require('../../db/database');
2
- const { BasePlatform } = require('./base');
3
- const { wearableDeviceAuth } = require('../wearables/device_auth');
4
-
5
- class WaveshareWearablePlatform extends BasePlatform {
6
- constructor(config = {}) {
7
- super('waveshare_wearable', config);
8
- this.supportsGroups = false;
9
- this.supportsMedia = false;
10
- this.supportsVoice = false;
11
- this.status = 'disconnected';
12
- }
13
-
14
- async connect() {
15
- this.status = 'connected';
16
- this.emit('connected');
17
- }
18
-
19
- async disconnect() {
20
- this.status = 'disconnected';
21
- this.emit('disconnected', { reason: 'manual' });
22
- }
23
-
24
- async sendMessage() {
25
- this.emit('message_sent');
26
- return { queued: true };
27
- }
28
-
29
- getAuthInfo() {
30
- return {
31
- label: this.config?.deviceLabel || 'Wearable provisioning enabled',
32
- };
33
- }
34
-
35
- listDevices(userId, options = {}) {
36
- return wearableDeviceAuth.listDevicesForUser(userId, options);
37
- }
38
- }
39
-
40
- module.exports = { WaveshareWearablePlatform };