neoagent 3.2.1-beta.8 → 3.3.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.
- package/flutter_app/lib/main_app_shell.dart +178 -34
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +59 -4
- package/flutter_app/lib/main_models.dart +171 -22
- package/flutter_app/lib/main_operations.dart +0 -49
- package/flutter_app/lib/main_settings.dart +338 -0
- package/flutter_app/lib/src/backend_client.dart +19 -0
- package/package.json +1 -1
- package/server/db/database.js +2 -2
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +85079 -83904
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +131 -18
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +280 -18
- package/server/services/ai/loop/conversation_loop.js +92 -34
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loopPolicy.js +24 -2
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +31 -122
- package/server/services/ai/taskAnalysis.js +101 -12
- package/server/services/ai/toolEvidence.js +198 -0
- package/server/services/ai/tools.js +60 -19
- 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 +172 -0
- package/server/services/behavior/modules/persona_prompt.js +238 -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 +118 -0
- package/server/services/behavior/modules/turn_taking.js +238 -0
- package/server/services/behavior/pipeline.js +341 -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/memory/manager.js +14 -33
- package/server/services/messaging/access_policy.js +269 -74
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +73 -28
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +57 -5
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/whatsapp.js +11 -0
- package/server/services/social_reach/channels/social_video.js +1 -1
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +194 -29
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/runtime.js +2 -2
- package/server/utils/logger.js +19 -0
- 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.
|
|
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
|
-
&&
|
|
84
|
+
&& (
|
|
85
|
+
last.message.isGroup === true
|
|
86
|
+
|| String(last.message.sender || '') === String(msg.sender || '')
|
|
87
|
+
)
|
|
35
88
|
) {
|
|
36
|
-
last
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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') {
|