neoagent 3.2.1-beta.10 → 3.2.1-beta.12
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_controller.dart +46 -4
- package/flutter_app/lib/main_models.dart +4 -2
- 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/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +65635 -65088
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +97 -16
- package/server/services/ai/loop/completion_judge.js +211 -44
- package/server/services/ai/loop/conversation_loop.js +6 -11
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/messagingFallback.js +2 -2
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +30 -128
- package/server/services/ai/taskAnalysis.js +47 -2
- package/server/services/ai/toolEvidence.js +65 -159
- package/server/services/ai/tools.js +60 -8
- 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 +237 -0
- package/server/services/behavior/pipeline.js +324 -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 +10 -6
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +2 -4
- package/server/services/messaging/http_platforms.js +4 -3
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +18 -0
- 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 -18
|
@@ -198,7 +198,10 @@ class DiscordPlatform extends BasePlatform {
|
|
|
198
198
|
: `${senderDisplayName} in #${message.channel.name || channelId}${message.guild ? ` (${message.guild.name})` : ''}`;
|
|
199
199
|
|
|
200
200
|
// Fetch recent channel history for context on guild/channel mentions
|
|
201
|
-
const channelContext =
|
|
201
|
+
const channelContext = !isDM ? await this._fetchContext(message.channel, 20) : null;
|
|
202
|
+
const repliedToAgent = !isDM
|
|
203
|
+
&& message.reference?.messageId
|
|
204
|
+
&& message.mentions?.repliedUser?.id === this._botUser?.id;
|
|
202
205
|
|
|
203
206
|
this.emit('message', {
|
|
204
207
|
platform: 'discord',
|
|
@@ -209,6 +212,13 @@ class DiscordPlatform extends BasePlatform {
|
|
|
209
212
|
senderUsername,
|
|
210
213
|
senderTag,
|
|
211
214
|
guildId,
|
|
215
|
+
serverId: guildId,
|
|
216
|
+
groupId: isDM ? null : channelId,
|
|
217
|
+
channelId: isDM ? null : channelId,
|
|
218
|
+
roleIds: !isDM && message.member ? [...message.member.roles.cache.keys()] : [],
|
|
219
|
+
wasMentioned: !isDM && this._isMentioned(message),
|
|
220
|
+
repliedToAgent: Boolean(repliedToAgent),
|
|
221
|
+
replyToMessageId: message.reference?.messageId || null,
|
|
212
222
|
content,
|
|
213
223
|
mediaType: null,
|
|
214
224
|
isGroup: !isDM,
|
|
@@ -31,11 +31,10 @@ function buildPlatformFormattingGuide(_platform, options = {}) {
|
|
|
31
31
|
? ''
|
|
32
32
|
: 'Reply formatting guide:';
|
|
33
33
|
const body = [
|
|
34
|
-
'Write like a favorite contact texting back: compact, natural, human.',
|
|
35
34
|
'Prefer short paragraphs or multi-line chat bursts over document structure.',
|
|
36
35
|
'Use simple single-level lists only when they genuinely improve clarity.',
|
|
37
36
|
'Avoid tables, raw HTML, and formal report formatting in chat replies.',
|
|
38
|
-
'
|
|
37
|
+
'A blank line may be delivered as a separate message bubble, so use one only for an intentional conversational beat.',
|
|
39
38
|
'The runtime will adapt the final text to the destination platform.'
|
|
40
39
|
].map((line) => `- ${line}`).join('\n');
|
|
41
40
|
return [intro, body].filter(Boolean).join('\n');
|
|
@@ -43,8 +42,7 @@ function buildPlatformFormattingGuide(_platform, options = {}) {
|
|
|
43
42
|
|
|
44
43
|
function buildSendMessageFormattingReference() {
|
|
45
44
|
return [
|
|
46
|
-
'Use one plain chat-style reply
|
|
47
|
-
'Lead with the result and keep filler out.',
|
|
45
|
+
'Use one plain chat-style reply unless intentional bubble breaks improve it.',
|
|
48
46
|
'The runtime adapts final formatting for the destination platform.',
|
|
49
47
|
'For WhatsApp, media attachments still use media_path.'
|
|
50
48
|
].join(' ');
|
|
@@ -345,9 +345,6 @@ class SlackPlatform extends BasePlatform {
|
|
|
345
345
|
const isGroup = String(event.channel_type || '') !== 'im';
|
|
346
346
|
const wasMentioned = event.type === 'app_mention'
|
|
347
347
|
|| (this._botUserId && String(event.text || '').includes(`<@${this._botUserId}>`));
|
|
348
|
-
if (isGroup && !wasMentioned) {
|
|
349
|
-
return { handled: true, status: 202, body: 'ignored' };
|
|
350
|
-
}
|
|
351
348
|
const content = this._botUserId
|
|
352
349
|
? String(event.text).replace(new RegExp(`<@${this._botUserId}>`, 'g'), '').trim()
|
|
353
350
|
: String(event.text);
|
|
@@ -365,8 +362,12 @@ class SlackPlatform extends BasePlatform {
|
|
|
365
362
|
messageId: String(event.client_msg_id || event.ts || crypto.randomUUID()),
|
|
366
363
|
timestamp: event.event_ts ? new Date(Number(event.event_ts) * 1000).toISOString() : new Date().toISOString(),
|
|
367
364
|
threadTs: event.thread_ts || null,
|
|
365
|
+
threadId: event.thread_ts || null,
|
|
366
|
+
channelId: isGroup ? String(event.channel || '') : null,
|
|
367
|
+
groupId: isGroup ? String(event.channel || '') : null,
|
|
368
368
|
rawMessage: body,
|
|
369
369
|
wasMentioned,
|
|
370
|
+
repliedToAgent: Boolean(event.thread_ts && event.parent_user_id === this._botUserId),
|
|
370
371
|
};
|
|
371
372
|
const access = this._checkInboundAccess({
|
|
372
373
|
platform: 'slack',
|
|
@@ -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,13 @@ 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
|
+
channelId: msg.channelId || null,
|
|
194
|
+
serverId: msg.serverId || msg.guildId || null,
|
|
195
|
+
roomId: msg.roomId || null,
|
|
196
|
+
roleIds: Array.isArray(msg.roleIds) ? msg.roleIds.map(String) : [],
|
|
190
197
|
mediaType: msg.mediaType,
|
|
191
198
|
localMediaPath: msg.localMediaPath || null,
|
|
192
199
|
...(msg.metadata && typeof msg.metadata === 'object' ? msg.metadata : {}),
|
|
@@ -201,6 +208,8 @@ class MessagingManager extends EventEmitter {
|
|
|
201
208
|
senderDisplayName: msg.senderDisplayName || null,
|
|
202
209
|
senderUsername: msg.senderUsername || null,
|
|
203
210
|
senderTag: msg.senderTag || null,
|
|
211
|
+
wasMentioned: msg.wasMentioned === true,
|
|
212
|
+
repliedToAgent: msg.repliedToAgent === true,
|
|
204
213
|
content: normalizedIncomingContent,
|
|
205
214
|
mediaType: msg.mediaType || null,
|
|
206
215
|
localMediaPath: msg.localMediaPath || null,
|
|
@@ -211,6 +220,15 @@ class MessagingManager extends EventEmitter {
|
|
|
211
220
|
groupName: msg.groupName || null,
|
|
212
221
|
guildName: msg.guildName || null,
|
|
213
222
|
roomName: msg.roomName || null,
|
|
223
|
+
groupId: msg.groupId || null,
|
|
224
|
+
channelId: msg.channelId || null,
|
|
225
|
+
serverId: msg.serverId || msg.guildId || null,
|
|
226
|
+
guildId: msg.guildId || msg.serverId || null,
|
|
227
|
+
roomId: msg.roomId || null,
|
|
228
|
+
roleIds: Array.isArray(msg.roleIds) ? msg.roleIds.map(String) : [],
|
|
229
|
+
replyToMessageId: msg.replyToMessageId || null,
|
|
230
|
+
threadId: msg.threadId || msg.threadTs || null,
|
|
231
|
+
eventType: msg.eventType || 'message',
|
|
214
232
|
metadata: msg.metadata && typeof msg.metadata === 'object' ? msg.metadata : null,
|
|
215
233
|
};
|
|
216
234
|
const queued = enqueueInboundMessage({
|
|
@@ -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') {
|