neoagent 3.2.1-beta.1 → 3.2.1-beta.11

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 (198) 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 +654 -90
  4. package/flutter_app/lib/main_chat.dart +118 -739
  5. package/flutter_app/lib/main_controller.dart +157 -24
  6. package/flutter_app/lib/main_integrations.dart +607 -8
  7. package/flutter_app/lib/main_models.dart +7 -2
  8. package/flutter_app/lib/main_operations.dart +334 -370
  9. package/flutter_app/lib/main_security.dart +266 -112
  10. package/flutter_app/lib/main_settings.dart +342 -3
  11. package/flutter_app/lib/main_shared.dart +14 -11
  12. package/flutter_app/lib/src/backend_client.dart +97 -0
  13. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  14. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  15. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  16. package/landing/index.html +3 -1
  17. package/lib/manager.js +106 -89
  18. package/lib/schema_migrations.js +115 -13
  19. package/package.json +30 -15
  20. package/runtime/paths.js +49 -5
  21. package/server/db/database.js +2 -2
  22. package/server/guest-agent.cli.package.json +13 -0
  23. package/server/guest_agent.js +85 -40
  24. package/server/http/middleware.js +24 -0
  25. package/server/http/routes.js +12 -6
  26. package/server/public/.last_build_id +1 -1
  27. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  28. package/server/public/flutter_bootstrap.js +2 -2
  29. package/server/public/main.dart.js +81930 -80509
  30. package/server/routes/admin.js +1 -1
  31. package/server/routes/android.js +30 -34
  32. package/server/routes/behavior.js +80 -0
  33. package/server/routes/browser.js +23 -15
  34. package/server/routes/desktop.js +18 -1
  35. package/server/routes/integrations.js +107 -1
  36. package/server/routes/memory.js +1 -0
  37. package/server/routes/settings.js +20 -5
  38. package/server/routes/social_reach.js +12 -3
  39. package/server/routes/social_video.js +4 -0
  40. package/server/services/agents/manager.js +1 -1
  41. package/server/services/ai/capabilityHealth.js +62 -96
  42. package/server/services/ai/compaction.js +7 -2
  43. package/server/services/ai/history.js +45 -6
  44. package/server/services/ai/integrated_tools/http_request.js +8 -0
  45. package/server/services/ai/loop/agent_engine_core.js +452 -176
  46. package/server/services/ai/loop/blank_recovery.js +5 -4
  47. package/server/services/ai/loop/callbacks.js +1 -0
  48. package/server/services/ai/loop/completion_judge.js +291 -8
  49. package/server/services/ai/loop/conversation_loop.js +515 -342
  50. package/server/services/ai/loop/messaging_delivery.js +176 -57
  51. package/server/services/ai/loop/model_call_guard.js +91 -0
  52. package/server/services/ai/loop/model_io.js +20 -45
  53. package/server/services/ai/loop/progress_classification.js +2 -0
  54. package/server/services/ai/loop/tool_dispatch.js +19 -8
  55. package/server/services/ai/loopPolicy.js +48 -21
  56. package/server/services/ai/messagingFallback.js +17 -17
  57. package/server/services/ai/model_discovery.js +227 -0
  58. package/server/services/ai/model_failure_cache.js +108 -0
  59. package/server/services/ai/model_identity.js +71 -0
  60. package/server/services/ai/models.js +68 -163
  61. package/server/services/ai/providerRetry.js +17 -59
  62. package/server/services/ai/provider_selector.js +166 -0
  63. package/server/services/ai/providers/anthropic.js +2 -2
  64. package/server/services/ai/providers/claudeCode.js +21 -33
  65. package/server/services/ai/providers/githubCopilot.js +41 -20
  66. package/server/services/ai/providers/google.js +135 -97
  67. package/server/services/ai/providers/grok.js +4 -3
  68. package/server/services/ai/providers/grokOauth.js +19 -27
  69. package/server/services/ai/providers/nvidia.js +10 -5
  70. package/server/services/ai/providers/ollama.js +111 -84
  71. package/server/services/ai/providers/ollama_stream.js +142 -0
  72. package/server/services/ai/providers/openai.js +39 -5
  73. package/server/services/ai/providers/openaiCodex.js +11 -4
  74. package/server/services/ai/providers/openrouter.js +29 -7
  75. package/server/services/ai/providers/provider_error.js +36 -0
  76. package/server/services/ai/settings.js +26 -2
  77. package/server/services/ai/systemPrompt.js +32 -123
  78. package/server/services/ai/taskAnalysis.js +104 -11
  79. package/server/services/ai/toolEvidence.js +256 -29
  80. package/server/services/ai/tools.js +248 -117
  81. package/server/services/android/controller.js +770 -237
  82. package/server/services/android/process.js +140 -0
  83. package/server/services/android/sdk_download.js +143 -0
  84. package/server/services/android/uia.js +6 -5
  85. package/server/services/artifacts/store.js +24 -0
  86. package/server/services/behavior/config.js +251 -0
  87. package/server/services/behavior/defaults.js +68 -0
  88. package/server/services/behavior/delivery.js +176 -0
  89. package/server/services/behavior/index.js +43 -0
  90. package/server/services/behavior/model_client.js +35 -0
  91. package/server/services/behavior/modules/agent_identity.js +37 -0
  92. package/server/services/behavior/modules/channel_style.js +28 -0
  93. package/server/services/behavior/modules/index.js +29 -0
  94. package/server/services/behavior/modules/norms.js +101 -0
  95. package/server/services/behavior/modules/persona.js +48 -0
  96. package/server/services/behavior/modules/persona_prompt.js +33 -0
  97. package/server/services/behavior/modules/social_memory.js +86 -0
  98. package/server/services/behavior/modules/social_observability.js +94 -0
  99. package/server/services/behavior/modules/social_signals.js +41 -0
  100. package/server/services/behavior/modules/theory_of_mind.js +110 -0
  101. package/server/services/behavior/modules/turn_taking.js +237 -0
  102. package/server/services/behavior/pipeline.js +285 -0
  103. package/server/services/behavior/registry.js +78 -0
  104. package/server/services/behavior/signals.js +107 -0
  105. package/server/services/behavior/state.js +99 -0
  106. package/server/services/behavior/system_prompt.js +75 -0
  107. package/server/services/browser/controller.js +843 -385
  108. package/server/services/browser/extension/gateway.js +40 -16
  109. package/server/services/browser/extension/protocol.js +15 -1
  110. package/server/services/browser/extension/provider.js +71 -47
  111. package/server/services/browser/extension/registry.js +155 -34
  112. package/server/services/cli/executor.js +62 -9
  113. package/server/services/credentials/bitwarden_cli.js +322 -0
  114. package/server/services/credentials/broker.js +594 -0
  115. package/server/services/desktop/gateway.js +41 -4
  116. package/server/services/desktop/protocol.js +3 -0
  117. package/server/services/desktop/provider.js +39 -42
  118. package/server/services/desktop/registry.js +137 -52
  119. package/server/services/integrations/bitwarden/constants.js +14 -0
  120. package/server/services/integrations/bitwarden/provider.js +197 -0
  121. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  122. package/server/services/integrations/figma/provider.js +78 -12
  123. package/server/services/integrations/github/common.js +11 -6
  124. package/server/services/integrations/github/provider.js +52 -53
  125. package/server/services/integrations/google/provider.js +55 -19
  126. package/server/services/integrations/home_assistant/network.js +17 -20
  127. package/server/services/integrations/home_assistant/provider.js +7 -5
  128. package/server/services/integrations/home_assistant/tools.js +17 -5
  129. package/server/services/integrations/http.js +51 -0
  130. package/server/services/integrations/manager.js +159 -53
  131. package/server/services/integrations/microsoft/provider.js +80 -13
  132. package/server/services/integrations/neoarchive/provider.js +55 -29
  133. package/server/services/integrations/neorecall/client.js +17 -10
  134. package/server/services/integrations/neorecall/provider.js +20 -11
  135. package/server/services/integrations/notion/provider.js +16 -13
  136. package/server/services/integrations/oauth_provider.js +115 -51
  137. package/server/services/integrations/registry.js +2 -0
  138. package/server/services/integrations/slack/provider.js +98 -9
  139. package/server/services/integrations/spotify/provider.js +67 -71
  140. package/server/services/integrations/trello/provider.js +21 -7
  141. package/server/services/integrations/weather/provider.js +18 -12
  142. package/server/services/integrations/whatsapp/provider.js +76 -16
  143. package/server/services/manager.js +110 -1
  144. package/server/services/memory/embedding_index.js +20 -8
  145. package/server/services/memory/embeddings.js +151 -90
  146. package/server/services/memory/ingestion.js +50 -9
  147. package/server/services/memory/ingestion_documents.js +13 -3
  148. package/server/services/memory/manager.js +66 -52
  149. package/server/services/messaging/access_policy.js +10 -6
  150. package/server/services/messaging/automation.js +240 -34
  151. package/server/services/messaging/discord.js +11 -1
  152. package/server/services/messaging/formatting_guides.js +5 -4
  153. package/server/services/messaging/http_platforms.js +37 -16
  154. package/server/services/messaging/inbound_queue.js +143 -28
  155. package/server/services/messaging/inbound_store.js +257 -0
  156. package/server/services/messaging/manager.js +344 -51
  157. package/server/services/messaging/telegram.js +10 -1
  158. package/server/services/messaging/typing_keepalive.js +5 -2
  159. package/server/services/messaging/whatsapp.js +33 -14
  160. package/server/services/network/http.js +210 -0
  161. package/server/services/network/safe_request.js +307 -0
  162. package/server/services/runtime/backends/local-vm.js +227 -67
  163. package/server/services/runtime/docker-vm-manager.js +9 -0
  164. package/server/services/runtime/guest_bootstrap.js +30 -4
  165. package/server/services/runtime/guest_image.js +43 -12
  166. package/server/services/runtime/manager.js +77 -23
  167. package/server/services/runtime/validation.js +7 -6
  168. package/server/services/security/tool_categories.js +6 -0
  169. package/server/services/social_reach/channels/github.js +10 -4
  170. package/server/services/social_reach/channels/reddit.js +4 -4
  171. package/server/services/social_reach/channels/rss.js +2 -2
  172. package/server/services/social_reach/channels/social_video.js +13 -8
  173. package/server/services/social_reach/channels/v2ex.js +21 -8
  174. package/server/services/social_reach/channels/x.js +2 -2
  175. package/server/services/social_reach/channels/xueqiu.js +5 -5
  176. package/server/services/social_reach/service.js +9 -6
  177. package/server/services/social_reach/utils.js +65 -14
  178. package/server/services/social_video/captions.js +2 -2
  179. package/server/services/social_video/service.js +343 -68
  180. package/server/services/tasks/integration_runtime.js +18 -8
  181. package/server/services/tasks/runtime.js +39 -4
  182. package/server/services/voice/agentBridge.js +17 -4
  183. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  184. package/server/services/voice/liveSession.js +31 -0
  185. package/server/services/voice/message.js +1 -1
  186. package/server/services/voice/openaiSpeech.js +33 -8
  187. package/server/services/voice/providers.js +233 -151
  188. package/server/services/voice/runtime.js +2 -2
  189. package/server/services/voice/runtimeManager.js +118 -20
  190. package/server/services/voice/turnRunner.js +6 -0
  191. package/server/services/wearable/firmware_manifest.js +51 -13
  192. package/server/services/wearable/service.js +1 -0
  193. package/server/utils/abort.js +96 -0
  194. package/server/utils/cloud-security.js +110 -3
  195. package/server/utils/files.js +31 -0
  196. package/server/utils/image_payload.js +95 -0
  197. package/server/utils/logger.js +19 -0
  198. package/server/utils/retry.js +107 -0
@@ -2,89 +2,203 @@
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 queueKeyForMessage(userId, msg) {
25
+ return [
26
+ String(userId),
27
+ String(msg.agentId || 'main'),
28
+ String(msg.platform || ''),
29
+ String(msg.chatId || ''),
30
+ ].join(':');
31
+ }
32
+
33
+ function batchEntry(msg) {
34
+ return {
35
+ sender: msg.sender || null,
36
+ senderName: msg.senderDisplayName || msg.senderName || msg.senderUsername || null,
37
+ content: String(msg.content || ''),
38
+ messageId: msg.messageId || null,
39
+ timestamp: msg.timestamp || null,
40
+ wasMentioned: msg.wasMentioned === true,
41
+ repliedToAgent: msg.repliedToAgent === true,
42
+ };
43
+ }
44
+
45
+ function mergeMessage(target, msg) {
46
+ const existingBatch = Array.isArray(target.message.messageBatch)
47
+ ? target.message.messageBatch
48
+ : [batchEntry(target.message)];
49
+ const nextBatch = [...existingBatch, batchEntry(msg)];
50
+ target.message.messageBatch = nextBatch;
51
+ target.message.content = target.message.isGroup
52
+ ? nextBatch.map((entry) => (
53
+ `[${entry.senderName || entry.sender || 'participant'}]: ${entry.content}`
54
+ )).join('\n')
55
+ : nextBatch.map((entry) => entry.content).join('\n');
56
+ target.message.messageId = msg.messageId || target.message.messageId;
57
+ target.message.timestamp = msg.timestamp || target.message.timestamp;
58
+ target.message.wasMentioned = target.message.wasMentioned === true || msg.wasMentioned === true;
59
+ target.message.repliedToAgent = target.message.repliedToAgent === true || msg.repliedToAgent === true;
60
+ target.message.behaviorTurnEpoch = Math.max(
61
+ Number(target.message.behaviorTurnEpoch || 0),
62
+ Number(msg.behaviorTurnEpoch || 0),
63
+ );
64
+ target.message.inboundJobIds = Array.from(new Set([
65
+ ...(target.message.inboundJobIds || []),
66
+ target.message.inboundJobId,
67
+ ...(msg.inboundJobIds || []),
68
+ msg.inboundJobId,
69
+ ].filter(Boolean)));
70
+ }
71
+
72
+ function queuedResult(queue, msg) {
73
+ let resolveCompletion;
74
+ const completion = new Promise((resolve) => {
75
+ resolveCompletion = resolve;
76
+ });
77
+ const last = queue.collecting && queue.activeItem
78
+ ? queue.activeItem
79
+ : queue.pending[queue.pending.length - 1];
80
+ if (
81
+ last
82
+ && last.message.platform === msg.platform
83
+ && last.message.chatId === msg.chatId
84
+ && (
85
+ last.message.isGroup === true
86
+ || String(last.message.sender || '') === String(msg.sender || '')
87
+ )
88
+ ) {
89
+ mergeMessage(last, msg);
90
+ last.waiters.push(resolveCompletion);
91
+ } else {
92
+ queue.pending.push({
93
+ message: { ...msg },
94
+ waiters: [resolveCompletion],
95
+ });
96
+ }
97
+ const result = { queued: true };
98
+ Object.defineProperty(result, 'completion', { value: completion });
99
+ return result;
100
+ }
101
+
5
102
  async function processInboundQueue({
6
103
  userQueues,
7
104
  userId,
8
105
  msg,
9
106
  executeMessage,
10
- onProcessingError = null
107
+ onProcessingError = null,
108
+ batchWindowMs = 0,
11
109
  }) {
12
- const agentId = msg.agentId || null;
13
- const queueKey = `${userId}:${agentId || 'main'}`;
110
+ const queueKey = queueKeyForMessage(userId, msg);
14
111
  if (!userQueues[queueKey]) {
15
- userQueues[queueKey] = { running: false, pending: [], cancelRequested: false };
112
+ userQueues[queueKey] = {
113
+ running: false,
114
+ pending: [],
115
+ activeItem: null,
116
+ collecting: false,
117
+ cancelRequested: false,
118
+ cancelPending() {
119
+ cancelPending(this);
120
+ },
121
+ };
16
122
  }
17
123
  const queue = userQueues[queueKey];
18
124
 
19
125
  if (queue.cancelRequested && !queue.running) {
20
- queue.pending = [];
126
+ cancelPending(queue);
21
127
  queue.cancelRequested = false;
22
128
  }
23
129
 
24
130
  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 };
131
+ return queuedResult(queue, msg);
38
132
  }
39
133
 
40
134
  queue.running = true;
41
- let currentMessage = msg;
135
+ let currentItem = { message: msg, waiters: [] };
42
136
  let processedCount = 0;
43
137
  let failedCount = 0;
44
138
  let cancelled = false;
139
+ let initialOutcome = null;
45
140
 
46
141
  try {
47
- while (currentMessage) {
142
+ while (currentItem) {
143
+ queue.activeItem = currentItem;
144
+ const delayMs = currentItem.message.isGroup
145
+ ? Math.max(0, Math.min(5000, Number(batchWindowMs) || 0))
146
+ : 0;
147
+ if (delayMs > 0) {
148
+ queue.collecting = true;
149
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
150
+ queue.collecting = false;
151
+ }
48
152
  let outcome;
49
153
  try {
50
- outcome = await executeMessage(currentMessage);
154
+ outcome = await executeMessage(currentItem.message);
51
155
  } catch (error) {
52
156
  outcome = { runId: null, result: null, error };
53
157
  }
54
158
  processedCount += 1;
159
+ const itemCancelled = queue.cancelRequested;
160
+ const itemResult = completionResult(outcome, itemCancelled);
161
+ if (!initialOutcome) initialOutcome = itemResult;
162
+ settleWaiters(currentItem, itemResult);
55
163
 
56
- if (outcome?.error) {
164
+ if (outcome?.error && !queue.cancelRequested) {
57
165
  failedCount += 1;
58
166
  await notifyProcessingError(onProcessingError, {
59
167
  error: outcome.error,
60
168
  runId: outcome.runId,
61
169
  userId,
62
- failedMessage: currentMessage
170
+ failedMessage: currentItem.message
63
171
  });
64
172
  }
65
173
 
66
174
  if (queue.cancelRequested) {
67
- queue.pending = [];
175
+ cancelPending(queue);
68
176
  cancelled = true;
69
177
  break;
70
178
  }
71
179
 
72
- currentMessage = queue.pending.shift() || null;
180
+ currentItem = queue.pending.shift() || null;
73
181
  }
74
182
  } finally {
75
183
  queue.running = false;
76
- queue.pending = [];
184
+ queue.collecting = false;
185
+ queue.activeItem = null;
186
+ cancelPending(queue);
77
187
  queue.cancelRequested = false;
78
188
  if (userQueues[queueKey] === queue) {
79
189
  delete userQueues[queueKey];
80
190
  }
81
191
  }
82
192
 
83
- return {
193
+ const result = {
84
194
  processedCount,
85
195
  failedCount,
86
196
  cancelled
87
197
  };
198
+ Object.defineProperty(result, 'outcome', {
199
+ value: initialOutcome || completionResult(null, cancelled),
200
+ });
201
+ return result;
88
202
  }
89
203
 
90
204
  async function notifyProcessingError(handler, details) {
@@ -107,5 +221,6 @@ async function notifyProcessingError(handler, details) {
107
221
  }
108
222
 
109
223
  module.exports = {
110
- processInboundQueue
224
+ processInboundQueue,
225
+ queueKeyForMessage,
111
226
  };
@@ -0,0 +1,257 @@
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 annotateInboundJobs(jobIds, annotation) {
160
+ const ids = Array.from(new Set(
161
+ (Array.isArray(jobIds) ? jobIds : [jobIds])
162
+ .map((value) => String(value || '').trim())
163
+ .filter(Boolean),
164
+ ));
165
+ if (!ids.length) return 0;
166
+ const read = db.prepare(
167
+ `SELECT messages.id, messages.metadata
168
+ FROM messaging_inbound_jobs
169
+ JOIN messages ON messages.id = messaging_inbound_jobs.message_id
170
+ WHERE messaging_inbound_jobs.id = ?`,
171
+ );
172
+ const update = db.prepare('UPDATE messages SET metadata = ? WHERE id = ?');
173
+ return db.transaction(() => {
174
+ let count = 0;
175
+ for (const id of ids) {
176
+ const row = read.get(id);
177
+ if (!row) continue;
178
+ let metadata = {};
179
+ try {
180
+ metadata = row.metadata ? JSON.parse(row.metadata) : {};
181
+ } catch {
182
+ metadata = {};
183
+ }
184
+ update.run(JSON.stringify({ ...metadata, ...annotation }), row.id);
185
+ count += 1;
186
+ }
187
+ return count;
188
+ })();
189
+ }
190
+
191
+ function reconcileInterruptedInboundJobs() {
192
+ return db.transaction(() => {
193
+ const completed = db.prepare(
194
+ `UPDATE messaging_inbound_jobs
195
+ SET status = 'completed', completed_at = datetime('now'), updated_at = datetime('now')
196
+ WHERE status = 'processing'
197
+ AND run_id IN (SELECT id FROM agent_runs WHERE status = 'completed')`,
198
+ ).run().changes;
199
+ const failed = db.prepare(
200
+ `UPDATE messaging_inbound_jobs
201
+ SET status = 'failed',
202
+ last_error = COALESCE(last_error, 'The server restarted after this agent run began; it will not be replayed automatically.'),
203
+ completed_at = datetime('now'),
204
+ updated_at = datetime('now')
205
+ WHERE status = 'processing'
206
+ AND run_id IN (SELECT id FROM agent_runs)`,
207
+ ).run().changes;
208
+ const pending = db.prepare(
209
+ `UPDATE messaging_inbound_jobs
210
+ SET status = 'pending',
211
+ last_error = NULL,
212
+ updated_at = datetime('now')
213
+ WHERE status = 'processing'
214
+ AND (run_id IS NULL OR run_id NOT IN (SELECT id FROM agent_runs))`,
215
+ ).run().changes;
216
+ return { completed, failed, pending };
217
+ })();
218
+ }
219
+
220
+ function listPendingInboundJobs(filters = {}) {
221
+ const clauses = ["status = 'pending'"];
222
+ const values = [];
223
+ for (const [column, value] of [
224
+ ['user_id', filters.userId],
225
+ ['agent_id', filters.agentId],
226
+ ['platform', filters.platform],
227
+ ]) {
228
+ if (value === undefined) continue;
229
+ if (value === null) clauses.push(`${column} IS NULL`);
230
+ else {
231
+ clauses.push(`${column} = ?`);
232
+ values.push(value);
233
+ }
234
+ }
235
+ const limit = Math.max(1, Math.min(500, Number(filters.limit) || 100));
236
+ return db.prepare(
237
+ `SELECT * FROM messaging_inbound_jobs
238
+ WHERE ${clauses.join(' AND ')}
239
+ ORDER BY created_at ASC, id ASC
240
+ LIMIT ?`,
241
+ ).all(...values, limit);
242
+ }
243
+
244
+ function payloadForInboundJob(job) {
245
+ return decodePayload(job?.payload_json);
246
+ }
247
+
248
+ module.exports = {
249
+ annotateInboundJobs,
250
+ attachRunToInboundJobs,
251
+ claimInboundJob,
252
+ enqueueInboundMessage,
253
+ listPendingInboundJobs,
254
+ payloadForInboundJob,
255
+ reconcileInterruptedInboundJobs,
256
+ settleInboundJob,
257
+ };