kiku-agent 1.2.0 → 1.3.1
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/README.md +459 -407
- package/package.json +32 -3
- package/server/agent/agentCore.js +635 -267
- package/server/agent/appPaths.js +2 -0
- package/server/agent/catalogEntityResolver.js +637 -0
- package/server/agent/citations.js +0 -21
- package/server/agent/clarificationAnalytics.js +1 -1
- package/server/agent/clarifyingQuestions.js +286 -216
- package/server/agent/cli.js +532 -73
- package/server/agent/commandFeatures.js +123 -24
- package/server/agent/commandRegistry.js +128 -62
- package/server/agent/commands.js +1410 -95
- package/server/agent/config.js +253 -163
- package/server/agent/contextCompactor.js +239 -25
- package/server/agent/dynamicCatalog.js +192 -0
- package/server/agent/envBootstrap.js +46 -0
- package/server/agent/envManager.js +317 -0
- package/server/agent/eventBus.js +3 -3
- package/server/agent/feedbackLearning.js +0 -8
- package/server/agent/gpuMetrics.js +188 -55
- package/server/agent/ingestionWorker.js +339 -0
- package/server/agent/ink/App.jsx +348 -0
- package/server/agent/ink/Panel.jsx +54 -0
- package/server/agent/ink/PromptInput.jsx +321 -0
- package/server/agent/ink/TaskPlanPanel.jsx +88 -0
- package/server/agent/ink/ThinkingPanel.jsx +68 -0
- package/server/agent/ink/ToolResult.jsx +133 -0
- package/server/agent/ink/main.jsx +195 -0
- package/server/agent/ink/useEventBus.js +218 -0
- package/server/agent/lanDiscovery.js +369 -0
- package/server/agent/llmClient.js +111 -39
- package/server/agent/lmStudioWakeup.js +144 -0
- package/server/agent/mcpClient.js +2 -2
- package/server/agent/memory/contextAllocator.js +214 -0
- package/server/agent/memory/hierarchicalStore.js +523 -0
- package/server/agent/memory/reflectionWorker.js +110 -0
- package/server/agent/memory.js +635 -494
- package/server/agent/modelConfig.js +487 -0
- package/server/agent/newsIntelligence.js +219 -0
- package/server/agent/newsWatch.js +85 -0
- package/server/agent/prompts.js +146 -0
- package/server/agent/publicTools.js +7 -0
- package/server/agent/queryDetails.js +62 -1
- package/server/agent/research.js +314 -35
- package/server/agent/routingInterceptor.js +77 -0
- package/server/agent/rumoredProjects.js +15 -0
- package/server/agent/sanitizer.js +117 -17
- package/server/agent/subagent.js +52 -9
- package/server/agent/suggestionTui.js +33 -13
- package/server/agent/suggestions.js +29 -13
- package/server/agent/telemetry/hardwareTiers.js +332 -0
- package/server/agent/telemetry/runtimeWatchdog.js +66 -0
- package/server/agent/telemetry/techPowerUpClient.js +682 -0
- package/server/agent/telemetry/techpowerup-mcp.json +22 -0
- package/server/agent/telemetry.js +0 -11
- package/server/agent/terminalImage.js +0 -1
- package/server/agent/textToolProtocol.js +190 -0
- package/server/agent/tokenTracker.js +220 -0
- package/server/agent/toolRouter.js +362 -216
- package/server/agent/tools.js +1155 -194
- package/server/agent/ui.js +144 -41
- package/server/agent/vramService.js +572 -0
- package/server/agent/webSearch.js +2279 -273
- package/server/agent/websearch/adapters/duckduckgo.js +97 -0
- package/server/agent/websearch/adapters/jina.js +77 -0
- package/server/agent/websearch/adapters/searxng.js +57 -0
- package/server/agent/websearch/core/canonical.js +89 -0
- package/server/agent/websearch/core/fusion.js +81 -0
- package/server/agent/websearch/core/types.js +29 -0
- package/server/agent/websearch/extract.js +605 -0
- package/server/agent/websearch/index.js +43 -0
- package/server/agent/websearch/orchestrator.js +266 -0
- package/server/agent/websearch/resilience/breaker.js +97 -0
- package/server/agent/websearch/resilience/cache.js +59 -0
- package/server/agent/websearch/resilience/limiter.js +93 -0
- package/server/agent/websearch/security/filter.js +36 -0
- package/server/agent/websearch/security/ssrf.js +289 -0
- package/server/agent/websearch/telemetry.js +71 -0
- package/server/agent/youtubeAuth.js +82 -73
- package/server/agent/youtubeTranscript.js +209 -0
- package/server/apiRouter.js +390 -190
- package/server/mcp/data/brands.js +5 -5
- package/server/mcp/data/discography.js +56 -45
- package/server/mcp/data/gallery.js +9 -0
- package/server/mcp/data/index.js +1 -0
- package/server/mcp/data/industryIndex.js +251 -0
- package/server/mcp/data/profile.js +5 -5
- package/server/mcp/data/timeline.js +2 -2
- package/server/mcp/data/works.js +2 -2
- package/server/mcp/handler.js +0 -2
- package/server/mcp/prompts.js +42 -2
- package/server/mcp/resources.js +5 -1
- package/server/mcp/security.js +25 -14
- package/server/mcp/server.js +92 -3
- package/server/mcp/tools.js +20 -11
- package/server/routes/agent.js +55 -0
- package/server/web-chat/core/connection.js +218 -194
- package/server/web-chat/core/conversationContext.js +1 -1
- package/server/web-chat/core/directCatalogEngine.js +190 -184
- package/server/web-chat/core/followUpEngine.js +88 -11
- package/server/web-chat/core/security.js +7 -4
- package/server/web-chat/router.js +54 -17
- package/server/web-chat/routes/bilibiliRoute.js +34 -0
- package/server/web-chat/routes/chatRoute.js +488 -397
- package/server/web-chat/routes/clarifyConfigRoute.js +1 -2
- package/server/web-chat/routes/exportRoute.js +2 -1
- package/server/web-chat/routes/lanDiscoveryRoute.js +90 -0
- package/server/web-chat/routes/newsRoute.js +36 -6
- package/server/web-chat/routes/providerRoute.js +87 -0
- package/server/web-chat/routes/staticRoute.js +61 -13
- package/server/web-chat/routes/statusRoute.js +23 -2
- package/server/web-chat/routes/vramRoute.js +22 -0
- package/server/web-chat/server.js +135 -106
- package/server/web-chat/ui/htmlBuilder.js +8 -8
- package/server/web-chat/ui/previews/modelSettings.preview.html +41 -0
- package/server/web-chat/ui/scripts/agentBubbleSystem.js +0 -5
- package/server/web-chat/ui/scripts/client.js +10526 -8397
- package/server/web-chat/ui/scripts/webchatI18n.js +190 -138
- package/server/web-chat/ui/styles/main.css +7471 -4246
- package/server/web-chat/ui/templates/body.html +305 -179
- package/src/data/brandsData.js +29 -6
- package/src/data/discographyData.js +918 -900
- package/src/data/galleryData.js +66 -0
- package/src/data/industryIndexData.js +292 -0
- package/src/data/musicVideoData.js +48 -5
- package/src/data/profileData.js +15 -1
- package/src/data/timelineData.js +4 -3
- package/src/data/worksData.js +536 -535
- package/src/lib/vram/estimator.js +464 -0
- package/src/lib/vram/modelRegistry.js +387 -0
- package/tokens.css +33 -2
package/server/agent/memory.js
CHANGED
|
@@ -1,40 +1,52 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { EXPORT_DIR, KIKU_DATA_DIR, SESSION_DIR } from './appPaths.js';
|
|
4
4
|
import { redactSensitiveValue } from './session.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import { HierarchicalStore } from './memory/hierarchicalStore.js';
|
|
6
|
+
import { ContextAllocator } from './memory/contextAllocator.js';
|
|
7
|
+
import { ReflectionWorker } from './memory/reflectionWorker.js';
|
|
8
|
+
|
|
9
|
+
const MEMORY_DIR = KIKU_DATA_DIR;
|
|
10
|
+
const MEMORY_FILE = path.join(MEMORY_DIR, 'long_term_memory.json');
|
|
11
|
+
const SESSIONS_DIR = SESSION_DIR;
|
|
12
|
+
const MAX_FACTS = 500;
|
|
13
|
+
const SAVE_DEBOUNCE_MS = 400;
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
export class AgentMemory {
|
|
12
17
|
constructor(options = {}) {
|
|
13
18
|
this.persistenceEnabled = options.persistence !== false;
|
|
14
19
|
this.storageFile = options.storageFile ? path.resolve(String(options.storageFile)) : MEMORY_FILE;
|
|
15
|
-
this.facts = [];
|
|
16
|
-
this.preferences = {
|
|
17
|
-
language: null,
|
|
18
|
-
topicsOfInterest: [],
|
|
19
|
-
responseStyle: 'default',
|
|
20
|
-
userSkills: [],
|
|
21
|
-
skillsEnabled: true,
|
|
22
|
-
favorites: [],
|
|
23
|
-
learnedStyles: [],
|
|
20
|
+
this.facts = [];
|
|
21
|
+
this.preferences = {
|
|
22
|
+
language: null,
|
|
23
|
+
topicsOfInterest: [],
|
|
24
|
+
responseStyle: 'default',
|
|
25
|
+
userSkills: [],
|
|
26
|
+
skillsEnabled: true,
|
|
27
|
+
favorites: [],
|
|
28
|
+
learnedStyles: [],
|
|
29
|
+
};
|
|
30
|
+
this.sessions = [];
|
|
31
|
+
this.summaries = [];
|
|
32
|
+
this.stats = {
|
|
33
|
+
totalFacts: 0,
|
|
34
|
+
totalSessions: 0,
|
|
35
|
+
totalConsolidations: 0,
|
|
36
|
+
lastConsolidationAt: null,
|
|
37
|
+
createdAt: new Date()
|
|
24
38
|
};
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
|
|
36
|
-
this._storageError = null;
|
|
37
|
-
}
|
|
39
|
+
this.hierarchicalStore = options.hierarchicalStore || new HierarchicalStore({ persistence: this.persistenceEnabled });
|
|
40
|
+
this.contextAllocator = new ContextAllocator({ store: this.hierarchicalStore });
|
|
41
|
+
this.reflectionWorker = new ReflectionWorker({ store: this.hierarchicalStore, memory: this });
|
|
42
|
+
this.activeRunId = options.activeRunId || null;
|
|
43
|
+
this._loaded = false;
|
|
44
|
+
this._storageWarningShown = false;
|
|
45
|
+
this._storageError = null;
|
|
46
|
+
this._saveTimer = null;
|
|
47
|
+
this._saveTimerResolve = null;
|
|
48
|
+
this._saveChain = Promise.resolve();
|
|
49
|
+
}
|
|
38
50
|
|
|
39
51
|
|
|
40
52
|
async init() {
|
|
@@ -45,6 +57,7 @@ export class AgentMemory {
|
|
|
45
57
|
try {
|
|
46
58
|
await fs.mkdir(MEMORY_DIR, { recursive: true, mode: 0o700 });
|
|
47
59
|
await fs.mkdir(SESSIONS_DIR, { recursive: true, mode: 0o700 });
|
|
60
|
+
await this.hierarchicalStore.init();
|
|
48
61
|
this._storageError = null;
|
|
49
62
|
return true;
|
|
50
63
|
} catch (error) {
|
|
@@ -77,12 +90,12 @@ export class AgentMemory {
|
|
|
77
90
|
lastAccessedAt: f.lastAccessedAt ? new Date(f.lastAccessedAt) : new Date()
|
|
78
91
|
}));
|
|
79
92
|
|
|
80
|
-
const loadedPrefs = parsed.preferences || {};
|
|
81
|
-
this.preferences = {
|
|
82
|
-
...this.preferences,
|
|
83
|
-
...loadedPrefs,
|
|
84
|
-
userSkills: Array.isArray(loadedPrefs.userSkills) ? loadedPrefs.userSkills : [],
|
|
85
|
-
learnedStyles: Array.isArray(loadedPrefs.learnedStyles) ? loadedPrefs.learnedStyles : [],
|
|
93
|
+
const loadedPrefs = parsed.preferences || {};
|
|
94
|
+
this.preferences = {
|
|
95
|
+
...this.preferences,
|
|
96
|
+
...loadedPrefs,
|
|
97
|
+
userSkills: Array.isArray(loadedPrefs.userSkills) ? loadedPrefs.userSkills : [],
|
|
98
|
+
learnedStyles: Array.isArray(loadedPrefs.learnedStyles) ? loadedPrefs.learnedStyles : [],
|
|
86
99
|
favorites: Array.isArray(loadedPrefs.favorites) ? loadedPrefs.favorites : [],
|
|
87
100
|
topicsOfInterest: (loadedPrefs.topicsOfInterest || []).map(t => ({
|
|
88
101
|
...t,
|
|
@@ -101,11 +114,36 @@ export class AgentMemory {
|
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
|
|
105
|
-
if (!this.persistenceEnabled) return false;
|
|
117
|
+
scheduleSave() {
|
|
118
|
+
if (!this.persistenceEnabled) return Promise.resolve(false);
|
|
119
|
+
if (this._saveTimer) {
|
|
120
|
+
clearTimeout(this._saveTimer);
|
|
121
|
+
this._saveTimer = null;
|
|
122
|
+
const superseded = this._saveTimerResolve;
|
|
123
|
+
this._saveTimerResolve = null;
|
|
124
|
+
if (superseded) superseded(false);
|
|
125
|
+
}
|
|
126
|
+
return new Promise(resolve => {
|
|
127
|
+
this._saveTimerResolve = resolve;
|
|
128
|
+
this._saveTimer = setTimeout(() => {
|
|
129
|
+
this._saveTimer = null;
|
|
130
|
+
const settle = this._saveTimerResolve;
|
|
131
|
+
this._saveTimerResolve = null;
|
|
132
|
+
this._flushSave().then(() => settle(true), () => settle(false));
|
|
133
|
+
}, SAVE_DEBOUNCE_MS);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_flushSave() {
|
|
138
|
+
const next = this._saveChain.then(() => this._writeMemoryFile());
|
|
139
|
+
this._saveChain = next.then(() => true, () => true);
|
|
140
|
+
return next;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async _writeMemoryFile() {
|
|
106
144
|
try {
|
|
107
145
|
if (!await this.init()) return false;
|
|
108
|
-
|
|
146
|
+
|
|
109
147
|
const serializablePrefs = {
|
|
110
148
|
...this.preferences,
|
|
111
149
|
topicsOfInterest: (this.preferences.topicsOfInterest || []).map(t => ({
|
|
@@ -125,7 +163,7 @@ export class AgentMemory {
|
|
|
125
163
|
stats: this.stats,
|
|
126
164
|
lastSavedAt: new Date().toISOString()
|
|
127
165
|
};
|
|
128
|
-
await fs.writeFile(this.storageFile, JSON.stringify(data
|
|
166
|
+
await fs.writeFile(this.storageFile, JSON.stringify(data), { encoding: 'utf-8', mode: 0o600 });
|
|
129
167
|
return true;
|
|
130
168
|
} catch (error) {
|
|
131
169
|
this._warnStorage(error);
|
|
@@ -133,6 +171,18 @@ export class AgentMemory {
|
|
|
133
171
|
}
|
|
134
172
|
}
|
|
135
173
|
|
|
174
|
+
async save() {
|
|
175
|
+
if (!this.persistenceEnabled) return false;
|
|
176
|
+
if (this._saveTimer) {
|
|
177
|
+
clearTimeout(this._saveTimer);
|
|
178
|
+
this._saveTimer = null;
|
|
179
|
+
const superseded = this._saveTimerResolve;
|
|
180
|
+
this._saveTimerResolve = null;
|
|
181
|
+
if (superseded) superseded(false);
|
|
182
|
+
}
|
|
183
|
+
return this._flushSave();
|
|
184
|
+
}
|
|
185
|
+
|
|
136
186
|
addFact(fact, meta = {}) {
|
|
137
187
|
const entry = {
|
|
138
188
|
id: `fact_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
@@ -146,12 +196,37 @@ export class AgentMemory {
|
|
|
146
196
|
tags: meta.tags || []
|
|
147
197
|
};
|
|
148
198
|
this.facts.push(entry);
|
|
199
|
+
if (this.facts.length > MAX_FACTS) {
|
|
200
|
+
let oldestIndex = 0;
|
|
201
|
+
let oldestTime = new Date(this.facts[0].lastAccessedAt || this.facts[0].createdAt || 0).getTime();
|
|
202
|
+
for (let i = 1; i < this.facts.length; i += 1) {
|
|
203
|
+
const time = new Date(this.facts[i].lastAccessedAt || this.facts[i].createdAt || 0).getTime();
|
|
204
|
+
if (time < oldestTime) {
|
|
205
|
+
oldestTime = time;
|
|
206
|
+
oldestIndex = i;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.facts.splice(oldestIndex, 1);
|
|
210
|
+
}
|
|
149
211
|
this.stats.totalFacts = this.facts.length;
|
|
150
|
-
this.
|
|
212
|
+
this.scheduleSave().catch(() => {});
|
|
151
213
|
return entry;
|
|
152
214
|
}
|
|
153
215
|
|
|
154
|
-
|
|
216
|
+
_compileTermMatchers(terms = []) {
|
|
217
|
+
const matchers = [];
|
|
218
|
+
for (const term of terms) {
|
|
219
|
+
if (!term || term.length < 2) continue;
|
|
220
|
+
matchers.push({
|
|
221
|
+
term,
|
|
222
|
+
lenBonus: Math.min(2.0, term.length / 4),
|
|
223
|
+
wordRegex: new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i')
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return matchers;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
scoreFactRelevance(fact, queryTerms = [], now = Date.now(), termMatchers = null) {
|
|
155
230
|
if (!fact) return 0;
|
|
156
231
|
const content = (fact.content || '').toLowerCase();
|
|
157
232
|
const tags = (fact.tags || []).map(t => String(t).toLowerCase());
|
|
@@ -159,9 +234,10 @@ export class AgentMemory {
|
|
|
159
234
|
|
|
160
235
|
let matchScore = 0;
|
|
161
236
|
if (queryTerms.length > 0) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
237
|
+
const matchers = termMatchers || this._compileTermMatchers(queryTerms);
|
|
238
|
+
for (const matcher of matchers) {
|
|
239
|
+
const term = matcher.term;
|
|
240
|
+
const termLenBonus = matcher.lenBonus;
|
|
165
241
|
|
|
166
242
|
if (tags.includes(term)) {
|
|
167
243
|
matchScore += 5.0 * termLenBonus;
|
|
@@ -173,8 +249,7 @@ export class AgentMemory {
|
|
|
173
249
|
matchScore += 2.5;
|
|
174
250
|
}
|
|
175
251
|
|
|
176
|
-
|
|
177
|
-
if (wordRegex.test(content)) {
|
|
252
|
+
if (matcher.wordRegex.test(content)) {
|
|
178
253
|
matchScore += 3.5 * termLenBonus;
|
|
179
254
|
} else if (content.includes(term)) {
|
|
180
255
|
matchScore += 1.8 * termLenBonus;
|
|
@@ -199,10 +274,11 @@ export class AgentMemory {
|
|
|
199
274
|
const q = String(query || '').toLowerCase().trim();
|
|
200
275
|
const terms = q ? q.split(/\s+/).filter(t => t.length >= 2) : [];
|
|
201
276
|
const now = Date.now();
|
|
277
|
+
const matchers = this._compileTermMatchers(terms);
|
|
202
278
|
|
|
203
279
|
const scored = this.facts.map(fact => ({
|
|
204
280
|
fact,
|
|
205
|
-
score: this.scoreFactRelevance(fact, terms, now)
|
|
281
|
+
score: this.scoreFactRelevance(fact, terms, now, matchers)
|
|
206
282
|
}));
|
|
207
283
|
|
|
208
284
|
scored.sort((a, b) => b.score - a.score);
|
|
@@ -234,12 +310,14 @@ export class AgentMemory {
|
|
|
234
310
|
|
|
235
311
|
const terms = q ? q.split(/\s+/).filter(t => t.length >= 2) : [];
|
|
236
312
|
const now = Date.now();
|
|
313
|
+
const matchers = this._compileTermMatchers(terms);
|
|
237
314
|
|
|
238
|
-
results.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
315
|
+
const decorated = results.map(fact => ({
|
|
316
|
+
fact,
|
|
317
|
+
score: this.scoreFactRelevance(fact, terms, now, matchers)
|
|
318
|
+
}));
|
|
319
|
+
decorated.sort((a, b) => b.score - a.score);
|
|
320
|
+
results = decorated.map(entry => entry.fact);
|
|
243
321
|
|
|
244
322
|
for (const fact of results.slice(0, limit)) {
|
|
245
323
|
fact.lastAccessedAt = new Date();
|
|
@@ -254,13 +332,13 @@ export class AgentMemory {
|
|
|
254
332
|
if (idx === -1) return false;
|
|
255
333
|
this.facts.splice(idx, 1);
|
|
256
334
|
this.stats.totalFacts = this.facts.length;
|
|
257
|
-
this.
|
|
335
|
+
this.scheduleSave().catch(() => {});
|
|
258
336
|
return true;
|
|
259
337
|
}
|
|
260
338
|
|
|
261
339
|
setPreference(key, value) {
|
|
262
340
|
this.preferences[key] = value;
|
|
263
|
-
this.
|
|
341
|
+
this.scheduleSave().catch(() => {});
|
|
264
342
|
}
|
|
265
343
|
|
|
266
344
|
addFavorite(entity = {}) {
|
|
@@ -285,7 +363,7 @@ export class AgentMemory {
|
|
|
285
363
|
};
|
|
286
364
|
this.preferences.favorites.unshift(favorite);
|
|
287
365
|
this.preferences.favorites = this.preferences.favorites.slice(0, 200);
|
|
288
|
-
this.
|
|
366
|
+
this.scheduleSave().catch(() => {});
|
|
289
367
|
return { favorite, created: true };
|
|
290
368
|
}
|
|
291
369
|
|
|
@@ -301,7 +379,7 @@ export class AgentMemory {
|
|
|
301
379
|
const before = favorites.length;
|
|
302
380
|
this.preferences.favorites = favorites.filter(item => item.id !== id);
|
|
303
381
|
if (this.preferences.favorites.length === before) return false;
|
|
304
|
-
this.
|
|
382
|
+
this.scheduleSave().catch(() => {});
|
|
305
383
|
return true;
|
|
306
384
|
}
|
|
307
385
|
|
|
@@ -330,275 +408,338 @@ export class AgentMemory {
|
|
|
330
408
|
await fs.writeFile(filePath, content, 'utf-8');
|
|
331
409
|
return { filePath, filename, count: favorites.length, size: (await fs.stat(filePath)).size };
|
|
332
410
|
}
|
|
333
|
-
|
|
334
|
-
addUserSkill(instruction) {
|
|
335
|
-
const value = String(instruction || '').replace(/\s+/g, ' ').trim();
|
|
336
|
-
if (!value) return null;
|
|
337
|
-
if (value.length > 500) throw new Error('Skill instruction must be 500 characters or fewer.');
|
|
338
|
-
const duplicate = this.preferences.userSkills.find(skill => skill.instruction.toLowerCase() === value.toLowerCase());
|
|
339
|
-
if (duplicate) return duplicate;
|
|
340
|
-
const skill = { id: `skill_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, instruction: value };
|
|
341
|
-
this.preferences.userSkills.push(skill);
|
|
342
|
-
this.preferences.userSkills = this.preferences.userSkills.slice(-20);
|
|
343
|
-
this.
|
|
344
|
-
return skill;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
removeUserSkill(id) {
|
|
348
|
-
const before = this.preferences.userSkills.length;
|
|
349
|
-
this.preferences.userSkills = this.preferences.userSkills.filter(skill => skill.id !== id);
|
|
350
|
-
if (this.preferences.userSkills.length === before) return false;
|
|
351
|
-
this.
|
|
352
|
-
return true;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
clearUserSkills() {
|
|
356
|
-
this.preferences.userSkills = [];
|
|
357
|
-
this.
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
buildUserSkillContext() {
|
|
361
|
-
if (!this.preferences.skillsEnabled || this.preferences.userSkills.length === 0) return '';
|
|
362
|
-
return this.preferences.userSkills.map((skill, index) => `${index + 1}. ${skill.instruction}`).join('\n');
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
applyFeedbackDirectives(directives = []) {
|
|
366
|
-
const clean = (Array.isArray(directives) ? directives : [])
|
|
367
|
-
.filter(d => d && typeof d.text === 'string' && d.text.trim())
|
|
368
|
-
.slice(0, 6)
|
|
369
|
-
.map(d => ({
|
|
370
|
-
id: String(d.id || '').slice(0, 60),
|
|
371
|
-
kind: d.kind === 'avoid' ? 'avoid' : 'prefer',
|
|
372
|
-
text: String(d.text).replace(/\s+/g, ' ').trim().slice(0, 200),
|
|
373
|
-
support: Number.isFinite(Number(d.support)) ? Number(d.support) : 0,
|
|
374
|
-
updatedAt: d.updatedAt || new Date().toISOString(),
|
|
375
|
-
}));
|
|
376
|
-
this.preferences.learnedStyles = clean;
|
|
377
|
-
this.
|
|
378
|
-
return clean;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
buildFeedbackStyleContext() {
|
|
382
|
-
const styles = Array.isArray(this.preferences.learnedStyles) ? this.preferences.learnedStyles : [];
|
|
383
|
-
if (styles.length === 0) return '';
|
|
384
|
-
return styles.map(s => `- ${s.text}`).join('\n');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
async savePreferences() {
|
|
389
|
-
await this.save();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
trackTopicInterest(topic) {
|
|
394
|
-
const existing = this.preferences.topicsOfInterest.find(
|
|
395
|
-
t => t.topic.toLowerCase() === topic.toLowerCase()
|
|
396
|
-
);
|
|
397
|
-
if (existing) {
|
|
398
|
-
existing.count++;
|
|
399
|
-
existing.lastSeen = new Date();
|
|
400
|
-
} else {
|
|
401
|
-
this.preferences.topicsOfInterest.push({
|
|
402
|
-
topic,
|
|
403
|
-
count: 1,
|
|
404
|
-
lastSeen: new Date()
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
this.preferences.topicsOfInterest.sort((a, b) => b.count - a.count);
|
|
409
|
-
this.preferences.topicsOfInterest = this.preferences.topicsOfInterest.slice(0, 20);
|
|
410
|
-
this.
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
getTopTopics(limit = 5) {
|
|
415
|
-
return this.preferences.topicsOfInterest.slice(0, limit);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
411
|
+
|
|
412
|
+
addUserSkill(instruction) {
|
|
413
|
+
const value = String(instruction || '').replace(/\s+/g, ' ').trim();
|
|
414
|
+
if (!value) return null;
|
|
415
|
+
if (value.length > 500) throw new Error('Skill instruction must be 500 characters or fewer.');
|
|
416
|
+
const duplicate = this.preferences.userSkills.find(skill => skill.instruction.toLowerCase() === value.toLowerCase());
|
|
417
|
+
if (duplicate) return duplicate;
|
|
418
|
+
const skill = { id: `skill_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, instruction: value };
|
|
419
|
+
this.preferences.userSkills.push(skill);
|
|
420
|
+
this.preferences.userSkills = this.preferences.userSkills.slice(-20);
|
|
421
|
+
this.scheduleSave().catch(() => {});
|
|
422
|
+
return skill;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
removeUserSkill(id) {
|
|
426
|
+
const before = this.preferences.userSkills.length;
|
|
427
|
+
this.preferences.userSkills = this.preferences.userSkills.filter(skill => skill.id !== id);
|
|
428
|
+
if (this.preferences.userSkills.length === before) return false;
|
|
429
|
+
this.scheduleSave().catch(() => {});
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
clearUserSkills() {
|
|
434
|
+
this.preferences.userSkills = [];
|
|
435
|
+
this.scheduleSave().catch(() => {});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
buildUserSkillContext() {
|
|
439
|
+
if (!this.preferences.skillsEnabled || this.preferences.userSkills.length === 0) return '';
|
|
440
|
+
return this.preferences.userSkills.map((skill, index) => `${index + 1}. ${skill.instruction}`).join('\n');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
applyFeedbackDirectives(directives = []) {
|
|
444
|
+
const clean = (Array.isArray(directives) ? directives : [])
|
|
445
|
+
.filter(d => d && typeof d.text === 'string' && d.text.trim())
|
|
446
|
+
.slice(0, 6)
|
|
447
|
+
.map(d => ({
|
|
448
|
+
id: String(d.id || '').slice(0, 60),
|
|
449
|
+
kind: d.kind === 'avoid' ? 'avoid' : 'prefer',
|
|
450
|
+
text: String(d.text).replace(/\s+/g, ' ').trim().slice(0, 200),
|
|
451
|
+
support: Number.isFinite(Number(d.support)) ? Number(d.support) : 0,
|
|
452
|
+
updatedAt: d.updatedAt || new Date().toISOString(),
|
|
453
|
+
}));
|
|
454
|
+
this.preferences.learnedStyles = clean;
|
|
455
|
+
this.scheduleSave().catch(() => {});
|
|
456
|
+
return clean;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
buildFeedbackStyleContext() {
|
|
460
|
+
const styles = Array.isArray(this.preferences.learnedStyles) ? this.preferences.learnedStyles : [];
|
|
461
|
+
if (styles.length === 0) return '';
|
|
462
|
+
return styles.map(s => `- ${s.text}`).join('\n');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
async savePreferences() {
|
|
467
|
+
await this.save();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
trackTopicInterest(topic) {
|
|
472
|
+
const existing = this.preferences.topicsOfInterest.find(
|
|
473
|
+
t => t.topic.toLowerCase() === topic.toLowerCase()
|
|
474
|
+
);
|
|
475
|
+
if (existing) {
|
|
476
|
+
existing.count++;
|
|
477
|
+
existing.lastSeen = new Date();
|
|
478
|
+
} else {
|
|
479
|
+
this.preferences.topicsOfInterest.push({
|
|
480
|
+
topic,
|
|
481
|
+
count: 1,
|
|
482
|
+
lastSeen: new Date()
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
this.preferences.topicsOfInterest.sort((a, b) => b.count - a.count);
|
|
487
|
+
this.preferences.topicsOfInterest = this.preferences.topicsOfInterest.slice(0, 20);
|
|
488
|
+
this.scheduleSave().catch(() => {});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
getTopTopics(limit = 5) {
|
|
493
|
+
return this.preferences.topicsOfInterest.slice(0, limit);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
419
497
|
async archiveSession(sessionData) {
|
|
420
498
|
if (!this.persistenceEnabled) return null;
|
|
421
|
-
const archive = {
|
|
422
|
-
id: sessionData.id,
|
|
423
|
-
createdAt: sessionData.createdAt,
|
|
424
|
-
endedAt: new Date(),
|
|
425
|
-
messageCount: sessionData.messages?.length || 0,
|
|
426
|
-
stats: { ...sessionData.stats },
|
|
427
|
-
thinkingMode: sessionData.thinkingMode,
|
|
428
|
-
summary: null
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
let archivedToDisk = false;
|
|
433
|
-
try {
|
|
434
|
-
if (!await this.init()) return null;
|
|
499
|
+
const archive = {
|
|
500
|
+
id: sessionData.id,
|
|
501
|
+
createdAt: sessionData.createdAt,
|
|
502
|
+
endedAt: new Date(),
|
|
503
|
+
messageCount: sessionData.messages?.length || 0,
|
|
504
|
+
stats: { ...sessionData.stats },
|
|
505
|
+
thinkingMode: sessionData.thinkingMode,
|
|
506
|
+
summary: null
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
let archivedToDisk = false;
|
|
511
|
+
try {
|
|
512
|
+
if (!await this.init()) return null;
|
|
435
513
|
const safeId = String(sessionData.id || '').replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 100);
|
|
436
514
|
if (!safeId) return null;
|
|
437
515
|
const sessionFile = path.join(SESSIONS_DIR, `${safeId}.json`);
|
|
438
516
|
await fs.writeFile(sessionFile, JSON.stringify(redactSensitiveValue(sessionData), null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
439
|
-
archivedToDisk = true;
|
|
440
|
-
} catch (err) {
|
|
441
|
-
this._warnStorage(err);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (!archivedToDisk) return null;
|
|
445
|
-
|
|
446
|
-
this.sessions.push(archive);
|
|
447
|
-
this.stats.totalSessions = this.sessions.length;
|
|
448
|
-
await this.save();
|
|
449
|
-
return archive;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
getArchivedSessions(limit = 20) {
|
|
453
|
-
return this.sessions
|
|
454
|
-
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
455
|
-
.slice(0, limit);
|
|
456
|
-
}
|
|
457
|
-
|
|
517
|
+
archivedToDisk = true;
|
|
518
|
+
} catch (err) {
|
|
519
|
+
this._warnStorage(err);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!archivedToDisk) return null;
|
|
523
|
+
|
|
524
|
+
this.sessions.push(archive);
|
|
525
|
+
this.stats.totalSessions = this.sessions.length;
|
|
526
|
+
await this.save();
|
|
527
|
+
return archive;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
getArchivedSessions(limit = 20) {
|
|
531
|
+
return [...this.sessions]
|
|
532
|
+
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
533
|
+
.slice(0, limit);
|
|
534
|
+
}
|
|
535
|
+
|
|
458
536
|
async loadArchivedSession(sessionId) {
|
|
459
537
|
if (!this.persistenceEnabled) return null;
|
|
460
538
|
try {
|
|
461
539
|
const safeId = String(sessionId || '').replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 100);
|
|
462
540
|
if (!safeId) return null;
|
|
463
541
|
const sessionFile = path.join(SESSIONS_DIR, `${safeId}.json`);
|
|
464
|
-
const data = await fs.readFile(sessionFile, 'utf-8');
|
|
465
|
-
return JSON.parse(data);
|
|
466
|
-
} catch {
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
async consolidateSession(sessionData) {
|
|
473
|
-
if (!sessionData?.messages?.length) return null;
|
|
474
|
-
|
|
475
|
-
const userMessages = sessionData.messages
|
|
476
|
-
.filter(m => m.role === 'user')
|
|
477
|
-
.map(m => {
|
|
478
|
-
const textPart = m.parts?.find(p => p.type === 'text');
|
|
479
|
-
return textPart?.content || '';
|
|
480
|
-
})
|
|
481
|
-
.filter(Boolean);
|
|
482
|
-
|
|
483
|
-
const assistantMessages = sessionData.messages
|
|
484
|
-
.filter(m => m.role === 'assistant')
|
|
485
|
-
.map(m => {
|
|
486
|
-
const textPart = m.parts?.find(p => p.type === 'text');
|
|
487
|
-
return textPart?.content || '';
|
|
488
|
-
})
|
|
489
|
-
.filter(Boolean);
|
|
490
|
-
|
|
491
|
-
const topics = this._extractTopics(userMessages);
|
|
492
|
-
|
|
493
|
-
for (const topic of topics) {
|
|
494
|
-
this.trackTopicInterest(topic);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const summary = {
|
|
498
|
-
id: `sum_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
499
|
-
sessionId: sessionData.id,
|
|
500
|
-
createdAt: new Date(),
|
|
501
|
-
topics,
|
|
502
|
-
questionCount: userMessages.length,
|
|
503
|
-
keyExchanges: userMessages.slice(0, 5).map((q, i) => ({
|
|
504
|
-
question: q.slice(0, 200),
|
|
505
|
-
answerPreview: (assistantMessages[i] || '').slice(0, 200)
|
|
506
|
-
})),
|
|
507
|
-
durationMs: sessionData.stats?.totalDurationMs || 0,
|
|
508
|
-
toolCalls: sessionData.stats?.totalToolCalls || 0
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
this.summaries.push(summary);
|
|
512
|
-
this.stats.totalConsolidations = this.summaries.length;
|
|
513
|
-
this.stats.lastConsolidationAt = new Date();
|
|
514
|
-
|
|
515
|
-
const archived = this.sessions.find(s => s.id === sessionData.id);
|
|
516
|
-
if (archived) {
|
|
517
|
-
archived.summary = summary;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
await this.save();
|
|
521
|
-
return summary;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
_extractTopics(messages) {
|
|
525
|
-
const topicKeywords = {
|
|
526
|
-
'drama': ['drama', 'series', 'ละคร', '剧', '电视', 'tv series', 'film', 'movie', 'work'],
|
|
527
|
-
'music': ['song', 'music', 'mv', 'single', 'ost', 'เพลง', '歌', 'album', 'discography'],
|
|
528
|
-
'brand': ['brand', 'ambassador', 'endorsement', 'แบรนด์', '品牌', 'commercial'],
|
|
529
|
-
'awards': ['award', 'honor', 'ceremony', 'รางวัล', '奖', 'winner'],
|
|
530
|
-
'profile': ['profile', 'bio', 'birthday', 'age', 'ประวัติ', '资料', '个人信息'],
|
|
531
|
-
'timeline': ['career', 'timeline', 'history', 'era', 'เส้นทาง', '经历', 'ปี'],
|
|
532
|
-
'news': ['news', 'latest', 'recent', 'update', 'ข่าว', '最新', 'ล่าสุด'],
|
|
533
|
-
'video': ['video', 'stage', 'performance', 'live', 'คลิป', '视频', '舞台'],
|
|
534
|
-
'social': ['weibo', 'douyin', 'instagram', 'social media', 'โซเชียล', '微博'],
|
|
535
|
-
'gallery': ['photo', 'picture', 'gallery', 'fashion', 'photoshoot', 'ภาพ', '照片']
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
const topicCounts = {};
|
|
539
|
-
const allText = messages.join(' ').toLowerCase();
|
|
540
|
-
|
|
541
|
-
for (const [topic, keywords] of Object.entries(topicKeywords)) {
|
|
542
|
-
const count = keywords.reduce((acc, kw) => {
|
|
543
|
-
const regex = new RegExp(`\\b${kw}\\b`, 'gi');
|
|
544
|
-
return acc + (allText.match(regex) || []).length;
|
|
545
|
-
}, 0);
|
|
546
|
-
if (count > 0) topicCounts[topic] = count;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return Object.entries(topicCounts)
|
|
550
|
-
.sort(([, a], [, b]) => b - a)
|
|
551
|
-
.slice(0, 5)
|
|
552
|
-
.map(([topic]) => topic);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
542
|
+
const data = await fs.readFile(sessionFile, 'utf-8');
|
|
543
|
+
return JSON.parse(data);
|
|
544
|
+
} catch {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
async consolidateSession(sessionData) {
|
|
551
|
+
if (!sessionData?.messages?.length) return null;
|
|
552
|
+
|
|
553
|
+
const userMessages = sessionData.messages
|
|
554
|
+
.filter(m => m.role === 'user')
|
|
555
|
+
.map(m => {
|
|
556
|
+
const textPart = m.parts?.find(p => p.type === 'text');
|
|
557
|
+
return textPart?.content || '';
|
|
558
|
+
})
|
|
559
|
+
.filter(Boolean);
|
|
560
|
+
|
|
561
|
+
const assistantMessages = sessionData.messages
|
|
562
|
+
.filter(m => m.role === 'assistant')
|
|
563
|
+
.map(m => {
|
|
564
|
+
const textPart = m.parts?.find(p => p.type === 'text');
|
|
565
|
+
return textPart?.content || '';
|
|
566
|
+
})
|
|
567
|
+
.filter(Boolean);
|
|
568
|
+
|
|
569
|
+
const topics = this._extractTopics(userMessages);
|
|
570
|
+
|
|
571
|
+
for (const topic of topics) {
|
|
572
|
+
this.trackTopicInterest(topic);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const summary = {
|
|
576
|
+
id: `sum_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
577
|
+
sessionId: sessionData.id,
|
|
578
|
+
createdAt: new Date(),
|
|
579
|
+
topics,
|
|
580
|
+
questionCount: userMessages.length,
|
|
581
|
+
keyExchanges: userMessages.slice(0, 5).map((q, i) => ({
|
|
582
|
+
question: q.slice(0, 200),
|
|
583
|
+
answerPreview: (assistantMessages[i] || '').slice(0, 200)
|
|
584
|
+
})),
|
|
585
|
+
durationMs: sessionData.stats?.totalDurationMs || 0,
|
|
586
|
+
toolCalls: sessionData.stats?.totalToolCalls || 0
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
this.summaries.push(summary);
|
|
590
|
+
this.stats.totalConsolidations = this.summaries.length;
|
|
591
|
+
this.stats.lastConsolidationAt = new Date();
|
|
592
|
+
|
|
593
|
+
const archived = this.sessions.find(s => s.id === sessionData.id);
|
|
594
|
+
if (archived) {
|
|
595
|
+
archived.summary = summary;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
await this.save();
|
|
599
|
+
return summary;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
_extractTopics(messages) {
|
|
603
|
+
const topicKeywords = {
|
|
604
|
+
'drama': ['drama', 'series', 'ละคร', '剧', '电视', 'tv series', 'film', 'movie', 'work'],
|
|
605
|
+
'music': ['song', 'music', 'mv', 'single', 'ost', 'เพลง', '歌', 'album', 'discography'],
|
|
606
|
+
'brand': ['brand', 'ambassador', 'endorsement', 'แบรนด์', '品牌', 'commercial'],
|
|
607
|
+
'awards': ['award', 'honor', 'ceremony', 'รางวัล', '奖', 'winner'],
|
|
608
|
+
'profile': ['profile', 'bio', 'birthday', 'age', 'ประวัติ', '资料', '个人信息'],
|
|
609
|
+
'timeline': ['career', 'timeline', 'history', 'era', 'เส้นทาง', '经历', 'ปี'],
|
|
610
|
+
'news': ['news', 'latest', 'recent', 'update', 'ข่าว', '最新', 'ล่าสุด'],
|
|
611
|
+
'video': ['video', 'stage', 'performance', 'live', 'คลิป', '视频', '舞台'],
|
|
612
|
+
'social': ['weibo', 'douyin', 'instagram', 'social media', 'โซเชียล', '微博'],
|
|
613
|
+
'gallery': ['photo', 'picture', 'gallery', 'fashion', 'photoshoot', 'ภาพ', '照片']
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const topicCounts = {};
|
|
617
|
+
const allText = messages.join(' ').toLowerCase();
|
|
618
|
+
|
|
619
|
+
for (const [topic, keywords] of Object.entries(topicKeywords)) {
|
|
620
|
+
const count = keywords.reduce((acc, kw) => {
|
|
621
|
+
const regex = new RegExp(`\\b${kw}\\b`, 'gi');
|
|
622
|
+
return acc + (allText.match(regex) || []).length;
|
|
623
|
+
}, 0);
|
|
624
|
+
if (count > 0) topicCounts[topic] = count;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return Object.entries(topicCounts)
|
|
628
|
+
.sort(([, a], [, b]) => b - a)
|
|
629
|
+
.slice(0, 5)
|
|
630
|
+
.map(([topic]) => topic);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
|
|
556
634
|
buildMemoryContext(currentQuery = '') {
|
|
557
|
-
const parts = [];
|
|
558
|
-
|
|
559
|
-
if (this.preferences.language) {
|
|
560
|
-
parts.push(`User's preferred language: ${this.preferences.language}`);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const userSkillContext = this.buildUserSkillContext();
|
|
564
|
-
if (userSkillContext) {
|
|
565
|
-
parts.push(`User-taught response skills (follow these preferences unless they conflict with safety or verified facts):\n${userSkillContext}`);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const feedbackStyleContext = this.buildFeedbackStyleContext();
|
|
569
|
-
if (feedbackStyleContext) {
|
|
570
|
-
parts.push(`Learned answer-style preferences (distilled from this user's like/dislike feedback — answer this way unless it conflicts with safety or verified facts):\n${feedbackStyleContext}`);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const topTopics = this.getTopTopics(5);
|
|
574
|
-
if (topTopics.length > 0) {
|
|
575
|
-
const topicList = topTopics.map(t => `${t.topic} (${t.count}x)`).join(', ');
|
|
576
|
-
parts.push(`User frequently asks about: ${topicList}`);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const relevantFacts = currentQuery ? this.getRelevantFacts(currentQuery, 8) : this.facts.slice(-8);
|
|
580
|
-
if (relevantFacts.length > 0) {
|
|
581
|
-
const factList = relevantFacts.map(f => `- ${f.content}`).join('\n');
|
|
582
|
-
parts.push(`Known facts from previous conversations:\n${factList}`);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const recentSummaries = this.summaries
|
|
586
|
-
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
587
|
-
.slice(0, 3);
|
|
588
|
-
if (recentSummaries.length > 0) {
|
|
589
|
-
const sumList = recentSummaries.map(s => {
|
|
590
|
-
const date = new Date(s.createdAt).toLocaleDateString();
|
|
591
|
-
const topics = s.topics.join(', ');
|
|
592
|
-
return `- [${date}] Topics: ${topics} (${s.questionCount} questions)`;
|
|
593
|
-
}).join('\n');
|
|
594
|
-
parts.push(`Recent conversation themes:\n${sumList}`);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (parts.length === 0) return '';
|
|
598
|
-
|
|
635
|
+
const parts = [];
|
|
636
|
+
|
|
637
|
+
if (this.preferences.language) {
|
|
638
|
+
parts.push(`User's preferred language: ${this.preferences.language}`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const userSkillContext = this.buildUserSkillContext();
|
|
642
|
+
if (userSkillContext) {
|
|
643
|
+
parts.push(`User-taught response skills (follow these preferences unless they conflict with safety or verified facts):\n${userSkillContext}`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const feedbackStyleContext = this.buildFeedbackStyleContext();
|
|
647
|
+
if (feedbackStyleContext) {
|
|
648
|
+
parts.push(`Learned answer-style preferences (distilled from this user's like/dislike feedback — answer this way unless it conflicts with safety or verified facts):\n${feedbackStyleContext}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const topTopics = this.getTopTopics(5);
|
|
652
|
+
if (topTopics.length > 0) {
|
|
653
|
+
const topicList = topTopics.map(t => `${t.topic} (${t.count}x)`).join(', ');
|
|
654
|
+
parts.push(`User frequently asks about: ${topicList}`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const relevantFacts = currentQuery ? this.getRelevantFacts(currentQuery, 8) : this.facts.slice(-8);
|
|
658
|
+
if (relevantFacts.length > 0) {
|
|
659
|
+
const factList = relevantFacts.map(f => `- ${f.content}`).join('\n');
|
|
660
|
+
parts.push(`Known facts from previous conversations:\n${factList}`);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const recentSummaries = [...this.summaries]
|
|
664
|
+
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
665
|
+
.slice(0, 3);
|
|
666
|
+
if (recentSummaries.length > 0) {
|
|
667
|
+
const sumList = recentSummaries.map(s => {
|
|
668
|
+
const date = new Date(s.createdAt).toLocaleDateString();
|
|
669
|
+
const topics = s.topics.join(', ');
|
|
670
|
+
return `- [${date}] Topics: ${topics} (${s.questionCount} questions)`;
|
|
671
|
+
}).join('\n');
|
|
672
|
+
parts.push(`Recent conversation themes:\n${sumList}`);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (parts.length === 0) return '';
|
|
676
|
+
|
|
599
677
|
return `\n\n=== LONG-TERM MEMORY CONTEXT ===\n${parts.join('\n\n')}\n=== END MEMORY CONTEXT ===`;
|
|
600
678
|
}
|
|
601
679
|
|
|
680
|
+
setActiveRunId(runId) {
|
|
681
|
+
this.activeRunId = runId;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
getActiveRunId() {
|
|
685
|
+
return this.activeRunId;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async checkpointStep({ runId, stepName, status, summary, metadata } = {}) {
|
|
689
|
+
const targetRunId = runId || this.activeRunId;
|
|
690
|
+
return this.hierarchicalStore.checkpointStep({ runId: targetRunId, stepName, status, summary, metadata });
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async recordFailure({ runId, errorTrace, recoveryAction, toolName } = {}) {
|
|
694
|
+
const targetRunId = runId || this.activeRunId;
|
|
695
|
+
return this.hierarchicalStore.recordFailure({ runId: targetRunId, errorTrace, recoveryAction, toolName });
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async loadActiveRun(runId) {
|
|
699
|
+
const targetRunId = runId || this.activeRunId;
|
|
700
|
+
return this.hierarchicalStore.loadActiveRun(targetRunId);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async setGuideline(domain, text, type = 'user') {
|
|
704
|
+
return this.hierarchicalStore.setNode(`kiku/guidelines/${domain}`, text, { type });
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async getGuideline(domain) {
|
|
708
|
+
const node = await this.hierarchicalStore.getNode(`kiku/guidelines/${domain}`);
|
|
709
|
+
return node?.content || null;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async addReflection(toolOrDomain, data) {
|
|
713
|
+
return this.hierarchicalStore.setNode(`kiku/reflections/${toolOrDomain}`, data, { type: 'context' });
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async getReflections(toolOrDomain = '') {
|
|
717
|
+
if (toolOrDomain) {
|
|
718
|
+
const node = await this.hierarchicalStore.getNode(`kiku/reflections/${toolOrDomain}`);
|
|
719
|
+
return node?.content || null;
|
|
720
|
+
}
|
|
721
|
+
const nodes = await this.hierarchicalStore.listNodes('kiku/reflections/');
|
|
722
|
+
return nodes.map(n => n.content);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async triggerPostRunCompaction(runId, finalStatus = 'COMPLETED', metadata = {}) {
|
|
726
|
+
const targetRunId = runId || this.activeRunId;
|
|
727
|
+
return this.reflectionWorker.processPostRunCompaction(targetRunId, finalStatus, metadata);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
async purgeExpiredArchives() {
|
|
731
|
+
return this.hierarchicalStore.purgeExpiredArchives();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async addMemoryContext(options = {}) {
|
|
735
|
+
return this.contextAllocator.allocateContext({
|
|
736
|
+
memory: this,
|
|
737
|
+
currentQuery: options.currentQuery || '',
|
|
738
|
+
activeRunId: options.activeRunId || this.activeRunId,
|
|
739
|
+
maxTokens: options.maxTokens
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
602
743
|
getFactsByCategory() {
|
|
603
744
|
return this.facts.reduce((counts, fact) => {
|
|
604
745
|
const category = String(fact?.category || 'general');
|
|
@@ -608,160 +749,160 @@ export class AgentMemory {
|
|
|
608
749
|
}
|
|
609
750
|
|
|
610
751
|
getStatus() {
|
|
611
|
-
return {
|
|
612
|
-
factsCount: this.facts.length,
|
|
613
|
-
factsByCategory: this.getFactsByCategory(),
|
|
614
|
-
preferences: this.preferences,
|
|
615
|
-
sessionsCount: this.sessions.length,
|
|
616
|
-
summariesCount: this.summaries.length,
|
|
617
|
-
topTopics: this.getTopTopics(10),
|
|
618
|
-
stats: this.stats,
|
|
619
|
-
lastConsolidationAt: this.stats.lastConsolidationAt,
|
|
620
|
-
memoryFileSize: null
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
async importFromJSON(filePath) {
|
|
625
|
-
try {
|
|
626
|
-
const raw = await fs.readFile(filePath, 'utf-8');
|
|
627
|
-
const data = JSON.parse(raw);
|
|
628
|
-
|
|
629
|
-
if (!data.facts && !data.preferences && !data.sessions) {
|
|
630
|
-
return { success: false, message: 'Invalid memory file format — missing facts, preferences, or sessions.' };
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
let factsImported = 0;
|
|
634
|
-
let factsSkipped = 0;
|
|
635
|
-
let topicsImported = 0;
|
|
636
|
-
|
|
637
|
-
if (Array.isArray(data.facts)) {
|
|
638
|
-
for (const fact of data.facts) {
|
|
639
|
-
const isDuplicate = this.facts.some(
|
|
640
|
-
existing => existing.content === fact.content
|
|
641
|
-
);
|
|
642
|
-
if (!isDuplicate && fact.content) {
|
|
643
|
-
this.facts.push({
|
|
644
|
-
id: fact.id || `fact_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
645
|
-
content: fact.content,
|
|
646
|
-
category: fact.category || 'imported',
|
|
647
|
-
source: fact.source || 'import',
|
|
648
|
-
confidence: fact.confidence || 'high',
|
|
649
|
-
createdAt: fact.createdAt ? new Date(fact.createdAt) : new Date(),
|
|
650
|
-
lastAccessedAt: new Date(),
|
|
651
|
-
accessCount: 0,
|
|
652
|
-
tags: fact.tags || []
|
|
653
|
-
});
|
|
654
|
-
factsImported++;
|
|
655
|
-
} else {
|
|
656
|
-
factsSkipped++;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
if (data.preferences) {
|
|
662
|
-
if (data.preferences.language) {
|
|
663
|
-
this.preferences.language = data.preferences.language;
|
|
664
|
-
}
|
|
665
|
-
if (data.preferences.responseStyle) {
|
|
666
|
-
this.preferences.responseStyle = data.preferences.responseStyle;
|
|
667
|
-
}
|
|
668
|
-
if (Array.isArray(data.preferences.topicsOfInterest)) {
|
|
669
|
-
for (const topic of data.preferences.topicsOfInterest) {
|
|
670
|
-
const existing = this.preferences.topicsOfInterest.find(
|
|
671
|
-
t => t.topic.toLowerCase() === topic.topic.toLowerCase()
|
|
672
|
-
);
|
|
673
|
-
if (existing) {
|
|
674
|
-
existing.count += topic.count || 1;
|
|
675
|
-
existing.lastSeen = new Date();
|
|
676
|
-
} else {
|
|
677
|
-
this.preferences.topicsOfInterest.push({
|
|
678
|
-
topic: topic.topic,
|
|
679
|
-
count: topic.count || 1,
|
|
680
|
-
lastSeen: topic.lastSeen ? new Date(topic.lastSeen) : new Date()
|
|
681
|
-
});
|
|
682
|
-
topicsImported++;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
this.preferences.topicsOfInterest.sort((a, b) => b.count - a.count);
|
|
686
|
-
this.preferences.topicsOfInterest = this.preferences.topicsOfInterest.slice(0, 20);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
if (Array.isArray(data.sessions)) {
|
|
691
|
-
for (const session of data.sessions) {
|
|
692
|
-
const isDuplicate = this.sessions.some(s => s.id === session.id);
|
|
693
|
-
if (!isDuplicate) {
|
|
694
|
-
this.sessions.push(session);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
if (Array.isArray(data.summaries)) {
|
|
700
|
-
for (const summary of data.summaries) {
|
|
701
|
-
const isDuplicate = this.summaries.some(s => s.id === summary.id);
|
|
702
|
-
if (!isDuplicate) {
|
|
703
|
-
this.summaries.push(summary);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
this.stats.totalFacts = this.facts.length;
|
|
709
|
-
this.stats.totalSessions = this.sessions.length;
|
|
710
|
-
this.stats.totalConsolidations = this.summaries.length;
|
|
711
|
-
|
|
712
|
-
await this.save();
|
|
713
|
-
|
|
714
|
-
return {
|
|
715
|
-
success: true,
|
|
716
|
-
factsImported,
|
|
717
|
-
factsSkipped,
|
|
718
|
-
topicsImported,
|
|
719
|
-
totalFacts: this.facts.length,
|
|
720
|
-
totalSessions: this.sessions.length,
|
|
721
|
-
totalSummaries: this.summaries.length
|
|
722
|
-
};
|
|
723
|
-
} catch (error) {
|
|
724
|
-
return { success: false, message: error.message };
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
async exportToJSON() {
|
|
729
|
-
const exportDir = EXPORT_DIR;
|
|
730
|
-
try {
|
|
731
|
-
await fs.mkdir(exportDir, { recursive: true });
|
|
732
|
-
} catch {}
|
|
733
|
-
|
|
734
|
-
const now = new Date();
|
|
735
|
-
const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
736
|
-
const filename = `kiku-memory-${timestamp}.json`;
|
|
737
|
-
const filePath = path.join(exportDir, filename);
|
|
738
|
-
|
|
739
|
-
const exportData = {
|
|
740
|
-
exportedAt: now.toISOString(),
|
|
741
|
-
stats: this.stats,
|
|
742
|
-
preferences: {
|
|
743
|
-
...this.preferences,
|
|
744
|
-
topicsOfInterest: (this.preferences.topicsOfInterest || []).map(t => ({
|
|
745
|
-
...t,
|
|
746
|
-
lastSeen: t.lastSeen ? t.lastSeen.toISOString() : null
|
|
747
|
-
}))
|
|
748
|
-
},
|
|
749
|
-
facts: this.facts.map(f => ({
|
|
750
|
-
...f,
|
|
751
|
-
createdAt: f.createdAt ? f.createdAt.toISOString() : f.createdAt,
|
|
752
|
-
lastAccessedAt: f.lastAccessedAt ? f.lastAccessedAt.toISOString() : f.lastAccessedAt
|
|
753
|
-
})),
|
|
754
|
-
factsByCategory: this.getFactsByCategory(),
|
|
755
|
-
sessions: this.sessions,
|
|
756
|
-
summaries: this.summaries
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
await fs.writeFile(filePath, JSON.stringify(exportData, null, 2), 'utf-8');
|
|
760
|
-
return { filePath, filename, size: (await fs.stat(filePath)).size };
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
async clearAll() {
|
|
764
|
-
this.facts = [];
|
|
752
|
+
return {
|
|
753
|
+
factsCount: this.facts.length,
|
|
754
|
+
factsByCategory: this.getFactsByCategory(),
|
|
755
|
+
preferences: this.preferences,
|
|
756
|
+
sessionsCount: this.sessions.length,
|
|
757
|
+
summariesCount: this.summaries.length,
|
|
758
|
+
topTopics: this.getTopTopics(10),
|
|
759
|
+
stats: this.stats,
|
|
760
|
+
lastConsolidationAt: this.stats.lastConsolidationAt,
|
|
761
|
+
memoryFileSize: null
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async importFromJSON(filePath) {
|
|
766
|
+
try {
|
|
767
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
768
|
+
const data = JSON.parse(raw);
|
|
769
|
+
|
|
770
|
+
if (!data.facts && !data.preferences && !data.sessions) {
|
|
771
|
+
return { success: false, message: 'Invalid memory file format — missing facts, preferences, or sessions.' };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
let factsImported = 0;
|
|
775
|
+
let factsSkipped = 0;
|
|
776
|
+
let topicsImported = 0;
|
|
777
|
+
|
|
778
|
+
if (Array.isArray(data.facts)) {
|
|
779
|
+
for (const fact of data.facts) {
|
|
780
|
+
const isDuplicate = this.facts.some(
|
|
781
|
+
existing => existing.content === fact.content
|
|
782
|
+
);
|
|
783
|
+
if (!isDuplicate && fact.content) {
|
|
784
|
+
this.facts.push({
|
|
785
|
+
id: fact.id || `fact_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
786
|
+
content: fact.content,
|
|
787
|
+
category: fact.category || 'imported',
|
|
788
|
+
source: fact.source || 'import',
|
|
789
|
+
confidence: fact.confidence || 'high',
|
|
790
|
+
createdAt: fact.createdAt ? new Date(fact.createdAt) : new Date(),
|
|
791
|
+
lastAccessedAt: new Date(),
|
|
792
|
+
accessCount: 0,
|
|
793
|
+
tags: fact.tags || []
|
|
794
|
+
});
|
|
795
|
+
factsImported++;
|
|
796
|
+
} else {
|
|
797
|
+
factsSkipped++;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (data.preferences) {
|
|
803
|
+
if (data.preferences.language) {
|
|
804
|
+
this.preferences.language = data.preferences.language;
|
|
805
|
+
}
|
|
806
|
+
if (data.preferences.responseStyle) {
|
|
807
|
+
this.preferences.responseStyle = data.preferences.responseStyle;
|
|
808
|
+
}
|
|
809
|
+
if (Array.isArray(data.preferences.topicsOfInterest)) {
|
|
810
|
+
for (const topic of data.preferences.topicsOfInterest) {
|
|
811
|
+
const existing = this.preferences.topicsOfInterest.find(
|
|
812
|
+
t => t.topic.toLowerCase() === topic.topic.toLowerCase()
|
|
813
|
+
);
|
|
814
|
+
if (existing) {
|
|
815
|
+
existing.count += topic.count || 1;
|
|
816
|
+
existing.lastSeen = new Date();
|
|
817
|
+
} else {
|
|
818
|
+
this.preferences.topicsOfInterest.push({
|
|
819
|
+
topic: topic.topic,
|
|
820
|
+
count: topic.count || 1,
|
|
821
|
+
lastSeen: topic.lastSeen ? new Date(topic.lastSeen) : new Date()
|
|
822
|
+
});
|
|
823
|
+
topicsImported++;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
this.preferences.topicsOfInterest.sort((a, b) => b.count - a.count);
|
|
827
|
+
this.preferences.topicsOfInterest = this.preferences.topicsOfInterest.slice(0, 20);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (Array.isArray(data.sessions)) {
|
|
832
|
+
for (const session of data.sessions) {
|
|
833
|
+
const isDuplicate = this.sessions.some(s => s.id === session.id);
|
|
834
|
+
if (!isDuplicate) {
|
|
835
|
+
this.sessions.push(session);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (Array.isArray(data.summaries)) {
|
|
841
|
+
for (const summary of data.summaries) {
|
|
842
|
+
const isDuplicate = this.summaries.some(s => s.id === summary.id);
|
|
843
|
+
if (!isDuplicate) {
|
|
844
|
+
this.summaries.push(summary);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
this.stats.totalFacts = this.facts.length;
|
|
850
|
+
this.stats.totalSessions = this.sessions.length;
|
|
851
|
+
this.stats.totalConsolidations = this.summaries.length;
|
|
852
|
+
|
|
853
|
+
await this.save();
|
|
854
|
+
|
|
855
|
+
return {
|
|
856
|
+
success: true,
|
|
857
|
+
factsImported,
|
|
858
|
+
factsSkipped,
|
|
859
|
+
topicsImported,
|
|
860
|
+
totalFacts: this.facts.length,
|
|
861
|
+
totalSessions: this.sessions.length,
|
|
862
|
+
totalSummaries: this.summaries.length
|
|
863
|
+
};
|
|
864
|
+
} catch (error) {
|
|
865
|
+
return { success: false, message: error.message };
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
async exportToJSON() {
|
|
870
|
+
const exportDir = EXPORT_DIR;
|
|
871
|
+
try {
|
|
872
|
+
await fs.mkdir(exportDir, { recursive: true });
|
|
873
|
+
} catch {}
|
|
874
|
+
|
|
875
|
+
const now = new Date();
|
|
876
|
+
const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
877
|
+
const filename = `kiku-memory-${timestamp}.json`;
|
|
878
|
+
const filePath = path.join(exportDir, filename);
|
|
879
|
+
|
|
880
|
+
const exportData = {
|
|
881
|
+
exportedAt: now.toISOString(),
|
|
882
|
+
stats: this.stats,
|
|
883
|
+
preferences: {
|
|
884
|
+
...this.preferences,
|
|
885
|
+
topicsOfInterest: (this.preferences.topicsOfInterest || []).map(t => ({
|
|
886
|
+
...t,
|
|
887
|
+
lastSeen: t.lastSeen ? t.lastSeen.toISOString() : null
|
|
888
|
+
}))
|
|
889
|
+
},
|
|
890
|
+
facts: this.facts.map(f => ({
|
|
891
|
+
...f,
|
|
892
|
+
createdAt: f.createdAt ? f.createdAt.toISOString() : f.createdAt,
|
|
893
|
+
lastAccessedAt: f.lastAccessedAt ? f.lastAccessedAt.toISOString() : f.lastAccessedAt
|
|
894
|
+
})),
|
|
895
|
+
factsByCategory: this.getFactsByCategory(),
|
|
896
|
+
sessions: this.sessions,
|
|
897
|
+
summaries: this.summaries
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
await fs.writeFile(filePath, JSON.stringify(exportData, null, 2), 'utf-8');
|
|
901
|
+
return { filePath, filename, size: (await fs.stat(filePath)).size };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
async clearAll() {
|
|
905
|
+
this.facts = [];
|
|
765
906
|
this.preferences = {
|
|
766
907
|
language: null,
|
|
767
908
|
topicsOfInterest: [],
|
|
@@ -769,34 +910,34 @@ export class AgentMemory {
|
|
|
769
910
|
userSkills: [],
|
|
770
911
|
skillsEnabled: true,
|
|
771
912
|
favorites: [],
|
|
772
|
-
};
|
|
773
|
-
this.sessions = [];
|
|
774
|
-
this.summaries = [];
|
|
775
|
-
this.stats = {
|
|
776
|
-
totalFacts: 0,
|
|
777
|
-
totalSessions: 0,
|
|
778
|
-
totalConsolidations: 0,
|
|
779
|
-
lastConsolidationAt: null,
|
|
780
|
-
createdAt: new Date()
|
|
781
|
-
};
|
|
782
|
-
await this.save();
|
|
783
|
-
return true;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
async getStorageSize() {
|
|
787
|
-
try {
|
|
913
|
+
};
|
|
914
|
+
this.sessions = [];
|
|
915
|
+
this.summaries = [];
|
|
916
|
+
this.stats = {
|
|
917
|
+
totalFacts: 0,
|
|
918
|
+
totalSessions: 0,
|
|
919
|
+
totalConsolidations: 0,
|
|
920
|
+
lastConsolidationAt: null,
|
|
921
|
+
createdAt: new Date()
|
|
922
|
+
};
|
|
923
|
+
await this.save();
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async getStorageSize() {
|
|
928
|
+
try {
|
|
788
929
|
const stat = await fs.stat(this.storageFile);
|
|
789
|
-
return stat.size;
|
|
790
|
-
} catch {
|
|
791
|
-
return 0;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
static formatBytes(bytes) {
|
|
796
|
-
if (bytes === 0) return '0 B';
|
|
797
|
-
const k = 1024;
|
|
798
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
799
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
800
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
801
|
-
}
|
|
802
|
-
}
|
|
930
|
+
return stat.size;
|
|
931
|
+
} catch {
|
|
932
|
+
return 0;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
static formatBytes(bytes) {
|
|
937
|
+
if (bytes === 0) return '0 B';
|
|
938
|
+
const k = 1024;
|
|
939
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
940
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
941
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
942
|
+
}
|
|
943
|
+
}
|