neoagent 3.2.1-beta.2 → 3.2.1-beta.4

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 (145) hide show
  1. package/extensions/chrome-browser/background.mjs +318 -88
  2. package/extensions/chrome-browser/http.mjs +136 -0
  3. package/extensions/chrome-browser/protocol.mjs +511 -89
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +29 -20
  6. package/flutter_app/lib/main_models.dart +3 -0
  7. package/flutter_app/lib/main_operations.dart +334 -321
  8. package/flutter_app/lib/main_settings.dart +4 -3
  9. package/flutter_app/lib/main_shared.dart +14 -11
  10. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  11. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  12. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  13. package/lib/manager.js +106 -89
  14. package/lib/schema_migrations.js +67 -13
  15. package/package.json +20 -13
  16. package/runtime/paths.js +49 -5
  17. package/server/guest_agent.js +52 -30
  18. package/server/http/middleware.js +24 -0
  19. package/server/http/routes.js +4 -6
  20. package/server/public/.last_build_id +1 -1
  21. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  22. package/server/public/flutter_bootstrap.js +1 -1
  23. package/server/public/main.dart.js +30803 -30805
  24. package/server/routes/admin.js +1 -1
  25. package/server/routes/android.js +30 -34
  26. package/server/routes/browser.js +23 -15
  27. package/server/routes/desktop.js +18 -1
  28. package/server/routes/integrations.js +5 -1
  29. package/server/routes/memory.js +1 -0
  30. package/server/routes/settings.js +16 -5
  31. package/server/routes/social_reach.js +12 -3
  32. package/server/routes/social_video.js +4 -0
  33. package/server/services/ai/compaction.js +7 -2
  34. package/server/services/ai/history.js +44 -5
  35. package/server/services/ai/integrated_tools/http_request.js +8 -0
  36. package/server/services/ai/loop/agent_engine_core.js +347 -162
  37. package/server/services/ai/loop/callbacks.js +1 -0
  38. package/server/services/ai/loop/completion_judge.js +12 -0
  39. package/server/services/ai/loop/conversation_loop.js +348 -242
  40. package/server/services/ai/loop/messaging_delivery.js +129 -57
  41. package/server/services/ai/loop/model_call_guard.js +91 -0
  42. package/server/services/ai/loop/model_io.js +8 -41
  43. package/server/services/ai/loop/tool_dispatch.js +19 -8
  44. package/server/services/ai/loopPolicy.js +24 -19
  45. package/server/services/ai/model_discovery.js +227 -0
  46. package/server/services/ai/model_identity.js +71 -0
  47. package/server/services/ai/models.js +67 -162
  48. package/server/services/ai/providerRetry.js +17 -59
  49. package/server/services/ai/provider_selector.js +111 -0
  50. package/server/services/ai/providers/anthropic.js +2 -2
  51. package/server/services/ai/providers/claudeCode.js +15 -28
  52. package/server/services/ai/providers/githubCopilot.js +36 -16
  53. package/server/services/ai/providers/google.js +23 -5
  54. package/server/services/ai/providers/grok.js +4 -3
  55. package/server/services/ai/providers/grokOauth.js +13 -22
  56. package/server/services/ai/providers/nvidia.js +10 -5
  57. package/server/services/ai/providers/ollama.js +102 -82
  58. package/server/services/ai/providers/ollama_stream.js +142 -0
  59. package/server/services/ai/providers/openai.js +39 -5
  60. package/server/services/ai/providers/openaiCodex.js +11 -4
  61. package/server/services/ai/providers/openrouter.js +29 -7
  62. package/server/services/ai/providers/provider_error.js +36 -0
  63. package/server/services/ai/settings.js +26 -2
  64. package/server/services/ai/taskAnalysis.js +5 -1
  65. package/server/services/ai/terminal_reply.js +45 -0
  66. package/server/services/ai/toolEvidence.js +58 -29
  67. package/server/services/ai/tools.js +124 -111
  68. package/server/services/android/controller.js +770 -237
  69. package/server/services/android/process.js +140 -0
  70. package/server/services/android/sdk_download.js +143 -0
  71. package/server/services/android/uia.js +6 -5
  72. package/server/services/artifacts/store.js +24 -0
  73. package/server/services/browser/controller.js +736 -385
  74. package/server/services/browser/extension/gateway.js +40 -16
  75. package/server/services/browser/extension/protocol.js +12 -1
  76. package/server/services/browser/extension/provider.js +59 -47
  77. package/server/services/browser/extension/registry.js +155 -34
  78. package/server/services/cli/executor.js +62 -9
  79. package/server/services/desktop/gateway.js +41 -4
  80. package/server/services/desktop/protocol.js +3 -0
  81. package/server/services/desktop/provider.js +39 -42
  82. package/server/services/desktop/registry.js +137 -52
  83. package/server/services/integrations/figma/provider.js +78 -12
  84. package/server/services/integrations/github/common.js +11 -6
  85. package/server/services/integrations/github/provider.js +52 -53
  86. package/server/services/integrations/google/provider.js +55 -19
  87. package/server/services/integrations/home_assistant/network.js +17 -20
  88. package/server/services/integrations/home_assistant/provider.js +7 -5
  89. package/server/services/integrations/home_assistant/tools.js +17 -5
  90. package/server/services/integrations/http.js +51 -0
  91. package/server/services/integrations/manager.js +158 -53
  92. package/server/services/integrations/microsoft/provider.js +80 -13
  93. package/server/services/integrations/neoarchive/provider.js +55 -29
  94. package/server/services/integrations/neorecall/client.js +17 -10
  95. package/server/services/integrations/neorecall/provider.js +20 -11
  96. package/server/services/integrations/notion/provider.js +16 -13
  97. package/server/services/integrations/oauth_provider.js +115 -51
  98. package/server/services/integrations/slack/provider.js +98 -9
  99. package/server/services/integrations/spotify/provider.js +67 -71
  100. package/server/services/integrations/trello/provider.js +21 -7
  101. package/server/services/integrations/weather/provider.js +18 -12
  102. package/server/services/integrations/whatsapp/provider.js +76 -16
  103. package/server/services/manager.js +87 -1
  104. package/server/services/memory/embedding_index.js +20 -8
  105. package/server/services/memory/embeddings.js +151 -90
  106. package/server/services/memory/ingestion.js +50 -9
  107. package/server/services/memory/ingestion_documents.js +13 -3
  108. package/server/services/memory/manager.js +52 -19
  109. package/server/services/messaging/automation.js +84 -9
  110. package/server/services/messaging/http_platforms.js +33 -13
  111. package/server/services/messaging/inbound_queue.js +78 -24
  112. package/server/services/messaging/inbound_store.js +224 -0
  113. package/server/services/messaging/manager.js +326 -51
  114. package/server/services/messaging/typing_keepalive.js +5 -2
  115. package/server/services/messaging/whatsapp.js +22 -14
  116. package/server/services/network/http.js +210 -0
  117. package/server/services/network/safe_request.js +307 -0
  118. package/server/services/runtime/backends/local-vm.js +214 -66
  119. package/server/services/runtime/manager.js +17 -12
  120. package/server/services/social_reach/channels/github.js +10 -4
  121. package/server/services/social_reach/channels/reddit.js +4 -4
  122. package/server/services/social_reach/channels/rss.js +2 -2
  123. package/server/services/social_reach/channels/social_video.js +12 -7
  124. package/server/services/social_reach/channels/v2ex.js +21 -8
  125. package/server/services/social_reach/channels/x.js +2 -2
  126. package/server/services/social_reach/channels/xueqiu.js +5 -5
  127. package/server/services/social_reach/service.js +9 -6
  128. package/server/services/social_reach/utils.js +65 -14
  129. package/server/services/social_video/service.js +160 -50
  130. package/server/services/tasks/integration_runtime.js +18 -8
  131. package/server/services/tasks/runtime.js +39 -4
  132. package/server/services/voice/agentBridge.js +17 -4
  133. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  134. package/server/services/voice/liveSession.js +31 -0
  135. package/server/services/voice/openaiSpeech.js +33 -8
  136. package/server/services/voice/providers.js +233 -151
  137. package/server/services/voice/runtimeManager.js +118 -20
  138. package/server/services/voice/turnRunner.js +6 -0
  139. package/server/services/wearable/firmware_manifest.js +51 -13
  140. package/server/services/wearable/service.js +1 -0
  141. package/server/utils/abort.js +96 -0
  142. package/server/utils/cloud-security.js +110 -3
  143. package/server/utils/files.js +31 -0
  144. package/server/utils/image_payload.js +95 -0
  145. package/server/utils/retry.js +107 -0
@@ -2,6 +2,56 @@
2
2
 
3
3
  const { getErrorMessage } = require('../bootstrap_helpers');
4
4
 
5
+ function completionResult(outcome, cancelled = false) {
6
+ return {
7
+ runId: outcome?.runId || null,
8
+ result: outcome?.result || null,
9
+ error: outcome?.error || null,
10
+ cancelled,
11
+ };
12
+ }
13
+
14
+ function settleWaiters(item, result) {
15
+ for (const resolve of item?.waiters || []) resolve(result);
16
+ if (item?.waiters) item.waiters = [];
17
+ }
18
+
19
+ function cancelPending(queue) {
20
+ const result = completionResult(null, true);
21
+ for (const item of queue.pending.splice(0)) settleWaiters(item, result);
22
+ }
23
+
24
+ function queuedResult(queue, msg) {
25
+ let resolveCompletion;
26
+ const completion = new Promise((resolve) => {
27
+ resolveCompletion = resolve;
28
+ });
29
+ const last = queue.pending[queue.pending.length - 1];
30
+ if (
31
+ last
32
+ && last.message.platform === msg.platform
33
+ && last.message.chatId === msg.chatId
34
+ && String(last.message.sender || '') === String(msg.sender || '')
35
+ ) {
36
+ last.message.content += `\n${msg.content}`;
37
+ last.message.messageId = msg.messageId;
38
+ last.message.inboundJobIds = Array.from(new Set([
39
+ ...(last.message.inboundJobIds || []),
40
+ ...(msg.inboundJobIds || []),
41
+ msg.inboundJobId,
42
+ ].filter(Boolean)));
43
+ last.waiters.push(resolveCompletion);
44
+ } else {
45
+ queue.pending.push({
46
+ message: { ...msg },
47
+ waiters: [resolveCompletion],
48
+ });
49
+ }
50
+ const result = { queued: true };
51
+ Object.defineProperty(result, 'completion', { value: completion });
52
+ return result;
53
+ }
54
+
5
55
  async function processInboundQueue({
6
56
  userQueues,
7
57
  userId,
@@ -12,79 +62,83 @@ async function processInboundQueue({
12
62
  const agentId = msg.agentId || null;
13
63
  const queueKey = `${userId}:${agentId || 'main'}`;
14
64
  if (!userQueues[queueKey]) {
15
- userQueues[queueKey] = { running: false, pending: [], cancelRequested: false };
65
+ userQueues[queueKey] = {
66
+ running: false,
67
+ pending: [],
68
+ cancelRequested: false,
69
+ cancelPending() {
70
+ cancelPending(this);
71
+ },
72
+ };
16
73
  }
17
74
  const queue = userQueues[queueKey];
18
75
 
19
76
  if (queue.cancelRequested && !queue.running) {
20
- queue.pending = [];
77
+ cancelPending(queue);
21
78
  queue.cancelRequested = false;
22
79
  }
23
80
 
24
81
  if (queue.running) {
25
- const last = queue.pending[queue.pending.length - 1];
26
- if (
27
- last
28
- && last.platform === msg.platform
29
- && last.chatId === msg.chatId
30
- && String(last.sender || '') === String(msg.sender || '')
31
- ) {
32
- last.content += `\n${msg.content}`;
33
- last.messageId = msg.messageId;
34
- } else {
35
- queue.pending.push({ ...msg });
36
- }
37
- return { queued: true };
82
+ return queuedResult(queue, msg);
38
83
  }
39
84
 
40
85
  queue.running = true;
41
- let currentMessage = msg;
86
+ let currentItem = { message: msg, waiters: [] };
42
87
  let processedCount = 0;
43
88
  let failedCount = 0;
44
89
  let cancelled = false;
90
+ let initialOutcome = null;
45
91
 
46
92
  try {
47
- while (currentMessage) {
93
+ while (currentItem) {
48
94
  let outcome;
49
95
  try {
50
- outcome = await executeMessage(currentMessage);
96
+ outcome = await executeMessage(currentItem.message);
51
97
  } catch (error) {
52
98
  outcome = { runId: null, result: null, error };
53
99
  }
54
100
  processedCount += 1;
101
+ const itemCancelled = queue.cancelRequested;
102
+ const itemResult = completionResult(outcome, itemCancelled);
103
+ if (!initialOutcome) initialOutcome = itemResult;
104
+ settleWaiters(currentItem, itemResult);
55
105
 
56
- if (outcome?.error) {
106
+ if (outcome?.error && !queue.cancelRequested) {
57
107
  failedCount += 1;
58
108
  await notifyProcessingError(onProcessingError, {
59
109
  error: outcome.error,
60
110
  runId: outcome.runId,
61
111
  userId,
62
- failedMessage: currentMessage
112
+ failedMessage: currentItem.message
63
113
  });
64
114
  }
65
115
 
66
116
  if (queue.cancelRequested) {
67
- queue.pending = [];
117
+ cancelPending(queue);
68
118
  cancelled = true;
69
119
  break;
70
120
  }
71
121
 
72
- currentMessage = queue.pending.shift() || null;
122
+ currentItem = queue.pending.shift() || null;
73
123
  }
74
124
  } finally {
75
125
  queue.running = false;
76
- queue.pending = [];
126
+ cancelPending(queue);
77
127
  queue.cancelRequested = false;
78
128
  if (userQueues[queueKey] === queue) {
79
129
  delete userQueues[queueKey];
80
130
  }
81
131
  }
82
132
 
83
- return {
133
+ const result = {
84
134
  processedCount,
85
135
  failedCount,
86
136
  cancelled
87
137
  };
138
+ Object.defineProperty(result, 'outcome', {
139
+ value: initialOutcome || completionResult(null, cancelled),
140
+ });
141
+ return result;
88
142
  }
89
143
 
90
144
  async function notifyProcessingError(handler, details) {
@@ -0,0 +1,224 @@
1
+ 'use strict';
2
+
3
+ const { randomUUID } = require('node:crypto');
4
+ const db = require('../../db/database');
5
+
6
+ const MAX_INBOUND_PAYLOAD_BYTES = 1024 * 1024;
7
+ const JOB_STATUSES = new Set(['pending', 'processing', 'completed', 'failed']);
8
+
9
+ function encodePayload(payload) {
10
+ const encoded = JSON.stringify(payload);
11
+ if (Buffer.byteLength(encoded, 'utf8') > MAX_INBOUND_PAYLOAD_BYTES) {
12
+ const error = new Error('Inbound messaging payload exceeds the 1 MiB durability limit.');
13
+ error.code = 'MESSAGING_INBOUND_PAYLOAD_TOO_LARGE';
14
+ throw error;
15
+ }
16
+ return encoded;
17
+ }
18
+
19
+ function decodePayload(value) {
20
+ try {
21
+ const payload = JSON.parse(String(value || ''));
22
+ return payload && typeof payload === 'object' && !Array.isArray(payload)
23
+ ? payload
24
+ : null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function getJobByMessageId(messageId) {
31
+ return db.prepare(
32
+ 'SELECT * FROM messaging_inbound_jobs WHERE message_id = ?',
33
+ ).get(messageId) || null;
34
+ }
35
+
36
+ function enqueueInboundMessage({
37
+ userId,
38
+ agentId,
39
+ platform,
40
+ platformMessageId,
41
+ chatId,
42
+ content,
43
+ metadata,
44
+ createdAt,
45
+ payload,
46
+ }) {
47
+ return db.transaction(() => {
48
+ if (platformMessageId) {
49
+ const existing = db.prepare(
50
+ `SELECT id
51
+ FROM messages
52
+ WHERE user_id = ? AND platform = ? AND platform_msg_id = ? AND role = 'user'
53
+ LIMIT 1`,
54
+ ).get(userId, platform, platformMessageId);
55
+ if (existing) {
56
+ return {
57
+ created: false,
58
+ job: getJobByMessageId(existing.id),
59
+ messageId: existing.id,
60
+ };
61
+ }
62
+ }
63
+
64
+ const jobId = randomUUID();
65
+ const durablePayload = {
66
+ ...payload,
67
+ inboundJobId: jobId,
68
+ inboundJobIds: [jobId],
69
+ };
70
+ const insert = db.prepare(
71
+ `INSERT INTO messages (
72
+ user_id, agent_id, role, content, platform, platform_msg_id,
73
+ platform_chat_id, metadata, created_at
74
+ ) VALUES (?, ?, 'user', ?, ?, ?, ?, ?, ?)`,
75
+ ).run(
76
+ userId,
77
+ agentId,
78
+ content,
79
+ platform,
80
+ platformMessageId || null,
81
+ chatId,
82
+ metadata ? JSON.stringify(metadata) : null,
83
+ createdAt || new Date().toISOString(),
84
+ );
85
+ const messageId = Number(insert.lastInsertRowid);
86
+ db.prepare(
87
+ `INSERT INTO messaging_inbound_jobs (
88
+ id, message_id, user_id, agent_id, platform, platform_msg_id,
89
+ platform_chat_id, payload_json, status
90
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')`,
91
+ ).run(
92
+ jobId,
93
+ messageId,
94
+ userId,
95
+ agentId,
96
+ platform,
97
+ platformMessageId || null,
98
+ chatId,
99
+ encodePayload(durablePayload),
100
+ );
101
+ return {
102
+ created: true,
103
+ job: getJobByMessageId(messageId),
104
+ messageId,
105
+ payload: durablePayload,
106
+ };
107
+ })();
108
+ }
109
+
110
+ function claimInboundJob(jobId) {
111
+ const result = db.prepare(
112
+ `UPDATE messaging_inbound_jobs
113
+ SET status = 'processing',
114
+ attempts = attempts + 1,
115
+ last_error = NULL,
116
+ updated_at = datetime('now')
117
+ WHERE id = ? AND status = 'pending'`,
118
+ ).run(jobId);
119
+ return result.changes === 1;
120
+ }
121
+
122
+ function settleInboundJob(jobId, status, error = null) {
123
+ if (!JOB_STATUSES.has(status) || status === 'processing') {
124
+ throw new Error(`Invalid inbound messaging job status: ${status}`);
125
+ }
126
+ db.prepare(
127
+ `UPDATE messaging_inbound_jobs
128
+ SET status = ?,
129
+ last_error = ?,
130
+ completed_at = CASE WHEN ? IN ('completed', 'failed') THEN datetime('now') ELSE NULL END,
131
+ updated_at = datetime('now')
132
+ WHERE id = ?`,
133
+ ).run(
134
+ status,
135
+ error ? String(error).slice(0, 4000) : null,
136
+ status,
137
+ jobId,
138
+ );
139
+ }
140
+
141
+ function attachRunToInboundJobs(jobIds, runId) {
142
+ const ids = Array.from(new Set(
143
+ (Array.isArray(jobIds) ? jobIds : [jobIds])
144
+ .map((value) => String(value || '').trim())
145
+ .filter(Boolean),
146
+ ));
147
+ if (!ids.length || !runId) return 0;
148
+ const update = db.prepare(
149
+ `UPDATE messaging_inbound_jobs
150
+ SET run_id = ?, updated_at = datetime('now')
151
+ WHERE id = ? AND status = 'processing'`,
152
+ );
153
+ return db.transaction(() => ids.reduce(
154
+ (count, id) => count + update.run(runId, id).changes,
155
+ 0,
156
+ ))();
157
+ }
158
+
159
+ function reconcileInterruptedInboundJobs() {
160
+ return db.transaction(() => {
161
+ const completed = db.prepare(
162
+ `UPDATE messaging_inbound_jobs
163
+ SET status = 'completed', completed_at = datetime('now'), updated_at = datetime('now')
164
+ WHERE status = 'processing'
165
+ AND run_id IN (SELECT id FROM agent_runs WHERE status = 'completed')`,
166
+ ).run().changes;
167
+ const failed = db.prepare(
168
+ `UPDATE messaging_inbound_jobs
169
+ SET status = 'failed',
170
+ last_error = COALESCE(last_error, 'The server restarted after this agent run began; it will not be replayed automatically.'),
171
+ completed_at = datetime('now'),
172
+ updated_at = datetime('now')
173
+ WHERE status = 'processing'
174
+ AND run_id IN (SELECT id FROM agent_runs)`,
175
+ ).run().changes;
176
+ const pending = db.prepare(
177
+ `UPDATE messaging_inbound_jobs
178
+ SET status = 'pending',
179
+ last_error = NULL,
180
+ updated_at = datetime('now')
181
+ WHERE status = 'processing'
182
+ AND (run_id IS NULL OR run_id NOT IN (SELECT id FROM agent_runs))`,
183
+ ).run().changes;
184
+ return { completed, failed, pending };
185
+ })();
186
+ }
187
+
188
+ function listPendingInboundJobs(filters = {}) {
189
+ const clauses = ["status = 'pending'"];
190
+ const values = [];
191
+ for (const [column, value] of [
192
+ ['user_id', filters.userId],
193
+ ['agent_id', filters.agentId],
194
+ ['platform', filters.platform],
195
+ ]) {
196
+ if (value === undefined) continue;
197
+ if (value === null) clauses.push(`${column} IS NULL`);
198
+ else {
199
+ clauses.push(`${column} = ?`);
200
+ values.push(value);
201
+ }
202
+ }
203
+ const limit = Math.max(1, Math.min(500, Number(filters.limit) || 100));
204
+ return db.prepare(
205
+ `SELECT * FROM messaging_inbound_jobs
206
+ WHERE ${clauses.join(' AND ')}
207
+ ORDER BY created_at ASC, id ASC
208
+ LIMIT ?`,
209
+ ).all(...values, limit);
210
+ }
211
+
212
+ function payloadForInboundJob(job) {
213
+ return decodePayload(job?.payload_json);
214
+ }
215
+
216
+ module.exports = {
217
+ attachRunToInboundJobs,
218
+ claimInboundJob,
219
+ enqueueInboundMessage,
220
+ listPendingInboundJobs,
221
+ payloadForInboundJob,
222
+ reconcileInterruptedInboundJobs,
223
+ settleInboundJob,
224
+ };