neoagent 2.3.1-beta.86 → 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/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/memory.js +90 -0
- package/server/routes/social_video.js +62 -0
- 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/manager.js +15 -0
- package/server/services/memory/llm_transfer.js +217 -0
- 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/routes/memory.js
CHANGED
|
@@ -4,6 +4,11 @@ const rateLimit = require('express-rate-limit');
|
|
|
4
4
|
const { requireAuth } = require('../middleware/auth');
|
|
5
5
|
const { sanitizeError } = require('../utils/security');
|
|
6
6
|
const { getAgentIdFromRequest, resolveAgentId } = require('../services/agents/manager');
|
|
7
|
+
const {
|
|
8
|
+
buildLlmTransferPrompt,
|
|
9
|
+
parseLlmTransferText,
|
|
10
|
+
importanceForCategory,
|
|
11
|
+
} = require('../services/memory/llm_transfer');
|
|
7
12
|
|
|
8
13
|
router.use(requireAuth);
|
|
9
14
|
|
|
@@ -15,6 +20,16 @@ const apiKeyMutationLimiter = rateLimit({
|
|
|
15
20
|
legacyHeaders: false,
|
|
16
21
|
});
|
|
17
22
|
|
|
23
|
+
const transferImportLimiter = rateLimit({
|
|
24
|
+
windowMs: 10 * 60 * 1000,
|
|
25
|
+
max: 20,
|
|
26
|
+
message: { error: 'Too many memory imports, try again later' },
|
|
27
|
+
standardHeaders: true,
|
|
28
|
+
legacyHeaders: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const MAX_TRANSFER_TEXT_BYTES = 60 * 1024;
|
|
32
|
+
|
|
18
33
|
function normalizeMemoryIds(value) {
|
|
19
34
|
return [...new Set(
|
|
20
35
|
(Array.isArray(value) ? value : [])
|
|
@@ -262,6 +277,81 @@ router.get('/core', (req, res) => {
|
|
|
262
277
|
res.json(coreMemory);
|
|
263
278
|
});
|
|
264
279
|
|
|
280
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
281
|
+
// LLM Transfer (prompt + import)
|
|
282
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
router.get('/transfer-prompt', (req, res) => {
|
|
285
|
+
const prompt = buildLlmTransferPrompt({ agentLabel: 'NeoAgent' });
|
|
286
|
+
res.json({ prompt });
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
router.post('/transfer-import', transferImportLimiter, async (req, res) => {
|
|
290
|
+
const mm = req.app.locals.memoryManager;
|
|
291
|
+
const userId = req.session.userId;
|
|
292
|
+
const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
|
|
293
|
+
const text = String(req.body?.text || '').trim();
|
|
294
|
+
if (!text) {
|
|
295
|
+
return res.status(400).json({ error: 'text is required' });
|
|
296
|
+
}
|
|
297
|
+
if (Buffer.byteLength(text, 'utf8') > MAX_TRANSFER_TEXT_BYTES) {
|
|
298
|
+
return res.status(413).json({ error: 'text exceeds 60KB limit' });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const applyBehaviorNotes = req.body?.applyBehaviorNotes !== false;
|
|
302
|
+
const applyCoreMemory = req.body?.applyCoreMemory !== false;
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const parsed = parseLlmTransferText(text);
|
|
306
|
+
let importedCount = 0;
|
|
307
|
+
let skippedCount = 0;
|
|
308
|
+
|
|
309
|
+
for (const memory of parsed.memories) {
|
|
310
|
+
const content = String(memory.content || '').trim();
|
|
311
|
+
if (!content) {
|
|
312
|
+
skippedCount += 1;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
await mm.saveMemory(userId, content, memory.category, importanceForCategory(memory.category), {
|
|
316
|
+
agentId,
|
|
317
|
+
sourceRef: {
|
|
318
|
+
sourceType: 'llm_import',
|
|
319
|
+
sourceLabel: 'LLM memory transfer',
|
|
320
|
+
},
|
|
321
|
+
metadata: {
|
|
322
|
+
importedFrom: 'llm_transfer',
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
importedCount += 1;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let coreUpdatedCount = 0;
|
|
329
|
+
if (applyCoreMemory && parsed.coreEntries) {
|
|
330
|
+
for (const [key, value] of Object.entries(parsed.coreEntries)) {
|
|
331
|
+
if (!key || key === 'active_context') continue;
|
|
332
|
+
mm.updateCore(userId, key, value, { agentId });
|
|
333
|
+
coreUpdatedCount += 1;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let behaviorNotesUpdated = false;
|
|
338
|
+
if (applyBehaviorNotes && parsed.behaviorNotes) {
|
|
339
|
+
mm.setAssistantBehaviorNotes(userId, parsed.behaviorNotes, { agentId });
|
|
340
|
+
behaviorNotesUpdated = true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
res.json({
|
|
344
|
+
importedCount,
|
|
345
|
+
skippedCount,
|
|
346
|
+
coreUpdatedCount,
|
|
347
|
+
behaviorNotesUpdated,
|
|
348
|
+
warnings: parsed.warnings || [],
|
|
349
|
+
});
|
|
350
|
+
} catch (err) {
|
|
351
|
+
res.status(500).json({ error: sanitizeError(err) });
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
265
355
|
router.put('/core/:key', (req, res) => {
|
|
266
356
|
const mm = req.app.locals.memoryManager;
|
|
267
357
|
const userId = req.session.userId;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { requireAuth } = require('../middleware/auth');
|
|
3
|
+
const { sanitizeError } = require('../utils/security');
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
router.use(requireAuth);
|
|
8
|
+
|
|
9
|
+
router.get('/health', async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const service = req.app?.locals?.socialVideoService;
|
|
12
|
+
if (!service || typeof service.getHealthStatus !== 'function') {
|
|
13
|
+
return res.status(503).json({
|
|
14
|
+
ready: false,
|
|
15
|
+
error: 'Social video service is unavailable.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const health = await service.getHealthStatus({
|
|
19
|
+
forceRefresh: req.query?.refresh === '1' || req.query?.refresh === 'true',
|
|
20
|
+
});
|
|
21
|
+
return res.json(health);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return res.status(500).json({ error: sanitizeError(error) });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
router.post('/extract', async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const service = req.app?.locals?.socialVideoService;
|
|
30
|
+
if (!service || typeof service.extractFromUrl !== 'function') {
|
|
31
|
+
return res.status(503).json({ error: 'Social video service is unavailable.' });
|
|
32
|
+
}
|
|
33
|
+
if (!req.session || !req.session.userId) {
|
|
34
|
+
return res.status(401).json({ error: 'Authentication required.' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sourceUrl = String(req.body?.url || '').trim();
|
|
38
|
+
if (!sourceUrl) {
|
|
39
|
+
return res.status(400).json({ error: 'url is required.' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await service.extractFromUrl(req.session.userId, sourceUrl, {
|
|
43
|
+
includeFrame: req.body?.include_frame !== false,
|
|
44
|
+
forceStt: req.body?.force_stt === true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (result?.setup?.ready === false) {
|
|
48
|
+
return res.status(503).json(result);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (Array.isArray(result.errors) && result.errors.length > 0) {
|
|
52
|
+
const failed = !result.title && !result.description && !result.transcript && !result.frameImage;
|
|
53
|
+
return res.status(failed ? 422 : 200).json(result);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return res.json(result);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return res.status(500).json({ error: sanitizeError(error) });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
module.exports = router;
|
|
@@ -94,6 +94,7 @@ Separate facts from inferences. If you are inferring from logs, code, or partial
|
|
|
94
94
|
When evidence conflicts, state the conflict instead of smoothing it over.
|
|
95
95
|
Source priority for factual work is: direct tool output and first-party integrations in this run, then authoritative primary sources, then other web sources, then model memory. Search-result snippets, link previews, and remembered facts are leads, not evidence.
|
|
96
96
|
If the user provides a URL, open or fetch that URL before describing its contents unless the user only wants formatting help with the URL itself.
|
|
97
|
+
If the user sends only a video link with no extra instruction, default to researching and fact-checking the video's key claims and context.
|
|
97
98
|
|
|
98
99
|
DON'T REPEAT YOURSELF
|
|
99
100
|
State a limitation or error once. If the user pushes back, try a different approach before restating the same failure. Repeating the same dead-end across five messages is useless.
|
|
@@ -112,6 +112,26 @@ function compactToolResult(toolName, toolArgs = {}, toolResult, options = {}) {
|
|
|
112
112
|
});
|
|
113
113
|
break;
|
|
114
114
|
|
|
115
|
+
case 'social_video_extract':
|
|
116
|
+
envelope = trimObject({
|
|
117
|
+
tool: toolName,
|
|
118
|
+
platform: toolResult?.platform,
|
|
119
|
+
sourceUrl: toolResult?.sourceUrl,
|
|
120
|
+
resolvedUrl: toolResult?.resolvedUrl,
|
|
121
|
+
title: clampText(toolResult?.title || '', Math.floor(softLimit * 0.25)),
|
|
122
|
+
description: clampText(toolResult?.description || '', Math.floor(softLimit * 0.25)),
|
|
123
|
+
transcriptSource: toolResult?.transcriptSource,
|
|
124
|
+
transcriptPreview: lineExcerpt(toolResult?.transcript || '', 6, Math.floor(softLimit * 0.35)),
|
|
125
|
+
frameImage: trimObject({
|
|
126
|
+
url: toolResult?.frameImage?.url,
|
|
127
|
+
source: toolResult?.frameImage?.source,
|
|
128
|
+
}),
|
|
129
|
+
setupReady: toolResult?.setup?.ready,
|
|
130
|
+
warningCount: Array.isArray(toolResult?.warnings) ? toolResult.warnings.length : 0,
|
|
131
|
+
errorCount: Array.isArray(toolResult?.errors) ? toolResult.errors.length : 0,
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
|
|
115
135
|
case 'android_dump_ui':
|
|
116
136
|
case 'android_observe':
|
|
117
137
|
envelope = trimObject({
|
|
@@ -1267,6 +1267,19 @@ function getAvailableTools(app, options = {}) {
|
|
|
1267
1267
|
},
|
|
1268
1268
|
required: ['query']
|
|
1269
1269
|
}
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
name: 'social_video_extract',
|
|
1273
|
+
description: 'Extract title, description, transcript, and one representative frame image from a public social video URL (YouTube, TikTok, Instagram, or X) without social API keys.',
|
|
1274
|
+
parameters: {
|
|
1275
|
+
type: 'object',
|
|
1276
|
+
properties: {
|
|
1277
|
+
url: { type: 'string', description: 'Public social video URL.' },
|
|
1278
|
+
include_frame: { type: 'boolean', description: 'Whether to return one representative frame image artifact (default true).' },
|
|
1279
|
+
force_stt: { type: 'boolean', description: 'Force speech-to-text fallback even if captions are present.' }
|
|
1280
|
+
},
|
|
1281
|
+
required: ['url']
|
|
1282
|
+
}
|
|
1270
1283
|
}
|
|
1271
1284
|
];
|
|
1272
1285
|
|
|
@@ -1392,6 +1405,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1392
1405
|
const sk = () => app?.locals?.skillRunner || engine.skillRunner;
|
|
1393
1406
|
const taskRuntime = () => app?.locals?.taskRuntime || engine.taskRuntime;
|
|
1394
1407
|
const rec = () => app?.locals?.recordingManager || null;
|
|
1408
|
+
const socialVideo = () => app?.locals?.socialVideoService || null;
|
|
1395
1409
|
const widgets = () => app?.locals?.widgetService || null;
|
|
1396
1410
|
const artifactStore = app?.locals?.artifactStore || null;
|
|
1397
1411
|
|
|
@@ -1950,6 +1964,21 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
1950
1964
|
};
|
|
1951
1965
|
}
|
|
1952
1966
|
|
|
1967
|
+
case 'social_video_extract': {
|
|
1968
|
+
const service = socialVideo();
|
|
1969
|
+
if (!service || typeof service.extractFromUrl !== 'function') {
|
|
1970
|
+
return { error: 'Social video extraction service is unavailable.' };
|
|
1971
|
+
}
|
|
1972
|
+
const sourceUrl = String(args.url || '').trim();
|
|
1973
|
+
if (!sourceUrl) {
|
|
1974
|
+
return { error: 'url is required' };
|
|
1975
|
+
}
|
|
1976
|
+
return await service.extractFromUrl(userId, sourceUrl, {
|
|
1977
|
+
includeFrame: args.include_frame !== false,
|
|
1978
|
+
forceStt: args.force_stt === true,
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1953
1982
|
case 'memory_write': {
|
|
1954
1983
|
const { MemoryManager } = require('../memory/manager');
|
|
1955
1984
|
const mm = new MemoryManager();
|
|
@@ -13,6 +13,7 @@ const { WidgetService } = require('./widgets/service');
|
|
|
13
13
|
const { setupWebSocket } = require('./websocket');
|
|
14
14
|
const { registerMessagingAutomation } = require('./messaging/automation');
|
|
15
15
|
const { RecordingManager } = require('./recordings/manager');
|
|
16
|
+
const { SocialVideoService } = require('./social_video');
|
|
16
17
|
const { VoiceRuntimeManager } = require('./voice/runtimeManager');
|
|
17
18
|
const { AuthProviderManager } = require('./account/auth_provider_manager');
|
|
18
19
|
const { IntegrationManager } = require('./integrations/manager');
|
|
@@ -318,6 +319,19 @@ function createRecordingManager(app, io) {
|
|
|
318
319
|
return recordingManager;
|
|
319
320
|
}
|
|
320
321
|
|
|
322
|
+
function createSocialVideoService(app) {
|
|
323
|
+
const socialVideoService = registerLocal(
|
|
324
|
+
app,
|
|
325
|
+
'socialVideoService',
|
|
326
|
+
new SocialVideoService({
|
|
327
|
+
artifactStore: app.locals.artifactStore,
|
|
328
|
+
runtimeManager: app.locals.runtimeManager,
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
logServiceReady('Social video service ready');
|
|
332
|
+
return socialVideoService;
|
|
333
|
+
}
|
|
334
|
+
|
|
321
335
|
function createWidgetService(app) {
|
|
322
336
|
const widgetService = registerLocal(
|
|
323
337
|
app,
|
|
@@ -460,6 +474,7 @@ async function startServices(app, io) {
|
|
|
460
474
|
|
|
461
475
|
const messagingManager = createMessagingManager(app, io, agentEngine);
|
|
462
476
|
const recordingManager = createRecordingManager(app, io);
|
|
477
|
+
createSocialVideoService(app);
|
|
463
478
|
createWidgetService(app);
|
|
464
479
|
createWearableService(app);
|
|
465
480
|
createScreenRecorder(app);
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const HEADING_ALIASES = {
|
|
2
|
+
profile: 'identity',
|
|
3
|
+
identity: 'identity',
|
|
4
|
+
preferences: 'preferences',
|
|
5
|
+
preference: 'preferences',
|
|
6
|
+
projects: 'projects',
|
|
7
|
+
project: 'projects',
|
|
8
|
+
contacts: 'contacts',
|
|
9
|
+
contact: 'contacts',
|
|
10
|
+
events: 'events',
|
|
11
|
+
event: 'events',
|
|
12
|
+
tasks: 'tasks',
|
|
13
|
+
task: 'tasks',
|
|
14
|
+
'assistant self': 'assistant_self',
|
|
15
|
+
self: 'assistant_self',
|
|
16
|
+
'behavior notes': '__behavior_notes',
|
|
17
|
+
'assistant behavior notes': '__behavior_notes',
|
|
18
|
+
'core memory': '__core',
|
|
19
|
+
'core memories': '__core',
|
|
20
|
+
'other memories': 'episodic',
|
|
21
|
+
other: 'episodic',
|
|
22
|
+
misc: 'episodic',
|
|
23
|
+
miscellaneous: 'episodic',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const SECTION_PRIORITY = {
|
|
27
|
+
identity: 8,
|
|
28
|
+
preferences: 7,
|
|
29
|
+
projects: 6,
|
|
30
|
+
contacts: 6,
|
|
31
|
+
events: 6,
|
|
32
|
+
tasks: 6,
|
|
33
|
+
assistant_self: 7,
|
|
34
|
+
episodic: 5,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const MAX_MEMORY_LENGTH = 1200;
|
|
38
|
+
const MAX_MEMORIES = 200;
|
|
39
|
+
|
|
40
|
+
function normalizeHeadingLabel(input) {
|
|
41
|
+
return String(input || '')
|
|
42
|
+
.trim()
|
|
43
|
+
.replace(/^#+\s*/, '')
|
|
44
|
+
.replace(/[:]+$/g, '')
|
|
45
|
+
.replace(/\s+/g, ' ')
|
|
46
|
+
.toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function detectHeading(line) {
|
|
50
|
+
const trimmed = String(line || '').trim();
|
|
51
|
+
if (!trimmed) return null;
|
|
52
|
+
const normalized = normalizeHeadingLabel(trimmed);
|
|
53
|
+
if (HEADING_ALIASES[normalized]) return normalized;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function splitSections(text) {
|
|
58
|
+
const lines = String(text || '').split(/\r?\n/);
|
|
59
|
+
const sections = [];
|
|
60
|
+
let current = { heading: 'other', lines: [] };
|
|
61
|
+
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
const heading = detectHeading(line);
|
|
64
|
+
if (heading) {
|
|
65
|
+
if (current.lines.length || current.heading) {
|
|
66
|
+
sections.push(current);
|
|
67
|
+
}
|
|
68
|
+
current = { heading, lines: [] };
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
current.lines.push(line);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (current.lines.length || current.heading) {
|
|
75
|
+
sections.push(current);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sections;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collectBulletItems(lines) {
|
|
82
|
+
const items = [];
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
const match = String(line || '').match(/^\s*(?:[-*]|\u2022|\d+\.)\s+(.*)$/);
|
|
85
|
+
if (match && match[1]) {
|
|
86
|
+
const value = match[1].trim();
|
|
87
|
+
if (value) items.push(value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return items;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collapseParagraph(lines) {
|
|
94
|
+
const parts = lines
|
|
95
|
+
.map((line) => String(line || '').trim())
|
|
96
|
+
.filter((line) => line.length > 0);
|
|
97
|
+
if (!parts.length) return '';
|
|
98
|
+
return parts.join(' ');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeMemoryContent(text) {
|
|
102
|
+
const cleaned = String(text || '').trim();
|
|
103
|
+
if (!cleaned) return '';
|
|
104
|
+
if (cleaned.length <= MAX_MEMORY_LENGTH) return cleaned;
|
|
105
|
+
return cleaned.slice(0, MAX_MEMORY_LENGTH).trim();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildLlmTransferPrompt({ agentLabel = 'NeoAgent' } = {}) {
|
|
109
|
+
return [
|
|
110
|
+
'You are preparing a memory export for ' + agentLabel + '.',
|
|
111
|
+
'Return a concise, structured, natural language summary of everything you remember about the user.',
|
|
112
|
+
'',
|
|
113
|
+
'Rules:',
|
|
114
|
+
'- Use only plain text. No JSON or code blocks.',
|
|
115
|
+
'- Use short bullet points where possible.',
|
|
116
|
+
'- Omit secrets, passwords, API keys, or anything sensitive.',
|
|
117
|
+
'- If a section has no data, omit the section.',
|
|
118
|
+
'',
|
|
119
|
+
'Use these sections and formatting:',
|
|
120
|
+
'# Profile',
|
|
121
|
+
'- Key identity facts about the user.',
|
|
122
|
+
'# Preferences',
|
|
123
|
+
'- Stable preferences, likes, dislikes, habits.',
|
|
124
|
+
'# Projects',
|
|
125
|
+
'- Ongoing projects, goals, responsibilities.',
|
|
126
|
+
'# Contacts',
|
|
127
|
+
'- Important people or organizations and the relationship.',
|
|
128
|
+
'# Events',
|
|
129
|
+
'- Important dates or recurring events.',
|
|
130
|
+
'# Tasks',
|
|
131
|
+
'- Open tasks or commitments the user expects to remember.',
|
|
132
|
+
'# Behavior Notes',
|
|
133
|
+
'Short guidance for how the assistant should behave.',
|
|
134
|
+
'# Core Memory',
|
|
135
|
+
'key: value entries for critical facts that should always be pinned.',
|
|
136
|
+
'# Other Memories',
|
|
137
|
+
'- Anything else that does not fit above.',
|
|
138
|
+
].join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseLlmTransferText(text) {
|
|
142
|
+
const sections = splitSections(text);
|
|
143
|
+
const memories = [];
|
|
144
|
+
const coreEntries = {};
|
|
145
|
+
let behaviorNotes = '';
|
|
146
|
+
const warnings = [];
|
|
147
|
+
|
|
148
|
+
for (const section of sections) {
|
|
149
|
+
const heading = normalizeHeadingLabel(section.heading || 'other');
|
|
150
|
+
const alias = HEADING_ALIASES[heading] || 'episodic';
|
|
151
|
+
const lines = section.lines || [];
|
|
152
|
+
|
|
153
|
+
if (alias === '__behavior_notes') {
|
|
154
|
+
const notes = collapseParagraph(lines);
|
|
155
|
+
if (notes) behaviorNotes = notes;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (alias === '__core') {
|
|
160
|
+
for (const rawLine of lines) {
|
|
161
|
+
const line = String(rawLine || '').trim();
|
|
162
|
+
if (!line) continue;
|
|
163
|
+
const cleanedLine = line.replace(/^[-*]\s*/, '');
|
|
164
|
+
const colonIndex = cleanedLine.indexOf(':');
|
|
165
|
+
if (colonIndex <= 0) continue;
|
|
166
|
+
const key = cleanedLine.slice(0, colonIndex).trim();
|
|
167
|
+
const value = cleanedLine.slice(colonIndex + 1).trim();
|
|
168
|
+
if (!key || !value) continue;
|
|
169
|
+
if (key === 'active_context') continue;
|
|
170
|
+
coreEntries[key] = value;
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const bulletItems = collectBulletItems(lines);
|
|
176
|
+
if (bulletItems.length) {
|
|
177
|
+
for (const item of bulletItems) {
|
|
178
|
+
const content = normalizeMemoryContent(item);
|
|
179
|
+
if (!content) continue;
|
|
180
|
+
memories.push({
|
|
181
|
+
category: alias,
|
|
182
|
+
content,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const paragraph = normalizeMemoryContent(collapseParagraph(lines));
|
|
189
|
+
if (paragraph) {
|
|
190
|
+
memories.push({
|
|
191
|
+
category: alias,
|
|
192
|
+
content: paragraph,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (memories.length > MAX_MEMORIES) {
|
|
198
|
+
warnings.push('Import exceeded ' + MAX_MEMORIES + ' items; extra entries were skipped.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
memories: memories.slice(0, MAX_MEMORIES),
|
|
203
|
+
coreEntries,
|
|
204
|
+
behaviorNotes,
|
|
205
|
+
warnings,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function importanceForCategory(category) {
|
|
210
|
+
return SECTION_PRIORITY[category] || 5;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
buildLlmTransferPrompt,
|
|
215
|
+
parseLlmTransferText,
|
|
216
|
+
importanceForCategory,
|
|
217
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class SocialVideoAdapter {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.platform = String(options.platform || 'unknown');
|
|
6
|
+
this.hosts = Array.isArray(options.hosts)
|
|
7
|
+
? options.hosts.map((host) => String(host || '').toLowerCase()).filter(Boolean)
|
|
8
|
+
: [];
|
|
9
|
+
this.captionLanguages = Array.isArray(options.captionLanguages)
|
|
10
|
+
? options.captionLanguages.map((value) => String(value || '').toLowerCase()).filter(Boolean)
|
|
11
|
+
: ['en', 'en-us'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
supportsHost(hostname) {
|
|
15
|
+
const host = String(hostname || '').toLowerCase();
|
|
16
|
+
return this.hosts.some((candidate) => host === candidate || host.endsWith(`.${candidate}`));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getCaptionLanguagePreferences() {
|
|
20
|
+
return [...this.captionLanguages];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
SocialVideoAdapter,
|
|
26
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { YouTubeAdapter } = require('./youtube');
|
|
4
|
+
const { TikTokAdapter } = require('./tiktok');
|
|
5
|
+
const { InstagramAdapter } = require('./instagram');
|
|
6
|
+
const { XAdapter } = require('./x');
|
|
7
|
+
|
|
8
|
+
const ADAPTERS = [
|
|
9
|
+
new YouTubeAdapter(),
|
|
10
|
+
new TikTokAdapter(),
|
|
11
|
+
new InstagramAdapter(),
|
|
12
|
+
new XAdapter(),
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function getAdapterForPlatform(platform) {
|
|
16
|
+
const normalized = String(platform || '').trim().toLowerCase();
|
|
17
|
+
return ADAPTERS.find((adapter) => adapter.platform === normalized) || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getSupportedAdapters() {
|
|
21
|
+
return [...ADAPTERS];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
getAdapterForPlatform,
|
|
26
|
+
getSupportedAdapters,
|
|
27
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SocialVideoAdapter } = require('./base');
|
|
4
|
+
|
|
5
|
+
class InstagramAdapter extends SocialVideoAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
platform: 'instagram',
|
|
9
|
+
hosts: ['instagram.com', 'www.instagram.com', 'm.instagram.com', 'instagr.am'],
|
|
10
|
+
captionLanguages: ['en', 'en-us'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
InstagramAdapter,
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SocialVideoAdapter } = require('./base');
|
|
4
|
+
|
|
5
|
+
class TikTokAdapter extends SocialVideoAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
platform: 'tiktok',
|
|
9
|
+
hosts: ['tiktok.com', 'www.tiktok.com', 'm.tiktok.com', 'vm.tiktok.com', 'vt.tiktok.com'],
|
|
10
|
+
captionLanguages: ['en', 'en-us'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
TikTokAdapter,
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SocialVideoAdapter } = require('./base');
|
|
4
|
+
|
|
5
|
+
class XAdapter extends SocialVideoAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
platform: 'x',
|
|
9
|
+
hosts: ['x.com', 'twitter.com'],
|
|
10
|
+
captionLanguages: ['en', 'en-us'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
XAdapter,
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SocialVideoAdapter } = require('./base');
|
|
4
|
+
|
|
5
|
+
class YouTubeAdapter extends SocialVideoAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
platform: 'youtube',
|
|
9
|
+
hosts: ['youtube.com', 'www.youtube.com', 'youtu.be'],
|
|
10
|
+
captionLanguages: ['en', 'en-us', 'en-gb'],
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
YouTubeAdapter,
|
|
17
|
+
};
|