@dotsetlabs/dotclaw 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +54 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/config-examples/groups/global/CLAUDE.md +21 -0
- package/config-examples/groups/main/CLAUDE.md +47 -0
- package/config-examples/mount-allowlist.json +25 -0
- package/config-examples/plugin-http.json +18 -0
- package/config-examples/runtime.json +30 -0
- package/config-examples/tool-budgets.json +24 -0
- package/config-examples/tool-policy.json +51 -0
- package/container/.dockerignore +6 -0
- package/container/Dockerfile +74 -0
- package/container/agent-runner/package-lock.json +92 -0
- package/container/agent-runner/package.json +20 -0
- package/container/agent-runner/src/agent-config.ts +295 -0
- package/container/agent-runner/src/container-protocol.ts +73 -0
- package/container/agent-runner/src/daemon.ts +91 -0
- package/container/agent-runner/src/index.ts +1428 -0
- package/container/agent-runner/src/ipc.ts +321 -0
- package/container/agent-runner/src/memory.ts +336 -0
- package/container/agent-runner/src/prompt-packs.ts +341 -0
- package/container/agent-runner/src/tools.ts +1720 -0
- package/container/agent-runner/tsconfig.json +19 -0
- package/container/build.sh +23 -0
- package/container/skills/agent-browser.md +159 -0
- package/dist/admin-commands.d.ts +7 -0
- package/dist/admin-commands.d.ts.map +1 -0
- package/dist/admin-commands.js +87 -0
- package/dist/admin-commands.js.map +1 -0
- package/dist/agent-context.d.ts +42 -0
- package/dist/agent-context.d.ts.map +1 -0
- package/dist/agent-context.js +92 -0
- package/dist/agent-context.js.map +1 -0
- package/dist/agent-execution.d.ts +68 -0
- package/dist/agent-execution.d.ts.map +1 -0
- package/dist/agent-execution.js +169 -0
- package/dist/agent-execution.js.map +1 -0
- package/dist/agent-semaphore.d.ts +2 -0
- package/dist/agent-semaphore.d.ts.map +1 -0
- package/dist/agent-semaphore.js +52 -0
- package/dist/agent-semaphore.js.map +1 -0
- package/dist/behavior-config.d.ts +14 -0
- package/dist/behavior-config.d.ts.map +1 -0
- package/dist/behavior-config.js +52 -0
- package/dist/behavior-config.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +626 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/container-protocol.d.ts +72 -0
- package/dist/container-protocol.d.ts.map +1 -0
- package/dist/container-protocol.js +3 -0
- package/dist/container-protocol.js.map +1 -0
- package/dist/container-runner.d.ts +59 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +813 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/cost.d.ts +9 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +11 -0
- package/dist/cost.js.map +1 -0
- package/dist/dashboard.d.ts +58 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +471 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/db.d.ts +99 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +423 -0
- package/dist/db.js.map +1 -0
- package/dist/error-messages.d.ts +17 -0
- package/dist/error-messages.d.ts.map +1 -0
- package/dist/error-messages.js +109 -0
- package/dist/error-messages.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2072 -0
- package/dist/index.js.map +1 -0
- package/dist/locks.d.ts +2 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +26 -0
- package/dist/locks.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/maintenance.d.ts +13 -0
- package/dist/maintenance.d.ts.map +1 -0
- package/dist/maintenance.js +151 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/memory-embeddings.d.ts +13 -0
- package/dist/memory-embeddings.d.ts.map +1 -0
- package/dist/memory-embeddings.js +126 -0
- package/dist/memory-embeddings.js.map +1 -0
- package/dist/memory-recall.d.ts +8 -0
- package/dist/memory-recall.d.ts.map +1 -0
- package/dist/memory-recall.js +127 -0
- package/dist/memory-recall.js.map +1 -0
- package/dist/memory-store.d.ts +149 -0
- package/dist/memory-store.d.ts.map +1 -0
- package/dist/memory-store.js +787 -0
- package/dist/memory-store.js.map +1 -0
- package/dist/metrics.d.ts +12 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +134 -0
- package/dist/metrics.js.map +1 -0
- package/dist/model-registry.d.ts +67 -0
- package/dist/model-registry.d.ts.map +1 -0
- package/dist/model-registry.js +230 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/mount-security.d.ts +37 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +284 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/paths.d.ts +80 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +149 -0
- package/dist/paths.js.map +1 -0
- package/dist/personalization.d.ts +6 -0
- package/dist/personalization.d.ts.map +1 -0
- package/dist/personalization.js +180 -0
- package/dist/personalization.js.map +1 -0
- package/dist/progress.d.ts +15 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +92 -0
- package/dist/progress.js.map +1 -0
- package/dist/runtime-config.d.ts +227 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +297 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/task-scheduler.d.ts +9 -0
- package/dist/task-scheduler.d.ts.map +1 -0
- package/dist/task-scheduler.js +195 -0
- package/dist/task-scheduler.js.map +1 -0
- package/dist/telegram-format.d.ts +3 -0
- package/dist/telegram-format.d.ts.map +1 -0
- package/dist/telegram-format.js +200 -0
- package/dist/telegram-format.js.map +1 -0
- package/dist/tool-budgets.d.ts +16 -0
- package/dist/tool-budgets.d.ts.map +1 -0
- package/dist/tool-budgets.js +83 -0
- package/dist/tool-budgets.js.map +1 -0
- package/dist/tool-policy.d.ts +18 -0
- package/dist/tool-policy.d.ts.map +1 -0
- package/dist/tool-policy.js +84 -0
- package/dist/tool-policy.js.map +1 -0
- package/dist/trace-writer.d.ts +39 -0
- package/dist/trace-writer.d.ts.map +1 -0
- package/dist/trace-writer.js +27 -0
- package/dist/trace-writer.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +30 -0
- package/dist/utils.js.map +1 -0
- package/launchd/com.dotclaw.plist +32 -0
- package/package.json +89 -0
- package/scripts/autotune.js +53 -0
- package/scripts/bootstrap.js +348 -0
- package/scripts/configure.js +200 -0
- package/scripts/doctor.js +164 -0
- package/scripts/init.js +209 -0
- package/scripts/install.sh +219 -0
- package/systemd/dotclaw.service +22 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { MAIN_GROUP_FOLDER, STORE_DIR } from './config.js';
|
|
6
|
+
import { loadRuntimeConfig } from './runtime-config.js';
|
|
7
|
+
const MEMORY_DB_PATH = path.join(STORE_DIR, 'memory.db');
|
|
8
|
+
let memoryDb = null;
|
|
9
|
+
let ftsEnabled = true;
|
|
10
|
+
const MEMORY_SCHEMA_VERSION = 3;
|
|
11
|
+
function getUserVersion(db) {
|
|
12
|
+
const version = db.pragma('user_version', { simple: true });
|
|
13
|
+
return Number.isFinite(version) ? version : 0;
|
|
14
|
+
}
|
|
15
|
+
function setUserVersion(db, version) {
|
|
16
|
+
db.pragma(`user_version = ${Math.max(0, Math.floor(version))}`);
|
|
17
|
+
}
|
|
18
|
+
function getMemoryColumns(db) {
|
|
19
|
+
const rows = db.prepare(`PRAGMA table_info(memory_items)`).all();
|
|
20
|
+
const columns = new Set();
|
|
21
|
+
for (const row of rows) {
|
|
22
|
+
if (row?.name)
|
|
23
|
+
columns.add(String(row.name));
|
|
24
|
+
}
|
|
25
|
+
return columns;
|
|
26
|
+
}
|
|
27
|
+
function ensureColumn(db, columns, name, ddl) {
|
|
28
|
+
if (columns.has(name))
|
|
29
|
+
return;
|
|
30
|
+
db.exec(ddl);
|
|
31
|
+
columns.add(name);
|
|
32
|
+
}
|
|
33
|
+
function ensureMemorySchema(db) {
|
|
34
|
+
const currentVersion = getUserVersion(db);
|
|
35
|
+
const columns = getMemoryColumns(db);
|
|
36
|
+
if (columns.size === 0) {
|
|
37
|
+
// Table might not exist yet; caller should have created it.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
ensureColumn(db, columns, 'embedding_json', `ALTER TABLE memory_items ADD COLUMN embedding_json TEXT`);
|
|
41
|
+
ensureColumn(db, columns, 'embedding_model', `ALTER TABLE memory_items ADD COLUMN embedding_model TEXT`);
|
|
42
|
+
ensureColumn(db, columns, 'embedding_updated_at', `ALTER TABLE memory_items ADD COLUMN embedding_updated_at TEXT`);
|
|
43
|
+
ensureColumn(db, columns, 'kind', `ALTER TABLE memory_items ADD COLUMN kind TEXT`);
|
|
44
|
+
ensureColumn(db, columns, 'conflict_key', `ALTER TABLE memory_items ADD COLUMN conflict_key TEXT`);
|
|
45
|
+
ensureColumn(db, columns, 'access_count', `ALTER TABLE memory_items ADD COLUMN access_count INTEGER DEFAULT 0`);
|
|
46
|
+
ensureColumn(db, columns, 'priority', `ALTER TABLE memory_items ADD COLUMN priority TEXT DEFAULT 'normal'`);
|
|
47
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_memory_conflict ON memory_items(group_folder, scope, subject_id, type, conflict_key)`);
|
|
48
|
+
db.exec(`
|
|
49
|
+
CREATE TABLE IF NOT EXISTS memory_meta (
|
|
50
|
+
key TEXT PRIMARY KEY,
|
|
51
|
+
value TEXT
|
|
52
|
+
);
|
|
53
|
+
`);
|
|
54
|
+
if (currentVersion !== MEMORY_SCHEMA_VERSION) {
|
|
55
|
+
setUserVersion(db, MEMORY_SCHEMA_VERSION);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getMemoryMeta(db, key) {
|
|
59
|
+
const row = db.prepare(`SELECT value FROM memory_meta WHERE key = ?`).get(key);
|
|
60
|
+
return row?.value ?? null;
|
|
61
|
+
}
|
|
62
|
+
function setMemoryMeta(db, key, value) {
|
|
63
|
+
db.prepare(`
|
|
64
|
+
INSERT INTO memory_meta (key, value)
|
|
65
|
+
VALUES (?, ?)
|
|
66
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
67
|
+
`).run(key, value);
|
|
68
|
+
}
|
|
69
|
+
function getDb() {
|
|
70
|
+
if (!memoryDb) {
|
|
71
|
+
initMemoryStore();
|
|
72
|
+
}
|
|
73
|
+
if (!memoryDb) {
|
|
74
|
+
throw new Error('Memory store is not initialized');
|
|
75
|
+
}
|
|
76
|
+
return memoryDb;
|
|
77
|
+
}
|
|
78
|
+
export function initMemoryStore() {
|
|
79
|
+
if (memoryDb)
|
|
80
|
+
return;
|
|
81
|
+
fs.mkdirSync(STORE_DIR, { recursive: true });
|
|
82
|
+
memoryDb = new Database(MEMORY_DB_PATH);
|
|
83
|
+
memoryDb.pragma('journal_mode = WAL');
|
|
84
|
+
memoryDb.pragma('busy_timeout = 3000');
|
|
85
|
+
memoryDb.exec(`
|
|
86
|
+
CREATE TABLE IF NOT EXISTS memory_items (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
group_folder TEXT NOT NULL,
|
|
89
|
+
scope TEXT NOT NULL,
|
|
90
|
+
subject_id TEXT,
|
|
91
|
+
type TEXT NOT NULL,
|
|
92
|
+
kind TEXT,
|
|
93
|
+
conflict_key TEXT,
|
|
94
|
+
content TEXT NOT NULL,
|
|
95
|
+
normalized TEXT NOT NULL,
|
|
96
|
+
importance REAL NOT NULL,
|
|
97
|
+
confidence REAL NOT NULL,
|
|
98
|
+
tags_json TEXT,
|
|
99
|
+
tags_text TEXT,
|
|
100
|
+
created_at TEXT NOT NULL,
|
|
101
|
+
updated_at TEXT NOT NULL,
|
|
102
|
+
last_accessed_at TEXT,
|
|
103
|
+
expires_at TEXT,
|
|
104
|
+
source TEXT,
|
|
105
|
+
metadata_json TEXT,
|
|
106
|
+
embedding_json TEXT,
|
|
107
|
+
embedding_model TEXT,
|
|
108
|
+
embedding_updated_at TEXT
|
|
109
|
+
);
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_memory_group_scope ON memory_items(group_folder, scope, subject_id);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_memory_updated_at ON memory_items(updated_at);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS memory_sources (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
group_folder TEXT NOT NULL,
|
|
116
|
+
source_type TEXT NOT NULL,
|
|
117
|
+
source_path TEXT NOT NULL,
|
|
118
|
+
source_hash TEXT,
|
|
119
|
+
indexed_at TEXT NOT NULL
|
|
120
|
+
);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_memory_sources_group ON memory_sources(group_folder, source_type);
|
|
122
|
+
`);
|
|
123
|
+
try {
|
|
124
|
+
memoryDb.exec(`
|
|
125
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
126
|
+
id,
|
|
127
|
+
content,
|
|
128
|
+
tags,
|
|
129
|
+
tokenize = 'porter'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE TRIGGER IF NOT EXISTS memory_items_ai AFTER INSERT ON memory_items BEGIN
|
|
133
|
+
INSERT INTO memory_fts (id, content, tags)
|
|
134
|
+
VALUES (new.id, new.content, new.tags_text);
|
|
135
|
+
END;
|
|
136
|
+
CREATE TRIGGER IF NOT EXISTS memory_items_au AFTER UPDATE ON memory_items BEGIN
|
|
137
|
+
UPDATE memory_fts
|
|
138
|
+
SET content = new.content, tags = new.tags_text
|
|
139
|
+
WHERE id = new.id;
|
|
140
|
+
END;
|
|
141
|
+
CREATE TRIGGER IF NOT EXISTS memory_items_ad AFTER DELETE ON memory_items BEGIN
|
|
142
|
+
DELETE FROM memory_fts WHERE id = old.id;
|
|
143
|
+
END;
|
|
144
|
+
`);
|
|
145
|
+
ftsEnabled = true;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
ftsEnabled = false;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
ensureMemorySchema(memoryDb);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.error(`[memory-store] Schema check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function normalizeContent(content) {
|
|
159
|
+
return content
|
|
160
|
+
.toLowerCase()
|
|
161
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
162
|
+
.replace(/\s+/g, ' ')
|
|
163
|
+
.trim();
|
|
164
|
+
}
|
|
165
|
+
function clamp(value, min, max) {
|
|
166
|
+
if (!Number.isFinite(value))
|
|
167
|
+
return min;
|
|
168
|
+
return Math.min(max, Math.max(min, value));
|
|
169
|
+
}
|
|
170
|
+
function tagsToText(tags) {
|
|
171
|
+
if (!tags || tags.length === 0)
|
|
172
|
+
return '';
|
|
173
|
+
return tags
|
|
174
|
+
.map(tag => tag.trim().toLowerCase())
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.join(' ');
|
|
177
|
+
}
|
|
178
|
+
function getExpiresAt(ttlDays) {
|
|
179
|
+
if (!ttlDays || !Number.isFinite(ttlDays) || ttlDays <= 0)
|
|
180
|
+
return null;
|
|
181
|
+
const ms = ttlDays * 24 * 60 * 60 * 1000;
|
|
182
|
+
return new Date(Date.now() + ms).toISOString();
|
|
183
|
+
}
|
|
184
|
+
function resolveScope(input, groupFolder) {
|
|
185
|
+
if (input.scope === 'global' && groupFolder !== MAIN_GROUP_FOLDER) {
|
|
186
|
+
return 'group';
|
|
187
|
+
}
|
|
188
|
+
return input.scope;
|
|
189
|
+
}
|
|
190
|
+
function resolveKind(input) {
|
|
191
|
+
if (input.kind === 'semantic' || input.kind === 'episodic' || input.kind === 'procedural' || input.kind === 'preference') {
|
|
192
|
+
return input.kind;
|
|
193
|
+
}
|
|
194
|
+
if (input.type === 'preference')
|
|
195
|
+
return 'preference';
|
|
196
|
+
if (input.type === 'task' || input.type === 'project')
|
|
197
|
+
return 'procedural';
|
|
198
|
+
if (input.type === 'archive')
|
|
199
|
+
return 'episodic';
|
|
200
|
+
return 'semantic';
|
|
201
|
+
}
|
|
202
|
+
function normalizeConflictKey(value) {
|
|
203
|
+
if (!value || typeof value !== 'string')
|
|
204
|
+
return null;
|
|
205
|
+
const trimmed = value.trim().toLowerCase();
|
|
206
|
+
return trimmed ? trimmed : null;
|
|
207
|
+
}
|
|
208
|
+
function parseJsonArray(raw) {
|
|
209
|
+
if (!raw)
|
|
210
|
+
return [];
|
|
211
|
+
try {
|
|
212
|
+
const parsed = JSON.parse(raw);
|
|
213
|
+
if (Array.isArray(parsed)) {
|
|
214
|
+
return parsed.filter((item) => typeof item === 'string');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// ignore parse errors
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
function parseJsonRecord(raw) {
|
|
223
|
+
if (!raw)
|
|
224
|
+
return null;
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(raw);
|
|
227
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
228
|
+
return parsed;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// ignore parse errors
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
export function upsertMemoryItems(groupFolder, items, source) {
|
|
237
|
+
if (items.length === 0)
|
|
238
|
+
return [];
|
|
239
|
+
const db = getDb();
|
|
240
|
+
const now = new Date().toISOString();
|
|
241
|
+
const results = [];
|
|
242
|
+
const selectStmt = db.prepare(`
|
|
243
|
+
SELECT id, content, importance, confidence, tags_json, tags_text, embedding_json, embedding_model, embedding_updated_at, kind, conflict_key
|
|
244
|
+
FROM memory_items
|
|
245
|
+
WHERE group_folder = ? AND scope = ? AND IFNULL(subject_id, '') = ? AND type = ? AND normalized = ?
|
|
246
|
+
`);
|
|
247
|
+
const deleteConflictStmt = db.prepare(`
|
|
248
|
+
DELETE FROM memory_items
|
|
249
|
+
WHERE group_folder = ? AND scope = ? AND IFNULL(subject_id, '') = ? AND type = ? AND conflict_key = ?
|
|
250
|
+
`);
|
|
251
|
+
const insertStmt = db.prepare(`
|
|
252
|
+
INSERT INTO memory_items (
|
|
253
|
+
id, group_folder, scope, subject_id, type, kind, conflict_key, content, normalized,
|
|
254
|
+
importance, confidence, tags_json, tags_text, created_at, updated_at,
|
|
255
|
+
last_accessed_at, expires_at, source, metadata_json,
|
|
256
|
+
embedding_json, embedding_model, embedding_updated_at
|
|
257
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
258
|
+
`);
|
|
259
|
+
const updateStmt = db.prepare(`
|
|
260
|
+
UPDATE memory_items
|
|
261
|
+
SET content = ?, importance = ?, confidence = ?, tags_json = ?, tags_text = ?,
|
|
262
|
+
updated_at = ?, expires_at = ?, source = ?, metadata_json = ?,
|
|
263
|
+
embedding_json = ?, embedding_model = ?, embedding_updated_at = ?,
|
|
264
|
+
kind = ?, conflict_key = ?
|
|
265
|
+
WHERE id = ?
|
|
266
|
+
`);
|
|
267
|
+
const transaction = db.transaction((inputs) => {
|
|
268
|
+
for (const item of inputs) {
|
|
269
|
+
if (!item.content || typeof item.content !== 'string')
|
|
270
|
+
continue;
|
|
271
|
+
const normalized = normalizeContent(item.content);
|
|
272
|
+
if (!normalized)
|
|
273
|
+
continue;
|
|
274
|
+
const scope = resolveScope(item, groupFolder);
|
|
275
|
+
const kind = resolveKind(item);
|
|
276
|
+
const conflictKey = normalizeConflictKey(item.conflict_key);
|
|
277
|
+
const subjectId = scope === 'user' ? (item.subject_id || null) : null;
|
|
278
|
+
const importance = clamp(item.importance ?? 0.5, 0, 1);
|
|
279
|
+
const confidence = clamp(item.confidence ?? 0.6, 0, 1);
|
|
280
|
+
const tagsJson = item.tags ? JSON.stringify(item.tags) : null;
|
|
281
|
+
const tagsText = tagsToText(item.tags);
|
|
282
|
+
const expiresAt = getExpiresAt(item.ttl_days ?? null);
|
|
283
|
+
const metadataJson = item.metadata ? JSON.stringify(item.metadata) : null;
|
|
284
|
+
if (conflictKey) {
|
|
285
|
+
deleteConflictStmt.run(groupFolder, scope, subjectId || '', item.type, conflictKey);
|
|
286
|
+
}
|
|
287
|
+
const existing = selectStmt.get(groupFolder, scope, subjectId || '', item.type, normalized);
|
|
288
|
+
if (existing) {
|
|
289
|
+
const mergedImportance = Math.max(existing.importance, importance);
|
|
290
|
+
const mergedConfidence = Math.max(existing.confidence, confidence);
|
|
291
|
+
const mergedContent = existing.content.length >= item.content.length ? existing.content : item.content;
|
|
292
|
+
const mergedTags = Array.from(new Set([...(existing.tags_text || '').split(' ').filter(Boolean), ...tagsText.split(' ').filter(Boolean)])).join(' ');
|
|
293
|
+
const contentChanged = mergedContent !== existing.content;
|
|
294
|
+
const nextEmbeddingJson = contentChanged ? null : existing.embedding_json;
|
|
295
|
+
const nextEmbeddingModel = contentChanged ? null : existing.embedding_model;
|
|
296
|
+
const nextEmbeddingUpdatedAt = contentChanged ? null : existing.embedding_updated_at;
|
|
297
|
+
updateStmt.run(mergedContent, mergedImportance, mergedConfidence, tagsJson || existing.tags_json, mergedTags || existing.tags_text || '', now, expiresAt, item.source || source, metadataJson, nextEmbeddingJson, nextEmbeddingModel, nextEmbeddingUpdatedAt, kind, conflictKey, existing.id);
|
|
298
|
+
results.push({
|
|
299
|
+
id: existing.id,
|
|
300
|
+
group_folder: groupFolder,
|
|
301
|
+
scope,
|
|
302
|
+
subject_id: subjectId,
|
|
303
|
+
type: item.type,
|
|
304
|
+
kind,
|
|
305
|
+
conflict_key: conflictKey,
|
|
306
|
+
content: mergedContent,
|
|
307
|
+
normalized,
|
|
308
|
+
importance: mergedImportance,
|
|
309
|
+
confidence: mergedConfidence,
|
|
310
|
+
tags: item.tags,
|
|
311
|
+
tags_text: mergedTags || existing.tags_text || '',
|
|
312
|
+
created_at: now,
|
|
313
|
+
updated_at: now,
|
|
314
|
+
last_accessed_at: null,
|
|
315
|
+
expires_at: expiresAt,
|
|
316
|
+
ttl_days: item.ttl_days,
|
|
317
|
+
source: item.source || source,
|
|
318
|
+
metadata: item.metadata,
|
|
319
|
+
embedding_json: nextEmbeddingJson,
|
|
320
|
+
embedding_model: nextEmbeddingModel,
|
|
321
|
+
embedding_updated_at: nextEmbeddingUpdatedAt
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const id = `mem-${crypto.randomUUID()}`;
|
|
326
|
+
insertStmt.run(id, groupFolder, scope, subjectId, item.type, kind, conflictKey, item.content, normalized, importance, confidence, tagsJson, tagsText, now, now, null, expiresAt, item.source || source, metadataJson, null, null, null);
|
|
327
|
+
results.push({
|
|
328
|
+
id,
|
|
329
|
+
group_folder: groupFolder,
|
|
330
|
+
scope,
|
|
331
|
+
subject_id: subjectId,
|
|
332
|
+
type: item.type,
|
|
333
|
+
kind,
|
|
334
|
+
conflict_key: conflictKey,
|
|
335
|
+
content: item.content,
|
|
336
|
+
normalized,
|
|
337
|
+
importance,
|
|
338
|
+
confidence,
|
|
339
|
+
tags: item.tags,
|
|
340
|
+
tags_text: tagsText,
|
|
341
|
+
created_at: now,
|
|
342
|
+
updated_at: now,
|
|
343
|
+
last_accessed_at: null,
|
|
344
|
+
expires_at: expiresAt,
|
|
345
|
+
ttl_days: item.ttl_days,
|
|
346
|
+
source: item.source || source,
|
|
347
|
+
metadata: item.metadata,
|
|
348
|
+
embedding_json: null,
|
|
349
|
+
embedding_model: null,
|
|
350
|
+
embedding_updated_at: null
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
transaction(items);
|
|
355
|
+
return results;
|
|
356
|
+
}
|
|
357
|
+
function buildFtsQuery(text) {
|
|
358
|
+
const tokens = text.toLowerCase().match(/[a-z0-9]+/g) || [];
|
|
359
|
+
if (tokens.length === 0)
|
|
360
|
+
return null;
|
|
361
|
+
const unique = Array.from(new Set(tokens)).slice(0, 10);
|
|
362
|
+
return unique.map(token => `${token}*`).join(' OR ');
|
|
363
|
+
}
|
|
364
|
+
export function searchMemories(params) {
|
|
365
|
+
const db = getDb();
|
|
366
|
+
if (!ftsEnabled) {
|
|
367
|
+
return searchMemoriesFallback(params);
|
|
368
|
+
}
|
|
369
|
+
const ftsQuery = buildFtsQuery(params.query);
|
|
370
|
+
if (!ftsQuery)
|
|
371
|
+
return [];
|
|
372
|
+
const now = new Date().toISOString();
|
|
373
|
+
const limit = Math.min(params.limit || 12, 50);
|
|
374
|
+
const rows = db.prepare(`
|
|
375
|
+
SELECT m.*, bm25(memory_fts) AS bm25
|
|
376
|
+
FROM memory_fts
|
|
377
|
+
JOIN memory_items m ON m.id = memory_fts.id
|
|
378
|
+
WHERE memory_fts MATCH ?
|
|
379
|
+
AND (m.group_folder = ? OR m.group_folder = 'global')
|
|
380
|
+
AND (m.scope != 'user' OR m.subject_id = ?)
|
|
381
|
+
AND (m.expires_at IS NULL OR m.expires_at > ?)
|
|
382
|
+
ORDER BY bm25
|
|
383
|
+
LIMIT ?
|
|
384
|
+
`).all(ftsQuery, params.groupFolder, params.userId || '', now, limit);
|
|
385
|
+
const scored = rows.map(row => {
|
|
386
|
+
const ageDays = row.updated_at ? (Date.now() - new Date(row.updated_at).getTime()) / (1000 * 60 * 60 * 24) : 365;
|
|
387
|
+
const recency = Math.exp(-ageDays / 30);
|
|
388
|
+
const bm25Score = 1 / (1 + (row.bm25 || 0));
|
|
389
|
+
const score = (bm25Score * 0.55) + (row.importance * 0.3) + (recency * 0.15);
|
|
390
|
+
return { ...row, score };
|
|
391
|
+
});
|
|
392
|
+
scored.sort((a, b) => b.score - a.score);
|
|
393
|
+
return scored;
|
|
394
|
+
}
|
|
395
|
+
function searchMemoriesFallback(params) {
|
|
396
|
+
const db = getDb();
|
|
397
|
+
const normalizedQuery = normalizeContent(params.query);
|
|
398
|
+
const tokens = normalizedQuery.split(' ').filter(Boolean).slice(0, 10);
|
|
399
|
+
if (tokens.length === 0)
|
|
400
|
+
return [];
|
|
401
|
+
const clauses = tokens.map(() => '(m.normalized LIKE ? OR m.tags_text LIKE ?)');
|
|
402
|
+
const values = [];
|
|
403
|
+
for (const token of tokens) {
|
|
404
|
+
const pattern = `%${token}%`;
|
|
405
|
+
values.push(pattern, pattern);
|
|
406
|
+
}
|
|
407
|
+
const now = new Date().toISOString();
|
|
408
|
+
const limit = Math.min(params.limit || 12, 50);
|
|
409
|
+
const rows = db.prepare(`
|
|
410
|
+
SELECT m.*
|
|
411
|
+
FROM memory_items m
|
|
412
|
+
WHERE ${clauses.join(' AND ')}
|
|
413
|
+
AND (m.group_folder = ? OR m.group_folder = 'global')
|
|
414
|
+
AND (m.scope != 'user' OR m.subject_id = ?)
|
|
415
|
+
AND (m.expires_at IS NULL OR m.expires_at > ?)
|
|
416
|
+
ORDER BY m.updated_at DESC
|
|
417
|
+
LIMIT ?
|
|
418
|
+
`).all(...values, params.groupFolder, params.userId || '', now, limit);
|
|
419
|
+
const scored = rows.map(row => {
|
|
420
|
+
const normalized = row.normalized || '';
|
|
421
|
+
const tagsText = row.tags_text || '';
|
|
422
|
+
let matches = 0;
|
|
423
|
+
for (const token of tokens) {
|
|
424
|
+
if (normalized.includes(token) || tagsText.includes(token)) {
|
|
425
|
+
matches += 1;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const matchScore = tokens.length > 0 ? matches / tokens.length : 0;
|
|
429
|
+
const ageDays = row.updated_at ? (Date.now() - new Date(row.updated_at).getTime()) / (1000 * 60 * 60 * 24) : 365;
|
|
430
|
+
const recency = Math.exp(-ageDays / 30);
|
|
431
|
+
const score = (matchScore * 0.5) + (row.importance * 0.3) + (recency * 0.2);
|
|
432
|
+
return { ...row, bm25: 0, score };
|
|
433
|
+
});
|
|
434
|
+
scored.sort((a, b) => b.score - a.score);
|
|
435
|
+
return scored;
|
|
436
|
+
}
|
|
437
|
+
export function listMemories(params) {
|
|
438
|
+
const db = getDb();
|
|
439
|
+
const clauses = ['group_folder = ?'];
|
|
440
|
+
const values = [params.groupFolder];
|
|
441
|
+
if (params.scope) {
|
|
442
|
+
clauses.push('scope = ?');
|
|
443
|
+
values.push(params.scope);
|
|
444
|
+
}
|
|
445
|
+
if (params.type) {
|
|
446
|
+
clauses.push('type = ?');
|
|
447
|
+
values.push(params.type);
|
|
448
|
+
}
|
|
449
|
+
if (params.scope === 'user') {
|
|
450
|
+
clauses.push('subject_id = ?');
|
|
451
|
+
values.push(params.userId || '');
|
|
452
|
+
}
|
|
453
|
+
const limit = Math.min(params.limit || 50, 200);
|
|
454
|
+
const sql = `
|
|
455
|
+
SELECT * FROM memory_items
|
|
456
|
+
WHERE ${clauses.join(' AND ')}
|
|
457
|
+
ORDER BY updated_at DESC
|
|
458
|
+
LIMIT ?
|
|
459
|
+
`;
|
|
460
|
+
return db.prepare(sql).all(...values, limit);
|
|
461
|
+
}
|
|
462
|
+
export function forgetMemories(params) {
|
|
463
|
+
const db = getDb();
|
|
464
|
+
if (params.ids && params.ids.length > 0) {
|
|
465
|
+
const placeholders = params.ids.map(() => '?').join(',');
|
|
466
|
+
const stmt = db.prepare(`DELETE FROM memory_items WHERE group_folder = ? AND id IN (${placeholders})`);
|
|
467
|
+
const info = stmt.run(params.groupFolder, ...params.ids);
|
|
468
|
+
return info.changes;
|
|
469
|
+
}
|
|
470
|
+
if (params.content) {
|
|
471
|
+
const normalized = normalizeContent(params.content);
|
|
472
|
+
const clauses = ['group_folder = ?', 'normalized = ?'];
|
|
473
|
+
const values = [params.groupFolder, normalized];
|
|
474
|
+
if (params.scope) {
|
|
475
|
+
clauses.push('scope = ?');
|
|
476
|
+
values.push(params.scope);
|
|
477
|
+
}
|
|
478
|
+
if (params.scope === 'user') {
|
|
479
|
+
clauses.push('subject_id = ?');
|
|
480
|
+
values.push(params.userId || '');
|
|
481
|
+
}
|
|
482
|
+
const stmt = db.prepare(`DELETE FROM memory_items WHERE ${clauses.join(' AND ')}`);
|
|
483
|
+
const info = stmt.run(...values);
|
|
484
|
+
return info.changes;
|
|
485
|
+
}
|
|
486
|
+
return 0;
|
|
487
|
+
}
|
|
488
|
+
export function getMemoryStats(params) {
|
|
489
|
+
const db = getDb();
|
|
490
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM memory_items WHERE group_folder = ? OR group_folder = 'global'`)
|
|
491
|
+
.get(params.groupFolder);
|
|
492
|
+
const user = db.prepare(`
|
|
493
|
+
SELECT COUNT(*) as count FROM memory_items
|
|
494
|
+
WHERE group_folder = ? AND scope = 'user' AND subject_id = ?
|
|
495
|
+
`).get(params.groupFolder, params.userId || '');
|
|
496
|
+
const group = db.prepare(`
|
|
497
|
+
SELECT COUNT(*) as count FROM memory_items
|
|
498
|
+
WHERE group_folder = ? AND scope = 'group'
|
|
499
|
+
`).get(params.groupFolder);
|
|
500
|
+
const global = db.prepare(`
|
|
501
|
+
SELECT COUNT(*) as count FROM memory_items
|
|
502
|
+
WHERE group_folder = 'global'
|
|
503
|
+
`).get();
|
|
504
|
+
return {
|
|
505
|
+
total: total?.count || 0,
|
|
506
|
+
user: user?.count || 0,
|
|
507
|
+
group: group?.count || 0,
|
|
508
|
+
global: global?.count || 0
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
export function cleanupExpiredMemories() {
|
|
512
|
+
const db = getDb();
|
|
513
|
+
const now = new Date().toISOString();
|
|
514
|
+
const info = db.prepare(`DELETE FROM memory_items WHERE expires_at IS NOT NULL AND expires_at <= ?`).run(now);
|
|
515
|
+
return info.changes;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Record memory access: update last_accessed_at, increment access_count, and boost importance
|
|
519
|
+
* Called when memories are recalled for context
|
|
520
|
+
*/
|
|
521
|
+
export function recordMemoryAccess(ids) {
|
|
522
|
+
if (ids.length === 0)
|
|
523
|
+
return;
|
|
524
|
+
const db = getDb();
|
|
525
|
+
const now = new Date().toISOString();
|
|
526
|
+
// Boost importance by 2% per access, capped at 1.0
|
|
527
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
528
|
+
db.prepare(`
|
|
529
|
+
UPDATE memory_items
|
|
530
|
+
SET last_accessed_at = ?,
|
|
531
|
+
access_count = COALESCE(access_count, 0) + 1,
|
|
532
|
+
importance = MIN(1.0, importance + 0.02)
|
|
533
|
+
WHERE id IN (${placeholders})
|
|
534
|
+
`).run(now, ...ids);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Decay importance for memories not accessed recently
|
|
538
|
+
* Reduces importance by 1% for memories not accessed in 30+ days (minimum 0.1)
|
|
539
|
+
* Also doesn't decay critical priority memories
|
|
540
|
+
*/
|
|
541
|
+
export function decayUnusedMemories() {
|
|
542
|
+
const db = getDb();
|
|
543
|
+
const info = db.prepare(`
|
|
544
|
+
UPDATE memory_items
|
|
545
|
+
SET importance = MAX(0.1, importance - 0.01)
|
|
546
|
+
WHERE (last_accessed_at IS NULL OR last_accessed_at < datetime('now', '-30 days'))
|
|
547
|
+
AND importance > 0.1
|
|
548
|
+
AND (priority IS NULL OR priority != 'critical')
|
|
549
|
+
`).run();
|
|
550
|
+
return info.changes;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Set priority for a memory item
|
|
554
|
+
* Priority values: 'critical', 'normal', 'low'
|
|
555
|
+
* Critical memories are never pruned
|
|
556
|
+
*/
|
|
557
|
+
export function setMemoryPriority(id, priority) {
|
|
558
|
+
const db = getDb();
|
|
559
|
+
const info = db.prepare(`UPDATE memory_items SET priority = ? WHERE id = ?`).run(priority, id);
|
|
560
|
+
return info.changes > 0;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get memory by ID
|
|
564
|
+
*/
|
|
565
|
+
export function getMemoryById(id) {
|
|
566
|
+
const db = getDb();
|
|
567
|
+
const row = db.prepare(`SELECT * FROM memory_items WHERE id = ?`).get(id);
|
|
568
|
+
return row ? row : null;
|
|
569
|
+
}
|
|
570
|
+
export function pruneMemoryOverflow(params) {
|
|
571
|
+
const db = getDb();
|
|
572
|
+
const maxItems = Math.max(0, Math.floor(params.maxItems));
|
|
573
|
+
if (maxItems <= 0)
|
|
574
|
+
return 0;
|
|
575
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM memory_items`).get();
|
|
576
|
+
if (!total || total.count <= maxItems)
|
|
577
|
+
return 0;
|
|
578
|
+
let remaining = total.count - maxItems;
|
|
579
|
+
let removed = 0;
|
|
580
|
+
// First pass: delete low-importance items (excluding critical priority)
|
|
581
|
+
const deleteByThreshold = db.prepare(`
|
|
582
|
+
DELETE FROM memory_items
|
|
583
|
+
WHERE id IN (
|
|
584
|
+
SELECT id FROM memory_items
|
|
585
|
+
WHERE importance <= ?
|
|
586
|
+
AND (priority IS NULL OR priority != 'critical')
|
|
587
|
+
ORDER BY importance ASC, updated_at ASC
|
|
588
|
+
LIMIT ?
|
|
589
|
+
)
|
|
590
|
+
`);
|
|
591
|
+
const infoThreshold = deleteByThreshold.run(params.importanceThreshold, remaining);
|
|
592
|
+
removed += infoThreshold.changes;
|
|
593
|
+
remaining -= infoThreshold.changes;
|
|
594
|
+
// Second pass: delete by overflow (still excluding critical)
|
|
595
|
+
if (remaining > 0) {
|
|
596
|
+
const deleteByOverflow = db.prepare(`
|
|
597
|
+
DELETE FROM memory_items
|
|
598
|
+
WHERE id IN (
|
|
599
|
+
SELECT id FROM memory_items
|
|
600
|
+
WHERE (priority IS NULL OR priority != 'critical')
|
|
601
|
+
ORDER BY importance ASC, updated_at ASC
|
|
602
|
+
LIMIT ?
|
|
603
|
+
)
|
|
604
|
+
`);
|
|
605
|
+
const infoOverflow = deleteByOverflow.run(remaining);
|
|
606
|
+
removed += infoOverflow.changes;
|
|
607
|
+
}
|
|
608
|
+
return removed;
|
|
609
|
+
}
|
|
610
|
+
export function runMemoryMaintenance() {
|
|
611
|
+
const db = getDb();
|
|
612
|
+
const expired = cleanupExpiredMemories();
|
|
613
|
+
// Decay importance for unused memories
|
|
614
|
+
const decayed = decayUnusedMemories();
|
|
615
|
+
const runtime = loadRuntimeConfig();
|
|
616
|
+
const maintenance = runtime.host.memory.maintenance;
|
|
617
|
+
const maxItems = maintenance.maxItems;
|
|
618
|
+
const threshold = maintenance.pruneImportanceThreshold;
|
|
619
|
+
const pruned = pruneMemoryOverflow({ maxItems, importanceThreshold: threshold });
|
|
620
|
+
const shouldVacuum = maintenance.vacuumEnabled;
|
|
621
|
+
const vacuumIntervalDays = maintenance.vacuumIntervalDays;
|
|
622
|
+
const analyzeEnabled = maintenance.analyzeEnabled;
|
|
623
|
+
let vacuumed = false;
|
|
624
|
+
let analyzed = false;
|
|
625
|
+
if ((expired + pruned) > 0 && shouldVacuum && vacuumIntervalDays > 0) {
|
|
626
|
+
const now = Date.now();
|
|
627
|
+
const lastVacuumRaw = getMemoryMeta(db, 'last_vacuum_at');
|
|
628
|
+
const lastVacuum = lastVacuumRaw ? Date.parse(lastVacuumRaw) : 0;
|
|
629
|
+
const intervalMs = vacuumIntervalDays * 24 * 60 * 60 * 1000;
|
|
630
|
+
if (!Number.isFinite(lastVacuum) || (now - lastVacuum) >= intervalMs) {
|
|
631
|
+
try {
|
|
632
|
+
db.exec('VACUUM');
|
|
633
|
+
setMemoryMeta(db, 'last_vacuum_at', new Date(now).toISOString());
|
|
634
|
+
vacuumed = true;
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
// ignore vacuum errors
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if ((expired + pruned) > 0 && analyzeEnabled) {
|
|
642
|
+
try {
|
|
643
|
+
db.exec('ANALYZE');
|
|
644
|
+
analyzed = true;
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
// ignore analyze errors
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return { expired, pruned, decayed, vacuumed, analyzed };
|
|
651
|
+
}
|
|
652
|
+
export function buildUserProfile(params) {
|
|
653
|
+
if (!params.userId)
|
|
654
|
+
return null;
|
|
655
|
+
const db = getDb();
|
|
656
|
+
const limit = Math.min(params.limit || 8, 20);
|
|
657
|
+
const rows = db.prepare(`
|
|
658
|
+
SELECT content, type
|
|
659
|
+
FROM memory_items
|
|
660
|
+
WHERE group_folder = ? AND scope = 'user' AND subject_id = ?
|
|
661
|
+
AND type IN ('identity', 'preference', 'relationship', 'project')
|
|
662
|
+
ORDER BY importance DESC, updated_at DESC
|
|
663
|
+
LIMIT ?
|
|
664
|
+
`).all(params.groupFolder, params.userId, limit);
|
|
665
|
+
if (rows.length === 0)
|
|
666
|
+
return null;
|
|
667
|
+
return rows.map(row => `- (${row.type}) ${row.content}`).join('\n');
|
|
668
|
+
}
|
|
669
|
+
export function listMemoriesMissingEmbeddings(params) {
|
|
670
|
+
const db = getDb();
|
|
671
|
+
const limit = Math.min(params.limit || 100, 1000);
|
|
672
|
+
if (params.groupFolder) {
|
|
673
|
+
return db.prepare(`
|
|
674
|
+
SELECT id, content, group_folder
|
|
675
|
+
FROM memory_items
|
|
676
|
+
WHERE group_folder = ?
|
|
677
|
+
AND content IS NOT NULL
|
|
678
|
+
AND (embedding_json IS NULL OR embedding_json = '')
|
|
679
|
+
ORDER BY updated_at DESC
|
|
680
|
+
LIMIT ?
|
|
681
|
+
`).all(params.groupFolder, limit);
|
|
682
|
+
}
|
|
683
|
+
return db.prepare(`
|
|
684
|
+
SELECT id, content, group_folder
|
|
685
|
+
FROM memory_items
|
|
686
|
+
WHERE content IS NOT NULL
|
|
687
|
+
AND (embedding_json IS NULL OR embedding_json = '')
|
|
688
|
+
ORDER BY updated_at DESC
|
|
689
|
+
LIMIT ?
|
|
690
|
+
`).all(limit);
|
|
691
|
+
}
|
|
692
|
+
export function countMemoriesMissingEmbeddings(params) {
|
|
693
|
+
const db = getDb();
|
|
694
|
+
if (params.groupFolder) {
|
|
695
|
+
const row = db.prepare(`
|
|
696
|
+
SELECT COUNT(*) as count
|
|
697
|
+
FROM memory_items
|
|
698
|
+
WHERE group_folder = ?
|
|
699
|
+
AND content IS NOT NULL
|
|
700
|
+
AND (embedding_json IS NULL OR embedding_json = '')
|
|
701
|
+
`).get(params.groupFolder);
|
|
702
|
+
return row?.count ? Number(row.count) : 0;
|
|
703
|
+
}
|
|
704
|
+
const row = db.prepare(`
|
|
705
|
+
SELECT COUNT(*) as count
|
|
706
|
+
FROM memory_items
|
|
707
|
+
WHERE content IS NOT NULL
|
|
708
|
+
AND (embedding_json IS NULL OR embedding_json = '')
|
|
709
|
+
`).get();
|
|
710
|
+
return row?.count ? Number(row.count) : 0;
|
|
711
|
+
}
|
|
712
|
+
export function updateMemoryEmbedding(params) {
|
|
713
|
+
const db = getDb();
|
|
714
|
+
const now = new Date().toISOString();
|
|
715
|
+
const embeddingJson = JSON.stringify(params.embedding);
|
|
716
|
+
db.prepare(`
|
|
717
|
+
UPDATE memory_items
|
|
718
|
+
SET embedding_json = ?, embedding_model = ?, embedding_updated_at = ?
|
|
719
|
+
WHERE id = ?
|
|
720
|
+
`).run(embeddingJson, params.model, now, params.id);
|
|
721
|
+
}
|
|
722
|
+
export function listEmbeddedMemories(params) {
|
|
723
|
+
const db = getDb();
|
|
724
|
+
const limit = Math.min(params.limit || 2000, 5000);
|
|
725
|
+
const now = new Date().toISOString();
|
|
726
|
+
return db.prepare(`
|
|
727
|
+
SELECT id, content, type, importance, updated_at, embedding_json
|
|
728
|
+
FROM memory_items
|
|
729
|
+
WHERE (group_folder = ? OR group_folder = 'global')
|
|
730
|
+
AND (scope != 'user' OR subject_id = ?)
|
|
731
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
732
|
+
AND embedding_json IS NOT NULL
|
|
733
|
+
ORDER BY updated_at DESC
|
|
734
|
+
LIMIT ?
|
|
735
|
+
`).all(params.groupFolder, params.userId || '', now, limit);
|
|
736
|
+
}
|
|
737
|
+
export function listPreferenceMemories(params) {
|
|
738
|
+
const db = getDb();
|
|
739
|
+
const limit = Math.min(params.limit || 50, 200);
|
|
740
|
+
if (params.userId) {
|
|
741
|
+
const rows = db.prepare(`
|
|
742
|
+
SELECT conflict_key, content, tags_json, metadata_json, scope, subject_id, updated_at, importance
|
|
743
|
+
FROM memory_items
|
|
744
|
+
WHERE (group_folder = ? OR group_folder = 'global')
|
|
745
|
+
AND type = 'preference'
|
|
746
|
+
AND conflict_key IS NOT NULL
|
|
747
|
+
AND (
|
|
748
|
+
(scope = 'user' AND subject_id = ?)
|
|
749
|
+
OR scope = 'group'
|
|
750
|
+
OR scope = 'global'
|
|
751
|
+
)
|
|
752
|
+
ORDER BY updated_at DESC
|
|
753
|
+
LIMIT ?
|
|
754
|
+
`).all(params.groupFolder, params.userId, limit);
|
|
755
|
+
return rows.map(row => ({
|
|
756
|
+
conflict_key: String(row.conflict_key),
|
|
757
|
+
content: row.content ? String(row.content) : '',
|
|
758
|
+
tags: parseJsonArray(row.tags_json ? String(row.tags_json) : null),
|
|
759
|
+
metadata: parseJsonRecord(row.metadata_json ? String(row.metadata_json) : null),
|
|
760
|
+
scope: row.scope === 'user' || row.scope === 'group' || row.scope === 'global' ? row.scope : 'group',
|
|
761
|
+
subject_id: row.subject_id ? String(row.subject_id) : null,
|
|
762
|
+
updated_at: row.updated_at ? String(row.updated_at) : new Date(0).toISOString(),
|
|
763
|
+
importance: typeof row.importance === 'number' ? row.importance : Number(row.importance || 0)
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
const rows = db.prepare(`
|
|
767
|
+
SELECT conflict_key, content, tags_json, metadata_json, scope, subject_id, updated_at, importance
|
|
768
|
+
FROM memory_items
|
|
769
|
+
WHERE (group_folder = ? OR group_folder = 'global')
|
|
770
|
+
AND type = 'preference'
|
|
771
|
+
AND conflict_key IS NOT NULL
|
|
772
|
+
AND (scope = 'group' OR scope = 'global')
|
|
773
|
+
ORDER BY updated_at DESC
|
|
774
|
+
LIMIT ?
|
|
775
|
+
`).all(params.groupFolder, limit);
|
|
776
|
+
return rows.map(row => ({
|
|
777
|
+
conflict_key: String(row.conflict_key),
|
|
778
|
+
content: row.content ? String(row.content) : '',
|
|
779
|
+
tags: parseJsonArray(row.tags_json ? String(row.tags_json) : null),
|
|
780
|
+
metadata: parseJsonRecord(row.metadata_json ? String(row.metadata_json) : null),
|
|
781
|
+
scope: row.scope === 'user' || row.scope === 'group' || row.scope === 'global' ? row.scope : 'group',
|
|
782
|
+
subject_id: row.subject_id ? String(row.subject_id) : null,
|
|
783
|
+
updated_at: row.updated_at ? String(row.updated_at) : new Date(0).toISOString(),
|
|
784
|
+
importance: typeof row.importance === 'number' ? row.importance : Number(row.importance || 0)
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
787
|
+
//# sourceMappingURL=memory-store.js.map
|