@pheem49/mint 1.5.5 → 1.6.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/.codex +0 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +79 -0
- package/Cargo.lock +5792 -0
- package/Cargo.toml +32 -0
- package/README.md +387 -353
- package/assets/icon.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +23 -0
- package/crates/mint-cli/src/agent.rs +851 -0
- package/crates/mint-cli/src/gmail.rs +216 -0
- package/crates/mint-cli/src/image.rs +142 -0
- package/crates/mint-cli/src/main.rs +2837 -0
- package/crates/mint-cli/src/mcp.rs +63 -0
- package/crates/mint-cli/src/onboard.rs +1149 -0
- package/crates/mint-cli/src/setup.rs +390 -0
- package/crates/mint-cli/src/skills.rs +8 -0
- package/crates/mint-cli/src/updater.rs +279 -0
- package/crates/mint-core/Cargo.toml +22 -0
- package/crates/mint-core/src/agent_loop.rs +94 -0
- package/crates/mint-core/src/api_server.rs +991 -0
- package/crates/mint-core/src/channels.rs +248 -0
- package/crates/mint-core/src/chat.rs +895 -0
- package/crates/mint-core/src/code_tools.rs +729 -0
- package/crates/mint-core/src/config.rs +368 -0
- package/crates/mint-core/src/files.rs +159 -0
- package/crates/mint-core/src/knowledge.rs +541 -0
- package/crates/mint-core/src/lib.rs +84 -0
- package/crates/mint-core/src/mcp.rs +273 -0
- package/crates/mint-core/src/memory.rs +673 -0
- package/crates/mint-core/src/orchestration.rs +2157 -0
- package/crates/mint-core/src/pictures.rs +314 -0
- package/crates/mint-core/src/plugins.rs +727 -0
- package/crates/mint-core/src/safety.rs +416 -0
- package/crates/mint-core/src/semantic.rs +254 -0
- package/crates/mint-core/src/shell.rs +317 -0
- package/crates/mint-core/src/skills.rs +71 -0
- package/crates/mint-core/src/symbols.rs +157 -0
- package/crates/mint-core/src/tasks.rs +308 -0
- package/crates/mint-core/src/tts.rs +92 -0
- package/crates/mint-core/src/weather.rs +93 -0
- package/crates/mint-core/src/web_search.rs +200 -0
- package/crates/mint-core/src/workflows.rs +81 -0
- package/crates/mint-core/tests/mcp_stdio.rs +45 -0
- package/crates/mint-core/tests/memory_persistence.rs +172 -0
- package/crates/mint-core/tests/pictures_storage.rs +14 -0
- package/crates/mint-core/tests/task_lifecycle.rs +87 -0
- package/package.json +35 -99
- package/src/bin/index.js +16 -0
- package/src/renderer/index-web.html +17 -0
- package/src/renderer/index.html +17 -0
- package/src/renderer/public/Live2DCubismCore.js +9 -0
- package/src/renderer/public/assets/icon.png +0 -0
- package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
- package/src/renderer/src/App.tsx +33 -0
- package/src/renderer/src/calculator.ts +47 -0
- package/src/renderer/src/components/ChatPanel.tsx +1598 -0
- package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
- package/src/renderer/src/components/Live2DStage.tsx +374 -0
- package/src/renderer/src/components/MintDashboard.tsx +950 -0
- package/src/renderer/src/components/ModelPanel.tsx +154 -0
- package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
- package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
- package/src/renderer/src/components/ScreenPicker.tsx +579 -0
- package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
- package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
- package/src/renderer/src/components/WidgetWindow.tsx +36 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
- package/src/{UI → renderer/src/css}/settings.css +69 -16
- package/src/renderer/src/css/spotlight.css +113 -0
- package/src/renderer/src/css/styles.css +3722 -0
- package/src/renderer/src/css/widget.css +185 -0
- package/src/renderer/src/env.d.ts +116 -0
- package/src/renderer/src/index.css +379 -0
- package/src/renderer/src/main.tsx +13 -0
- package/src/renderer/src/tauri.ts +996 -0
- package/src/renderer/src-web/App.tsx +25 -0
- package/src/renderer/src-web/calculator.ts +47 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
- package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
- package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
- package/src/renderer/src-web/css/settings.css +1100 -0
- package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
- package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
- package/src/{UI → renderer/src-web/css}/widget.css +2 -2
- package/src/renderer/src-web/env.d.ts +107 -0
- package/src/renderer/src-web/index.css +379 -0
- package/src/renderer/src-web/main.tsx +13 -0
- package/src/renderer/src-web/tauri.ts +983 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +33 -0
- package/vite.config.web.ts +51 -0
- package/GUIDE_TH.md +0 -125
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/benchmark_ai.js +0 -71
- package/install.ps1 +0 -64
- package/install.sh +0 -54
- package/main.js +0 -139
- package/mint-cli-logic.js +0 -3
- package/mint-cli.js +0 -410
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
- package/preload-picker.js +0 -11
- package/preload-settings.js +0 -11
- package/preload.js +0 -41
- package/scripts/install_linux_desktop_entry.js +0 -48
- package/src/AI_Brain/Gemini_API.js +0 -813
- package/src/AI_Brain/agent_orchestrator.js +0 -73
- package/src/AI_Brain/autonomous_brain.js +0 -179
- package/src/AI_Brain/behavior_memory.js +0 -135
- package/src/AI_Brain/headless_agent.js +0 -143
- package/src/AI_Brain/knowledge_base.js +0 -349
- package/src/AI_Brain/memory_store.js +0 -662
- package/src/AI_Brain/proactive_engine.js +0 -172
- package/src/AI_Brain/provider_adapter.js +0 -365
- package/src/Automation_Layer/browser_automation.js +0 -149
- package/src/Automation_Layer/file_operations.js +0 -286
- package/src/Automation_Layer/open_app.js +0 -85
- package/src/Automation_Layer/open_website.js +0 -38
- package/src/CLI/approval_handler.js +0 -47
- package/src/CLI/chat_router.js +0 -247
- package/src/CLI/chat_ui.js +0 -1159
- package/src/CLI/cli_colors.js +0 -115
- package/src/CLI/cli_formatters.js +0 -94
- package/src/CLI/code_agent.js +0 -1667
- package/src/CLI/code_session_memory.js +0 -62
- package/src/CLI/gmail_auth.js +0 -210
- package/src/CLI/image_input.js +0 -90
- package/src/CLI/intent_detectors.js +0 -181
- package/src/CLI/interactive_chat.js +0 -658
- package/src/CLI/list_features.js +0 -64
- package/src/CLI/onboarding.js +0 -416
- package/src/CLI/repo_summarizer.js +0 -282
- package/src/CLI/semantic_code_search.js +0 -312
- package/src/CLI/skill_manager.js +0 -41
- package/src/CLI/slash_command_handler.js +0 -418
- package/src/CLI/symbol_indexer.js +0 -231
- package/src/CLI/updater.js +0 -230
- package/src/CLI/workspace_manager.js +0 -90
- package/src/Channels/brave_search_bridge.js +0 -35
- package/src/Channels/discord_bridge.js +0 -66
- package/src/Channels/google_search_bridge.js +0 -38
- package/src/Channels/line_bridge.js +0 -60
- package/src/Channels/slack_bridge.js +0 -48
- package/src/Channels/telegram_bridge.js +0 -41
- package/src/Channels/whatsapp_bridge.js +0 -57
- package/src/Command_Parser/parser.js +0 -45
- package/src/Plugins/dev_tools.js +0 -41
- package/src/Plugins/discord.js +0 -20
- package/src/Plugins/docker.js +0 -47
- package/src/Plugins/gmail.js +0 -251
- package/src/Plugins/google_calendar.js +0 -252
- package/src/Plugins/mcp_manager.js +0 -95
- package/src/Plugins/notion.js +0 -256
- package/src/Plugins/obsidian.js +0 -54
- package/src/Plugins/plugin_manager.js +0 -81
- package/src/Plugins/spotify.js +0 -173
- package/src/Plugins/system_metrics.js +0 -31
- package/src/Plugins/system_monitor.js +0 -72
- package/src/System/action_executor.js +0 -178
- package/src/System/bridge_manager.js +0 -76
- package/src/System/chat_history_manager.js +0 -83
- package/src/System/config_manager.js +0 -194
- package/src/System/custom_workflows.js +0 -163
- package/src/System/daemon_manager.js +0 -67
- package/src/System/google_tts_urls.js +0 -51
- package/src/System/granular_automation.js +0 -157
- package/src/System/ipc_handlers.js +0 -332
- package/src/System/notifications.js +0 -23
- package/src/System/optional_require.js +0 -23
- package/src/System/picture_store.js +0 -109
- package/src/System/proactive_loop.js +0 -153
- package/src/System/safety_manager.js +0 -273
- package/src/System/sandbox_runner.js +0 -182
- package/src/System/screen_capture.js +0 -175
- package/src/System/smart_context.js +0 -227
- package/src/System/system_automation.js +0 -162
- package/src/System/system_events.js +0 -79
- package/src/System/system_info.js +0 -125
- package/src/System/task_manager.js +0 -222
- package/src/System/tool_registry.js +0 -293
- package/src/System/window_manager.js +0 -220
- package/src/UI/floating.css +0 -80
- package/src/UI/floating.html +0 -17
- package/src/UI/floating.js +0 -67
- package/src/UI/live2d_manager.js +0 -600
- package/src/UI/preload-floating.js +0 -7
- package/src/UI/preload-spotlight.js +0 -11
- package/src/UI/preload-widget.js +0 -5
- package/src/UI/proactive-glow.html +0 -42
- package/src/UI/renderer.js +0 -2127
- package/src/UI/screenPicker.html +0 -214
- package/src/UI/screenPicker.js +0 -262
- package/src/UI/settings.html +0 -577
- package/src/UI/settings.js +0 -770
- package/src/UI/spotlight.html +0 -23
- package/src/UI/spotlight.js +0 -185
- package/src/UI/widget.html +0 -29
- package/src/UI/widget.js +0 -10
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
|
@@ -1,662 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mint Long-Term Memory Store
|
|
3
|
-
* ---------------------------
|
|
4
|
-
* Persists user preferences, session summaries, and usage patterns
|
|
5
|
-
* across all Mint sessions using SQLite (same DB as knowledge_base).
|
|
6
|
-
*
|
|
7
|
-
* Auto-injects a "User Context" block into the system prompt so Mint
|
|
8
|
-
* remembers who it's talking to even after restart.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const os = require('os');
|
|
14
|
-
const crypto = require('crypto');
|
|
15
|
-
const { readConfig } = require('../System/config_manager');
|
|
16
|
-
|
|
17
|
-
// ── Electron-safe app path ──────────────────────────────────────────────────
|
|
18
|
-
let app;
|
|
19
|
-
try {
|
|
20
|
-
const electron = require('electron');
|
|
21
|
-
app = electron.app;
|
|
22
|
-
} catch (_) {
|
|
23
|
-
app = null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function getDbPath() {
|
|
27
|
-
const fileName = 'mint-knowledge.sqlite'; // shared DB with knowledge_base
|
|
28
|
-
const configDir = path.join(os.homedir(), '.config', 'mint');
|
|
29
|
-
const dbPath = path.join(configDir, fileName);
|
|
30
|
-
|
|
31
|
-
if (!fs.existsSync(configDir)) {
|
|
32
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Migration Logic
|
|
36
|
-
if (!fs.existsSync(dbPath)) {
|
|
37
|
-
const electronDb = app && app.getPath ? path.join(app.getPath('userData'), fileName) : null;
|
|
38
|
-
const legacyDb = path.join(os.homedir(), '.mint', fileName);
|
|
39
|
-
|
|
40
|
-
if (electronDb && fs.existsSync(electronDb)) {
|
|
41
|
-
try {
|
|
42
|
-
fs.copyFileSync(electronDb, dbPath);
|
|
43
|
-
console.log('[Memory] Migrated database from Electron userData');
|
|
44
|
-
} catch (e) { console.error('[Memory] Migration from Electron failed:', e); }
|
|
45
|
-
} else if (fs.existsSync(legacyDb)) {
|
|
46
|
-
try {
|
|
47
|
-
fs.copyFileSync(legacyDb, dbPath);
|
|
48
|
-
console.log('[Memory] Migrated database from ~/.mint');
|
|
49
|
-
} catch (e) { console.error('[Memory] Migration from ~/.mint failed:', e); }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return dbPath;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ── Lazy DatabaseSync init ─────────────────────────────────────────────────
|
|
57
|
-
let DatabaseSync = null;
|
|
58
|
-
function getDatabaseSync() {
|
|
59
|
-
if (!DatabaseSync) ({ DatabaseSync } = require('node:sqlite'));
|
|
60
|
-
return DatabaseSync;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let dbInstance = null;
|
|
64
|
-
function getDb() {
|
|
65
|
-
if (dbInstance) return dbInstance;
|
|
66
|
-
const Database = getDatabaseSync();
|
|
67
|
-
dbInstance = new Database(getDbPath());
|
|
68
|
-
|
|
69
|
-
// Enable WAL mode for better concurrency
|
|
70
|
-
dbInstance.exec('PRAGMA journal_mode = WAL;');
|
|
71
|
-
dbInstance.exec('PRAGMA synchronous = NORMAL;');
|
|
72
|
-
|
|
73
|
-
dbInstance.exec(`
|
|
74
|
-
-- User profile: arbitrary key-value pairs
|
|
75
|
-
CREATE TABLE IF NOT EXISTS user_profile (
|
|
76
|
-
key TEXT PRIMARY KEY,
|
|
77
|
-
value TEXT,
|
|
78
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
-- Condensed summaries of past sessions
|
|
82
|
-
CREATE TABLE IF NOT EXISTS session_memories (
|
|
83
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
-
summary TEXT NOT NULL,
|
|
85
|
-
tags TEXT DEFAULT '',
|
|
86
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
-- Frequently used topics / commands
|
|
90
|
-
CREATE TABLE IF NOT EXISTS usage_patterns (
|
|
91
|
-
pattern TEXT PRIMARY KEY,
|
|
92
|
-
count INTEGER DEFAULT 1,
|
|
93
|
-
last_used DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
-- Raw episodic memories of user/assistant turns.
|
|
97
|
-
CREATE TABLE IF NOT EXISTS interaction_memories (
|
|
98
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
99
|
-
user_text TEXT NOT NULL,
|
|
100
|
-
ai_text TEXT NOT NULL,
|
|
101
|
-
keywords TEXT DEFAULT '',
|
|
102
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
-- Response Cache: For repetitive exact queries
|
|
106
|
-
CREATE TABLE IF NOT EXISTS response_cache (
|
|
107
|
-
query_hash TEXT PRIMARY KEY,
|
|
108
|
-
response TEXT NOT NULL,
|
|
109
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
-- Learned skill/instruction documents imported from local files.
|
|
113
|
-
CREATE TABLE IF NOT EXISTS learned_skills (
|
|
114
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
115
|
-
name TEXT NOT NULL,
|
|
116
|
-
source_path TEXT NOT NULL UNIQUE,
|
|
117
|
-
content TEXT NOT NULL,
|
|
118
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
119
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
120
|
-
);
|
|
121
|
-
`);
|
|
122
|
-
|
|
123
|
-
return dbInstance;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function ensureLearnedSkillsTable() {
|
|
127
|
-
getDb().exec(`
|
|
128
|
-
CREATE TABLE IF NOT EXISTS learned_skills (
|
|
129
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
130
|
-
name TEXT NOT NULL,
|
|
131
|
-
source_path TEXT NOT NULL UNIQUE,
|
|
132
|
-
content TEXT NOT NULL,
|
|
133
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
134
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
135
|
-
);
|
|
136
|
-
`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ── Profile helpers ────────────────────────────────────────────────────────
|
|
140
|
-
function setProfile(key, value) {
|
|
141
|
-
try {
|
|
142
|
-
const db = getDb();
|
|
143
|
-
db.prepare(`
|
|
144
|
-
INSERT INTO user_profile (key, value, updated_at)
|
|
145
|
-
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
146
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP
|
|
147
|
-
`).run(key, String(value));
|
|
148
|
-
} catch (err) {
|
|
149
|
-
console.error('[Memory] setProfile error:', err.message);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function deleteProfile(key) {
|
|
154
|
-
try {
|
|
155
|
-
getDb().prepare('DELETE FROM user_profile WHERE key = ?').run(key);
|
|
156
|
-
} catch (err) {
|
|
157
|
-
console.error('[Memory] deleteProfile error:', err.message);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function clearConversationScopedProfile() {
|
|
162
|
-
deleteProfile('preferred_language');
|
|
163
|
-
clearResponseCache();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function getProfile(key, defaultValue = null) {
|
|
167
|
-
try {
|
|
168
|
-
const row = getDb().prepare('SELECT value FROM user_profile WHERE key = ?').get(key);
|
|
169
|
-
return row ? row.value : defaultValue;
|
|
170
|
-
} catch (_) {
|
|
171
|
-
return defaultValue;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function getAllProfile() {
|
|
176
|
-
try {
|
|
177
|
-
const rows = getDb().prepare('SELECT key, value FROM user_profile').all();
|
|
178
|
-
return Object.fromEntries(rows.map(r => [r.key, r.value]));
|
|
179
|
-
} catch (_) {
|
|
180
|
-
return {};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── Session memory helpers ─────────────────────────────────────────────────
|
|
185
|
-
const MAX_SESSION_MEMORIES = 20; // keep last N summaries
|
|
186
|
-
|
|
187
|
-
function addSessionMemory(summary, tags = []) {
|
|
188
|
-
try {
|
|
189
|
-
const db = getDb();
|
|
190
|
-
db.prepare('INSERT INTO session_memories (summary, tags) VALUES (?, ?)').run(
|
|
191
|
-
summary.slice(0, 800), // cap length
|
|
192
|
-
tags.join(',')
|
|
193
|
-
);
|
|
194
|
-
// Prune oldest beyond limit
|
|
195
|
-
db.exec(`
|
|
196
|
-
DELETE FROM session_memories WHERE id NOT IN (
|
|
197
|
-
SELECT id FROM session_memories ORDER BY id DESC LIMIT ${MAX_SESSION_MEMORIES}
|
|
198
|
-
)
|
|
199
|
-
`);
|
|
200
|
-
} catch (err) {
|
|
201
|
-
console.error('[Memory] addSessionMemory error:', err.message);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function getRecentMemories(limit = 5) {
|
|
206
|
-
try {
|
|
207
|
-
return getDb()
|
|
208
|
-
.prepare('SELECT summary, tags, created_at FROM session_memories ORDER BY id DESC LIMIT ?')
|
|
209
|
-
.all(limit);
|
|
210
|
-
} catch (_) {
|
|
211
|
-
return [];
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// ── Usage pattern helpers ──────────────────────────────────────────────────
|
|
216
|
-
function recordPattern(pattern) {
|
|
217
|
-
try {
|
|
218
|
-
const db = getDb();
|
|
219
|
-
db.prepare(`
|
|
220
|
-
INSERT INTO usage_patterns (pattern, count, last_used)
|
|
221
|
-
VALUES (?, 1, CURRENT_TIMESTAMP)
|
|
222
|
-
ON CONFLICT(pattern) DO UPDATE
|
|
223
|
-
SET count = count + 1, last_used = CURRENT_TIMESTAMP
|
|
224
|
-
`).run(pattern.slice(0, 120));
|
|
225
|
-
} catch (_) {}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function getTopPatterns(limit = 8) {
|
|
229
|
-
try {
|
|
230
|
-
return getDb()
|
|
231
|
-
.prepare('SELECT pattern, count FROM usage_patterns ORDER BY count DESC, last_used DESC LIMIT ?')
|
|
232
|
-
.all(limit);
|
|
233
|
-
} catch (_) {
|
|
234
|
-
return [];
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const MAX_INTERACTION_MEMORIES = 1000;
|
|
239
|
-
|
|
240
|
-
function stripRelevantMemoryBlock(text) {
|
|
241
|
-
return String(text || '')
|
|
242
|
-
.replace(/\n?\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\n?/g, '\n')
|
|
243
|
-
.replace(/^\s*\[Relevant long-term memory for this user message\][\s\S]*?\[End relevant memory\]\s*/g, '')
|
|
244
|
-
.replace(/\n?\[LOCAL KNOWLEDGE BASE - USE THIS CONTEXT TO ANSWER\][\s\S]*/g, '')
|
|
245
|
-
.trim();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function addInteractionMemory(userMessage, aiResponseText, keywords = []) {
|
|
249
|
-
try {
|
|
250
|
-
const db = getDb();
|
|
251
|
-
db.prepare(`
|
|
252
|
-
INSERT INTO interaction_memories (user_text, ai_text, keywords)
|
|
253
|
-
VALUES (?, ?, ?)
|
|
254
|
-
`).run(
|
|
255
|
-
String(userMessage || '').slice(0, 1200),
|
|
256
|
-
String(aiResponseText || '').slice(0, 1200),
|
|
257
|
-
keywords.join(',')
|
|
258
|
-
);
|
|
259
|
-
db.exec(`
|
|
260
|
-
DELETE FROM interaction_memories WHERE id NOT IN (
|
|
261
|
-
SELECT id FROM interaction_memories ORDER BY id DESC LIMIT ${MAX_INTERACTION_MEMORIES}
|
|
262
|
-
)
|
|
263
|
-
`);
|
|
264
|
-
} catch (err) {
|
|
265
|
-
console.error('[Memory] addInteractionMemory error:', err.message);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function getRecentInteractions(limit = 5) {
|
|
270
|
-
try {
|
|
271
|
-
return getDb()
|
|
272
|
-
.prepare('SELECT id, user_text, ai_text, keywords, created_at FROM interaction_memories ORDER BY id DESC LIMIT ?')
|
|
273
|
-
.all(limit);
|
|
274
|
-
} catch (_) {
|
|
275
|
-
return [];
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function deleteInteractionMemory(id) {
|
|
280
|
-
try {
|
|
281
|
-
const result = getDb().prepare('DELETE FROM interaction_memories WHERE id = ?').run(id);
|
|
282
|
-
return result.changes > 0;
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.error('[Memory] deleteInteractionMemory error:', err.message);
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function searchInteractions(query, limit = 8) {
|
|
290
|
-
try {
|
|
291
|
-
const keywords = extractKeywords(query);
|
|
292
|
-
const terms = keywords.length > 0 ? keywords : [String(query || '').trim()].filter(Boolean);
|
|
293
|
-
if (terms.length === 0) return [];
|
|
294
|
-
|
|
295
|
-
const rows = [];
|
|
296
|
-
const seen = new Set();
|
|
297
|
-
const stmt = getDb().prepare(`
|
|
298
|
-
SELECT id, user_text, ai_text, keywords, created_at
|
|
299
|
-
FROM interaction_memories
|
|
300
|
-
WHERE user_text LIKE ? OR ai_text LIKE ? OR keywords LIKE ?
|
|
301
|
-
ORDER BY id DESC
|
|
302
|
-
LIMIT ?
|
|
303
|
-
`);
|
|
304
|
-
|
|
305
|
-
for (const term of terms.slice(0, 5)) {
|
|
306
|
-
const like = `%${term}%`;
|
|
307
|
-
for (const row of stmt.all(like, like, like, limit)) {
|
|
308
|
-
if (!seen.has(row.id)) {
|
|
309
|
-
seen.add(row.id);
|
|
310
|
-
rows.push(row);
|
|
311
|
-
if (rows.length >= limit) return rows;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return rows;
|
|
316
|
-
} catch (_) {
|
|
317
|
-
return [];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function clearInteractionMemories() {
|
|
322
|
-
try {
|
|
323
|
-
getDb().prepare('DELETE FROM interaction_memories').run();
|
|
324
|
-
} catch (err) {
|
|
325
|
-
console.error('[Memory] clearInteractionMemories error:', err.message);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function exportMemorySnapshot() {
|
|
330
|
-
try {
|
|
331
|
-
return {
|
|
332
|
-
profile: getAllProfile(),
|
|
333
|
-
session_memories: getRecentMemories(MAX_SESSION_MEMORIES),
|
|
334
|
-
usage_patterns: getTopPatterns(50),
|
|
335
|
-
interaction_memories: getRecentInteractions(MAX_INTERACTION_MEMORIES)
|
|
336
|
-
};
|
|
337
|
-
} catch (err) {
|
|
338
|
-
console.error('[Memory] exportMemorySnapshot error:', err.message);
|
|
339
|
-
return {
|
|
340
|
-
profile: {},
|
|
341
|
-
session_memories: [],
|
|
342
|
-
usage_patterns: [],
|
|
343
|
-
interaction_memories: []
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// ── Simple keyword extractor (no external deps) ────────────────────────────
|
|
349
|
-
const STOP_WORDS = new Set([
|
|
350
|
-
'ที่', 'ให้', 'และ', 'ของ', 'กับ', 'ใน', 'บน', 'เป็น', 'อยู่', 'มี', 'ได้', 'the', 'a', 'an',
|
|
351
|
-
'is', 'are', 'was', 'were', 'it', 'in', 'on', 'at', 'for', 'to', 'of', 'with', 'and', 'or',
|
|
352
|
-
'this', 'that', 'i', 'you', 'me', 'my', 'your', 'can', 'do', 'be', 'will', 'please', 'how',
|
|
353
|
-
'what', 'which', 'when', 'where', 'why', 'help', 'want', 'need', 'make', 'create', 'get', 'run'
|
|
354
|
-
]);
|
|
355
|
-
|
|
356
|
-
function extractKeywords(text) {
|
|
357
|
-
return text
|
|
358
|
-
.toLowerCase()
|
|
359
|
-
.replace(/[^\w\u0E00-\u0E7F\s]/g, ' ')
|
|
360
|
-
.split(/\s+/)
|
|
361
|
-
.filter(w => w.length > 2 && !STOP_WORDS.has(w))
|
|
362
|
-
.slice(0, 6);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function cleanProfileValue(value) {
|
|
366
|
-
return String(value || '')
|
|
367
|
-
.replace(/[.,!?;:()[\]{}"'`“”‘’]+$/g, '')
|
|
368
|
-
.replace(/(นะ|น่ะ|ครับ|ค่ะ|คะ|จ้า|จ๊ะ|ฮะ|ค้าบ|ค่า)+$/u, '')
|
|
369
|
-
.trim();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function extractUserName(text) {
|
|
373
|
-
const input = String(text || '').trim();
|
|
374
|
-
const patterns = [
|
|
375
|
-
/(?:ผม|ฉัน|ชั้น|หนู|เรา|ข้า|ดิฉัน)?\s*ชื่อ(?:เล่น)?\s*(?:คือ|ว่า|เป็น)?\s*([A-Za-z\u0E00-\u0E7F][A-Za-z\u0E00-\u0E7F\s]{0,40})/iu,
|
|
376
|
-
/(?:เรียก(?:ผม|ฉัน|ชั้น|หนู|เรา)?ว่า)\s*([A-Za-z\u0E00-\u0E7F][A-Za-z\u0E00-\u0E7F\s]{0,40})/iu,
|
|
377
|
-
/\bmy name is\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu,
|
|
378
|
-
/\bcall me\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu,
|
|
379
|
-
/\bi am\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu,
|
|
380
|
-
/\bi'm\s+([A-Za-z][A-Za-z\s'-]{0,40})/iu
|
|
381
|
-
];
|
|
382
|
-
|
|
383
|
-
for (const pattern of patterns) {
|
|
384
|
-
const match = input.match(pattern);
|
|
385
|
-
if (match && match[1]) {
|
|
386
|
-
const name = cleanProfileValue(match[1])
|
|
387
|
-
.split(/\s+(?:and|แล้ว|นะ|ครับ|ค่ะ|คะ)\s+/i)[0]
|
|
388
|
-
.trim();
|
|
389
|
-
if (name && name.length <= 40) return name;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return '';
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ── Main public API ────────────────────────────────────────────────────────
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Called after every successful chat turn.
|
|
400
|
-
* Extracts patterns & infers preferences — runs async, non-blocking.
|
|
401
|
-
*/
|
|
402
|
-
function recordInteraction(userMessage, aiResponseText) {
|
|
403
|
-
try {
|
|
404
|
-
if (!userMessage || !aiResponseText) return;
|
|
405
|
-
|
|
406
|
-
// Extract keywords as usage patterns
|
|
407
|
-
const keywords = extractKeywords(userMessage);
|
|
408
|
-
keywords.forEach(kw => recordPattern(kw));
|
|
409
|
-
addInteractionMemory(userMessage, aiResponseText, keywords);
|
|
410
|
-
|
|
411
|
-
// Detect preferred language
|
|
412
|
-
const thaiRatio = (userMessage.match(/[\u0E00-\u0E7F]/g) || []).length / userMessage.length;
|
|
413
|
-
if (thaiRatio > 0.3) setProfile('preferred_language', 'thai');
|
|
414
|
-
else setProfile('preferred_language', 'english');
|
|
415
|
-
|
|
416
|
-
const userName = extractUserName(userMessage);
|
|
417
|
-
if (userName) {
|
|
418
|
-
setProfile('user_name', userName);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Detect coding intent (update project activity)
|
|
422
|
-
const codingKeywords = ['code', 'fix', 'debug', 'function', 'class', 'import', 'script',
|
|
423
|
-
'แก้', 'เขียน', 'โค้ด', 'สคริปต์', 'ฟังก์ชัน'];
|
|
424
|
-
if (codingKeywords.some(k => userMessage.toLowerCase().includes(k))) {
|
|
425
|
-
const cwd = process.cwd();
|
|
426
|
-
if (cwd !== os.homedir()) {
|
|
427
|
-
setProfile('last_active_project', path.basename(cwd));
|
|
428
|
-
setProfile('last_active_project_path', cwd);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Update interaction counter
|
|
433
|
-
const count = parseInt(getProfile('total_interactions', '0'), 10);
|
|
434
|
-
setProfile('total_interactions', String(count + 1));
|
|
435
|
-
setProfile('last_seen', new Date().toISOString());
|
|
436
|
-
} catch (err) {
|
|
437
|
-
console.error('[Memory] recordInteraction error:', err.message);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Saves a condensed summary of a completed conversation.
|
|
443
|
-
* Call this when user clears history or after N turns.
|
|
444
|
-
*/
|
|
445
|
-
function saveSessionSummary(summary, tags = []) {
|
|
446
|
-
if (!summary || summary.trim().length < 10) return;
|
|
447
|
-
addSessionMemory(summary.trim(), tags);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function addLearnedSkill(name, sourcePath, content) {
|
|
451
|
-
const cleanName = String(name || '').trim() || path.basename(sourcePath || 'skill.md');
|
|
452
|
-
const cleanPath = path.resolve(String(sourcePath || ''));
|
|
453
|
-
const cleanContent = String(content || '').trim();
|
|
454
|
-
if (!cleanContent) {
|
|
455
|
-
throw new Error('Skill file is empty.');
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const storedContent = cleanContent.slice(0, 12000);
|
|
459
|
-
ensureLearnedSkillsTable();
|
|
460
|
-
const db = getDb();
|
|
461
|
-
db.prepare(`
|
|
462
|
-
INSERT INTO learned_skills (name, source_path, content, created_at, updated_at)
|
|
463
|
-
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
464
|
-
ON CONFLICT(source_path) DO UPDATE SET
|
|
465
|
-
name = excluded.name,
|
|
466
|
-
content = excluded.content,
|
|
467
|
-
updated_at = CURRENT_TIMESTAMP
|
|
468
|
-
`).run(cleanName, cleanPath, storedContent);
|
|
469
|
-
|
|
470
|
-
return {
|
|
471
|
-
name: cleanName,
|
|
472
|
-
source_path: cleanPath,
|
|
473
|
-
content_length: cleanContent.length,
|
|
474
|
-
stored_length: storedContent.length
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function getLearnedSkills(limit = 10) {
|
|
479
|
-
try {
|
|
480
|
-
ensureLearnedSkillsTable();
|
|
481
|
-
return getDb().prepare(`
|
|
482
|
-
SELECT id, name, source_path, content, created_at, updated_at
|
|
483
|
-
FROM learned_skills
|
|
484
|
-
ORDER BY updated_at DESC, id DESC
|
|
485
|
-
LIMIT ?
|
|
486
|
-
`).all(limit);
|
|
487
|
-
} catch (err) {
|
|
488
|
-
console.error('[Memory] getLearnedSkills error:', err.message);
|
|
489
|
-
return [];
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function deleteLearnedSkill(identifier) {
|
|
494
|
-
try {
|
|
495
|
-
ensureLearnedSkillsTable();
|
|
496
|
-
const input = String(identifier || '').trim();
|
|
497
|
-
if (!input) return 0;
|
|
498
|
-
|
|
499
|
-
const db = getDb();
|
|
500
|
-
if (/^\d+$/.test(input)) {
|
|
501
|
-
return db.prepare('DELETE FROM learned_skills WHERE id = ?').run(Number(input)).changes;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const resolved = path.resolve(input);
|
|
505
|
-
return db.prepare('DELETE FROM learned_skills WHERE source_path = ? OR name = ?').run(resolved, input).changes;
|
|
506
|
-
} catch (err) {
|
|
507
|
-
console.error('[Memory] deleteLearnedSkill error:', err.message);
|
|
508
|
-
return 0;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Returns a formatted context string to inject into the AI system prompt.
|
|
514
|
-
* Lightweight — no async calls.
|
|
515
|
-
*/
|
|
516
|
-
function getUserContext(query = '') {
|
|
517
|
-
try {
|
|
518
|
-
const profile = getAllProfile();
|
|
519
|
-
const patterns = getTopPatterns(6);
|
|
520
|
-
const memories = getRecentMemories(3);
|
|
521
|
-
const interactions = getRecentInteractions(6);
|
|
522
|
-
const relevantInteractions = query ? searchInteractions(query, 5) : [];
|
|
523
|
-
|
|
524
|
-
const lines = ['\n\n[LONG-TERM USER CONTEXT — use this to personalize responses]'];
|
|
525
|
-
|
|
526
|
-
// Profile info
|
|
527
|
-
if (Object.keys(profile).length > 0) {
|
|
528
|
-
if (profile.user_name)
|
|
529
|
-
lines.push(`• User name: ${profile.user_name}`);
|
|
530
|
-
if (profile.preferred_language)
|
|
531
|
-
lines.push(`• Previously inferred language: ${profile.preferred_language} (do not override the current user message language)`);
|
|
532
|
-
if (profile.last_active_project)
|
|
533
|
-
lines.push(`• Last active project: ${profile.last_active_project} (${profile.last_active_project_path || ''})`);
|
|
534
|
-
if (profile.total_interactions)
|
|
535
|
-
lines.push(`• Total interactions with Mint: ${profile.total_interactions}`);
|
|
536
|
-
if (profile.last_seen) {
|
|
537
|
-
const d = new Date(profile.last_seen);
|
|
538
|
-
lines.push(`• Last session: ${d.toLocaleDateString('th-TH')} ${d.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit' })}`);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Usage patterns
|
|
543
|
-
if (patterns.length > 0) {
|
|
544
|
-
const topTopics = patterns.map(p => p.pattern).join(', ');
|
|
545
|
-
lines.push(`• Frequent topics/tools: ${topTopics}`);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// Past session memories
|
|
549
|
-
if (memories.length > 0) {
|
|
550
|
-
lines.push('\nRecent session summaries:');
|
|
551
|
-
memories.forEach((m, i) => lines.push(` ${i + 1}. ${m.summary}`));
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (interactions.length > 0) {
|
|
555
|
-
lines.push('\nRecent remembered interactions:');
|
|
556
|
-
interactions.forEach((m, i) => {
|
|
557
|
-
lines.push(` ${i + 1}. User: ${m.user_text}`);
|
|
558
|
-
lines.push(` Mint: ${m.ai_text}`);
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (relevantInteractions.length > 0) {
|
|
563
|
-
lines.push('\nRelevant remembered interactions for the current request:');
|
|
564
|
-
relevantInteractions.forEach((m, i) => {
|
|
565
|
-
lines.push(` ${i + 1}. User: ${m.user_text}`);
|
|
566
|
-
lines.push(` Mint: ${m.ai_text}`);
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const learnedSkills = getLearnedSkills(8);
|
|
571
|
-
if (learnedSkills.length > 0) {
|
|
572
|
-
lines.push('\nLearned skill/instruction files:');
|
|
573
|
-
learnedSkills.forEach((skill, i) => {
|
|
574
|
-
lines.push(`\n ${i + 1}. ${skill.name}`);
|
|
575
|
-
lines.push(` Source: ${skill.source_path}`);
|
|
576
|
-
lines.push(' Content:');
|
|
577
|
-
lines.push(skill.content
|
|
578
|
-
.split('\n')
|
|
579
|
-
.map(line => ` ${line}`)
|
|
580
|
-
.join('\n'));
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (lines.length === 1) return ''; // nothing to add
|
|
585
|
-
lines.push('[END USER CONTEXT]\n');
|
|
586
|
-
return lines.join('\n');
|
|
587
|
-
} catch (err) {
|
|
588
|
-
console.error('[Memory] getUserContext error:', err.message);
|
|
589
|
-
return '';
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// ── Response Cache helpers ────────────────────────────────────────────────
|
|
594
|
-
function getCachedResponse(query) {
|
|
595
|
-
try {
|
|
596
|
-
const hash = crypto.createHash('md5').update(query.trim().toLowerCase()).digest('hex');
|
|
597
|
-
const row = getDb().prepare('SELECT response, created_at FROM response_cache WHERE query_hash = ?').get(hash);
|
|
598
|
-
if (row) {
|
|
599
|
-
// Optional: check TTL (e.g., 24 hours)
|
|
600
|
-
const age = Date.now() - new Date(row.created_at).getTime();
|
|
601
|
-
if (age < 24 * 60 * 60 * 1000) {
|
|
602
|
-
const parsed = JSON.parse(row.response);
|
|
603
|
-
if (parsed && typeof parsed.response === 'string') {
|
|
604
|
-
parsed.response = stripRelevantMemoryBlock(parsed.response);
|
|
605
|
-
}
|
|
606
|
-
return parsed;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
} catch (_) {}
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function cacheResponse(query, responseObj) {
|
|
614
|
-
try {
|
|
615
|
-
const hash = crypto.createHash('md5').update(query.trim().toLowerCase()).digest('hex');
|
|
616
|
-
const sanitized = (responseObj && typeof responseObj === 'object')
|
|
617
|
-
? {
|
|
618
|
-
...responseObj,
|
|
619
|
-
response: typeof responseObj.response === 'string'
|
|
620
|
-
? stripRelevantMemoryBlock(responseObj.response)
|
|
621
|
-
: responseObj.response
|
|
622
|
-
}
|
|
623
|
-
: responseObj;
|
|
624
|
-
getDb().prepare(`
|
|
625
|
-
INSERT INTO response_cache (query_hash, response, created_at)
|
|
626
|
-
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
627
|
-
ON CONFLICT(query_hash) DO UPDATE SET response = excluded.response, created_at = CURRENT_TIMESTAMP
|
|
628
|
-
`).run(hash, JSON.stringify(sanitized));
|
|
629
|
-
} catch (_) {}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function clearResponseCache() {
|
|
633
|
-
try {
|
|
634
|
-
getDb().prepare('DELETE FROM response_cache').run();
|
|
635
|
-
} catch (err) {
|
|
636
|
-
console.error('[Memory] clearResponseCache error:', err.message);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
module.exports = {
|
|
641
|
-
recordInteraction,
|
|
642
|
-
saveSessionSummary,
|
|
643
|
-
getUserContext,
|
|
644
|
-
setProfile,
|
|
645
|
-
deleteProfile,
|
|
646
|
-
clearConversationScopedProfile,
|
|
647
|
-
getProfile,
|
|
648
|
-
getAllProfile,
|
|
649
|
-
addLearnedSkill,
|
|
650
|
-
getLearnedSkills,
|
|
651
|
-
deleteLearnedSkill,
|
|
652
|
-
getTopPatterns,
|
|
653
|
-
getRecentInteractions,
|
|
654
|
-
searchInteractions,
|
|
655
|
-
deleteInteractionMemory,
|
|
656
|
-
clearInteractionMemories,
|
|
657
|
-
exportMemorySnapshot,
|
|
658
|
-
getRecentMemories,
|
|
659
|
-
getCachedResponse,
|
|
660
|
-
cacheResponse,
|
|
661
|
-
clearResponseCache
|
|
662
|
-
};
|