@siduri-x/core 1.0.9 → 2.0.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/dist/action-policy.d.ts +28 -1
- package/dist/action-policy.js +167 -7
- package/dist/action-policy.test.js +104 -0
- package/dist/adversarial.test.js +4 -2
- package/dist/architecture-boundary.test.js +4 -12
- package/dist/capability.d.ts +11 -2
- package/dist/capability.js +8 -1
- package/dist/capability.test.js +43 -1
- package/dist/context-retriever.d.ts +8 -5
- package/dist/context-retriever.js +44 -15
- package/dist/database.d.ts +1 -0
- package/dist/database.js +17 -0
- package/dist/index.d.ts +46 -7
- package/dist/index.js +2 -0
- package/dist/prompt-compiler.d.ts +1 -0
- package/dist/prompt-compiler.js +7 -1
- package/dist/runtime.d.ts +12 -5
- package/dist/runtime.js +26 -8
- package/dist/schema-validator.test.js +2 -2
- package/dist/siduri-db.d.ts +138 -0
- package/dist/siduri-db.js +687 -0
- package/dist/siduri-db.test.d.ts +1 -0
- package/dist/siduri-db.test.js +863 -0
- package/dist/sqlite-action-store.d.ts +3 -2
- package/dist/sqlite-action-store.js +27 -4
- package/dist/sqlite-action-store.test.js +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SiduriDatabase = void 0;
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
7
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
8
|
+
class SiduriDatabase {
|
|
9
|
+
db;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
const dbPath = options.dbPath || ':memory:';
|
|
12
|
+
this.db = new DatabaseSync(dbPath);
|
|
13
|
+
this.db.exec('PRAGMA journal_mode = WAL');
|
|
14
|
+
this.initSchema();
|
|
15
|
+
}
|
|
16
|
+
initSchema() {
|
|
17
|
+
const schema = `
|
|
18
|
+
-- Self Tables
|
|
19
|
+
CREATE TABLE IF NOT EXISTS self_identity (
|
|
20
|
+
companion_id TEXT PRIMARY KEY,
|
|
21
|
+
name TEXT NOT NULL,
|
|
22
|
+
archetype TEXT,
|
|
23
|
+
version TEXT NOT NULL,
|
|
24
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS self_personality (
|
|
28
|
+
companion_id TEXT PRIMARY KEY,
|
|
29
|
+
warmth REAL DEFAULT 0.5,
|
|
30
|
+
formality REAL DEFAULT 0.5,
|
|
31
|
+
sarcasm REAL DEFAULT 0.5,
|
|
32
|
+
verbosity REAL DEFAULT 0.5,
|
|
33
|
+
curiosity REAL DEFAULT 0.5,
|
|
34
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE TABLE IF NOT EXISTS self_directives (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
companion_id TEXT NOT NULL,
|
|
40
|
+
priority INTEGER DEFAULT 50,
|
|
41
|
+
directive TEXT NOT NULL,
|
|
42
|
+
status TEXT DEFAULT 'ACTIVE',
|
|
43
|
+
category TEXT DEFAULT 'behavioral',
|
|
44
|
+
supersedes_id TEXT,
|
|
45
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS self_relationships (
|
|
49
|
+
companion_id TEXT NOT NULL,
|
|
50
|
+
entity_id TEXT NOT NULL,
|
|
51
|
+
entity_type TEXT NOT NULL,
|
|
52
|
+
trust_score REAL DEFAULT 0.5,
|
|
53
|
+
familiarity REAL DEFAULT 0.5,
|
|
54
|
+
interaction_conventions TEXT,
|
|
55
|
+
PRIMARY KEY(companion_id, entity_id)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- Knowledge Tables
|
|
59
|
+
CREATE TABLE IF NOT EXISTS life_inventory (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
companion_id TEXT NOT NULL,
|
|
62
|
+
domain TEXT NOT NULL,
|
|
63
|
+
entity_name TEXT NOT NULL,
|
|
64
|
+
properties TEXT NOT NULL,
|
|
65
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS life_finance (
|
|
69
|
+
id TEXT PRIMARY KEY,
|
|
70
|
+
companion_id TEXT NOT NULL,
|
|
71
|
+
category TEXT NOT NULL,
|
|
72
|
+
amount REAL NOT NULL,
|
|
73
|
+
currency TEXT DEFAULT 'USD',
|
|
74
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
75
|
+
metadata TEXT
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CREATE TABLE IF NOT EXISTS life_schedule (
|
|
79
|
+
id TEXT PRIMARY KEY,
|
|
80
|
+
companion_id TEXT NOT NULL,
|
|
81
|
+
title TEXT NOT NULL,
|
|
82
|
+
start_time TEXT NOT NULL,
|
|
83
|
+
end_time TEXT,
|
|
84
|
+
is_recurring INTEGER DEFAULT 0,
|
|
85
|
+
status TEXT DEFAULT 'active'
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
CREATE TABLE IF NOT EXISTS life_preferences (
|
|
89
|
+
id TEXT PRIMARY KEY,
|
|
90
|
+
companion_id TEXT NOT NULL,
|
|
91
|
+
preference_key TEXT NOT NULL,
|
|
92
|
+
preference_value TEXT NOT NULL,
|
|
93
|
+
category TEXT NOT NULL,
|
|
94
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
-- Memory Tables
|
|
98
|
+
CREATE TABLE IF NOT EXISTS memory_events (
|
|
99
|
+
id TEXT PRIMARY KEY,
|
|
100
|
+
companion_id TEXT NOT NULL,
|
|
101
|
+
source_type TEXT NOT NULL,
|
|
102
|
+
occurred_at TEXT DEFAULT (datetime('now')),
|
|
103
|
+
payload TEXT NOT NULL
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS memory_claims (
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
companion_id TEXT NOT NULL,
|
|
109
|
+
subject TEXT NOT NULL,
|
|
110
|
+
predicate TEXT NOT NULL,
|
|
111
|
+
value TEXT NOT NULL,
|
|
112
|
+
status TEXT DEFAULT 'PENDING',
|
|
113
|
+
confidence REAL DEFAULT 1.0,
|
|
114
|
+
valid_from TEXT,
|
|
115
|
+
valid_until TEXT,
|
|
116
|
+
evidence TEXT,
|
|
117
|
+
asserted_at TEXT DEFAULT (datetime('now')),
|
|
118
|
+
supersedes TEXT,
|
|
119
|
+
source_event_id TEXT
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
-- FTS5 Virtual Table for Memory Claims
|
|
123
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_search USING fts5(
|
|
124
|
+
subject,
|
|
125
|
+
predicate,
|
|
126
|
+
value,
|
|
127
|
+
content='memory_claims',
|
|
128
|
+
content_rowid='rowid'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
-- FTS5 Triggers
|
|
132
|
+
CREATE TRIGGER IF NOT EXISTS memory_claims_ai AFTER INSERT ON memory_claims BEGIN
|
|
133
|
+
INSERT INTO memory_search(rowid, subject, predicate, value)
|
|
134
|
+
VALUES (new.rowid, new.subject, new.predicate, new.value);
|
|
135
|
+
END;
|
|
136
|
+
|
|
137
|
+
CREATE TRIGGER IF NOT EXISTS memory_claims_ad AFTER DELETE ON memory_claims BEGIN
|
|
138
|
+
INSERT INTO memory_search(memory_search, rowid, subject, predicate, value)
|
|
139
|
+
VALUES('delete', old.rowid, old.subject, old.predicate, old.value);
|
|
140
|
+
END;
|
|
141
|
+
|
|
142
|
+
CREATE TRIGGER IF NOT EXISTS memory_claims_au AFTER UPDATE ON memory_claims BEGIN
|
|
143
|
+
INSERT INTO memory_search(memory_search, rowid, subject, predicate, value)
|
|
144
|
+
VALUES('delete', old.rowid, old.subject, old.predicate, old.value);
|
|
145
|
+
INSERT INTO memory_search(rowid, subject, predicate, value)
|
|
146
|
+
VALUES (new.rowid, new.subject, new.predicate, new.value);
|
|
147
|
+
END;
|
|
148
|
+
`;
|
|
149
|
+
this.db.exec(schema);
|
|
150
|
+
try {
|
|
151
|
+
this.db.exec("ALTER TABLE memory_claims ADD COLUMN supersedes TEXT");
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Column already exists
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
this.db.exec("ALTER TABLE memory_claims ADD COLUMN source_event_id TEXT");
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Column already exists
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
close() {
|
|
164
|
+
this.db.close();
|
|
165
|
+
}
|
|
166
|
+
// ==========================================
|
|
167
|
+
// Self Domain Methods
|
|
168
|
+
// ==========================================
|
|
169
|
+
getIdentity(companionId) {
|
|
170
|
+
const stmt = this.db.prepare('SELECT * FROM self_identity WHERE companion_id = ?');
|
|
171
|
+
const row = stmt.get(companionId);
|
|
172
|
+
if (!row)
|
|
173
|
+
return undefined;
|
|
174
|
+
return {
|
|
175
|
+
companionId: row.companion_id,
|
|
176
|
+
name: row.name,
|
|
177
|
+
archetype: row.archetype || undefined,
|
|
178
|
+
version: row.version,
|
|
179
|
+
updatedAt: row.updated_at
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
setIdentity(identity) {
|
|
183
|
+
const stmt = this.db.prepare(`
|
|
184
|
+
INSERT INTO self_identity (companion_id, name, archetype, version, updated_at)
|
|
185
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
186
|
+
ON CONFLICT(companion_id) DO UPDATE SET
|
|
187
|
+
name = excluded.name,
|
|
188
|
+
archetype = excluded.archetype,
|
|
189
|
+
version = excluded.version,
|
|
190
|
+
updated_at = datetime('now')
|
|
191
|
+
`);
|
|
192
|
+
stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.version);
|
|
193
|
+
}
|
|
194
|
+
getPersonality(companionId) {
|
|
195
|
+
const stmt = this.db.prepare('SELECT * FROM self_personality WHERE companion_id = ?');
|
|
196
|
+
const row = stmt.get(companionId);
|
|
197
|
+
if (!row)
|
|
198
|
+
return undefined;
|
|
199
|
+
return {
|
|
200
|
+
warmth: row.warmth,
|
|
201
|
+
formality: row.formality,
|
|
202
|
+
sarcasm: row.sarcasm,
|
|
203
|
+
verbosity: row.verbosity,
|
|
204
|
+
curiosity: row.curiosity
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
setPersonality(companionId, traits) {
|
|
208
|
+
const stmt = this.db.prepare(`
|
|
209
|
+
INSERT INTO self_personality (companion_id, warmth, formality, sarcasm, verbosity, curiosity, updated_at)
|
|
210
|
+
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
|
211
|
+
ON CONFLICT(companion_id) DO UPDATE SET
|
|
212
|
+
warmth = excluded.warmth,
|
|
213
|
+
formality = excluded.formality,
|
|
214
|
+
sarcasm = excluded.sarcasm,
|
|
215
|
+
verbosity = excluded.verbosity,
|
|
216
|
+
curiosity = excluded.curiosity,
|
|
217
|
+
updated_at = datetime('now')
|
|
218
|
+
`);
|
|
219
|
+
stmt.run(companionId, traits.warmth, traits.formality, traits.sarcasm, traits.verbosity, traits.curiosity);
|
|
220
|
+
}
|
|
221
|
+
getActiveDirectives(companionId) {
|
|
222
|
+
const stmt = this.db.prepare(`
|
|
223
|
+
SELECT * FROM self_directives
|
|
224
|
+
WHERE companion_id = ? AND status = 'ACTIVE'
|
|
225
|
+
ORDER BY priority DESC, created_at ASC
|
|
226
|
+
`);
|
|
227
|
+
return stmt.all(companionId).map((row) => ({
|
|
228
|
+
id: row.id,
|
|
229
|
+
companionId: row.companion_id,
|
|
230
|
+
priority: row.priority,
|
|
231
|
+
directive: row.directive,
|
|
232
|
+
status: row.status,
|
|
233
|
+
category: row.category,
|
|
234
|
+
supersedesId: row.supersedes_id || undefined,
|
|
235
|
+
createdAt: row.created_at
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
commitDirective(directive) {
|
|
239
|
+
const stmt = this.db.prepare(`
|
|
240
|
+
INSERT INTO self_directives (id, companion_id, priority, directive, status, category, supersedes_id, created_at)
|
|
241
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
242
|
+
`);
|
|
243
|
+
stmt.run(directive.id, directive.companionId, directive.priority, directive.directive, directive.status, directive.category, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
|
|
244
|
+
}
|
|
245
|
+
getDirective(id, companionId) {
|
|
246
|
+
const stmt = companionId
|
|
247
|
+
? this.db.prepare('SELECT * FROM self_directives WHERE id = ? AND companion_id = ?')
|
|
248
|
+
: this.db.prepare('SELECT * FROM self_directives WHERE id = ?');
|
|
249
|
+
const row = (companionId ? stmt.get(id, companionId) : stmt.get(id));
|
|
250
|
+
if (!row)
|
|
251
|
+
return undefined;
|
|
252
|
+
return {
|
|
253
|
+
id: row.id,
|
|
254
|
+
companionId: row.companion_id,
|
|
255
|
+
priority: row.priority,
|
|
256
|
+
directive: row.directive,
|
|
257
|
+
status: row.status,
|
|
258
|
+
category: row.category,
|
|
259
|
+
supersedesId: row.supersedes_id || undefined,
|
|
260
|
+
createdAt: row.created_at,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
approveDirective(id, companionId) {
|
|
264
|
+
const findStmt = companionId
|
|
265
|
+
? this.db.prepare("SELECT id, companion_id, status, supersedes_id FROM self_directives WHERE id = ? AND companion_id = ?")
|
|
266
|
+
: this.db.prepare("SELECT id, companion_id, status, supersedes_id FROM self_directives WHERE id = ?");
|
|
267
|
+
const row = (companionId ? findStmt.get(id, companionId) : findStmt.get(id));
|
|
268
|
+
if (!row) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (row.status !== 'PENDING') {
|
|
272
|
+
throw new Error(`Cannot approve directive '${id}': invalid transition from status '${row.status}' to 'ACTIVE' (only PENDING directives can be approved)`);
|
|
273
|
+
}
|
|
274
|
+
// If this directive supersedes an earlier directive, transition that prior directive to SUPERSEDED
|
|
275
|
+
if (row.supersedes_id) {
|
|
276
|
+
const supersededId = row.supersedes_id;
|
|
277
|
+
const effectiveCompanionId = companionId || row.companion_id;
|
|
278
|
+
if (effectiveCompanionId) {
|
|
279
|
+
const supersedeStmt = this.db.prepare("UPDATE self_directives SET status = 'SUPERSEDED' WHERE id = ? AND companion_id = ?");
|
|
280
|
+
supersedeStmt.run(supersededId, effectiveCompanionId);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
const supersedeStmt = this.db.prepare("UPDATE self_directives SET status = 'SUPERSEDED' WHERE id = ?");
|
|
284
|
+
supersedeStmt.run(supersededId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (companionId) {
|
|
288
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'ACTIVE' WHERE id = ? AND companion_id = ? AND status = 'PENDING'");
|
|
289
|
+
stmt.run(id, companionId);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'ACTIVE' WHERE id = ? AND status = 'PENDING'");
|
|
293
|
+
stmt.run(id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
rejectDirective(id, companionId) {
|
|
297
|
+
const findStmt = companionId
|
|
298
|
+
? this.db.prepare("SELECT id, companion_id, status FROM self_directives WHERE id = ? AND companion_id = ?")
|
|
299
|
+
: this.db.prepare("SELECT id, companion_id, status FROM self_directives WHERE id = ?");
|
|
300
|
+
const row = (companionId ? findStmt.get(id, companionId) : findStmt.get(id));
|
|
301
|
+
if (!row) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (row.status !== 'PENDING') {
|
|
305
|
+
throw new Error(`Cannot reject directive '${id}': invalid transition from status '${row.status}' to 'REJECTED' (only PENDING directives can be rejected)`);
|
|
306
|
+
}
|
|
307
|
+
if (companionId) {
|
|
308
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'REJECTED' WHERE id = ? AND companion_id = ? AND status = 'PENDING'");
|
|
309
|
+
stmt.run(id, companionId);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'REJECTED' WHERE id = ? AND status = 'PENDING'");
|
|
313
|
+
stmt.run(id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
revokeDirective(id, companionId) {
|
|
317
|
+
if (companionId) {
|
|
318
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'REVOKED' WHERE id = ? AND companion_id = ?");
|
|
319
|
+
stmt.run(id, companionId);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'REVOKED' WHERE id = ?");
|
|
323
|
+
stmt.run(id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
expireDirective(id, companionId) {
|
|
327
|
+
if (companionId) {
|
|
328
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'EXPIRED' WHERE id = ? AND companion_id = ?");
|
|
329
|
+
stmt.run(id, companionId);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'EXPIRED' WHERE id = ?");
|
|
333
|
+
stmt.run(id);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
disableDirective(id, companionId) {
|
|
337
|
+
if (companionId) {
|
|
338
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'DISABLED' WHERE id = ? AND companion_id = ?");
|
|
339
|
+
stmt.run(id, companionId);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const stmt = this.db.prepare("UPDATE self_directives SET status = 'DISABLED' WHERE id = ?");
|
|
343
|
+
stmt.run(id);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
getRelationship(companionId, entityId) {
|
|
347
|
+
const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?');
|
|
348
|
+
const row = stmt.get(companionId, entityId);
|
|
349
|
+
if (!row)
|
|
350
|
+
return undefined;
|
|
351
|
+
return {
|
|
352
|
+
companionId: row.companion_id,
|
|
353
|
+
entityId: row.entity_id,
|
|
354
|
+
entityType: row.entity_type,
|
|
355
|
+
trustScore: row.trust_score,
|
|
356
|
+
familiarity: row.familiarity,
|
|
357
|
+
interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : []
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
upsertRelationship(rel) {
|
|
361
|
+
const stmt = this.db.prepare(`
|
|
362
|
+
INSERT INTO self_relationships (companion_id, entity_id, entity_type, trust_score, familiarity, interaction_conventions)
|
|
363
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
364
|
+
ON CONFLICT(companion_id, entity_id) DO UPDATE SET
|
|
365
|
+
entity_type = excluded.entity_type,
|
|
366
|
+
trust_score = excluded.trust_score,
|
|
367
|
+
familiarity = excluded.familiarity,
|
|
368
|
+
interaction_conventions = excluded.interaction_conventions
|
|
369
|
+
`);
|
|
370
|
+
stmt.run(rel.companionId, rel.entityId, rel.entityType, rel.trustScore, rel.familiarity, JSON.stringify(rel.interactionConventions || []));
|
|
371
|
+
}
|
|
372
|
+
// ==========================================
|
|
373
|
+
// Knowledge / Life DB Methods
|
|
374
|
+
// ==========================================
|
|
375
|
+
getInventory(companionId, domain) {
|
|
376
|
+
let stmt;
|
|
377
|
+
let rows;
|
|
378
|
+
if (domain) {
|
|
379
|
+
stmt = this.db.prepare('SELECT * FROM life_inventory WHERE companion_id = ? AND domain = ?');
|
|
380
|
+
rows = stmt.all(companionId, domain);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
stmt = this.db.prepare('SELECT * FROM life_inventory WHERE companion_id = ?');
|
|
384
|
+
rows = stmt.all(companionId);
|
|
385
|
+
}
|
|
386
|
+
return rows.map((row) => ({
|
|
387
|
+
id: row.id,
|
|
388
|
+
companionId: row.companion_id,
|
|
389
|
+
domain: row.domain,
|
|
390
|
+
entityName: row.entity_name,
|
|
391
|
+
properties: JSON.parse(row.properties),
|
|
392
|
+
updatedAt: row.updated_at
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
upsertInventoryItem(item) {
|
|
396
|
+
const stmt = this.db.prepare(`
|
|
397
|
+
INSERT INTO life_inventory (id, companion_id, domain, entity_name, properties, updated_at)
|
|
398
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))
|
|
399
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
400
|
+
domain = excluded.domain,
|
|
401
|
+
entity_name = excluded.entity_name,
|
|
402
|
+
properties = excluded.properties,
|
|
403
|
+
updated_at = datetime('now')
|
|
404
|
+
`);
|
|
405
|
+
stmt.run(item.id, item.companionId, item.domain, item.entityName, JSON.stringify(item.properties));
|
|
406
|
+
}
|
|
407
|
+
getFinanceEntries(companionId, limit = 50) {
|
|
408
|
+
const stmt = this.db.prepare('SELECT * FROM life_finance WHERE companion_id = ? ORDER BY timestamp DESC LIMIT ?');
|
|
409
|
+
return stmt.all(companionId, limit).map((row) => ({
|
|
410
|
+
id: row.id,
|
|
411
|
+
companionId: row.companion_id,
|
|
412
|
+
category: row.category,
|
|
413
|
+
amount: row.amount,
|
|
414
|
+
currency: row.currency,
|
|
415
|
+
timestamp: row.timestamp,
|
|
416
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
addFinanceEntry(entry) {
|
|
420
|
+
const stmt = this.db.prepare(`
|
|
421
|
+
INSERT INTO life_finance (id, companion_id, category, amount, currency, timestamp, metadata)
|
|
422
|
+
VALUES (?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?)
|
|
423
|
+
`);
|
|
424
|
+
stmt.run(entry.id, entry.companionId, entry.category, entry.amount, entry.currency || 'USD', entry.timestamp || null, entry.metadata ? JSON.stringify(entry.metadata) : null);
|
|
425
|
+
}
|
|
426
|
+
getSchedule(companionId) {
|
|
427
|
+
const stmt = this.db.prepare('SELECT * FROM life_schedule WHERE companion_id = ? AND status = ? ORDER BY start_time ASC');
|
|
428
|
+
return stmt.all(companionId, 'active').map((row) => ({
|
|
429
|
+
id: row.id,
|
|
430
|
+
companionId: row.companion_id,
|
|
431
|
+
title: row.title,
|
|
432
|
+
startTime: row.start_time,
|
|
433
|
+
endTime: row.end_time || undefined,
|
|
434
|
+
isRecurring: row.is_recurring === 1,
|
|
435
|
+
status: row.status
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
upsertScheduleItem(item) {
|
|
439
|
+
const stmt = this.db.prepare(`
|
|
440
|
+
INSERT INTO life_schedule (id, companion_id, title, start_time, end_time, is_recurring, status)
|
|
441
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
442
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
443
|
+
title = excluded.title,
|
|
444
|
+
start_time = excluded.start_time,
|
|
445
|
+
end_time = excluded.end_time,
|
|
446
|
+
is_recurring = excluded.is_recurring,
|
|
447
|
+
status = excluded.status
|
|
448
|
+
`);
|
|
449
|
+
stmt.run(item.id, item.companionId, item.title, item.startTime, item.endTime || null, item.isRecurring ? 1 : 0, item.status || 'active');
|
|
450
|
+
}
|
|
451
|
+
getPreferences(companionId) {
|
|
452
|
+
const stmt = this.db.prepare('SELECT * FROM life_preferences WHERE companion_id = ?');
|
|
453
|
+
return stmt.all(companionId).map((row) => ({
|
|
454
|
+
id: row.id,
|
|
455
|
+
companionId: row.companion_id,
|
|
456
|
+
preferenceKey: row.preference_key,
|
|
457
|
+
preferenceValue: row.preference_value,
|
|
458
|
+
category: row.category,
|
|
459
|
+
updatedAt: row.updated_at
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
upsertPreference(pref) {
|
|
463
|
+
const stmt = this.db.prepare(`
|
|
464
|
+
INSERT INTO life_preferences (id, companion_id, preference_key, preference_value, category, updated_at)
|
|
465
|
+
VALUES (?, ?, ?, ?, ?, datetime('now'))
|
|
466
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
467
|
+
preference_key = excluded.preference_key,
|
|
468
|
+
preference_value = excluded.preference_value,
|
|
469
|
+
category = excluded.category,
|
|
470
|
+
updated_at = datetime('now')
|
|
471
|
+
`);
|
|
472
|
+
stmt.run(pref.id, pref.companionId, pref.preferenceKey, pref.preferenceValue, pref.category);
|
|
473
|
+
}
|
|
474
|
+
// ==========================================
|
|
475
|
+
// Memory Domain Methods
|
|
476
|
+
// ==========================================
|
|
477
|
+
recordEvent(event) {
|
|
478
|
+
const stmt = this.db.prepare(`
|
|
479
|
+
INSERT INTO memory_events (id, companion_id, source_type, occurred_at, payload)
|
|
480
|
+
VALUES (?, ?, ?, coalesce(?, datetime('now')), ?)
|
|
481
|
+
`);
|
|
482
|
+
stmt.run(event.id, event.companionId, event.sourceType, event.occurredAt || null, JSON.stringify(event.payload));
|
|
483
|
+
}
|
|
484
|
+
getRecentEvents(companionId, limit = 50) {
|
|
485
|
+
const stmt = this.db.prepare('SELECT * FROM memory_events WHERE companion_id = ? ORDER BY occurred_at DESC LIMIT ?');
|
|
486
|
+
return stmt.all(companionId, limit).map((row) => ({
|
|
487
|
+
id: row.id,
|
|
488
|
+
companionId: row.companion_id,
|
|
489
|
+
sourceType: row.source_type,
|
|
490
|
+
occurredAt: row.occurred_at,
|
|
491
|
+
payload: JSON.parse(row.payload)
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
getEvent(id) {
|
|
495
|
+
const stmt = this.db.prepare('SELECT * FROM memory_events WHERE id = ?');
|
|
496
|
+
const row = stmt.get(id);
|
|
497
|
+
if (!row)
|
|
498
|
+
return undefined;
|
|
499
|
+
return {
|
|
500
|
+
id: row.id,
|
|
501
|
+
companionId: row.companion_id,
|
|
502
|
+
sourceType: row.source_type,
|
|
503
|
+
occurredAt: row.occurred_at,
|
|
504
|
+
payload: JSON.parse(row.payload)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
proposeClaim(claim) {
|
|
508
|
+
const id = claim.id || crypto.randomUUID();
|
|
509
|
+
const status = 'PENDING';
|
|
510
|
+
const confidence = claim.confidence ?? 1.0;
|
|
511
|
+
const assertedAt = claim.assertedAt || new Date().toISOString();
|
|
512
|
+
const stmt = this.db.prepare(`
|
|
513
|
+
INSERT INTO memory_claims (id, companion_id, subject, predicate, value, status, confidence, valid_from, valid_until, evidence, asserted_at, supersedes, source_event_id)
|
|
514
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?, ?)
|
|
515
|
+
`);
|
|
516
|
+
stmt.run(id, claim.companionId, claim.subject, claim.predicate, claim.value, status, confidence, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, assertedAt || null, claim.supersedes || null, claim.sourceEventId || null);
|
|
517
|
+
return {
|
|
518
|
+
...claim,
|
|
519
|
+
id,
|
|
520
|
+
status,
|
|
521
|
+
confidence,
|
|
522
|
+
assertedAt,
|
|
523
|
+
supersedes: claim.supersedes,
|
|
524
|
+
sourceEventId: claim.sourceEventId,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
approveClaim(id, companionId) {
|
|
528
|
+
const findClaim = companionId
|
|
529
|
+
? this.db.prepare("SELECT id, status, supersedes FROM memory_claims WHERE id = ? AND companion_id = ?")
|
|
530
|
+
: this.db.prepare("SELECT id, status, supersedes FROM memory_claims WHERE id = ?");
|
|
531
|
+
const row = (companionId ? findClaim.get(id, companionId) : findClaim.get(id));
|
|
532
|
+
if (!row) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (row.status !== 'PENDING') {
|
|
536
|
+
throw new Error(`Cannot approve claim '${id}': invalid transition from status '${row.status}' to 'APPROVED' (only PENDING claims can be approved)`);
|
|
537
|
+
}
|
|
538
|
+
// If this claim supersedes an earlier claim, transition that prior claim to SUPERSEDED
|
|
539
|
+
if (row.supersedes) {
|
|
540
|
+
const supersededId = row.supersedes;
|
|
541
|
+
if (companionId) {
|
|
542
|
+
const supersedeStmt = this.db.prepare("UPDATE memory_claims SET status = 'SUPERSEDED' WHERE id = ? AND companion_id = ?");
|
|
543
|
+
supersedeStmt.run(supersededId, companionId);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const supersedeStmt = this.db.prepare("UPDATE memory_claims SET status = 'SUPERSEDED' WHERE id = ?");
|
|
547
|
+
supersedeStmt.run(supersededId);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (companionId) {
|
|
551
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'APPROVED' WHERE id = ? AND companion_id = ? AND status = 'PENDING'");
|
|
552
|
+
stmt.run(id, companionId);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'APPROVED' WHERE id = ? AND status = 'PENDING'");
|
|
556
|
+
stmt.run(id);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
rejectClaim(id, companionId) {
|
|
560
|
+
if (companionId) {
|
|
561
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REJECTED' WHERE id = ? AND companion_id = ?");
|
|
562
|
+
stmt.run(id, companionId);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REJECTED' WHERE id = ?");
|
|
566
|
+
stmt.run(id);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
revokeClaim(id, companionId) {
|
|
570
|
+
if (companionId) {
|
|
571
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REVOKED' WHERE id = ? AND companion_id = ?");
|
|
572
|
+
stmt.run(id, companionId);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REVOKED' WHERE id = ?");
|
|
576
|
+
stmt.run(id);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
expireClaim(id, companionId) {
|
|
580
|
+
if (companionId) {
|
|
581
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'EXPIRED' WHERE id = ? AND companion_id = ?");
|
|
582
|
+
stmt.run(id, companionId);
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'EXPIRED' WHERE id = ?");
|
|
586
|
+
stmt.run(id);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
markClaimSessionOnly(id, companionId) {
|
|
590
|
+
if (companionId) {
|
|
591
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'SESSION_ONLY' WHERE id = ? AND companion_id = ?");
|
|
592
|
+
stmt.run(id, companionId);
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
const stmt = this.db.prepare("UPDATE memory_claims SET status = 'SESSION_ONLY' WHERE id = ?");
|
|
596
|
+
stmt.run(id);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
searchClaims(companionId, query, limit = 20) {
|
|
600
|
+
const stmt = this.db.prepare(`
|
|
601
|
+
SELECT c.* FROM memory_claims c
|
|
602
|
+
JOIN memory_search s ON c.rowid = s.rowid
|
|
603
|
+
WHERE c.companion_id = ? AND c.status = 'APPROVED' AND memory_search MATCH ?
|
|
604
|
+
ORDER BY rank
|
|
605
|
+
LIMIT ?
|
|
606
|
+
`);
|
|
607
|
+
return stmt.all(companionId, query, limit).map((row) => ({
|
|
608
|
+
id: row.id,
|
|
609
|
+
companionId: row.companion_id,
|
|
610
|
+
subject: row.subject,
|
|
611
|
+
predicate: row.predicate,
|
|
612
|
+
value: row.value,
|
|
613
|
+
status: row.status,
|
|
614
|
+
confidence: row.confidence,
|
|
615
|
+
validFrom: row.valid_from || undefined,
|
|
616
|
+
validUntil: row.valid_until || undefined,
|
|
617
|
+
evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
|
|
618
|
+
assertedAt: row.asserted_at,
|
|
619
|
+
supersedes: row.supersedes || undefined,
|
|
620
|
+
sourceEventId: row.source_event_id || undefined,
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
623
|
+
getPendingClaims(companionId, limit = 50) {
|
|
624
|
+
const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? AND status = 'PENDING' ORDER BY asserted_at DESC LIMIT ?");
|
|
625
|
+
return stmt.all(companionId, limit).map((row) => ({
|
|
626
|
+
id: row.id,
|
|
627
|
+
companionId: row.companion_id,
|
|
628
|
+
subject: row.subject,
|
|
629
|
+
predicate: row.predicate,
|
|
630
|
+
value: row.value,
|
|
631
|
+
status: row.status,
|
|
632
|
+
confidence: row.confidence,
|
|
633
|
+
validFrom: row.valid_from || undefined,
|
|
634
|
+
validUntil: row.valid_until || undefined,
|
|
635
|
+
evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
|
|
636
|
+
assertedAt: row.asserted_at,
|
|
637
|
+
supersedes: row.supersedes || undefined,
|
|
638
|
+
sourceEventId: row.source_event_id || undefined,
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
getApprovedClaims(companionId, limit = 50) {
|
|
642
|
+
const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? AND status = 'APPROVED' ORDER BY asserted_at DESC LIMIT ?");
|
|
643
|
+
return stmt.all(companionId, limit).map((row) => ({
|
|
644
|
+
id: row.id,
|
|
645
|
+
companionId: row.companion_id,
|
|
646
|
+
subject: row.subject,
|
|
647
|
+
predicate: row.predicate,
|
|
648
|
+
value: row.value,
|
|
649
|
+
status: row.status,
|
|
650
|
+
confidence: row.confidence,
|
|
651
|
+
validFrom: row.valid_from || undefined,
|
|
652
|
+
validUntil: row.valid_until || undefined,
|
|
653
|
+
evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
|
|
654
|
+
assertedAt: row.asserted_at,
|
|
655
|
+
supersedes: row.supersedes || undefined,
|
|
656
|
+
sourceEventId: row.source_event_id || undefined,
|
|
657
|
+
}));
|
|
658
|
+
}
|
|
659
|
+
getClaim(id) {
|
|
660
|
+
const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE id = ?");
|
|
661
|
+
const row = stmt.get(id);
|
|
662
|
+
if (!row)
|
|
663
|
+
return undefined;
|
|
664
|
+
return {
|
|
665
|
+
id: row.id,
|
|
666
|
+
companionId: row.companion_id,
|
|
667
|
+
subject: row.subject,
|
|
668
|
+
predicate: row.predicate,
|
|
669
|
+
value: row.value,
|
|
670
|
+
status: row.status,
|
|
671
|
+
confidence: row.confidence,
|
|
672
|
+
validFrom: row.valid_from || undefined,
|
|
673
|
+
validUntil: row.valid_until || undefined,
|
|
674
|
+
evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
|
|
675
|
+
assertedAt: row.asserted_at,
|
|
676
|
+
supersedes: row.supersedes || undefined,
|
|
677
|
+
sourceEventId: row.source_event_id || undefined,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
resetMemory(companionId) {
|
|
681
|
+
const deleteClaims = this.db.prepare("DELETE FROM memory_claims WHERE companion_id = ?");
|
|
682
|
+
deleteClaims.run(companionId);
|
|
683
|
+
const deleteEvents = this.db.prepare("DELETE FROM memory_events WHERE companion_id = ?");
|
|
684
|
+
deleteEvents.run(companionId);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
exports.SiduriDatabase = SiduriDatabase;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|