neoagent 3.0.1-beta.8 → 3.1.0

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 (100) hide show
  1. package/.env.example +2 -10
  2. package/README.md +12 -3
  3. package/docs/automation.md +37 -0
  4. package/docs/benchmarking.md +102 -0
  5. package/docs/billing.md +34 -8
  6. package/docs/configuration.md +11 -2
  7. package/docs/getting-started.md +10 -8
  8. package/docs/operations.md +1 -1
  9. package/flutter_app/lib/main.dart +3 -0
  10. package/flutter_app/lib/main_account_settings.dart +138 -0
  11. package/flutter_app/lib/main_app_shell.dart +38 -13
  12. package/flutter_app/lib/main_billing.dart +1465 -0
  13. package/flutter_app/lib/main_chat.dart +1612 -214
  14. package/flutter_app/lib/main_controller.dart +398 -26
  15. package/flutter_app/lib/main_devices.dart +293 -207
  16. package/flutter_app/lib/main_models.dart +142 -0
  17. package/flutter_app/lib/main_navigation.dart +19 -1
  18. package/flutter_app/lib/main_operations.dart +288 -9
  19. package/flutter_app/lib/main_settings.dart +510 -300
  20. package/flutter_app/lib/main_shared.dart +2 -0
  21. package/flutter_app/lib/main_timeline.dart +1378 -0
  22. package/flutter_app/lib/src/backend_client.dart +149 -19
  23. package/flutter_app/lib/src/desktop_companion_actions.dart +132 -21
  24. package/flutter_app/lib/src/desktop_companion_io.dart +65 -1
  25. package/flutter_app/lib/src/desktop_companion_stub.dart +12 -0
  26. package/flutter_app/lib/src/desktop_ocr_bridge.dart +2 -0
  27. package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +125 -0
  28. package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +30 -0
  29. package/flutter_app/lib/src/desktop_passive_history.dart +332 -0
  30. package/flutter_app/lib/src/recording_bridge_io.dart +80 -72
  31. package/flutter_app/lib/src/recording_bridge_web.dart +127 -114
  32. package/flutter_app/lib/src/recording_chunk_queue.dart +149 -0
  33. package/flutter_app/lib/src/recording_chunk_queue_io.dart +182 -0
  34. package/flutter_app/lib/src/recording_payloads.dart +9 -0
  35. package/flutter_app/macos/Runner/AppDelegate.swift +25 -0
  36. package/flutter_app/windows/runner/flutter_window.cpp +75 -0
  37. package/landing/index.html +3 -3
  38. package/lib/manager.js +184 -66
  39. package/package.json +4 -1
  40. package/server/admin/access.js +12 -7
  41. package/server/admin/admin.css +78 -0
  42. package/server/admin/admin.js +436 -10
  43. package/server/admin/billing.js +165 -42
  44. package/server/admin/index.html +80 -5
  45. package/server/admin/users.js +15 -15
  46. package/server/db/database.js +125 -20
  47. package/server/http/routes.js +1 -0
  48. package/server/http/static.js +4 -2
  49. package/server/public/.last_build_id +1 -1
  50. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  51. package/server/public/canvaskit/wimp.js.symbols +8475 -8467
  52. package/server/public/canvaskit/wimp.wasm +0 -0
  53. package/server/public/flutter_bootstrap.js +2 -2
  54. package/server/public/main.dart.js +91077 -87037
  55. package/server/routes/account.js +53 -0
  56. package/server/routes/admin.js +345 -64
  57. package/server/routes/agents.js +203 -21
  58. package/server/routes/billing.js +5 -0
  59. package/server/routes/browser.js +8 -1
  60. package/server/routes/recordings.js +96 -6
  61. package/server/routes/screenHistory.js +140 -2
  62. package/server/routes/timeline.js +43 -0
  63. package/server/services/account/erasure.js +263 -0
  64. package/server/services/ai/hooks.js +4 -1
  65. package/server/services/ai/loop/agent_engine_core.js +8 -1
  66. package/server/services/ai/loop/blank_recovery.js +36 -0
  67. package/server/services/ai/loop/conversation_loop.js +166 -33
  68. package/server/services/ai/messagingFallback.js +22 -0
  69. package/server/services/ai/rate_limits.js +28 -5
  70. package/server/services/ai/systemPrompt.js +6 -5
  71. package/server/services/ai/taskAnalysis.js +2 -0
  72. package/server/services/ai/toolEvidence.js +15 -0
  73. package/server/services/ai/toolResult.js +40 -0
  74. package/server/services/ai/tools.js +163 -4
  75. package/server/services/android/controller.js +6 -2
  76. package/server/services/billing/plans.js +2 -1
  77. package/server/services/browser/anti_detection.js +192 -0
  78. package/server/services/browser/controller.js +180 -54
  79. package/server/services/desktop/auth.js +3 -0
  80. package/server/services/desktop/registry.js +50 -2
  81. package/server/services/integrations/google/calendar.js +22 -14
  82. package/server/services/manager.js +12 -42
  83. package/server/services/memory/ingestion_chunking.js +268 -0
  84. package/server/services/messaging/telnyx.js +9 -8
  85. package/server/services/recordings/manager.js +60 -27
  86. package/server/services/runtime/backends/local-vm.js +40 -22
  87. package/server/services/runtime/docker-vm-manager.js +157 -266
  88. package/server/services/runtime/guest_bootstrap.js +17 -5
  89. package/server/services/runtime/guest_image.js +188 -0
  90. package/server/services/runtime/manager.js +0 -1
  91. package/server/services/runtime/validation.js +3 -8
  92. package/server/services/social_video/service.js +60 -10
  93. package/server/services/tasks/runtime.js +234 -9
  94. package/server/services/tasks/schedule_utils.js +5 -5
  95. package/server/services/tasks/task_repository.js +13 -0
  96. package/server/services/timeline/service.js +558 -0
  97. package/server/services/wearable/gateway.js +1 -1
  98. package/server/services/websocket.js +21 -3
  99. package/server/services/desktop/screenRecorder.js +0 -292
  100. package/server/services/desktop/screen_recorder_support.js +0 -46
@@ -1,292 +0,0 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
- const fs = require('fs/promises');
5
- const path = require('path');
6
- const os = require('os');
7
- const { execFile } = require('child_process');
8
- const { promisify } = require('util');
9
- const tesseract = require('tesseract.js');
10
- const db = require('../../db/database');
11
- const { getErrorMessage } = require('../bootstrap_helpers');
12
- const {
13
- CLEANUP_INTERVAL_MS,
14
- DEFAULT_CAPTURE_INTERVAL_MS,
15
- DEFAULT_RETENTION_DAYS,
16
- FRONTMOST_APP_SCRIPT,
17
- MINIMUM_TEXT_LENGTH,
18
- MIN_CAPTURE_INTERVAL_MS,
19
- hasOpenConnectionForUser,
20
- isExplicitlyEnabled,
21
- parsePositiveInteger,
22
- } = require('./screen_recorder_support');
23
-
24
- const execFileAsync = promisify(execFile);
25
-
26
- class ScreenRecorder {
27
- constructor(options = {}) {
28
- this.env = options.env || process.env;
29
- this.platform = options.platform || process.platform;
30
- this.db = options.db || db;
31
- this.fs = options.fs || fs;
32
- this.execFile = options.execFile || execFileAsync;
33
- this.recognize = options.recognize || tesseract.recognize;
34
- this.setInterval = options.setInterval || setInterval;
35
- this.clearInterval = options.clearInterval || clearInterval;
36
- this.now = options.now || Date.now;
37
- this.hasActiveCaptureSessionForUser =
38
- typeof options.hasActiveCaptureSessionForUser === 'function'
39
- ? options.hasActiveCaptureSessionForUser
40
- : () => false;
41
-
42
- this.intervalId = null;
43
- this.cleanupIntervalId = null;
44
- this.isRecording = false;
45
- this.activeCapturePromise = null;
46
- this.tempFilePath = options.tempFilePath || path.join(
47
- os.tmpdir(),
48
- `neoagent-screen-${process.pid}-${crypto.randomUUID()}.png`,
49
- );
50
- this.lastBenignSkipAt = 0;
51
- this.lastErrorLogAt = 0;
52
- this.ownerUserId = null;
53
- this.intervalMs = DEFAULT_CAPTURE_INTERVAL_MS;
54
- this.retentionDays = DEFAULT_RETENTION_DAYS;
55
- this.state = 'idle';
56
- this.reason = 'not started';
57
- this.lastCaptureAt = null;
58
- this.lastSuccessAt = null;
59
- this.lastSkipReason = null;
60
- this.lastError = null;
61
- }
62
-
63
- getStatus() {
64
- return {
65
- state: this.state,
66
- reason: this.reason,
67
- ownerUserId: this.ownerUserId,
68
- intervalMs: this.intervalMs,
69
- retentionDays: this.retentionDays,
70
- lastCaptureAt: this.lastCaptureAt,
71
- lastSuccessAt: this.lastSuccessAt,
72
- lastSkipReason: this.lastSkipReason,
73
- lastError: this.lastError,
74
- };
75
- }
76
-
77
- _setInactiveState(state, reason) {
78
- this.state = state;
79
- this.reason = reason;
80
- this.isRecording = false;
81
- return this.getStatus();
82
- }
83
-
84
- _loadConfiguration() {
85
- const ownerUserId = parsePositiveInteger(
86
- this.env.NEOAGENT_SCREEN_RECORDER_USER_ID,
87
- 'NEOAGENT_SCREEN_RECORDER_USER_ID',
88
- null,
89
- );
90
- if (ownerUserId == null) {
91
- throw new Error('NEOAGENT_SCREEN_RECORDER_USER_ID is required when screen recording is enabled.');
92
- }
93
-
94
- this.intervalMs = parsePositiveInteger(
95
- this.env.NEOAGENT_SCREEN_RECORDER_INTERVAL_MS,
96
- 'NEOAGENT_SCREEN_RECORDER_INTERVAL_MS',
97
- DEFAULT_CAPTURE_INTERVAL_MS,
98
- MIN_CAPTURE_INTERVAL_MS,
99
- );
100
- this.retentionDays = parsePositiveInteger(
101
- this.env.NEOAGENT_SCREEN_RECORDER_RETENTION_DAYS,
102
- 'NEOAGENT_SCREEN_RECORDER_RETENTION_DAYS',
103
- DEFAULT_RETENTION_DAYS,
104
- );
105
- this.ownerUserId = ownerUserId;
106
- }
107
-
108
- _ownerExists() {
109
- return Boolean(
110
- this.db.prepare('SELECT id FROM users WHERE id = ?').get(this.ownerUserId),
111
- );
112
- }
113
-
114
- start() {
115
- if (this.isRecording) {
116
- return this.getStatus();
117
- }
118
-
119
- if (!isExplicitlyEnabled(this.env.NEOAGENT_SCREEN_RECORDER_ENABLED)) {
120
- return this._setInactiveState('disabled', 'NEOAGENT_SCREEN_RECORDER_ENABLED is not enabled');
121
- }
122
- if (this.platform !== 'darwin') {
123
- return this._setInactiveState('unsupported', 'screen recording is currently supported only on macOS');
124
- }
125
-
126
- try {
127
- this._loadConfiguration();
128
- if (!this._ownerExists()) {
129
- return this._setInactiveState(
130
- 'misconfigured',
131
- `configured screen recorder user ${this.ownerUserId} does not exist`,
132
- );
133
- }
134
- } catch (err) {
135
- return this._setInactiveState('misconfigured', getErrorMessage(err));
136
- }
137
-
138
- this.isRecording = true;
139
- this.state = 'running';
140
- this.reason = null;
141
- this.lastError = null;
142
-
143
- this.intervalId = this.setInterval(() => {
144
- void this.captureAndProcess();
145
- }, this.intervalMs);
146
- this.intervalId?.unref?.();
147
-
148
- this.cleanupIntervalId = this.setInterval(
149
- () => this.cleanupOldRecords(),
150
- CLEANUP_INTERVAL_MS,
151
- );
152
- this.cleanupIntervalId?.unref?.();
153
-
154
- void this.captureAndProcess();
155
- this.cleanupOldRecords();
156
- return this.getStatus();
157
- }
158
-
159
- async stop() {
160
- const wasRunning = this.isRecording;
161
- this.isRecording = false;
162
-
163
- if (this.intervalId) {
164
- this.clearInterval(this.intervalId);
165
- this.intervalId = null;
166
- }
167
- if (this.cleanupIntervalId) {
168
- this.clearInterval(this.cleanupIntervalId);
169
- this.cleanupIntervalId = null;
170
- }
171
-
172
- if (this.activeCapturePromise) {
173
- await this.activeCapturePromise;
174
- }
175
- if (wasRunning) {
176
- this.state = 'stopped';
177
- this.reason = 'service stopped';
178
- }
179
- return this.getStatus();
180
- }
181
-
182
- _isCaptureInactiveApp(appName) {
183
- const normalized = String(appName || '').trim().toLowerCase();
184
- return normalized === '' || normalized === 'loginwindow' || normalized === 'screensaverengine';
185
- }
186
-
187
- _recordSkip(reason) {
188
- this.lastSkipReason = reason;
189
- const now = this.now();
190
- if (now - this.lastBenignSkipAt < 5 * 60 * 1000) {
191
- return;
192
- }
193
- this.lastBenignSkipAt = now;
194
- console.log(`[ScreenRecorder] Capture skipped: ${reason}`);
195
- }
196
-
197
- _recordError(err) {
198
- this.lastError = getErrorMessage(err);
199
- const now = this.now();
200
- if (now - this.lastErrorLogAt < 5 * 60 * 1000) {
201
- return;
202
- }
203
- this.lastErrorLogAt = now;
204
- console.error('[ScreenRecorder] Capture/OCR failed:', this.lastError);
205
- }
206
-
207
- captureAndProcess() {
208
- if (!this.isRecording) {
209
- return Promise.resolve();
210
- }
211
- if (this.activeCapturePromise) {
212
- return this.activeCapturePromise;
213
- }
214
-
215
- this.activeCapturePromise = this._captureAndProcess().finally(() => {
216
- this.activeCapturePromise = null;
217
- });
218
- return this.activeCapturePromise;
219
- }
220
-
221
- async _captureAndProcess() {
222
- this.lastCaptureAt = new Date(this.now()).toISOString();
223
- try {
224
- if (!this.hasActiveCaptureSessionForUser(this.ownerUserId)) {
225
- this._recordSkip('no active external capture session for the configured user');
226
- return;
227
- }
228
-
229
- let frontmostApp = '';
230
- try {
231
- const { stdout } = await this.execFile('osascript', ['-e', FRONTMOST_APP_SCRIPT]);
232
- frontmostApp = String(stdout || '').trim();
233
- } catch {
234
- frontmostApp = '';
235
- }
236
-
237
- if (this._isCaptureInactiveApp(frontmostApp)) {
238
- this._recordSkip('no active frontmost app');
239
- return;
240
- }
241
-
242
- await this.execFile('screencapture', ['-x', this.tempFilePath]);
243
- await this.fs.access(this.tempFilePath);
244
-
245
- const { data } = await this.recognize(this.tempFilePath, 'eng+deu', {
246
- logger: () => {},
247
- });
248
- const textContent = String(data?.text || '').trim();
249
-
250
- if (!this.isRecording || textContent.length <= MINIMUM_TEXT_LENGTH) {
251
- return;
252
- }
253
-
254
- this.db.prepare(`
255
- INSERT INTO screen_history (user_id, app_name, text_content)
256
- VALUES (?, ?, ?)
257
- `).run(this.ownerUserId, frontmostApp, textContent);
258
- this.lastSuccessAt = new Date(this.now()).toISOString();
259
- this.lastSkipReason = null;
260
- this.lastError = null;
261
- } catch (err) {
262
- this._recordError(err);
263
- } finally {
264
- try {
265
- await this.fs.unlink(this.tempFilePath);
266
- } catch {
267
- // The file is absent when capture is skipped or fails before creating it.
268
- }
269
- }
270
- }
271
-
272
- cleanupOldRecords() {
273
- try {
274
- const result = this.db.prepare(`
275
- DELETE FROM screen_history
276
- WHERE timestamp < datetime('now', ?)
277
- `).run(`-${this.retentionDays} days`);
278
- if (result.changes > 0) {
279
- console.log(`[ScreenRecorder] Purged ${result.changes} old screen history records.`);
280
- }
281
- } catch (err) {
282
- console.error('[ScreenRecorder] Cleanup failed:', getErrorMessage(err));
283
- }
284
- }
285
- }
286
-
287
- module.exports = {
288
- ScreenRecorder,
289
- hasOpenConnectionForUser,
290
- isExplicitlyEnabled,
291
- parsePositiveInteger,
292
- };
@@ -1,46 +0,0 @@
1
- 'use strict';
2
-
3
- const DEFAULT_CAPTURE_INTERVAL_MS = 10 * 1000;
4
- const DEFAULT_RETENTION_DAYS = 7;
5
- const CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
6
- const MIN_CAPTURE_INTERVAL_MS = 1000;
7
- const MINIMUM_TEXT_LENGTH = 5;
8
- const FRONTMOST_APP_SCRIPT = 'tell application "System Events" to get name of first application process whose frontmost is true';
9
-
10
- function isExplicitlyEnabled(value) {
11
- return ['1', 'true', 'on', 'yes'].includes(String(value || '').trim().toLowerCase());
12
- }
13
-
14
- function parsePositiveInteger(value, name, fallback, minimum = 1) {
15
- if (value == null || String(value).trim() === '') {
16
- return fallback;
17
- }
18
-
19
- const parsed = Number(value);
20
- if (!Number.isSafeInteger(parsed) || parsed < minimum) {
21
- throw new Error(`${name} must be an integer greater than or equal to ${minimum}.`);
22
- }
23
- return parsed;
24
- }
25
-
26
- function hasOpenConnectionForUser(registry, userId) {
27
- const connections = registry?.connectionsByUser?.get(String(userId));
28
- if (connections instanceof Map) {
29
- return Array.from(connections.values()).some(
30
- (connection) => typeof connection?.isOpen === 'function' && connection.isOpen(),
31
- );
32
- }
33
- return typeof connections?.isOpen === 'function' && connections.isOpen();
34
- }
35
-
36
- module.exports = {
37
- CLEANUP_INTERVAL_MS,
38
- DEFAULT_CAPTURE_INTERVAL_MS,
39
- DEFAULT_RETENTION_DAYS,
40
- FRONTMOST_APP_SCRIPT,
41
- MINIMUM_TEXT_LENGTH,
42
- MIN_CAPTURE_INTERVAL_MS,
43
- hasOpenConnectionForUser,
44
- isExplicitlyEnabled,
45
- parsePositiveInteger,
46
- };