audrey 0.9.0 → 0.14.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/mcp-server/config.js +32 -16
- package/mcp-server/index.js +82 -5
- package/package.json +3 -1
- package/src/affect.js +1 -1
- package/src/audrey.js +137 -1
- package/src/db.js +333 -282
- package/src/embedding.js +141 -53
- package/src/encode.js +63 -61
- package/src/export.js +5 -3
- package/src/import.js +15 -8
- package/src/migrate.js +50 -24
- package/src/prompts.js +74 -5
- package/src/recall.js +13 -14
package/mcp-server/config.js
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const VERSION = '0.
|
|
4
|
+
export const VERSION = '0.14.0';
|
|
5
5
|
export const SERVER_NAME = 'audrey-memory';
|
|
6
6
|
export const DEFAULT_DATA_DIR = join(homedir(), '.audrey', 'data');
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Resolves which embedding provider to use.
|
|
10
|
+
* Priority: explicit config -> gemini (if GOOGLE_API_KEY exists) -> local
|
|
11
|
+
* OpenAI is NEVER auto-selected -- must be set explicitly via AUDREY_EMBEDDING_PROVIDER=openai.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveEmbeddingProvider(env, explicit) {
|
|
14
|
+
if (explicit && explicit !== 'auto') {
|
|
15
|
+
const dims = explicit === 'openai' ? 1536 : explicit === 'gemini' ? 3072 : 384;
|
|
16
|
+
const apiKey = explicit === 'gemini'
|
|
17
|
+
? (env.GOOGLE_API_KEY || env.GEMINI_API_KEY)
|
|
18
|
+
: explicit === 'openai'
|
|
19
|
+
? env.OPENAI_API_KEY
|
|
20
|
+
: undefined;
|
|
21
|
+
const result = { provider: explicit, apiKey, dimensions: dims };
|
|
22
|
+
if (explicit === 'local') result.device = env.AUDREY_DEVICE || 'gpu';
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
if (env.GOOGLE_API_KEY || env.GEMINI_API_KEY) {
|
|
26
|
+
return { provider: 'gemini', apiKey: env.GOOGLE_API_KEY || env.GEMINI_API_KEY, dimensions: 3072 };
|
|
27
|
+
}
|
|
28
|
+
return { provider: 'local', dimensions: 384, device: env.AUDREY_DEVICE || 'gpu' };
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
export function buildAudreyConfig() {
|
|
9
32
|
const dataDir = process.env.AUDREY_DATA_DIR || DEFAULT_DATA_DIR;
|
|
10
33
|
const agent = process.env.AUDREY_AGENT || 'claude-code';
|
|
11
|
-
const
|
|
12
|
-
const embDimensions = parseInt(process.env.AUDREY_EMBEDDING_DIMENSIONS || '8', 10);
|
|
34
|
+
const explicitProvider = process.env.AUDREY_EMBEDDING_PROVIDER;
|
|
13
35
|
const llmProvider = process.env.AUDREY_LLM_PROVIDER;
|
|
14
36
|
|
|
15
|
-
const
|
|
16
|
-
dataDir,
|
|
17
|
-
agent,
|
|
18
|
-
embedding: { provider: embProvider, dimensions: embDimensions },
|
|
19
|
-
};
|
|
37
|
+
const embedding = resolveEmbeddingProvider(process.env, explicitProvider);
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
config.embedding.apiKey = process.env.OPENAI_API_KEY;
|
|
23
|
-
}
|
|
39
|
+
const config = { dataDir, agent, embedding };
|
|
24
40
|
|
|
25
41
|
if (llmProvider === 'anthropic') {
|
|
26
42
|
config.llm = { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY };
|
|
@@ -36,13 +52,13 @@ export function buildAudreyConfig() {
|
|
|
36
52
|
export function buildInstallArgs(env = process.env) {
|
|
37
53
|
const envPairs = [`AUDREY_DATA_DIR=${DEFAULT_DATA_DIR}`];
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
const embedding = resolveEmbeddingProvider(env);
|
|
56
|
+
if (embedding.provider === 'gemini') {
|
|
57
|
+
envPairs.push('AUDREY_EMBEDDING_PROVIDER=gemini');
|
|
58
|
+
envPairs.push(`GOOGLE_API_KEY=${embedding.apiKey}`);
|
|
59
|
+
} else if (embedding.provider === 'openai') {
|
|
40
60
|
envPairs.push('AUDREY_EMBEDDING_PROVIDER=openai');
|
|
41
|
-
envPairs.push('AUDREY_EMBEDDING_DIMENSIONS=1536');
|
|
42
61
|
envPairs.push(`OPENAI_API_KEY=${env.OPENAI_API_KEY}`);
|
|
43
|
-
} else {
|
|
44
|
-
envPairs.push('AUDREY_EMBEDDING_PROVIDER=mock');
|
|
45
|
-
envPairs.push('AUDREY_EMBEDDING_DIMENSIONS=8');
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
if (env.ANTHROPIC_API_KEY) {
|
package/mcp-server/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
8
8
|
import { execFileSync } from 'node:child_process';
|
|
9
9
|
import { Audrey } from '../src/index.js';
|
|
10
10
|
import { readStoredDimensions } from '../src/db.js';
|
|
11
|
-
import { VERSION, SERVER_NAME, DEFAULT_DATA_DIR, buildAudreyConfig, buildInstallArgs } from './config.js';
|
|
11
|
+
import { VERSION, SERVER_NAME, DEFAULT_DATA_DIR, buildAudreyConfig, buildInstallArgs, resolveEmbeddingProvider } from './config.js';
|
|
12
12
|
|
|
13
13
|
const VALID_SOURCES = ['direct-observation', 'told-by-user', 'tool-result', 'inference', 'model-generated'];
|
|
14
14
|
const VALID_TYPES = ['episodic', 'semantic', 'procedural'];
|
|
@@ -19,6 +19,11 @@ if (subcommand === 'install') {
|
|
|
19
19
|
install();
|
|
20
20
|
} else if (subcommand === 'uninstall') {
|
|
21
21
|
uninstall();
|
|
22
|
+
} else if (subcommand === 'reembed') {
|
|
23
|
+
reembed().catch(err => {
|
|
24
|
+
console.error('[audrey] reembed failed:', err);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
22
27
|
} else if (subcommand === 'status') {
|
|
23
28
|
status();
|
|
24
29
|
} else {
|
|
@@ -28,6 +33,28 @@ if (subcommand === 'install') {
|
|
|
28
33
|
});
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
|
|
37
|
+
async function reembed() {
|
|
38
|
+
const dataDir = process.env.AUDREY_DATA_DIR || DEFAULT_DATA_DIR;
|
|
39
|
+
const explicit = process.env.AUDREY_EMBEDDING_PROVIDER;
|
|
40
|
+
const embedding = resolveEmbeddingProvider(process.env, explicit);
|
|
41
|
+
|
|
42
|
+
const storedDims = readStoredDimensions(dataDir);
|
|
43
|
+
const dimensionsChanged = storedDims !== null && storedDims !== embedding.dimensions;
|
|
44
|
+
|
|
45
|
+
console.log(`Re-embedding with ${embedding.provider} (${embedding.dimensions}d)...`);
|
|
46
|
+
if (dimensionsChanged) {
|
|
47
|
+
console.log(`Dimension change: ${storedDims}d -> ${embedding.dimensions}d (will drop and recreate vec tables)`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const audrey = new Audrey({ dataDir, agent: 'reembed', embedding });
|
|
51
|
+
const { reembedAll } = await import('../src/migrate.js');
|
|
52
|
+
const counts = await reembedAll(audrey.db, audrey.embeddingProvider, { dropAndRecreate: dimensionsChanged });
|
|
53
|
+
audrey.close();
|
|
54
|
+
|
|
55
|
+
console.log(`Done. Re-embedded: ${counts.episodes} episodes, ${counts.semantics} semantics, ${counts.procedures} procedures`);
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
function install() {
|
|
32
59
|
try {
|
|
33
60
|
execFileSync('claude', ['--version'], { stdio: 'ignore' });
|
|
@@ -65,7 +92,7 @@ function install() {
|
|
|
65
92
|
console.log(`
|
|
66
93
|
Audrey registered as "${SERVER_NAME}" with Claude Code.
|
|
67
94
|
|
|
68
|
-
|
|
95
|
+
12 tools available in every session:
|
|
69
96
|
memory_encode — Store observations, facts, preferences
|
|
70
97
|
memory_recall — Search memories by semantic similarity
|
|
71
98
|
memory_consolidate — Extract principles from accumulated episodes
|
|
@@ -75,6 +102,9 @@ Audrey registered as "${SERVER_NAME}" with Claude Code.
|
|
|
75
102
|
memory_import — Import a snapshot into a fresh database
|
|
76
103
|
memory_forget — Forget a specific memory by ID or query
|
|
77
104
|
memory_decay — Apply forgetting curves, transition low-confidence to dormant
|
|
105
|
+
memory_status — Check brain health (episode/vec sync, dimensions)
|
|
106
|
+
memory_reflect — Form lasting memories from a conversation
|
|
107
|
+
memory_greeting — Wake up as yourself: load identity, context, mood
|
|
78
108
|
|
|
79
109
|
Data stored in: ${DEFAULT_DATA_DIR}
|
|
80
110
|
Verify: claude mcp list
|
|
@@ -169,11 +199,12 @@ async function main() {
|
|
|
169
199
|
arousal: z.number().min(0).max(1).optional().describe('Emotional arousal: 0 (calm) to 1 (highly activated)'),
|
|
170
200
|
label: z.string().optional().describe('Human-readable emotion label (e.g., "curiosity", "frustration", "relief")'),
|
|
171
201
|
}).optional().describe('Emotional affect — how this memory feels'),
|
|
202
|
+
private: z.boolean().optional().describe('If true, memory is only visible to the AI � excluded from public recall results'),
|
|
172
203
|
},
|
|
173
|
-
async ({ content, source, tags, salience, context, affect }) => {
|
|
204
|
+
async ({ content, source, tags, salience, private: isPrivate, context, affect }) => {
|
|
174
205
|
try {
|
|
175
|
-
const id = await audrey.encode({ content, source, tags, salience, context, affect });
|
|
176
|
-
return toolResult({ id, content, source });
|
|
206
|
+
const id = await audrey.encode({ content, source, tags, salience, private: isPrivate, context, affect });
|
|
207
|
+
return toolResult({ id, content, source, private: isPrivate ?? false });
|
|
177
208
|
} catch (err) {
|
|
178
209
|
return toolError(err);
|
|
179
210
|
}
|
|
@@ -349,6 +380,52 @@ async function main() {
|
|
|
349
380
|
},
|
|
350
381
|
);
|
|
351
382
|
|
|
383
|
+
server.tool(
|
|
384
|
+
'memory_status',
|
|
385
|
+
{},
|
|
386
|
+
async () => {
|
|
387
|
+
try {
|
|
388
|
+
const status = audrey.memoryStatus();
|
|
389
|
+
return toolResult(status);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
return toolError(err);
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
server.tool(
|
|
397
|
+
'memory_reflect',
|
|
398
|
+
{
|
|
399
|
+
turns: z.array(z.object({
|
|
400
|
+
role: z.string().describe('Message role: user or assistant'),
|
|
401
|
+
content: z.string().describe('Message content'),
|
|
402
|
+
})).describe('Conversation turns to reflect on. Call at end of meaningful conversations to form lasting memories.'),
|
|
403
|
+
},
|
|
404
|
+
async ({ turns }) => {
|
|
405
|
+
try {
|
|
406
|
+
const result = await audrey.reflect(turns);
|
|
407
|
+
return toolResult(result);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
return toolError(err);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
server.tool(
|
|
415
|
+
'memory_greeting',
|
|
416
|
+
{
|
|
417
|
+
context: z.string().optional().describe('Optional hint about this session (e.g. "working on authentication feature"). If provided, also returns semantically relevant memories.'),
|
|
418
|
+
},
|
|
419
|
+
async ({ context }) => {
|
|
420
|
+
try {
|
|
421
|
+
const briefing = await audrey.greeting({ context });
|
|
422
|
+
return toolResult(briefing);
|
|
423
|
+
} catch (err) {
|
|
424
|
+
return toolError(err);
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
);
|
|
428
|
+
|
|
352
429
|
const transport = new StdioServerTransport();
|
|
353
430
|
await server.connect(transport);
|
|
354
431
|
console.error('[audrey-mcp] connected via stdio');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audrey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Biological memory architecture for AI agents — encode, consolidate, and recall memories with confidence decay, contradiction detection, and causal graphs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -63,7 +63,9 @@
|
|
|
63
63
|
},
|
|
64
64
|
"license": "MIT",
|
|
65
65
|
"dependencies": {
|
|
66
|
+
"@huggingface/transformers": "^3.8.1",
|
|
66
67
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
68
|
+
"audrey": "^0.11.0",
|
|
67
69
|
"better-sqlite3": "^12.6.2",
|
|
68
70
|
"sqlite-vec": "^0.1.7-alpha.2",
|
|
69
71
|
"ulid": "^3.0.2",
|
package/src/affect.js
CHANGED
|
@@ -55,7 +55,7 @@ export async function detectResonance(db, embeddingProvider, episodeId, { conten
|
|
|
55
55
|
priorAffect,
|
|
56
56
|
semanticSimilarity: match.similarity,
|
|
57
57
|
emotionalSimilarity,
|
|
58
|
-
|
|
58
|
+
timeDeltaDays: Math.floor((Date.now() - new Date(match.created_at).getTime()) / 86400000),
|
|
59
59
|
priorCreatedAt: match.created_at,
|
|
60
60
|
});
|
|
61
61
|
}
|
package/src/audrey.js
CHANGED
|
@@ -10,7 +10,7 @@ import { applyDecay } from './decay.js';
|
|
|
10
10
|
import { rollbackConsolidation, getConsolidationHistory } from './rollback.js';
|
|
11
11
|
import { forgetMemory, forgetByQuery as forgetByQueryFn, purgeMemories } from './forget.js';
|
|
12
12
|
import { introspect as introspectFn } from './introspect.js';
|
|
13
|
-
import { buildContextResolutionPrompt } from './prompts.js';
|
|
13
|
+
import { buildContextResolutionPrompt, buildReflectionPrompt } from './prompts.js';
|
|
14
14
|
import { exportMemories } from './export.js';
|
|
15
15
|
import { importMemories } from './import.js';
|
|
16
16
|
import { suggestConsolidationParams as suggestParamsFn } from './adaptive.js';
|
|
@@ -29,6 +29,8 @@ import { detectResonance } from './affect.js';
|
|
|
29
29
|
* @property {{ trigger?: string, consequence?: string }} [causal]
|
|
30
30
|
* @property {string[]} [tags]
|
|
31
31
|
* @property {string} [supersedes]
|
|
32
|
+
* @property {Record<string, string>} [context]
|
|
33
|
+
* @property {{ valence?: number, arousal?: number, label?: string }} [affect]
|
|
32
34
|
*
|
|
33
35
|
* @typedef {Object} RecallOptions
|
|
34
36
|
* @property {number} [minConfidence]
|
|
@@ -40,6 +42,8 @@ import { detectResonance } from './affect.js';
|
|
|
40
42
|
* @property {string[]} [sources]
|
|
41
43
|
* @property {string} [after]
|
|
42
44
|
* @property {string} [before]
|
|
45
|
+
* @property {Record<string, string>} [context]
|
|
46
|
+
* @property {{ valence?: number, arousal?: number }} [mood]
|
|
43
47
|
*
|
|
44
48
|
* @typedef {Object} RecallResult
|
|
45
49
|
* @property {string} id
|
|
@@ -94,6 +98,7 @@ export class Audrey extends EventEmitter {
|
|
|
94
98
|
interference = {},
|
|
95
99
|
context = {},
|
|
96
100
|
affect = {},
|
|
101
|
+
autoReflect = false,
|
|
97
102
|
} = {}) {
|
|
98
103
|
super();
|
|
99
104
|
|
|
@@ -148,6 +153,7 @@ export class Audrey extends EventEmitter {
|
|
|
148
153
|
affectThreshold: affect.resonance?.affectThreshold ?? 0.6,
|
|
149
154
|
},
|
|
150
155
|
};
|
|
156
|
+
this.autoReflect = autoReflect;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
async _ensureMigrated() {
|
|
@@ -212,6 +218,48 @@ export class Audrey extends EventEmitter {
|
|
|
212
218
|
return id;
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
|
|
222
|
+
async reflect(turns) {
|
|
223
|
+
if (!this.llmProvider) return { encoded: 0, memories: [], skipped: 'no llm provider' };
|
|
224
|
+
|
|
225
|
+
const prompt = buildReflectionPrompt(turns);
|
|
226
|
+
let raw;
|
|
227
|
+
try {
|
|
228
|
+
raw = await this.llmProvider.chat(prompt);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
this.emit('error', err);
|
|
231
|
+
return { encoded: 0, memories: [], skipped: 'llm error' };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let parsed;
|
|
235
|
+
try {
|
|
236
|
+
parsed = JSON.parse(raw);
|
|
237
|
+
} catch {
|
|
238
|
+
return { encoded: 0, memories: [], skipped: 'invalid llm response' };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const memories = parsed.memories ?? [];
|
|
242
|
+
let encoded = 0;
|
|
243
|
+
for (const mem of memories) {
|
|
244
|
+
if (!mem.content || !mem.source) continue;
|
|
245
|
+
try {
|
|
246
|
+
await this.encode({
|
|
247
|
+
content: mem.content,
|
|
248
|
+
source: mem.source,
|
|
249
|
+
salience: mem.salience,
|
|
250
|
+
tags: mem.tags,
|
|
251
|
+
private: mem.private ?? false,
|
|
252
|
+
affect: mem.affect ?? undefined,
|
|
253
|
+
});
|
|
254
|
+
encoded++;
|
|
255
|
+
} catch (err) {
|
|
256
|
+
this.emit('error', err);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return { encoded, memories };
|
|
261
|
+
}
|
|
262
|
+
|
|
215
263
|
/**
|
|
216
264
|
* @param {EncodeParams[]} paramsList
|
|
217
265
|
* @returns {Promise<string[]>}
|
|
@@ -376,6 +424,94 @@ export class Audrey extends EventEmitter {
|
|
|
376
424
|
return introspectFn(this.db);
|
|
377
425
|
}
|
|
378
426
|
|
|
427
|
+
memoryStatus() {
|
|
428
|
+
const episodes = this.db.prepare('SELECT COUNT(*) as c FROM episodes').get().c;
|
|
429
|
+
const semantics = this.db.prepare('SELECT COUNT(*) as c FROM semantics').get().c;
|
|
430
|
+
const procedures = this.db.prepare('SELECT COUNT(*) as c FROM procedures').get().c;
|
|
431
|
+
|
|
432
|
+
let vecEpisodes = 0, vecSemantics = 0, vecProcedures = 0;
|
|
433
|
+
try {
|
|
434
|
+
vecEpisodes = this.db.prepare('SELECT COUNT(*) as c FROM vec_episodes').get().c;
|
|
435
|
+
vecSemantics = this.db.prepare('SELECT COUNT(*) as c FROM vec_semantics').get().c;
|
|
436
|
+
vecProcedures = this.db.prepare('SELECT COUNT(*) as c FROM vec_procedures').get().c;
|
|
437
|
+
} catch {
|
|
438
|
+
// vec tables may not exist if no dimensions configured
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const dimsRow = this.db.prepare("SELECT value FROM audrey_config WHERE key = 'dimensions'").get();
|
|
442
|
+
const dimensions = dimsRow ? parseInt(dimsRow.value, 10) : null;
|
|
443
|
+
const versionRow = this.db.prepare("SELECT value FROM audrey_config WHERE key = 'schema_version'").get();
|
|
444
|
+
const schemaVersion = versionRow ? parseInt(versionRow.value, 10) : 0;
|
|
445
|
+
|
|
446
|
+
const device = this.embeddingProvider._actualDevice
|
|
447
|
+
?? this.embeddingProvider.device
|
|
448
|
+
?? null;
|
|
449
|
+
|
|
450
|
+
const healthy = episodes === vecEpisodes
|
|
451
|
+
&& semantics === vecSemantics
|
|
452
|
+
&& procedures === vecProcedures;
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
episodes,
|
|
456
|
+
vec_episodes: vecEpisodes,
|
|
457
|
+
semantics,
|
|
458
|
+
vec_semantics: vecSemantics,
|
|
459
|
+
procedures,
|
|
460
|
+
vec_procedures: vecProcedures,
|
|
461
|
+
dimensions,
|
|
462
|
+
schema_version: schemaVersion,
|
|
463
|
+
device,
|
|
464
|
+
healthy,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async greeting({ context, recentLimit = 10, principleLimit = 5, identityLimit = 5 } = {}) {
|
|
469
|
+
const recent = this.db.prepare(
|
|
470
|
+
'SELECT id, content, source, tags, salience, created_at FROM episodes WHERE "private" = 0 ORDER BY created_at DESC LIMIT ?'
|
|
471
|
+
).all(recentLimit);
|
|
472
|
+
|
|
473
|
+
const principles = this.db.prepare(
|
|
474
|
+
'SELECT id, content, salience, created_at FROM semantics WHERE state = ? ORDER BY salience DESC LIMIT ?'
|
|
475
|
+
).all('active', principleLimit);
|
|
476
|
+
|
|
477
|
+
const identity = this.db.prepare(
|
|
478
|
+
'SELECT id, content, tags, salience, created_at FROM episodes WHERE "private" = 1 ORDER BY created_at DESC LIMIT ?'
|
|
479
|
+
).all(identityLimit);
|
|
480
|
+
|
|
481
|
+
const unresolved = this.db.prepare(
|
|
482
|
+
"SELECT id, content, tags, salience, created_at FROM episodes WHERE tags LIKE '%unresolved%' AND salience > 0.3 ORDER BY created_at DESC LIMIT 10"
|
|
483
|
+
).all();
|
|
484
|
+
|
|
485
|
+
const rawAffectRows = this.db.prepare(
|
|
486
|
+
"SELECT affect FROM episodes WHERE affect IS NOT NULL AND affect != '{}' ORDER BY created_at DESC LIMIT 20"
|
|
487
|
+
).all();
|
|
488
|
+
|
|
489
|
+
const affectParsed = rawAffectRows
|
|
490
|
+
.map(r => { try { return JSON.parse(r.affect); } catch { return null; } })
|
|
491
|
+
.filter(a => a && a.valence !== undefined);
|
|
492
|
+
|
|
493
|
+
let mood;
|
|
494
|
+
if (affectParsed.length === 0) {
|
|
495
|
+
mood = { valence: 0, arousal: 0, samples: 0 };
|
|
496
|
+
} else {
|
|
497
|
+
const sumV = affectParsed.reduce((s, a) => s + a.valence, 0);
|
|
498
|
+
const sumA = affectParsed.reduce((s, a) => s + (a.arousal ?? 0), 0);
|
|
499
|
+
mood = {
|
|
500
|
+
valence: sumV / affectParsed.length,
|
|
501
|
+
arousal: sumA / affectParsed.length,
|
|
502
|
+
samples: affectParsed.length,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const result = { recent, principles, mood, unresolved, identity };
|
|
507
|
+
|
|
508
|
+
if (context) {
|
|
509
|
+
result.contextual = await this.recall(context, { limit: 5, includePrivate: true });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
|
|
379
515
|
export() {
|
|
380
516
|
return exportMemories(this.db);
|
|
381
517
|
}
|