neoagent 2.0.1 → 2.0.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.
Files changed (38) hide show
  1. package/.env.example +8 -0
  2. package/docs/configuration.md +3 -0
  3. package/package.json +1 -1
  4. package/server/db/database.js +75 -0
  5. package/server/http/middleware.js +26 -2
  6. package/server/http/routes.js +1 -0
  7. package/server/index.js +97 -6
  8. package/server/public/.last_build_id +1 -1
  9. package/server/public/assets/NOTICES +24298 -26578
  10. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  11. package/server/public/assets/shaders/ink_sparkle.frag +1 -0
  12. package/server/public/assets/shaders/stretch_effect.frag +64 -0
  13. package/server/public/assets/web/icons/Icon-192.png +0 -0
  14. package/server/public/canvaskit/canvaskit.js +91 -90
  15. package/server/public/canvaskit/canvaskit.js.symbols +11577 -11578
  16. package/server/public/canvaskit/canvaskit.wasm +0 -0
  17. package/server/public/canvaskit/chromium/canvaskit.js +92 -91
  18. package/server/public/canvaskit/chromium/canvaskit.js.symbols +10382 -10395
  19. package/server/public/canvaskit/chromium/canvaskit.wasm +0 -0
  20. package/server/public/canvaskit/skwasm.js +95 -89
  21. package/server/public/canvaskit/skwasm.js.symbols +12814 -12146
  22. package/server/public/canvaskit/skwasm.wasm +0 -0
  23. package/server/public/canvaskit/skwasm_heavy.js +95 -89
  24. package/server/public/canvaskit/skwasm_heavy.js.symbols +14435 -13747
  25. package/server/public/canvaskit/skwasm_heavy.wasm +0 -0
  26. package/server/public/canvaskit/wimp.js +136 -0
  27. package/server/public/canvaskit/wimp.js.symbols +11313 -0
  28. package/server/public/canvaskit/wimp.wasm +0 -0
  29. package/server/public/favicon.svg +12 -0
  30. package/server/public/flutter.js +3 -4
  31. package/server/public/flutter_bootstrap.js +5 -6
  32. package/server/public/flutter_service_worker.js +22 -199
  33. package/server/public/index.html +1 -0
  34. package/server/public/main.dart.js +73857 -65543
  35. package/server/routes/recordings.js +113 -0
  36. package/server/services/manager.js +9 -0
  37. package/server/services/recordings/deepgram.js +53 -0
  38. package/server/services/recordings/manager.js +715 -0
@@ -0,0 +1,113 @@
1
+ const express = require('express');
2
+
3
+ const { requireAuth } = require('../middleware/auth');
4
+ const { sanitizeError } = require('../utils/security');
5
+
6
+ const router = express.Router();
7
+
8
+ router.use(requireAuth);
9
+
10
+ async function readChunkBody(req) {
11
+ if (Buffer.isBuffer(req.body) && req.body.length > 0) {
12
+ return req.body;
13
+ }
14
+ if (req.readableEnded) {
15
+ return Buffer.alloc(0);
16
+ }
17
+
18
+ return new Promise((resolve, reject) => {
19
+ const chunks = [];
20
+ req.on('data', (chunk) => {
21
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
22
+ });
23
+ req.on('end', () => resolve(Buffer.concat(chunks)));
24
+ req.on('error', reject);
25
+ });
26
+ }
27
+
28
+ router.get('/', (req, res) => {
29
+ try {
30
+ const manager = req.app.locals.recordingManager;
31
+ const sessions = manager.listSessions(req.session.userId, {
32
+ limit: Number(req.query.limit) || 24,
33
+ });
34
+ res.json(sessions);
35
+ } catch (err) {
36
+ res.status(500).json({ error: sanitizeError(err) });
37
+ }
38
+ });
39
+
40
+ router.get('/:sessionId', (req, res) => {
41
+ try {
42
+ const manager = req.app.locals.recordingManager;
43
+ const session = manager.getSession(req.session.userId, req.params.sessionId);
44
+ res.json({ session });
45
+ } catch (err) {
46
+ const message = sanitizeError(err);
47
+ res.status(/not found/i.test(message) ? 404 : 500).json({ error: message });
48
+ }
49
+ });
50
+
51
+ router.post('/', (req, res) => {
52
+ try {
53
+ const manager = req.app.locals.recordingManager;
54
+ const session = manager.createSession(req.session.userId, req.body || {});
55
+ res.status(201).json({ session });
56
+ } catch (err) {
57
+ const message = sanitizeError(err);
58
+ res.status(/source|title|required|duplicate/i.test(message) ? 400 : 500).json({ error: message });
59
+ }
60
+ });
61
+
62
+ router.post('/:sessionId/chunks', async (req, res) => {
63
+ try {
64
+ const manager = req.app.locals.recordingManager;
65
+ const body = await readChunkBody(req);
66
+ const result = manager.appendChunk(
67
+ req.session.userId,
68
+ req.params.sessionId,
69
+ {
70
+ sourceKey: req.get('x-recording-source-key') || req.query.sourceKey,
71
+ sequenceIndex: req.get('x-recording-sequence') || req.query.sequenceIndex,
72
+ startMs: req.get('x-recording-start-ms') || req.query.startMs,
73
+ endMs: req.get('x-recording-end-ms') || req.query.endMs,
74
+ mimeType: req.get('content-type') || req.query.mimeType,
75
+ },
76
+ body,
77
+ );
78
+ res.status(result.duplicate ? 200 : 201).json(result);
79
+ } catch (err) {
80
+ console.error('[Recordings] Chunk upload failed:', err);
81
+ const message = sanitizeError(err);
82
+ const status = /not found/i.test(message)
83
+ ? 404
84
+ : /empty|required|unknown|non-negative|accepting/i.test(message)
85
+ ? 400
86
+ : 500;
87
+ res.status(status).json({ error: message });
88
+ }
89
+ });
90
+
91
+ router.post('/:sessionId/finalize', (req, res) => {
92
+ try {
93
+ const manager = req.app.locals.recordingManager;
94
+ const session = manager.finalizeSession(req.session.userId, req.params.sessionId, req.body || {});
95
+ res.json({ session });
96
+ } catch (err) {
97
+ const message = sanitizeError(err);
98
+ res.status(/not found/i.test(message) ? 404 : 500).json({ error: message });
99
+ }
100
+ });
101
+
102
+ router.post('/:sessionId/retry', async (req, res) => {
103
+ try {
104
+ const manager = req.app.locals.recordingManager;
105
+ const session = await manager.retrySession(req.session.userId, req.params.sessionId);
106
+ res.json({ session });
107
+ } catch (err) {
108
+ const message = sanitizeError(err);
109
+ res.status(/not found/i.test(message) ? 404 : /configured/i.test(message) ? 400 : 500).json({ error: message });
110
+ }
111
+ });
112
+
113
+ module.exports = router;
@@ -12,6 +12,7 @@ const { MessagingManager } = require('./messaging/manager');
12
12
  const { Scheduler } = require('./scheduler/cron');
13
13
  const { setupWebSocket } = require('./websocket');
14
14
  const { registerMessagingAutomation } = require('./messaging/automation');
15
+ const { RecordingManager } = require('./recordings/manager');
15
16
 
16
17
  async function startServices(app, io) {
17
18
  try {
@@ -55,6 +56,9 @@ async function startServices(app, io) {
55
56
 
56
57
  messagingManager.restoreConnections().catch(err => console.error('[Messaging] Restore error:', err.message));
57
58
 
59
+ const recordingManager = new RecordingManager(io);
60
+ app.locals.recordingManager = recordingManager;
61
+
58
62
  const users = db.prepare('SELECT id FROM users').all();
59
63
  for (const u of users) {
60
64
  mcpClient.loadFromDB(u.id).catch(err => console.error('[MCP] Auto-start error:', err.message));
@@ -77,11 +81,16 @@ async function startServices(app, io) {
77
81
  messagingManager,
78
82
  mcpClient,
79
83
  scheduler,
84
+ recordingManager,
80
85
  memoryManager,
81
86
  app
82
87
  });
83
88
  app.locals.io = io;
84
89
 
90
+ recordingManager.resumePendingSessions().catch((err) => {
91
+ console.error('[Recordings] Resume error:', err.message);
92
+ });
93
+
85
94
  console.log('All services initialized');
86
95
  } catch (err) {
87
96
  console.error('Service init error:', err);
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_MODEL = process.env.DEEPGRAM_MODEL || 'nova-3';
4
+ const DEFAULT_LANGUAGE = process.env.DEEPGRAM_LANGUAGE || 'multi';
5
+ const DEFAULT_BASE_URL = process.env.DEEPGRAM_BASE_URL || 'https://api.deepgram.com';
6
+
7
+ function isDeepgramConfigured() {
8
+ return typeof process.env.DEEPGRAM_API_KEY === 'string' && process.env.DEEPGRAM_API_KEY.trim().length > 0;
9
+ }
10
+
11
+ async function transcribeChunkWithDeepgram({ audioBytes, mimeType, detectLanguage = DEFAULT_LANGUAGE } = {}) {
12
+ if (!isDeepgramConfigured()) {
13
+ throw new Error('DEEPGRAM_API_KEY is not configured.');
14
+ }
15
+ if (!(audioBytes instanceof Uint8Array) || audioBytes.byteLength === 0) {
16
+ throw new Error('Audio payload is empty.');
17
+ }
18
+
19
+ const query = new URLSearchParams({
20
+ model: DEFAULT_MODEL,
21
+ language: detectLanguage || DEFAULT_LANGUAGE,
22
+ punctuate: 'true',
23
+ smart_format: 'true',
24
+ paragraphs: 'true',
25
+ utterances: 'true',
26
+ diarize: 'false',
27
+ });
28
+ const response = await fetch(
29
+ `${DEFAULT_BASE_URL.replace(/\/$/, '')}/v1/listen?${query.toString()}`,
30
+ {
31
+ method: 'POST',
32
+ headers: {
33
+ Authorization: `Token ${process.env.DEEPGRAM_API_KEY.trim()}`,
34
+ 'Content-Type': mimeType || 'application/octet-stream',
35
+ },
36
+ body: audioBytes,
37
+ },
38
+ );
39
+
40
+ if (!response.ok) {
41
+ const body = await response.text();
42
+ throw new Error(`Deepgram request failed (${response.status}): ${body || 'empty response'}`);
43
+ }
44
+
45
+ return response.json();
46
+ }
47
+
48
+ module.exports = {
49
+ DEFAULT_LANGUAGE,
50
+ DEFAULT_MODEL,
51
+ isDeepgramConfigured,
52
+ transcribeChunkWithDeepgram,
53
+ };