neoagent 3.2.1-beta.9 → 3.3.1-beta.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 (78) hide show
  1. package/LICENSE +67 -80
  2. package/README.md +7 -1
  3. package/docs/licensing.md +44 -0
  4. package/flutter_app/lib/main.dart +1 -0
  5. package/flutter_app/lib/main_app_shell.dart +186 -40
  6. package/flutter_app/lib/main_chat.dart +542 -80
  7. package/flutter_app/lib/main_controller.dart +66 -5
  8. package/flutter_app/lib/main_install.dart +9 -7
  9. package/flutter_app/lib/main_models.dart +171 -22
  10. package/flutter_app/lib/main_operations.dart +0 -49
  11. package/flutter_app/lib/main_settings.dart +338 -0
  12. package/flutter_app/lib/src/backend_client.dart +19 -0
  13. package/flutter_app/lib/src/local_runtime_paths.dart +60 -0
  14. package/lib/manager.js +10 -2
  15. package/package.json +1 -1
  16. package/runtime/paths.js +70 -0
  17. package/server/db/database.js +2 -2
  18. package/server/http/routes.js +1 -0
  19. package/server/public/.last_build_id +1 -1
  20. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  21. package/server/public/flutter_bootstrap.js +1 -1
  22. package/server/public/main.dart.js +84940 -83737
  23. package/server/routes/behavior.js +80 -0
  24. package/server/routes/settings.js +4 -0
  25. package/server/services/agents/manager.js +1 -1
  26. package/server/services/ai/history.js +1 -1
  27. package/server/services/ai/loop/agent_engine_core.js +110 -17
  28. package/server/services/ai/loop/blank_recovery.js +5 -4
  29. package/server/services/ai/loop/completion_judge.js +226 -33
  30. package/server/services/ai/loop/conversation_loop.js +92 -34
  31. package/server/services/ai/loop/messaging_delivery.js +47 -0
  32. package/server/services/ai/loop/progress_classification.js +2 -0
  33. package/server/services/ai/loopPolicy.js +24 -2
  34. package/server/services/ai/messagingFallback.js +17 -17
  35. package/server/services/ai/model_failure_cache.js +7 -0
  36. package/server/services/ai/systemPrompt.js +31 -122
  37. package/server/services/ai/taskAnalysis.js +86 -12
  38. package/server/services/ai/toolEvidence.js +68 -224
  39. package/server/services/ai/tools.js +60 -19
  40. package/server/services/behavior/config.js +251 -0
  41. package/server/services/behavior/defaults.js +68 -0
  42. package/server/services/behavior/delivery.js +176 -0
  43. package/server/services/behavior/index.js +43 -0
  44. package/server/services/behavior/model_client.js +35 -0
  45. package/server/services/behavior/modules/agent_identity.js +37 -0
  46. package/server/services/behavior/modules/channel_style.js +28 -0
  47. package/server/services/behavior/modules/index.js +29 -0
  48. package/server/services/behavior/modules/norms.js +101 -0
  49. package/server/services/behavior/modules/persona.js +172 -0
  50. package/server/services/behavior/modules/persona_prompt.js +238 -0
  51. package/server/services/behavior/modules/social_memory.js +86 -0
  52. package/server/services/behavior/modules/social_observability.js +94 -0
  53. package/server/services/behavior/modules/social_signals.js +41 -0
  54. package/server/services/behavior/modules/theory_of_mind.js +118 -0
  55. package/server/services/behavior/modules/turn_taking.js +238 -0
  56. package/server/services/behavior/pipeline.js +341 -0
  57. package/server/services/behavior/registry.js +78 -0
  58. package/server/services/behavior/signals.js +107 -0
  59. package/server/services/behavior/state.js +99 -0
  60. package/server/services/behavior/system_prompt.js +75 -0
  61. package/server/services/memory/manager.js +14 -33
  62. package/server/services/messaging/access_policy.js +269 -74
  63. package/server/services/messaging/automation.js +158 -27
  64. package/server/services/messaging/discord.js +11 -1
  65. package/server/services/messaging/formatting_guides.js +5 -4
  66. package/server/services/messaging/http_platforms.js +73 -28
  67. package/server/services/messaging/inbound_queue.js +74 -13
  68. package/server/services/messaging/inbound_store.js +33 -0
  69. package/server/services/messaging/manager.js +57 -5
  70. package/server/services/messaging/telegram.js +10 -1
  71. package/server/services/messaging/whatsapp.js +11 -0
  72. package/server/services/social_reach/channels/social_video.js +1 -1
  73. package/server/services/social_video/captions.js +2 -2
  74. package/server/services/social_video/service.js +194 -29
  75. package/server/services/voice/message.js +1 -1
  76. package/server/services/voice/runtime.js +2 -2
  77. package/server/utils/logger.js +19 -0
  78. package/server/services/ai/terminal_reply.js +0 -57
@@ -21,25 +21,72 @@ function cancelPending(queue) {
21
21
  for (const item of queue.pending.splice(0)) settleWaiters(item, result);
22
22
  }
23
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
+
24
72
  function queuedResult(queue, msg) {
25
73
  let resolveCompletion;
26
74
  const completion = new Promise((resolve) => {
27
75
  resolveCompletion = resolve;
28
76
  });
29
- const last = queue.pending[queue.pending.length - 1];
77
+ const last = queue.collecting && queue.activeItem
78
+ ? queue.activeItem
79
+ : queue.pending[queue.pending.length - 1];
30
80
  if (
31
81
  last
32
82
  && last.message.platform === msg.platform
33
83
  && last.message.chatId === msg.chatId
34
- && String(last.message.sender || '') === String(msg.sender || '')
84
+ && (
85
+ last.message.isGroup === true
86
+ || String(last.message.sender || '') === String(msg.sender || '')
87
+ )
35
88
  ) {
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)));
89
+ mergeMessage(last, msg);
43
90
  last.waiters.push(resolveCompletion);
44
91
  } else {
45
92
  queue.pending.push({
@@ -57,14 +104,16 @@ async function processInboundQueue({
57
104
  userId,
58
105
  msg,
59
106
  executeMessage,
60
- onProcessingError = null
107
+ onProcessingError = null,
108
+ batchWindowMs = 0,
61
109
  }) {
62
- const agentId = msg.agentId || null;
63
- const queueKey = `${userId}:${agentId || 'main'}`;
110
+ const queueKey = queueKeyForMessage(userId, msg);
64
111
  if (!userQueues[queueKey]) {
65
112
  userQueues[queueKey] = {
66
113
  running: false,
67
114
  pending: [],
115
+ activeItem: null,
116
+ collecting: false,
68
117
  cancelRequested: false,
69
118
  cancelPending() {
70
119
  cancelPending(this);
@@ -91,6 +140,15 @@ async function processInboundQueue({
91
140
 
92
141
  try {
93
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
+ }
94
152
  let outcome;
95
153
  try {
96
154
  outcome = await executeMessage(currentItem.message);
@@ -123,6 +181,8 @@ async function processInboundQueue({
123
181
  }
124
182
  } finally {
125
183
  queue.running = false;
184
+ queue.collecting = false;
185
+ queue.activeItem = null;
126
186
  cancelPending(queue);
127
187
  queue.cancelRequested = false;
128
188
  if (userQueues[queueKey] === queue) {
@@ -161,5 +221,6 @@ async function notifyProcessingError(handler, details) {
161
221
  }
162
222
 
163
223
  module.exports = {
164
- processInboundQueue
224
+ processInboundQueue,
225
+ queueKeyForMessage,
165
226
  };
@@ -156,6 +156,38 @@ function attachRunToInboundJobs(jobIds, runId) {
156
156
  ))();
157
157
  }
158
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
+
159
191
  function reconcileInterruptedInboundJobs() {
160
192
  return db.transaction(() => {
161
193
  const completed = db.prepare(
@@ -214,6 +246,7 @@ function payloadForInboundJob(job) {
214
246
  }
215
247
 
216
248
  module.exports = {
249
+ annotateInboundJobs,
217
250
  attachRunToInboundJobs,
218
251
  claimInboundJob,
219
252
  enqueueInboundMessage,
@@ -187,6 +187,17 @@ class MessagingManager extends EventEmitter {
187
187
  senderUsername: msg.senderUsername,
188
188
  senderTag: msg.senderTag,
189
189
  isGroup: msg.isGroup,
190
+ wasMentioned: msg.wasMentioned === true,
191
+ repliedToAgent: msg.repliedToAgent === true,
192
+ groupId: msg.groupId || null,
193
+ groupName: msg.groupName || null,
194
+ channelId: msg.channelId || null,
195
+ channelName: msg.channelName || null,
196
+ serverId: msg.serverId || msg.guildId || null,
197
+ serverName: msg.serverName || msg.guildName || null,
198
+ roomId: msg.roomId || null,
199
+ roomName: msg.roomName || null,
200
+ roleIds: Array.isArray(msg.roleIds) ? msg.roleIds.map(String) : [],
190
201
  mediaType: msg.mediaType,
191
202
  localMediaPath: msg.localMediaPath || null,
192
203
  ...(msg.metadata && typeof msg.metadata === 'object' ? msg.metadata : {}),
@@ -201,6 +212,8 @@ class MessagingManager extends EventEmitter {
201
212
  senderDisplayName: msg.senderDisplayName || null,
202
213
  senderUsername: msg.senderUsername || null,
203
214
  senderTag: msg.senderTag || null,
215
+ wasMentioned: msg.wasMentioned === true,
216
+ repliedToAgent: msg.repliedToAgent === true,
204
217
  content: normalizedIncomingContent,
205
218
  mediaType: msg.mediaType || null,
206
219
  localMediaPath: msg.localMediaPath || null,
@@ -211,6 +224,15 @@ class MessagingManager extends EventEmitter {
211
224
  groupName: msg.groupName || null,
212
225
  guildName: msg.guildName || null,
213
226
  roomName: msg.roomName || null,
227
+ groupId: msg.groupId || null,
228
+ channelId: msg.channelId || null,
229
+ serverId: msg.serverId || msg.guildId || null,
230
+ guildId: msg.guildId || msg.serverId || null,
231
+ roomId: msg.roomId || null,
232
+ roleIds: Array.isArray(msg.roleIds) ? msg.roleIds.map(String) : [],
233
+ replyToMessageId: msg.replyToMessageId || null,
234
+ threadId: msg.threadId || msg.threadTs || null,
235
+ eventType: msg.eventType || 'message',
214
236
  metadata: msg.metadata && typeof msg.metadata === 'object' ? msg.metadata : null,
215
237
  };
216
238
  const queued = enqueueInboundMessage({
@@ -391,7 +413,13 @@ class MessagingManager extends EventEmitter {
391
413
  const unique = [];
392
414
  const seen = new Set();
393
415
  for (const item of merged) {
394
- const id = `${item.bucket}:${item.rule.scope}:${item.rule.value}`;
416
+ const id = [
417
+ item.bucket,
418
+ item.rule.scope,
419
+ item.rule.value,
420
+ item.rule.spaceScope || '',
421
+ item.rule.spaceValue || '',
422
+ ].join(':');
395
423
  if (seen.has(id)) continue;
396
424
  seen.add(id);
397
425
  unique.push(item);
@@ -1105,7 +1133,22 @@ class MessagingManager extends EventEmitter {
1105
1133
 
1106
1134
  async getAccessCatalog(userId, platformName, options = {}) {
1107
1135
  const agentId = this._agentId(userId, options);
1108
- const discoveredTargets = await this.listAccessTargets(userId, platformName, { agentId });
1136
+ const capabilities = getPlatformAccessCapabilities(platformName);
1137
+ const normalizeTargetBucket = (target) => (
1138
+ target?.bucket === 'directRules'
1139
+ && capabilities.supportsSharedPolicy
1140
+ && capabilities.sharedActorRuleScopes.includes(target.scope)
1141
+ ? { ...target, bucket: 'sharedActorRules' }
1142
+ : target
1143
+ );
1144
+ const listedTargets = await this.listAccessTargets(
1145
+ userId,
1146
+ platformName,
1147
+ { agentId },
1148
+ );
1149
+ const discoveredTargets = (
1150
+ Array.isArray(listedTargets) ? listedTargets : []
1151
+ ).map(normalizeTargetBucket);
1109
1152
 
1110
1153
  const recentRows = db.prepare(
1111
1154
  `SELECT platform_chat_id, metadata
@@ -1122,20 +1165,29 @@ class MessagingManager extends EventEmitter {
1122
1165
  } catch {
1123
1166
  metadata = {};
1124
1167
  }
1125
- return classifyRecentTarget(platformName, { ...row, metadata });
1168
+ return normalizeTargetBucket(
1169
+ classifyRecentTarget(platformName, { ...row, metadata }),
1170
+ );
1126
1171
  })
1127
1172
  .filter(Boolean);
1128
1173
 
1129
1174
  const seen = new Set();
1130
1175
  const unique = (items) => items.filter((item) => {
1131
- const keyValue = `${item.bucket}:${item.scope}:${item.value}`;
1176
+ const rule = item?.rule || item || {};
1177
+ const keyValue = [
1178
+ item?.bucket,
1179
+ rule.scope,
1180
+ rule.value,
1181
+ rule.spaceScope || '',
1182
+ rule.spaceValue || '',
1183
+ ].join(':');
1132
1184
  if (seen.has(keyValue)) return false;
1133
1185
  seen.add(keyValue);
1134
1186
  return true;
1135
1187
  });
1136
1188
 
1137
1189
  return {
1138
- capabilities: getPlatformAccessCapabilities(platformName),
1190
+ capabilities,
1139
1191
  discoveredTargets: unique([...(Array.isArray(discoveredTargets) ? discoveredTargets : []), ...recentTargets]),
1140
1192
  suggestedTargets: unique(this.accessSuggestions.get(this._accessSuggestionKey(userId, agentId, platformName)) || []),
1141
1193
  policy: this._loadAccessPolicy(userId, agentId, platformName),
@@ -223,7 +223,10 @@ class TelegramPlatform extends BasePlatform {
223
223
  ? senderName
224
224
  : `${senderName} in ${msg.chat.title || rawChatId}`;
225
225
 
226
- const channelContext = (!isPrivate && this._isMentioned(msg)) ? this._getContext(rawChatId) : null;
226
+ const channelContext = !isPrivate ? this._getContext(rawChatId) : null;
227
+ const repliedToAgent = !isPrivate
228
+ && msg.reply_to_message?.from?.id
229
+ && String(msg.reply_to_message.from.id) === String(this._botUser?.id || '');
227
230
 
228
231
  this.emit('message', {
229
232
  platform: 'telegram',
@@ -233,6 +236,12 @@ class TelegramPlatform extends BasePlatform {
233
236
  senderDisplayName,
234
237
  senderUsername,
235
238
  senderTag: senderUsername,
239
+ wasMentioned: !isPrivate && this._isMentioned(msg),
240
+ repliedToAgent: Boolean(repliedToAgent),
241
+ replyToMessageId: msg.reply_to_message?.message_id
242
+ ? String(msg.reply_to_message.message_id)
243
+ : null,
244
+ groupId: isPrivate ? null : rawChatId,
236
245
  content,
237
246
  mediaType: null,
238
247
  isGroup: !isPrivate,
@@ -172,6 +172,7 @@ class WhatsAppPlatform extends BasePlatform {
172
172
  const isGroup = chatId?.endsWith('@g.us');
173
173
  const sender = isGroup ? msg.key.participant : chatId;
174
174
  const pushName = msg.pushName || '';
175
+ const wasMentioned = isGroup && this._isGroupAddressedToBot(msg.message || {});
175
176
 
176
177
  let content = '';
177
178
  let mediaType = null;
@@ -258,6 +259,16 @@ class WhatsAppPlatform extends BasePlatform {
258
259
  senderName: pushName,
259
260
  senderDisplayName: pushName || null,
260
261
  senderTag: normalizeWhatsAppId(sender) || sender,
262
+ wasMentioned,
263
+ repliedToAgent: Boolean(
264
+ msg.message?.extendedTextMessage?.contextInfo?.stanzaId
265
+ && msg.message?.extendedTextMessage?.contextInfo?.participant
266
+ && this._ownIds().has(normalizeWhatsAppId(
267
+ msg.message.extendedTextMessage.contextInfo.participant
268
+ ))
269
+ ),
270
+ replyToMessageId: msg.message?.extendedTextMessage?.contextInfo?.stanzaId || null,
271
+ groupId: isGroup ? chatId : null,
261
272
  content,
262
273
  mediaType,
263
274
  localMediaPath,
@@ -47,7 +47,7 @@ class SocialVideoReachChannel extends SocialReachChannel {
47
47
  };
48
48
  });
49
49
  const missing = (health.dependencies || [])
50
- .filter((item) => !item.available)
50
+ .filter((item) => item.required !== false && !item.available)
51
51
  .map((item) => item.name)
52
52
  .filter(Boolean);
53
53
 
@@ -27,7 +27,7 @@ function pickCaptionTrack(captionGroups = {}, preferredLanguages = []) {
27
27
 
28
28
  const preferred = preferredLanguages.map(normalizeLanguageCode).filter(Boolean);
29
29
  const preferredOrder = preferred.length > 0 ? preferred : ['en', 'en-us', 'en-gb'];
30
- const preferredExt = ['vtt', 'webvtt', 'srt', 'json3'];
30
+ const preferredExt = ['vtt', 'webvtt', 'srt', 'json3', 'ttml', 'xml', 'json'];
31
31
 
32
32
  const scoreTrack = (track) => {
33
33
  const langIdx = preferredOrder.findIndex((code) => track.language === code || track.language.startsWith(`${code}-`));
@@ -148,7 +148,7 @@ function parseCaptionText(raw, extension = '') {
148
148
  if (ext === 'vtt' || ext === 'webvtt') {
149
149
  return parseWebVttToText(raw);
150
150
  }
151
- if (ext === 'ttml') {
151
+ if (ext === 'ttml' || ext === 'xml') {
152
152
  return parseTtmlToText(raw);
153
153
  }
154
154
  if (ext === 'srt') {