neoagent 2.3.1-beta.85 → 2.3.1-beta.87
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/docs/capabilities.md +2 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +14 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +84 -0
- package/flutter_app/lib/main_chat.dart +156 -2
- package/flutter_app/lib/main_controller.dart +137 -10
- package/flutter_app/lib/main_models.dart +69 -0
- package/flutter_app/lib/main_operations.dart +248 -0
- package/flutter_app/lib/main_runtime.dart +11 -2
- package/flutter_app/lib/main_settings.dart +173 -176
- package/flutter_app/lib/main_shared.dart +78 -0
- package/flutter_app/lib/src/app_launch_bridge.dart +39 -10
- package/flutter_app/lib/src/backend_client.dart +28 -0
- package/package.json +1 -1
- package/server/guest-agent.android.package.json +13 -0
- package/server/guest-agent.browser.package.json +14 -0
- package/server/guest_agent.js +61 -44
- 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 +69936 -69277
- package/server/routes/android.js +2 -11
- package/server/routes/browser.js +2 -2
- package/server/routes/memory.js +90 -0
- package/server/routes/social_video.js +62 -0
- package/server/services/ai/capabilityHealth.js +6 -14
- package/server/services/ai/systemPrompt.js +1 -0
- package/server/services/ai/toolResult.js +20 -0
- package/server/services/ai/tools.js +29 -0
- package/server/services/android/android_bootstrap_worker.js +2 -2
- package/server/services/android/controller.js +528 -132
- package/server/services/browser/controller.js +51 -68
- package/server/services/manager.js +15 -0
- package/server/services/memory/llm_transfer.js +217 -0
- package/server/services/runtime/backends/local-vm.js +16 -3
- package/server/services/runtime/guest_bootstrap.js +224 -56
- package/server/services/runtime/manager.js +53 -15
- package/server/services/runtime/qemu.js +149 -24
- package/server/services/runtime/settings.js +9 -14
- package/server/services/runtime/validation.js +10 -11
- package/server/services/social_video/adapters/base.js +26 -0
- package/server/services/social_video/adapters/index.js +27 -0
- package/server/services/social_video/adapters/instagram.js +17 -0
- package/server/services/social_video/adapters/tiktok.js +17 -0
- package/server/services/social_video/adapters/x.js +17 -0
- package/server/services/social_video/adapters/youtube.js +17 -0
- package/server/services/social_video/captions.js +187 -0
- package/server/services/social_video/frame.js +42 -0
- package/server/services/social_video/index.js +7 -0
- package/server/services/social_video/metadata.js +63 -0
- package/server/services/social_video/result.js +63 -0
- package/server/services/social_video/service.js +576 -0
- package/server/services/social_video/url.js +83 -0
- package/server/utils/deployment.js +4 -4
- package/server/guest-agent.package.json +0 -15
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MAX_VTT_BYTES = 2 * 1024 * 1024;
|
|
4
|
+
const MAX_INPUT_BYTES = MAX_VTT_BYTES;
|
|
5
|
+
|
|
6
|
+
function normalizeLanguageCode(value) {
|
|
7
|
+
return String(value || '').trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function pickCaptionTrack(captionGroups = {}, preferredLanguages = []) {
|
|
11
|
+
const tracks = [];
|
|
12
|
+
for (const [language, items] of Object.entries(captionGroups || {})) {
|
|
13
|
+
if (!Array.isArray(items)) continue;
|
|
14
|
+
for (const item of items) {
|
|
15
|
+
if (!item || typeof item !== 'object') continue;
|
|
16
|
+
const url = String(item.url || '').trim();
|
|
17
|
+
if (!url) continue;
|
|
18
|
+
tracks.push({
|
|
19
|
+
language: normalizeLanguageCode(language),
|
|
20
|
+
url,
|
|
21
|
+
ext: normalizeLanguageCode(item.ext || ''),
|
|
22
|
+
name: String(item.name || '').trim(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (tracks.length === 0) return null;
|
|
27
|
+
|
|
28
|
+
const preferred = preferredLanguages.map(normalizeLanguageCode).filter(Boolean);
|
|
29
|
+
const preferredOrder = preferred.length > 0 ? preferred : ['en', 'en-us', 'en-gb'];
|
|
30
|
+
const preferredExt = ['vtt', 'webvtt', 'srt', 'json3'];
|
|
31
|
+
|
|
32
|
+
const scoreTrack = (track) => {
|
|
33
|
+
const langIdx = preferredOrder.findIndex((code) => track.language === code || track.language.startsWith(`${code}-`));
|
|
34
|
+
const langScore = langIdx === -1 ? 999 : langIdx;
|
|
35
|
+
const extIdx = preferredExt.findIndex((ext) => track.ext === ext);
|
|
36
|
+
const extScore = extIdx === -1 ? 999 : extIdx;
|
|
37
|
+
return (langScore * 100) + extScore;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return tracks
|
|
41
|
+
.map((track) => ({ track, score: scoreTrack(track) }))
|
|
42
|
+
.sort((left, right) => left.score - right.score)[0]?.track || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stripTags(text) {
|
|
46
|
+
return String(text || '')
|
|
47
|
+
.replace(/<[^>]+>/g, ' ')
|
|
48
|
+
.replace(/\{\\an\d+\}/g, ' ')
|
|
49
|
+
.replace(/\[[^\]]+\]/g, ' ')
|
|
50
|
+
.replace(/ /g, ' ')
|
|
51
|
+
.replace(/&/g, '&')
|
|
52
|
+
.replace(/\s+/g, ' ')
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function inputByteLength(value) {
|
|
57
|
+
if (Buffer.isBuffer(value)) return value.byteLength;
|
|
58
|
+
return Buffer.byteLength(String(value || ''), 'utf8');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripXml(value) {
|
|
62
|
+
return String(value || '')
|
|
63
|
+
.replace(/<[^>]+>/g, ' ')
|
|
64
|
+
.replace(/ /gi, ' ')
|
|
65
|
+
.replace(/&/gi, '&')
|
|
66
|
+
.replace(/</gi, '<')
|
|
67
|
+
.replace(/>/gi, '>')
|
|
68
|
+
.replace(/"/gi, '"')
|
|
69
|
+
.replace(/'/g, "'")
|
|
70
|
+
.replace(/\s+/g, ' ')
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseWebVttToText(vtt) {
|
|
75
|
+
if (inputByteLength(vtt) > MAX_VTT_BYTES) {
|
|
76
|
+
throw new Error(`parseWebVttToText input exceeds MAX_VTT_BYTES (${MAX_VTT_BYTES}).`);
|
|
77
|
+
}
|
|
78
|
+
const lines = String(vtt || '').split(/\r?\n/);
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const clean = line.trim();
|
|
82
|
+
if (!clean) continue;
|
|
83
|
+
if (/^WEBVTT/i.test(clean)) continue;
|
|
84
|
+
if (/^NOTE\b/i.test(clean)) continue;
|
|
85
|
+
if (/^\d+$/.test(clean)) continue;
|
|
86
|
+
if (/^\d{2}:\d{2}:\d{2}[.,]\d{3}\s+-->/.test(clean) || /^\d{2}:\d{2}[.,]\d{3}\s+-->/.test(clean)) continue;
|
|
87
|
+
const normalized = stripTags(clean);
|
|
88
|
+
if (!normalized) continue;
|
|
89
|
+
if (chunks[chunks.length - 1] === normalized) continue;
|
|
90
|
+
chunks.push(normalized);
|
|
91
|
+
}
|
|
92
|
+
return chunks.join(' ').trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseSrtToText(srt) {
|
|
96
|
+
return parseWebVttToText(String(srt || '').replace(/,/g, '.'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseJson3ToText(raw) {
|
|
100
|
+
if (inputByteLength(raw) > MAX_INPUT_BYTES) {
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
let payload;
|
|
104
|
+
try {
|
|
105
|
+
payload = JSON.parse(String(raw || ''));
|
|
106
|
+
} catch {
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
const events = Array.isArray(payload?.events) ? payload.events : [];
|
|
110
|
+
const parts = [];
|
|
111
|
+
for (const event of events) {
|
|
112
|
+
const segs = Array.isArray(event?.segs) ? event.segs : [];
|
|
113
|
+
const text = segs
|
|
114
|
+
.map((segment) => String(segment?.utf8 || '').trim())
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join(' ');
|
|
117
|
+
const normalized = stripTags(text);
|
|
118
|
+
if (!normalized) continue;
|
|
119
|
+
if (parts[parts.length - 1] === normalized) continue;
|
|
120
|
+
parts.push(normalized);
|
|
121
|
+
}
|
|
122
|
+
return parts.join(' ').trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseTtmlToText(raw) {
|
|
126
|
+
if (inputByteLength(raw) > MAX_INPUT_BYTES) {
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
const text = String(raw || '');
|
|
130
|
+
const parts = [];
|
|
131
|
+
const paragraphRe = /<p\b[^>]*>([\s\S]*?)<\/p>/gi;
|
|
132
|
+
let match = paragraphRe.exec(text);
|
|
133
|
+
while (match) {
|
|
134
|
+
const parsed = stripXml(match[1]);
|
|
135
|
+
if (parsed && parts[parts.length - 1] !== parsed) {
|
|
136
|
+
parts.push(parsed);
|
|
137
|
+
}
|
|
138
|
+
match = paragraphRe.exec(text);
|
|
139
|
+
}
|
|
140
|
+
if (parts.length > 0) {
|
|
141
|
+
return parts.join(' ').trim();
|
|
142
|
+
}
|
|
143
|
+
return stripXml(text);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function parseCaptionText(raw, extension = '') {
|
|
147
|
+
const ext = normalizeLanguageCode(extension);
|
|
148
|
+
if (ext === 'vtt' || ext === 'webvtt') {
|
|
149
|
+
return parseWebVttToText(raw);
|
|
150
|
+
}
|
|
151
|
+
if (ext === 'ttml') {
|
|
152
|
+
return parseTtmlToText(raw);
|
|
153
|
+
}
|
|
154
|
+
if (ext === 'srt') {
|
|
155
|
+
return parseSrtToText(raw);
|
|
156
|
+
}
|
|
157
|
+
if (ext === 'json3' || ext === 'json') {
|
|
158
|
+
return parseJson3ToText(raw);
|
|
159
|
+
}
|
|
160
|
+
if (/^\s*<tt\b/i.test(String(raw || '')) || /xmlns\s*=\s*["'][^"']*ttml/i.test(String(raw || ''))) {
|
|
161
|
+
return parseTtmlToText(raw);
|
|
162
|
+
}
|
|
163
|
+
return parseWebVttToText(raw);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function decideTranscriptPath(options = {}) {
|
|
167
|
+
if (options.forceStt) {
|
|
168
|
+
return { mode: 'stt', reason: 'forced' };
|
|
169
|
+
}
|
|
170
|
+
if (options.captionTrack) {
|
|
171
|
+
return { mode: 'captions', reason: 'captions_present' };
|
|
172
|
+
}
|
|
173
|
+
return { mode: 'stt', reason: 'captions_missing' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
decideTranscriptPath,
|
|
178
|
+
normalizeLanguageCode,
|
|
179
|
+
parseCaptionText,
|
|
180
|
+
parseJson3ToText,
|
|
181
|
+
parseSrtToText,
|
|
182
|
+
parseTtmlToText,
|
|
183
|
+
parseWebVttToText,
|
|
184
|
+
pickCaptionTrack,
|
|
185
|
+
MAX_INPUT_BYTES,
|
|
186
|
+
MAX_VTT_BYTES,
|
|
187
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function pickDeterministicFrameSecond(durationSeconds) {
|
|
6
|
+
const duration = Number(durationSeconds);
|
|
7
|
+
if (!Number.isFinite(duration) || duration <= 0) {
|
|
8
|
+
return 2;
|
|
9
|
+
}
|
|
10
|
+
const target = Math.round(duration * 0.33);
|
|
11
|
+
return Math.max(1, Math.min(target, 120));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function inferImageContentType(filePath) {
|
|
15
|
+
const ext = path.extname(String(filePath || '')).toLowerCase();
|
|
16
|
+
if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
|
|
17
|
+
if (ext === '.webp') return 'image/webp';
|
|
18
|
+
if (ext === '.png') return 'image/png';
|
|
19
|
+
return 'application/octet-stream';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeFrameReference(input = {}) {
|
|
23
|
+
if (!input || typeof input !== 'object') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (!input.url) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
url: input.url,
|
|
31
|
+
artifactId: input.artifactId || null,
|
|
32
|
+
mimeType: input.mimeType || null,
|
|
33
|
+
byteSize: Number.isFinite(Number(input.byteSize)) ? Number(input.byteSize) : null,
|
|
34
|
+
source: input.source || 'frame',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
inferImageContentType,
|
|
40
|
+
normalizeFrameReference,
|
|
41
|
+
pickDeterministicFrameSecond,
|
|
42
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function decodeHtmlEntities(text) {
|
|
4
|
+
return String(text || '')
|
|
5
|
+
.replace(/&/g, '&')
|
|
6
|
+
.replace(/</g, '<')
|
|
7
|
+
.replace(/>/g, '>')
|
|
8
|
+
.replace(/"/g, '"')
|
|
9
|
+
.replace(/'/g, "'")
|
|
10
|
+
.replace(/'/gi, "'")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function collapseWhitespace(text) {
|
|
15
|
+
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readHtmlTitle(html) {
|
|
19
|
+
const match = String(html || '').match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
20
|
+
return match ? collapseWhitespace(decodeHtmlEntities(match[1])) : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readCanonicalUrl(html) {
|
|
24
|
+
const match = String(html || '').match(
|
|
25
|
+
/<link\b(?=[^>]*\brel=["'][^"']*canonical[^"']*["'])(?=[^>]*\bhref=["']([^"']+)["'])[^>]*>/i,
|
|
26
|
+
);
|
|
27
|
+
return match ? match[1].trim() : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readMetaTagContent(html, key, attr = 'name') {
|
|
31
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
32
|
+
const pattern = new RegExp(
|
|
33
|
+
`<meta[^>]+${attr}=["']${escaped}["'][^>]*content=["']([\\s\\S]*?)["'][^>]*>|<meta[^>]+content=["']([\\s\\S]*?)["'][^>]*${attr}=["']${escaped}["'][^>]*>`,
|
|
34
|
+
'i',
|
|
35
|
+
);
|
|
36
|
+
const match = String(html || '').match(pattern);
|
|
37
|
+
const content = match ? match[1] || match[2] || '' : '';
|
|
38
|
+
return collapseWhitespace(decodeHtmlEntities(content));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractPublicMetadataFromHtml(html, fallbackUrl = '') {
|
|
42
|
+
const title = readMetaTagContent(html, 'og:title', 'property')
|
|
43
|
+
|| readMetaTagContent(html, 'twitter:title', 'name')
|
|
44
|
+
|| readHtmlTitle(html);
|
|
45
|
+
const description = readMetaTagContent(html, 'description', 'name')
|
|
46
|
+
|| readMetaTagContent(html, 'og:description', 'property')
|
|
47
|
+
|| readMetaTagContent(html, 'twitter:description', 'name');
|
|
48
|
+
const canonicalUrl = readCanonicalUrl(html) || fallbackUrl;
|
|
49
|
+
return {
|
|
50
|
+
title,
|
|
51
|
+
description,
|
|
52
|
+
canonicalUrl,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
collapseWhitespace,
|
|
58
|
+
decodeHtmlEntities,
|
|
59
|
+
extractPublicMetadataFromHtml,
|
|
60
|
+
readCanonicalUrl,
|
|
61
|
+
readHtmlTitle,
|
|
62
|
+
readMetaTagContent,
|
|
63
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { normalizeFrameReference } = require('./frame');
|
|
4
|
+
|
|
5
|
+
function isPlainObject(obj) {
|
|
6
|
+
return !!obj
|
|
7
|
+
&& typeof obj === 'object'
|
|
8
|
+
&& !Array.isArray(obj)
|
|
9
|
+
&& obj.constructor === Object;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeWarningList(items) {
|
|
13
|
+
return (Array.isArray(items) ? items : [])
|
|
14
|
+
.map((item) => String(item || '').trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeErrorList(items) {
|
|
19
|
+
return (Array.isArray(items) ? items : [])
|
|
20
|
+
.map((item) => {
|
|
21
|
+
if (typeof item === 'string') return { message: item };
|
|
22
|
+
if (!item || typeof item !== 'object') return null;
|
|
23
|
+
const message = String(item.message || '').trim();
|
|
24
|
+
if (!message) return null;
|
|
25
|
+
return {
|
|
26
|
+
code: item.code ? String(item.code) : undefined,
|
|
27
|
+
message,
|
|
28
|
+
};
|
|
29
|
+
})
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shapeSocialVideoResult(input = {}) {
|
|
34
|
+
const errors = normalizeErrorList(input.errors);
|
|
35
|
+
const warnings = normalizeWarningList(input.warnings);
|
|
36
|
+
return {
|
|
37
|
+
sourceUrl: String(input.sourceUrl || '').trim(),
|
|
38
|
+
resolvedUrl: String(input.resolvedUrl || '').trim(),
|
|
39
|
+
canonicalUrl: String(input.canonicalUrl || '').trim() || null,
|
|
40
|
+
platform: String(input.platform || 'unknown').trim(),
|
|
41
|
+
title: String(input.title || '').trim(),
|
|
42
|
+
description: String(input.description || '').trim(),
|
|
43
|
+
transcript: String(input.transcript || '').trim(),
|
|
44
|
+
transcriptSource: String(input.transcriptSource || '').trim() || 'unavailable',
|
|
45
|
+
frameImage: normalizeFrameReference(input.frameImage),
|
|
46
|
+
metadata: isPlainObject(input.metadata)
|
|
47
|
+
? input.metadata
|
|
48
|
+
: {},
|
|
49
|
+
setup: isPlainObject(input.setup)
|
|
50
|
+
? input.setup
|
|
51
|
+
: null,
|
|
52
|
+
warnings,
|
|
53
|
+
errors,
|
|
54
|
+
partial: warnings.length > 0 || errors.length > 0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
normalizeErrorList,
|
|
60
|
+
isPlainObject,
|
|
61
|
+
normalizeWarningList,
|
|
62
|
+
shapeSocialVideoResult,
|
|
63
|
+
};
|