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.
- package/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +157 -24
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +7 -2
- package/flutter_app/lib/main_operations.dart +334 -370
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +342 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +97 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +12 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +81930 -80509
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/behavior.js +80 -0
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +20 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +452 -176
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +291 -8
- package/server/services/ai/loop/conversation_loop.js +515 -342
- package/server/services/ai/loop/messaging_delivery.js +176 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +32 -123
- package/server/services/ai/taskAnalysis.js +104 -11
- package/server/services/ai/toolEvidence.js +256 -29
- package/server/services/ai/tools.js +248 -117
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +48 -0
- package/server/services/behavior/modules/persona_prompt.js +33 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +110 -0
- package/server/services/behavior/modules/turn_taking.js +237 -0
- package/server/services/behavior/pipeline.js +285 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -0
- package/server/services/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +66 -52
- package/server/services/messaging/access_policy.js +10 -6
- package/server/services/messaging/automation.js +240 -34
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +37 -16
- package/server/services/messaging/inbound_queue.js +143 -28
- package/server/services/messaging/inbound_store.js +257 -0
- package/server/services/messaging/manager.js +344 -51
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +33 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +13 -8
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +343 -68
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/logger.js +19 -0
- 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
|
|
13
|
-
const queueKey = `${userId}:${agentId || 'main'}`;
|
|
110
|
+
const queueKey = queueKeyForMessage(userId, msg);
|
|
14
111
|
if (!userQueues[queueKey]) {
|
|
15
|
-
userQueues[queueKey] = {
|
|
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
|
|
126
|
+
cancelPending(queue);
|
|
21
127
|
queue.cancelRequested = false;
|
|
22
128
|
}
|
|
23
129
|
|
|
24
130
|
if (queue.running) {
|
|
25
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
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:
|
|
170
|
+
failedMessage: currentItem.message
|
|
63
171
|
});
|
|
64
172
|
}
|
|
65
173
|
|
|
66
174
|
if (queue.cancelRequested) {
|
|
67
|
-
queue
|
|
175
|
+
cancelPending(queue);
|
|
68
176
|
cancelled = true;
|
|
69
177
|
break;
|
|
70
178
|
}
|
|
71
179
|
|
|
72
|
-
|
|
180
|
+
currentItem = queue.pending.shift() || null;
|
|
73
181
|
}
|
|
74
182
|
} finally {
|
|
75
183
|
queue.running = false;
|
|
76
|
-
queue.
|
|
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
|
-
|
|
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
|
+
};
|